spine-rigc 2.45.1 → 2.45.2
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 +15 -0
- package/package.json +1 -1
- package/src/core/raw.ts +7 -4
- package/src/render.ts +7 -4
- package/src/render_core.ts +64 -3
package/docs/AUTHORING.md
CHANGED
|
@@ -4906,6 +4906,21 @@ file can be read; it is a **key** outside its bound that `build` refuses (§4.4)
|
|
|
4906
4906
|
number: a non-number would reach the file as `null`, which the runtime reads as
|
|
4907
4907
|
**zero**, so `"mass": "heavy"` would ship a constraint that never settles.
|
|
4908
4908
|
|
|
4909
|
+
⚠️ **What `rigc render` draws a spring with is not a player's clock to the bit.** A
|
|
4910
|
+
constraint integrates whole steps of `1 / fps` and shows the rest as a lag weight, so
|
|
4911
|
+
a frame lasting a whole number of steps (12, 20, 30 or 60 fps against the default
|
|
4912
|
+
60, every second frame at 24) leaves zero in exact arithmetic and a few ulps either
|
|
4913
|
+
side of it in doubles — and spine-core stepped by `1/fps` per frame lets the rounding
|
|
4914
|
+
decide, drawing every spring a frame late or on time and switching between the two on
|
|
4915
|
+
single frames (issue #1380: frames 5, 12, 45 and 188 of a 20 fps render, and a loop
|
|
4916
|
+
whose two ends sat in different regimes, 31.7 units apart). `render`, and everything
|
|
4917
|
+
that poses through its posers (`check`, `bonedist`, `inspect`, `--geometry`), runs
|
|
4918
|
+
the physics clock `2⁻³⁰` faster than the frames, so every such frame takes the steps
|
|
4919
|
+
exact arithmetic takes; the animation time is unchanged. A spring that has converged
|
|
4920
|
+
by the loop point therefore closes, which the loop-seam suite's `LS06` holds at all
|
|
4921
|
+
five rates. A player with a real clock is a different question and rigc does not
|
|
4922
|
+
answer it.
|
|
4923
|
+
|
|
4909
4924
|
`mix` is a player-side `AnimationStateData` config and is **not** emitted into
|
|
4910
4925
|
skeleton JSON, so the parser is the only check it gets: it is
|
|
4911
4926
|
`{ "default": <seconds>, "pairs"?: [["<from>", "<to>", <seconds>], …] }`, and a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "2.45.
|
|
3
|
+
"version": "2.45.2",
|
|
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/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
|
-
/**
|
|
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`), or the render's offline clock passed to `poseRawAnimationEach` (issue #1380). */
|
|
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;
|
|
@@ -494,10 +494,13 @@ export function poseRawAnimation(doc: CompiledDocument, animation: string, steps
|
|
|
494
494
|
* animation's series — on the production rig the core poser's render peaked
|
|
495
495
|
* highest on, the largest animation's series was 153 MiB of retained heap.
|
|
496
496
|
* A refusal is thrown from the pose it is met at, after `visit` has seen the
|
|
497
|
-
* poses before it.
|
|
497
|
+
* poses before it. `clock`, when given, is what each step moves the physics
|
|
498
|
+
* clock by in place of the step itself — the render's offline schedule
|
|
499
|
+
* (`physicsClockStep` in `src/render_core.ts`, issue #1380); the track time and
|
|
500
|
+
* the animation time are the steps' running sum either way.
|
|
498
501
|
*/
|
|
499
|
-
export function poseRawAnimationEach(doc: CompiledDocument, animation: string, steps: readonly number[], plant: TimelinePlant, reset: RawReset, visit: (pose: RawPose, index: number) => void): void {
|
|
500
|
-
walkEach(doc, animation, steps, plant, reset, RAW_MODE, RAW_SHAPE, visit);
|
|
502
|
+
export function poseRawAnimationEach(doc: CompiledDocument, animation: string, steps: readonly number[], plant: TimelinePlant, reset: RawReset, visit: (pose: RawPose, index: number) => void, clock?: (dt: number) => number): void {
|
|
503
|
+
walkEach(doc, animation, steps, plant, reset, clock === undefined ? RAW_MODE : { ...RAW_MODE, clock: (dt) => clock(dt) }, RAW_SHAPE, visit);
|
|
501
504
|
}
|
|
502
505
|
|
|
503
506
|
/**
|
package/src/render.ts
CHANGED
|
@@ -128,7 +128,7 @@ import { readFileSync } from 'node:fs';
|
|
|
128
128
|
// `loadPosedSkeleton`, and kept, the plant reads red for its own reason rather than for a missing name.
|
|
129
129
|
import { dirname, join } from 'node:path';
|
|
130
130
|
import { Plate, readPlate } from '../tools/plate.ts';
|
|
131
|
-
import { clipSourceOf, corePoser, inactiveBoneSnapshot, subsetOver, unposedBones } from './render_core.ts';
|
|
131
|
+
import { clipSourceOf, corePoser, inactiveBoneSnapshot, physicsClockStep, subsetOver, unposedBones } from './render_core.ts';
|
|
132
132
|
import { type PosedVertices, type WorldTransform } from './nonfinite.ts';
|
|
133
133
|
import {
|
|
134
134
|
atlasScales,
|
|
@@ -488,9 +488,10 @@ export type PoseSource = Poser | SkeletonData;
|
|
|
488
488
|
* because that is the path a runtime actually takes, and 4.3's
|
|
489
489
|
* `Animation.apply` takes a `MixFrom` that only the state machine has any
|
|
490
490
|
* business choosing. Frame 0 applies, updates by 0 and resets physics; every
|
|
491
|
-
* later frame is one `state.update(1/fps)`, apply, `skeleton.update
|
|
491
|
+
* later frame is one `state.update(1/fps)`, apply, `skeleton.update` and
|
|
492
492
|
* `Physics.update` — one continuous trajectory, so the track time of frame `i`
|
|
493
|
-
* is the sum of `i` steps.
|
|
493
|
+
* is the sum of `i` steps. The skeleton's clock, which only the physics reads,
|
|
494
|
+
* moves by `physicsClockStep(1/fps)` (`PHYSICS_CLOCK_LEAD`, issue #1380).
|
|
494
495
|
*/
|
|
495
496
|
export function spinePoser(data: SkeletonData): Poser {
|
|
496
497
|
return {
|
|
@@ -507,12 +508,14 @@ export function spinePoser(data: SkeletonData): Poser {
|
|
|
507
508
|
state.setAnimation(0, name, false);
|
|
508
509
|
skeleton.setupPose();
|
|
509
510
|
const step = 1 / fps;
|
|
511
|
+
// The physics clock runs `PHYSICS_CLOCK_LEAD` fast, so no frame ends on a physics step's tie (issue #1380).
|
|
512
|
+
const clock = physicsClockStep(step);
|
|
510
513
|
const posed = spinePosed(skeleton);
|
|
511
514
|
for (let i = 0; i <= count; i++) {
|
|
512
515
|
if (i > 0) {
|
|
513
516
|
state.update(step);
|
|
514
517
|
state.apply(skeleton);
|
|
515
|
-
skeleton.update(
|
|
518
|
+
skeleton.update(clock);
|
|
516
519
|
skeleton.updateWorldTransform(Physics.update);
|
|
517
520
|
} else {
|
|
518
521
|
state.apply(skeleton);
|
package/src/render_core.ts
CHANGED
|
@@ -28,7 +28,11 @@
|
|
|
28
28
|
* frame of the nineteen tree rows by the core suite's `CR03`. The setup pose
|
|
29
29
|
* is `poseRawSetup`. Since issue #1180 the walk is `poseRawAnimationEach`,
|
|
30
30
|
* the same walk handing each pose over as it is posed (`CORE_RAW_WALK`), so
|
|
31
|
-
* one pose is held at a time.
|
|
31
|
+
* one pose is held at a time. Since issue #1380 the physics clock — and
|
|
32
|
+
* nothing else — moves by `physicsClockStep(1/fps)` rather than by the step
|
|
33
|
+
* (`PHYSICS_CLOCK_LEAD` below), in both posers: `CR03` still holds the walk
|
|
34
|
+
* under the step itself, and the loop-seam suite's `LS06` holds the two
|
|
35
|
+
* posers equal under the render's clock.
|
|
32
36
|
* - **Vertices, triangles, bones, slot colours, draw order** are the raw
|
|
33
37
|
* pose's, unchanged: the same doubles `computeWorldVertices`,
|
|
34
38
|
* `getWorldRotationX` and the slot pose hold (`CR03`).
|
|
@@ -361,8 +365,65 @@ export function keptPieceArrays(): PieceArrays {
|
|
|
361
365
|
*/
|
|
362
366
|
export type CoreRawWalk = (doc: CompiledDocument, animation: string, steps: readonly number[], plant: TimelinePlant, visit: (pose: RawPose, index: number) => void) => void;
|
|
363
367
|
|
|
364
|
-
/** The walk `corePoser` steps with unless a plant passes another: one pose posed, drawn and released before the next. */
|
|
365
|
-
export const CORE_RAW_WALK: CoreRawWalk = (doc, animation, steps, plant, visit) => poseRawAnimationEach(doc, animation, steps, plant, 'animation', visit);
|
|
368
|
+
/** The walk `corePoser` steps with unless a plant passes another: one pose posed, drawn and released before the next, the physics clock moved by `physicsClockStep` (issue #1380). */
|
|
369
|
+
export const CORE_RAW_WALK: CoreRawWalk = (doc, animation, steps, plant, visit) => poseRawAnimationEach(doc, animation, steps, plant, 'animation', visit, physicsClockStep);
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* How much faster than the frames a render's physics clock runs: each frame
|
|
373
|
+
* moves the skeleton's clock — the time a physics constraint integrates — by
|
|
374
|
+
* `physicsClockStep(1 / fps)`, the frame's step and `2⁻³⁰` of it more, while
|
|
375
|
+
* the animation is applied at the running sum of the frame steps as before
|
|
376
|
+
* (issue #1380).
|
|
377
|
+
*
|
|
378
|
+
* ⭐ **Why a render's physics clock is not the frame clock.** A physics
|
|
379
|
+
* constraint keeps the time it has not yet integrated (`remaining`), takes
|
|
380
|
+
* whole steps of `1 / fps` (its own `fps`, 60 by default) while at least one
|
|
381
|
+
* step remains, and shows `offset − lag·(1 − remaining / step)`, `lag` being
|
|
382
|
+
* everything this update's steps moved (`src/core/constraints_physics.ts`,
|
|
383
|
+
* *The step*). Whenever a frame lasts a whole number of physics steps — 12,
|
|
384
|
+
* 20, 30 and 60 fps against 60 Hz, and every second frame at 24 — the time
|
|
385
|
+
* left at the frame's end is zero in exact arithmetic, and in doubles it is
|
|
386
|
+
* either a few ulps above zero or a few ulps short of one whole step. Which of
|
|
387
|
+
* the two is decided by rounding, not by the motion: the shown weight is 1 or
|
|
388
|
+
* 0, so the spring is drawn one frame late or on time, and it changes regime
|
|
389
|
+
* on a single frame. Measured on the public trailer (`show`, 20 fps, 321
|
|
390
|
+
* frames): every spring jumps at frames 5, 12, 45 and 188 — the four frames
|
|
391
|
+
* that arithmetic alone predicts — and the loop's start (frame 80, on time)
|
|
392
|
+
* and end (frame 320, a frame late, which is what exact arithmetic draws)
|
|
393
|
+
* differ by 31.70 units of vertex and 10.37 of bone
|
|
394
|
+
* while every keyed bone agrees to 1.3e-12. spine-core 4.3.13 stepped the
|
|
395
|
+
* same way draws the same frames to the bit: this is the runtime under a
|
|
396
|
+
* fixed offline step, not a defect of the core's port, which is why the fix
|
|
397
|
+
* is the render's schedule and the physics is untouched.
|
|
398
|
+
*
|
|
399
|
+
* Running the clock `2⁻³⁰` fast puts the time left a little ABOVE every
|
|
400
|
+
* whole number of steps instead of on it, by `2⁻³⁰ · t` after `t` seconds:
|
|
401
|
+
* 1.5e-8 s at 16 s, against a rounding error of at most a few ulps of the
|
|
402
|
+
* clock (3.6e-15 s at 16 s) — so every tie resolves the way exact arithmetic
|
|
403
|
+
* resolves it, a step taken, and the shown weight sits at 1 − 9e-7 rather
|
|
404
|
+
* than flipping. It stays below the smallest gap a step boundary can fall
|
|
405
|
+
* into for 7.8e5 s even at 23 fps against 60 Hz (a gap of 1/1380 s), and a
|
|
406
|
+
* physics `reset` key, which restarts the time left from zero, is resolved
|
|
407
|
+
* the same way on every frame after it, because the lead is taken on every
|
|
408
|
+
* frame rather than once (read off the clock arithmetic with a reset at frame
|
|
409
|
+
* 100, 16 s at the five rates: no tie; no tree probe keys a reset under the
|
|
410
|
+
* render's clock). A pose stays a function of its frame
|
|
411
|
+
* index alone: the lead does not depend on how many frames are asked for.
|
|
412
|
+
*
|
|
413
|
+
* Rejected, measured over the same arithmetic (16 s at 12, 20, 24, 25, 30 and
|
|
414
|
+
* 60 fps): the clock as `i / fps` rather than a running sum (90 frames still
|
|
415
|
+
* flipped at 20 fps); a one-time lead of half a step (114 flipped at 24 fps,
|
|
416
|
+
* where half a step lands on the other tie); a one-time lead of any size,
|
|
417
|
+
* which a `reset` key cancels; and snapping `remaining` inside the physics,
|
|
418
|
+
* which changes the runtime's physics rather than the render's schedule and
|
|
419
|
+
* which spine-core's poser could not follow.
|
|
420
|
+
*/
|
|
421
|
+
export const PHYSICS_CLOCK_LEAD = 2 ** -30;
|
|
422
|
+
|
|
423
|
+
/** What one frame of `step` seconds moves a render's physics clock by — `PHYSICS_CLOCK_LEAD`; both posers step with it. */
|
|
424
|
+
export function physicsClockStep(step: number): number {
|
|
425
|
+
return step + step * PHYSICS_CLOCK_LEAD;
|
|
426
|
+
}
|
|
366
427
|
|
|
367
428
|
/** What a suite passes `corePoser` in place of the poser's own parts — `PieceArrays` (`RC43`) and the raw walk (`RC44`). */
|
|
368
429
|
export interface CorePoserPlants {
|