spine-rigc 2.48.0 → 2.49.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/docs/AUTHORING.md +9 -2
- package/package.json +1 -1
- package/src/core/entry.ts +3 -2
- package/src/core/raw.ts +15 -1
- package/src/core/track.ts +48 -4
- package/src/core/tracks.ts +15 -5
package/docs/AUTHORING.md
CHANGED
|
@@ -4927,8 +4927,15 @@ that poses through its posers (`check`, `bonedist`, `inspect`, `--geometry`), ru
|
|
|
4927
4927
|
the physics clock `2⁻³⁰` faster than the frames, so every such frame takes the steps
|
|
4928
4928
|
exact arithmetic takes; the animation time is unchanged. A spring that has converged
|
|
4929
4929
|
by the loop point therefore closes, which the loop-seam suite's `LS06` holds at all
|
|
4930
|
-
five rates. A player
|
|
4931
|
-
|
|
4930
|
+
five rates. A player that steps the core entry's live tracks at a fixed rate — a
|
|
4931
|
+
capture, an export, a test harness — is the render's case and takes the render's
|
|
4932
|
+
clock: `openTrack` and `openTracks` accept `clock`, what each step moves the physics
|
|
4933
|
+
clock by, and `physicsClockStep` (with `PHYSICS_CLOCK_LEAD`) is exported from
|
|
4934
|
+
`rig-c/core` to pass to it (issue #1396). It moves the physics alone — the track
|
|
4935
|
+
time, the animation time and the events stay on the steps, so the player keeps
|
|
4936
|
+
agreeing with `render` — and left out, a track steps the physics by the step itself,
|
|
4937
|
+
exactly as before. Which clock a player with a real clock uses is the player's
|
|
4938
|
+
question, and rigc does not answer it.
|
|
4932
4939
|
|
|
4933
4940
|
`mix` is a player-side `AnimationStateData` config and is **not** emitted into
|
|
4934
4941
|
skeleton JSON, so the parser is the only check it gets: it is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.49.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": {
|
package/src/core/entry.ts
CHANGED
|
@@ -17,13 +17,14 @@
|
|
|
17
17
|
*
|
|
18
18
|
* ⚠️ A symbol is listed here only because a dependant was seen using it,
|
|
19
19
|
* or asked for it in writing before it could be (`openTrack`, issue #1276;
|
|
20
|
-
* `openTracks`, issue #1336
|
|
20
|
+
* `openTracks`, issue #1336; `physicsClockStep` and `PHYSICS_CLOCK_LEAD`,
|
|
21
|
+
* the clock a live track's `clock` option takes, issue #1396).
|
|
21
22
|
* The list is `scripts/install_smoke.ts`'s `OBSERVED_SYMBOLS` rows for
|
|
22
23
|
* `./core`, and a symbol added here and not there is not promised.
|
|
23
24
|
*/
|
|
24
25
|
export { readModel, underSkin, underNoSkin, CoreInputError, CORE_DOCUMENT_SPEC, CORE_DOCUMENT_SPECS, CORE_BLEND_MODES, activeBones } from './index.ts';
|
|
25
26
|
export type { CompiledDocument, CoreSlotRow, CoreBlendMode } from './index.ts';
|
|
26
|
-
export { poseRawSetup, poseRawAnimation, poseRawAnimationEach, loopedTime, setupBounds } from './raw.ts';
|
|
27
|
+
export { poseRawSetup, poseRawAnimation, poseRawAnimationEach, loopedTime, setupBounds, physicsClockStep, PHYSICS_CLOCK_LEAD } from './raw.ts';
|
|
27
28
|
export type { RawPose, RawBone, RawDrawn, RawClip, RawClipped, RawEvent, RawReset } from './raw.ts';
|
|
28
29
|
export { poseWalkSetup, poseLoopingWalk } from './walk.ts';
|
|
29
30
|
export { openTrack } from './track.ts';
|
package/src/core/raw.ts
CHANGED
|
@@ -257,7 +257,7 @@ export interface WalkMode {
|
|
|
257
257
|
keep: boolean;
|
|
258
258
|
/** A plant only (the core suite's `CO26`): the animation time a looping step applies at, from its track time, the duration and the track time before it — `loopedTime` unless given. */
|
|
259
259
|
time?: (trackTime: number, duration: number, before: number) => number;
|
|
260
|
-
/** What the physics clock moves by, from the step and how far the animation time moved — the step itself unless given: a plant (`CO26`),
|
|
260
|
+
/** What the physics clock moves by, from the step and how far the animation time moved — the step itself unless given: a plant (`CO26`), the render's offline clock passed to `poseRawAnimationEach` (issue #1380), or a live track's `clock` option (`./track.ts`, issue #1396). */
|
|
261
261
|
clock?: (dt: number, moved: number) => number;
|
|
262
262
|
/** A plant only (`CO26`): `false` fires no `reset` key across the wrap — the reading the walk was measured against and rejected. */
|
|
263
263
|
wrapResets?: boolean;
|
|
@@ -561,6 +561,20 @@ export function physicsClockStep(step: number): number {
|
|
|
561
561
|
return step + step * PHYSICS_CLOCK_LEAD;
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
+
/**
|
|
565
|
+
* A live track's `clock` option (issue #1396, `./track.ts` and `./tracks.ts`):
|
|
566
|
+
* the caller's function, called with each step, its answer checked before the
|
|
567
|
+
* skeleton's clock moves by it — a finite time at or above 0, or the step
|
|
568
|
+
* refused by name, as a step itself is. `who` names the track in the refusal.
|
|
569
|
+
*/
|
|
570
|
+
export function checkedClock(clock: (dt: number) => number, who: string): (dt: number) => number {
|
|
571
|
+
return (dt) => {
|
|
572
|
+
const moved = clock(dt);
|
|
573
|
+
if (typeof moved !== 'number' || !Number.isFinite(moved) || moved < 0) throw new CoreInputError(`${who} clock moved the physics by ${String(moved)} for a step of ${dt}, not a finite time at or above 0`);
|
|
574
|
+
return moved;
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
564
578
|
/**
|
|
565
579
|
* The animation time a looping track applies its animation at (`./walk.ts`,
|
|
566
580
|
* *The time*): the track time wrapped by the duration, and 0 over a duration
|
package/src/core/track.ts
CHANGED
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
*
|
|
27
27
|
* - **Held** (`loop: false`): the raw entry's walk — the track time the
|
|
28
28
|
* running sum of the steps, the animation time held at the duration, the
|
|
29
|
-
* physics clock moved by the step
|
|
29
|
+
* physics clock moved by the step (or by `clock`, *The physics clock*
|
|
30
|
+
* below), events over `(previous animation time,
|
|
30
31
|
* animation time]` (`./raw.ts`, *One step*). `CO46` holds it to
|
|
31
32
|
* `poseRawAnimation` pose for pose, every field, bit for bit.
|
|
32
33
|
* - **Looped** (`loop: true`): A10's looping walk's time, clock and `reset`
|
|
@@ -135,6 +136,44 @@
|
|
|
135
136
|
* children, the meshes they carry and the physics do not.
|
|
136
137
|
* - **Pose 0 not adjusted** under `reset: 'animation'`.
|
|
137
138
|
*
|
|
139
|
+
* ## The physics clock (issue #1396)
|
|
140
|
+
*
|
|
141
|
+
* `clock` is what each step moves the skeleton's physics clock by — the time
|
|
142
|
+
* a physics constraint integrates — with `poseRawAnimationEach`'s meaning.
|
|
143
|
+
* Absent, it is the step itself, and every pose is the one the track posed
|
|
144
|
+
* before the option existed (`CO46`–`CO50`, unchanged). `physicsClockStep` is
|
|
145
|
+
* the render's clock (issue #1380), for a player stepping at a fixed rate.
|
|
146
|
+
* The track time, the animation time, the wrap, the events and `trackTime`
|
|
147
|
+
* stay on the running sum of the steps: the clock moves the physics and
|
|
148
|
+
* nothing else.
|
|
149
|
+
*
|
|
150
|
+
* - **Why the track takes it.** Stepped at a fixed rate whose frame lasts a
|
|
151
|
+
* whole number of physics steps, a spring shows the render's defect. On the
|
|
152
|
+
* public trailer's `show`, read with play#103's detector, the frame clock
|
|
153
|
+
* adds outlier poses 5, 11–13, 44–45 and 187–189 at a fixed 1/20 and 8,
|
|
154
|
+
* 15–17, 31–33 and 505–507 at 1/60, and `clock: physicsClockStep` removes
|
|
155
|
+
* every one (the PR of issue #1396 carries the table). A player scaling its
|
|
156
|
+
* own `dt` instead moves the animation time too: a key at 0.5 s fires on
|
|
157
|
+
* the tenth step of 1/20 where the render and the runtime fire it on the
|
|
158
|
+
* eleventh, and the player stops agreeing with `rigc render`.
|
|
159
|
+
* - **The reset.** Pose 0 under `reset: 'animation'` is a step of 0 and moves
|
|
160
|
+
* the clock by `clock(0)`, as the render's walk does — 0 for
|
|
161
|
+
* `physicsClockStep`; under `setup` nothing is stepped. A physics `reset`
|
|
162
|
+
* key restarts a constraint's time from the clock as it stands, so it is
|
|
163
|
+
* read on the same clock as every other step.
|
|
164
|
+
* - **The seek** stays the caller's — open, then step to the time — and its
|
|
165
|
+
* steps go through the same clock, so a seek's physics is the physics of
|
|
166
|
+
* the steps it took.
|
|
167
|
+
* - **A clock that returns no time** — not finite, or below 0 — refuses the
|
|
168
|
+
* step by name after the track time has moved, so the track stays refused.
|
|
169
|
+
*
|
|
170
|
+
* `CO53` holds the mechanism on LS05's spring probe (every constraint update
|
|
171
|
+
* takes the steps exact arithmetic takes; the frame clock is red), `CO54` the
|
|
172
|
+
* poses (equal to the render's walk, to `poseRawAnimationEach` on the same
|
|
173
|
+
* clock, between the two live entries, and to spine-core with its
|
|
174
|
+
* `AnimationState` updated by the step and its `Skeleton` by the clock), and
|
|
175
|
+
* `CO55` that the times and the events are those of the walk without it.
|
|
176
|
+
*
|
|
138
177
|
* ## The oracle chain
|
|
139
178
|
*
|
|
140
179
|
* Nothing here is held to spine-core directly but the events: the live track
|
|
@@ -153,15 +192,17 @@
|
|
|
153
192
|
* history, so a seek is "open, then step to the time".
|
|
154
193
|
*/
|
|
155
194
|
import { CoreInputError, type CompiledDocument } from './index.ts';
|
|
156
|
-
import { openRawTrack, stepRawTrack, type RawPose, type RawReset, type TrackState, type WalkMode } from './raw.ts';
|
|
195
|
+
import { checkedClock, openRawTrack, stepRawTrack, type RawPose, type RawReset, type TrackState, type WalkMode } from './raw.ts';
|
|
157
196
|
import type { LocalAdjust, TimelinePlant } from './animation.ts';
|
|
158
197
|
export type { AdjustableLocals, BoneLocals, LocalAdjust } from './animation.ts';
|
|
159
198
|
|
|
160
|
-
/** How a live track is opened: whether it loops, and where its physics is reset (`RawReset`) — both the caller's, neither with a default —
|
|
199
|
+
/** How a live track is opened: whether it loops, and where its physics is reset (`RawReset`) — both the caller's, neither with a default — the caller's adjustment of every pose's bone locals (the header's *The adjustment*), and what each step moves the physics clock by (the header's *The physics clock*). */
|
|
161
200
|
export interface TrackOptions {
|
|
162
201
|
loop: boolean;
|
|
163
202
|
reset: RawReset;
|
|
164
203
|
adjust?: LocalAdjust;
|
|
204
|
+
/** What each step of `dt` moves the skeleton's physics clock by — `physicsClockStep` is the render's; absent, the step itself. The track time, the animation time and the events stay on the running sum of the steps. */
|
|
205
|
+
clock?: (dt: number) => number;
|
|
165
206
|
}
|
|
166
207
|
|
|
167
208
|
/** A live track (`openTrack`): pose 0, then one pose per `step`. */
|
|
@@ -191,7 +232,10 @@ export function openTrack(doc: CompiledDocument, animation: string, options: Tra
|
|
|
191
232
|
if (typeof options !== 'object' || options === null || typeof options.loop !== 'boolean') throw new CoreInputError(`the live track's options.loop is ${JSON.stringify((options as Partial<TrackOptions> | null)?.loop)}, not true or false`);
|
|
192
233
|
if (options.reset !== 'animation' && options.reset !== 'setup') throw new CoreInputError(`the live track's options.reset is ${JSON.stringify(options.reset)}, not "animation" or "setup"`);
|
|
193
234
|
if (options.adjust !== undefined && typeof options.adjust !== 'function') throw new CoreInputError(`the live track's options.adjust is ${typeof options.adjust}, not a function`);
|
|
194
|
-
|
|
235
|
+
if (options.clock !== undefined && typeof options.clock !== 'function') throw new CoreInputError(`the live track's options.clock is ${typeof options.clock}, not a function`);
|
|
236
|
+
const mode = options.loop ? LOOPED : HELD;
|
|
237
|
+
const clock = options.clock === undefined ? undefined : checkedClock(options.clock, 'the live track\'s');
|
|
238
|
+
const { track, first } = openRawTrack(doc, animation, plant, options.reset, clock === undefined ? mode : { ...mode, clock: (dt) => clock(dt) }, options.adjust);
|
|
195
239
|
return new Track(track, first);
|
|
196
240
|
}
|
|
197
241
|
|
package/src/core/tracks.ts
CHANGED
|
@@ -73,7 +73,12 @@
|
|
|
73
73
|
* out fires none** — not "until the mix passes half way": `eventThreshold`
|
|
74
74
|
* defaults to 0 and an entry's events are applied only while `mix <
|
|
75
75
|
* eventThreshold`, which no mix is. `complete` is not carried (issue #1276).
|
|
76
|
-
* - **Physics** steps on top of the mixed locals, as on one track
|
|
76
|
+
* - **Physics** steps on top of the mixed locals, as on one track — and on
|
|
77
|
+
* the same clock: `TracksOptions.clock`, what each step moves the physics
|
|
78
|
+
* clock by, has the single track's meaning (`./track.ts`, *The physics
|
|
79
|
+
* clock*, issue #1396). The entries' track times, the mixes, the queue and
|
|
80
|
+
* the events stay on the steps; `start()` under `reset: 'animation'` moves
|
|
81
|
+
* the clock by `clock(0)`.
|
|
77
82
|
*
|
|
78
83
|
* `CO51` holds all of it to spine-core 4.3.13 at tolerance 0 — every bone's
|
|
79
84
|
* world transform, every drawn attachment's world vertices, every slot's
|
|
@@ -159,13 +164,15 @@ import { activeBones, CoreInputError, rawNumber, readColour, shownAttachment, ty
|
|
|
159
164
|
import { channelAt, keyIndexAt, SLOT_COLOUR_TIMELINES, slotRowsFrom, steppedLocalsWorld, type BoneTimelineKind, type CoreKey, type LocalAdjust, type SlotBase, type SlotColourKind, type TimelinePlant } from './animation.ts';
|
|
160
165
|
import { deformAt, setupArray, type CoreAttachmentTimeline } from './deform.ts';
|
|
161
166
|
import { eventsFired, type CoreEventKey, type CoreEventRow } from './events.ts';
|
|
162
|
-
import { openRawTracksPath, type RawPose, type RawReset, type RawTracksPath } from './raw.ts';
|
|
167
|
+
import { checkedClock, openRawTracksPath, type RawPose, type RawReset, type RawTracksPath } from './raw.ts';
|
|
163
168
|
import { slotTimelinesApply } from './skins.ts';
|
|
164
169
|
|
|
165
|
-
/** How the live tracks are opened: where the physics is reset (`RawReset`, the caller's, no default)
|
|
170
|
+
/** How the live tracks are opened: where the physics is reset (`RawReset`, the caller's, no default), the caller's adjustment of every pose's bone locals, and what each step moves the physics clock by (the header's *The physics clock*). */
|
|
166
171
|
export interface TracksOptions {
|
|
167
172
|
reset: RawReset;
|
|
168
173
|
adjust?: LocalAdjust;
|
|
174
|
+
/** What each step of `dt` moves the skeleton's physics clock by — `physicsClockStep` is the render's; absent, the step itself. Every entry's track time, the mixes, the queue and the events stay on the steps. */
|
|
175
|
+
clock?: (dt: number) => number;
|
|
169
176
|
}
|
|
170
177
|
|
|
171
178
|
/**
|
|
@@ -1299,7 +1306,9 @@ function delayOf(v: number, call: string): number {
|
|
|
1299
1306
|
export function openTracks(doc: CompiledDocument, options: TracksOptions, plant: TracksPlant = {}): LiveTracks {
|
|
1300
1307
|
if (typeof options !== 'object' || options === null || (options.reset !== 'animation' && options.reset !== 'setup')) throw new CoreInputError(`the live tracks' options.reset is ${JSON.stringify((options as Partial<TracksOptions> | null)?.reset)}, not "animation" or "setup"`);
|
|
1301
1308
|
if (options.adjust !== undefined && typeof options.adjust !== 'function') throw new CoreInputError(`the live tracks' options.adjust is ${typeof options.adjust}, not a function`);
|
|
1302
|
-
|
|
1309
|
+
if (options.clock !== undefined && typeof options.clock !== 'function') throw new CoreInputError(`the live tracks' options.clock is ${typeof options.clock}, not a function`);
|
|
1310
|
+
const clock = options.clock === undefined ? undefined : checkedClock(options.clock, 'the live tracks\'');
|
|
1311
|
+
return new Tracks(doc, options.reset, options.adjust, clock, plant, openRawTracksPath(doc, plant));
|
|
1303
1312
|
}
|
|
1304
1313
|
|
|
1305
1314
|
class Tracks implements LiveTracks {
|
|
@@ -1313,6 +1322,7 @@ class Tracks implements LiveTracks {
|
|
|
1313
1322
|
private readonly doc: CompiledDocument,
|
|
1314
1323
|
private readonly reset: RawReset,
|
|
1315
1324
|
private readonly adjust: LocalAdjust | undefined,
|
|
1325
|
+
private readonly clock: ((dt: number) => number) | undefined,
|
|
1316
1326
|
private readonly plant: TracksPlant,
|
|
1317
1327
|
private readonly path: RawTracksPath,
|
|
1318
1328
|
) {
|
|
@@ -1426,7 +1436,7 @@ class Tracks implements LiveTracks {
|
|
|
1426
1436
|
const { doc, path } = this;
|
|
1427
1437
|
const ctx = path.ctx;
|
|
1428
1438
|
const before = ctx.time;
|
|
1429
|
-
ctx.time += dt;
|
|
1439
|
+
ctx.time += this.clock === undefined ? dt : this.clock(dt);
|
|
1430
1440
|
const pose = new Pose(doc, this.plant);
|
|
1431
1441
|
const animationTime = this.mixer.apply(pose);
|
|
1432
1442
|
const mixed = new Map(doc.slots.map((s, k) => [s.name, { deform: pose.slots[k].deform, seen: pose.slots[k].seen }]));
|