@rig-c/core 2.74.0 → 2.75.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 +49 -0
- package/package.json +1 -1
- package/src/core/constraints_physics.ts +6 -4
- package/src/core/raw.ts +20 -4
- package/src/core/track.ts +11 -9
- package/src/core/tracks.ts +11 -9
package/docs/AUTHORING.md
CHANGED
|
@@ -5578,6 +5578,43 @@ skeleton's units and axes, and its change of rotation as `physicsRotate(0, 0,
|
|
|
5578
5578
|
Δ)`, before the `step` that poses the frame. Nothing set, every pose is the
|
|
5579
5579
|
one the track posed before these existed (`CO57`).
|
|
5580
5580
|
|
|
5581
|
+
Both `LiveTrack` and `LiveTracks` also accept an optional last `scope` argument
|
|
5582
|
+
for movement, a readonly array of **exact physics constraint names** (play #197):
|
|
5583
|
+
|
|
5584
|
+
```ts
|
|
5585
|
+
track.physicsTranslate(dx, dy, ['hair_left', 'hair_right']);
|
|
5586
|
+
track.physicsRotate(pivotX, pivotY, deltaDegrees, ['hair_left', 'hair_right']);
|
|
5587
|
+
```
|
|
5588
|
+
|
|
5589
|
+
Omitted or `undefined` means every existing state, exactly as before. `[]`
|
|
5590
|
+
selects none; repeated names select once, in the existing state order. Every
|
|
5591
|
+
name is checked against the document before any state changes: an unknown name
|
|
5592
|
+
is refused as `physics movement scope: no constraint "name"`; a non-physics
|
|
5593
|
+
constraint is refused naming its kind. A non-array scope or non-string name
|
|
5594
|
+
also refuses. Finite movement/pivot checks still apply, including an empty
|
|
5595
|
+
scope. A declared inactive constraint is a valid selection; movement creates
|
|
5596
|
+
no state for it. No document, parameter override, clock or held pose is changed.
|
|
5597
|
+
|
|
5598
|
+
These are synchronous **before-evaluation** inputs: only the selected remembered
|
|
5599
|
+
origins (`ux`, `uy`, `cx`, `cy`) change, so the next `step` sees extra apparent
|
|
5600
|
+
motion. Distances and the explicit pivot are skeleton world units, x right,
|
|
5601
|
+
y up; the angle is degrees CCW **in the XY plane**, not yaw or pitch. The turn
|
|
5602
|
+
is the displacement of each remembered origin around the pivot, not a direct
|
|
5603
|
+
angular impulse; an origin at the pivot has no displacement. The caller derives
|
|
5604
|
+
any projected yaw/pitch displacement from its own geometry and depth. Do not
|
|
5605
|
+
supply it again for movement the bone itself already makes.
|
|
5606
|
+
|
|
5607
|
+
The ordinary step still owns reset, inertia, limits and integration. A reset
|
|
5608
|
+
or first active update can overwrite this input; an intervening `step(0)` may
|
|
5609
|
+
consume it without the intended positive-time response. There is no queue or
|
|
5610
|
+
motion-history replay. Input writes leave all unselected states unchanged;
|
|
5611
|
+
after stepping, exact isolation holds for independent chains. Descendants or
|
|
5612
|
+
constraints that read a selected bone may react through their normal solver
|
|
5613
|
+
dependencies. Scope does not suppress that coupling or expand itself to cover it.
|
|
5614
|
+
`CO64` checks single/composed tracks, both resets, exact independent-state
|
|
5615
|
+
isolation, native per-constraint operations, empty/all/repeated selections and
|
|
5616
|
+
atomic named refusals; the existing `CO56`/`CO57` cover unscoped movement.
|
|
5617
|
+
|
|
5581
5618
|
For a live preview of one named constraint (issue #1541), both `LiveTrack` and
|
|
5582
5619
|
`LiveTracks` on `rig-c/core` expose:
|
|
5583
5620
|
|
|
@@ -12734,6 +12771,18 @@ omitted deform — each caught by name), a seeded population of
|
|
|
12734
12771
|
every construct above, the looping step against `openTrack`, and a steady-state
|
|
12735
12772
|
frame measured at 0 bytes with the collector off.
|
|
12736
12773
|
|
|
12774
|
+
**Timing is a separate measurement.** Exact poses and an allocation control do
|
|
12775
|
+
not establish a browser speed improvement. The
|
|
12776
|
+
[browser timing protocol](https://github.com/rig-c/core/blob/main/docs/RUNTIME_TIMING.md)
|
|
12777
|
+
requires an official `spine-core` pose-only or matching `spine-pixi-v8` reference
|
|
12778
|
+
with current rigc/official ratios first in every timing table, distinguishes that
|
|
12779
|
+
reference from rigc's own oracle and a previous product, and requires the actual
|
|
12780
|
+
product loader and evaluated runner to pass planted real-browser preflights
|
|
12781
|
+
before AA1. It preserves engine/animation row identity,
|
|
12782
|
+
and admits a gain only when both A/B paired medians strictly clear the envelope
|
|
12783
|
+
fixed by two A/A launches. Browser tooling and full machine receipts belong to
|
|
12784
|
+
the consumer; core owns this protocol, without a browser dependency.
|
|
12785
|
+
|
|
12737
12786
|
## 16. Compare sequential mesh candidates against one motion reference
|
|
12738
12787
|
|
|
12739
12788
|
Programmatic callers that gate candidates one at a time can import
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.75.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": {
|
|
@@ -645,8 +645,9 @@ export const DEFAULT_PHYSICS_ENVIRONMENT: PhysicsEnvironment = Object.freeze({ w
|
|
|
645
645
|
* pose. A constraint with no state yet has not been updated, and its first
|
|
646
646
|
* update is pending, so the runtime's translation of it is read by nothing.
|
|
647
647
|
*/
|
|
648
|
-
export function translatePhysics(ctx: PhysicsStepContext, x: number, y: number): void {
|
|
649
|
-
for (const s of ctx.states
|
|
648
|
+
export function translatePhysics(ctx: PhysicsStepContext, x: number, y: number, scope?: ReadonlySet<string>): void {
|
|
649
|
+
for (const [name, s] of ctx.states) {
|
|
650
|
+
if (scope !== undefined && !scope.has(name)) continue;
|
|
650
651
|
s.ux -= x;
|
|
651
652
|
s.uy -= y;
|
|
652
653
|
s.cx -= x;
|
|
@@ -661,11 +662,12 @@ export function translatePhysics(ctx: PhysicsStepContext, x: number, y: number):
|
|
|
661
662
|
* a translation of each constraint by how far that turn carries the origin it
|
|
662
663
|
* last saw, as `PhysicsConstraint.rotate` computes it.
|
|
663
664
|
*/
|
|
664
|
-
export function rotatePhysics(ctx: PhysicsStepContext, x: number, y: number, degrees: number): void {
|
|
665
|
+
export function rotatePhysics(ctx: PhysicsStepContext, x: number, y: number, degrees: number, scope?: ReadonlySet<string>): void {
|
|
665
666
|
const r = degrees * RUNTIME_RAD;
|
|
666
667
|
const cos = Math.cos(r);
|
|
667
668
|
const sin = Math.sin(r);
|
|
668
|
-
for (const s of ctx.states
|
|
669
|
+
for (const [name, s] of ctx.states) {
|
|
670
|
+
if (scope !== undefined && !scope.has(name)) continue;
|
|
669
671
|
const dx = s.cx - x;
|
|
670
672
|
const dy = s.cy - y;
|
|
671
673
|
const tx = dx * cos - dy * sin - dx;
|
package/src/core/raw.ts
CHANGED
|
@@ -728,20 +728,36 @@ export function environmentOf(ctx: PhysicsStepContext): PhysicsEnvironment {
|
|
|
728
728
|
return ctx.env ?? DEFAULT_PHYSICS_ENVIRONMENT;
|
|
729
729
|
}
|
|
730
730
|
|
|
731
|
+
/** Validate the complete explicit selection before any remembered state changes. */
|
|
732
|
+
export function checkedPhysicsScope(doc: CompiledDocument, scope: readonly string[] | undefined, who: string): ReadonlySet<string> | undefined {
|
|
733
|
+
if (scope === undefined) return undefined;
|
|
734
|
+
if (!Array.isArray(scope)) throw new CoreInputError(`${who} physics movement scope is ${String(scope)}, not an array of physics constraint names`);
|
|
735
|
+
const selected = new Set<string>();
|
|
736
|
+
for (const name of scope) {
|
|
737
|
+
if (typeof name !== 'string') throw new CoreInputError(`${who} physics movement scope name ${String(name)} is not a string`);
|
|
738
|
+
const constraint = doc.constraints.find((c) => c.kind === 'physics' && c.name === name)
|
|
739
|
+
?? doc.constraints.find((c) => c.name === name);
|
|
740
|
+
if (constraint === undefined) throw new CoreInputError(`${who} physics movement scope: no constraint "${name}"; this document declares [${doc.constraints.map((c) => c.name).join(', ')}]`);
|
|
741
|
+
if (constraint.kind !== 'physics') throw new CoreInputError(`${who} physics movement scope: constraint "${name}" is ${constraint.kind}, not physics`);
|
|
742
|
+
selected.add(name);
|
|
743
|
+
}
|
|
744
|
+
return selected;
|
|
745
|
+
}
|
|
746
|
+
|
|
731
747
|
/** A live track's `physicsTranslate` (issue #1416): `x` and `y` finite, or refused by name before anything moves; then `translatePhysics`. */
|
|
732
|
-
export function translateOn(ctx: PhysicsStepContext, x: number, y: number, who: string): void {
|
|
748
|
+
export function translateOn(ctx: PhysicsStepContext, x: number, y: number, who: string, scope?: ReadonlySet<string>): void {
|
|
733
749
|
for (const [k, v] of [['x', x], ['y', y]] as const) {
|
|
734
750
|
if (typeof v !== 'number' || !Number.isFinite(v)) throw new CoreInputError(`${who} physicsTranslate's ${k} is ${String(v)}, not a finite distance in the skeleton's world`);
|
|
735
751
|
}
|
|
736
|
-
translatePhysics(ctx, x, y);
|
|
752
|
+
translatePhysics(ctx, x, y, scope);
|
|
737
753
|
}
|
|
738
754
|
|
|
739
755
|
/** A live track's `physicsRotate` (issue #1416): `x`, `y` and `degrees` finite, or refused by name before anything moves; then `rotatePhysics`. */
|
|
740
|
-
export function rotateOn(ctx: PhysicsStepContext, x: number, y: number, degrees: number, who: string): void {
|
|
756
|
+
export function rotateOn(ctx: PhysicsStepContext, x: number, y: number, degrees: number, who: string, scope?: ReadonlySet<string>): void {
|
|
741
757
|
for (const [k, v] of [['x', x], ['y', y], ['degrees', degrees]] as const) {
|
|
742
758
|
if (typeof v !== 'number' || !Number.isFinite(v)) throw new CoreInputError(`${who} physicsRotate's ${k} is ${String(v)}, not a finite number`);
|
|
743
759
|
}
|
|
744
|
-
rotatePhysics(ctx, x, y, degrees);
|
|
760
|
+
rotatePhysics(ctx, x, y, degrees, scope);
|
|
745
761
|
}
|
|
746
762
|
|
|
747
763
|
/**
|
package/src/core/track.ts
CHANGED
|
@@ -222,7 +222,7 @@
|
|
|
222
222
|
* history, so a seek is "open, then step to the time".
|
|
223
223
|
*/
|
|
224
224
|
import { CoreInputError, type CompiledDocument } from './index.ts';
|
|
225
|
-
import { checkedClock, environmentOf, openRawTrack, rotateOn, setEnvironmentOn, stepRawTrack, translateOn, type RawPose, type RawReset, type TrackState, type WalkMode } from './raw.ts';
|
|
225
|
+
import { checkedClock, checkedPhysicsScope, environmentOf, openRawTrack, rotateOn, setEnvironmentOn, stepRawTrack, translateOn, type RawPose, type RawReset, type TrackState, type WalkMode } from './raw.ts';
|
|
226
226
|
import type { LocalAdjust, TimelinePlant } from './animation.ts';
|
|
227
227
|
import type { PhysicsEnvironment } from './constraints_physics.ts';
|
|
228
228
|
import { LivePhysicsParameters, type PhysicsParameterPatch, type PhysicsParameterRead } from './physics_parameters.ts';
|
|
@@ -261,10 +261,12 @@ export interface LiveTrack {
|
|
|
261
261
|
readPhysicsParameters(name: string): PhysicsParameterRead;
|
|
262
262
|
/** Removes the patch for the next pose without resetting velocities, offsets or the clock. */
|
|
263
263
|
clearPhysicsParameters(name: string): void;
|
|
264
|
-
/**
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
264
|
+
/** Scope omitted: all physics; otherwise exact physics names, validated atomically. Empty selects none, duplicates select once.
|
|
265
|
+
* Movement changes remembered world origins synchronously, before the next evaluation.
|
|
266
|
+
* spine-core's `Skeleton.physicsTranslate`: the consumer moved the whole rig by `(x, y)` in the skeleton's world (y up) since the last pose, and every physics constraint's next update sees it. */
|
|
267
|
+
physicsTranslate(x: number, y: number, scope?: readonly string[]): void;
|
|
268
|
+
/** Same scope contract as physicsTranslate. The consumer turned by `degrees` (counter-clockwise) about explicit `(x, y)` in the skeleton's world; changes remembered origins, not tip direction or angular velocity. */
|
|
269
|
+
physicsRotate(x: number, y: number, degrees: number, scope?: readonly string[]): void;
|
|
268
270
|
}
|
|
269
271
|
|
|
270
272
|
/** The two modes: a track that holds at its duration is the raw entry's walk; a looping one fires its events across the wrap and cuts its clipped rows (`live`). Neither keeps a non-finite value. */
|
|
@@ -336,14 +338,14 @@ class Track implements LiveTrack {
|
|
|
336
338
|
this.parameters.clear(name);
|
|
337
339
|
}
|
|
338
340
|
|
|
339
|
-
physicsTranslate(x: number, y: number): void {
|
|
341
|
+
physicsTranslate(x: number, y: number, scope?: readonly string[]): void {
|
|
340
342
|
this.live('physicsTranslate');
|
|
341
|
-
translateOn(this.track.ctx, x, y, 'the live track\'s');
|
|
343
|
+
translateOn(this.track.ctx, x, y, 'the live track\'s', checkedPhysicsScope(this.track.doc, scope, 'the live track\'s'));
|
|
342
344
|
}
|
|
343
345
|
|
|
344
|
-
physicsRotate(x: number, y: number, degrees: number): void {
|
|
346
|
+
physicsRotate(x: number, y: number, degrees: number, scope?: readonly string[]): void {
|
|
345
347
|
this.live('physicsRotate');
|
|
346
|
-
rotateOn(this.track.ctx, x, y, degrees, 'the live track\'s');
|
|
348
|
+
rotateOn(this.track.ctx, x, y, degrees, 'the live track\'s', checkedPhysicsScope(this.track.doc, scope, 'the live track\'s'));
|
|
347
349
|
}
|
|
348
350
|
|
|
349
351
|
/** A refused track refuses every call that would move it, naming the step it was refused at. */
|
package/src/core/tracks.ts
CHANGED
|
@@ -164,7 +164,7 @@ import { activeBones, CoreInputError, rawNumber, shownAttachment, viewColours, W
|
|
|
164
164
|
import { channelAt, keyIndexAt, SLOT_COLOUR_TIMELINES, slotRowsFrom, steppedLocalsWorld, type BoneTimelineKind, type CoreKey, type LocalAdjust, type SlotBase, type SlotColourKind, type TimelinePlant } from './animation.ts';
|
|
165
165
|
import { deformAt, setupArray, type CoreAttachmentTimeline } from './deform.ts';
|
|
166
166
|
import { eventsFired, type CoreEventKey, type CoreEventRow } from './events.ts';
|
|
167
|
-
import { checkedClock, environmentOf, openRawTracksPath, rotateOn, setEnvironmentOn, translateOn, type RawPose, type RawReset, type RawTracksPath } from './raw.ts';
|
|
167
|
+
import { checkedClock, checkedPhysicsScope, environmentOf, openRawTracksPath, rotateOn, setEnvironmentOn, translateOn, type RawPose, type RawReset, type RawTracksPath } from './raw.ts';
|
|
168
168
|
import type { PhysicsEnvironment } from './constraints_physics.ts';
|
|
169
169
|
import { LivePhysicsParameters, type PhysicsParameterPatch, type PhysicsParameterRead } from './physics_parameters.ts';
|
|
170
170
|
import { slotTimelinesApply } from './skins.ts';
|
|
@@ -240,10 +240,12 @@ export interface LiveTracks {
|
|
|
240
240
|
readPhysicsParameters(name: string): PhysicsParameterRead;
|
|
241
241
|
/** Removes the patch without resetting the physics history, as LiveTrack.clearPhysicsParameters. */
|
|
242
242
|
clearPhysicsParameters(name: string): void;
|
|
243
|
-
/**
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
243
|
+
/** Scope omitted: all physics; otherwise exact physics names, validated atomically. Empty selects none, duplicates select once.
|
|
244
|
+
* Movement changes remembered world origins synchronously, before the next evaluation.
|
|
245
|
+
* spine-core's `Skeleton.physicsTranslate`, as `LiveTrack.physicsTranslate`. */
|
|
246
|
+
physicsTranslate(x: number, y: number, scope?: readonly string[]): void;
|
|
247
|
+
/** Same exact-name scope and explicit world pivot as `LiveTrack.physicsRotate`. */
|
|
248
|
+
physicsRotate(x: number, y: number, degrees: number, scope?: readonly string[]): void;
|
|
247
249
|
}
|
|
248
250
|
|
|
249
251
|
/**
|
|
@@ -1384,14 +1386,14 @@ class Tracks implements LiveTracks {
|
|
|
1384
1386
|
this.parameters.clear(name);
|
|
1385
1387
|
}
|
|
1386
1388
|
|
|
1387
|
-
physicsTranslate(x: number, y: number): void {
|
|
1389
|
+
physicsTranslate(x: number, y: number, scope?: readonly string[]): void {
|
|
1388
1390
|
this.live('physicsTranslate');
|
|
1389
|
-
translateOn(this.path.ctx, x, y, 'the live tracks\'');
|
|
1391
|
+
translateOn(this.path.ctx, x, y, 'the live tracks\'', checkedPhysicsScope(this.doc, scope, 'the live tracks\''));
|
|
1390
1392
|
}
|
|
1391
1393
|
|
|
1392
|
-
physicsRotate(x: number, y: number, degrees: number): void {
|
|
1394
|
+
physicsRotate(x: number, y: number, degrees: number, scope?: readonly string[]): void {
|
|
1393
1395
|
this.live('physicsRotate');
|
|
1394
|
-
rotateOn(this.path.ctx, x, y, degrees, 'the live tracks\'');
|
|
1396
|
+
rotateOn(this.path.ctx, x, y, degrees, 'the live tracks\'', checkedPhysicsScope(this.doc, scope, 'the live tracks\''));
|
|
1395
1397
|
}
|
|
1396
1398
|
|
|
1397
1399
|
private live(call: string): void {
|