@glissade/core 0.7.0 → 0.8.0-pre.1
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/dist/index.d.ts +80 -16
- package/dist/index.js +207 -45
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -72,6 +72,9 @@ interface ValueType<T> {
|
|
|
72
72
|
scale?(a: T, k: number): T;
|
|
73
73
|
/** Type-class handoff default (§B.1): spring for kinetic, cut for hold-only. */
|
|
74
74
|
defaultHandoff?: HandoffKind;
|
|
75
|
+
/** Document (de)serialization; default identity for JSON-native types (§2.2). */
|
|
76
|
+
serialize?(value: T): unknown;
|
|
77
|
+
deserialize?(raw: unknown): T;
|
|
75
78
|
}
|
|
76
79
|
type ValueTypeId = 'number' | 'vec2' | 'color' | 'string' | 'boolean' | (string & {});
|
|
77
80
|
declare function registerValueType<T>(vt: ValueType<T>): void;
|
|
@@ -82,6 +85,8 @@ declare function getValueType<T = unknown>(id: ValueTypeId): ValueType<T>;
|
|
|
82
85
|
declare const numberType: ValueType<number>;
|
|
83
86
|
declare const vec2Equals: (a: Vec2, b: Vec2) => boolean;
|
|
84
87
|
declare const vec2Type: ValueType<Vec2>;
|
|
88
|
+
/** vec2 swept along a circular arc: polar lerp of radius + shortest-path angle (§2.2). */
|
|
89
|
+
declare const vec2ArcType: ValueType<Vec2>;
|
|
85
90
|
declare const colorType: ValueType<string>;
|
|
86
91
|
declare const stringType: ValueType<string>;
|
|
87
92
|
declare const booleanType: ValueType<boolean>;
|
|
@@ -280,6 +285,8 @@ interface Key<T = unknown> {
|
|
|
280
285
|
id?: string;
|
|
281
286
|
/** Builder-resolved implicit from-value; re-resolved on merge (§2.6, §6.2). */
|
|
282
287
|
derived?: boolean;
|
|
288
|
+
/** Reserved (§4.7): a v2 synthesized-transition leading key reads the live value. v1 accepts but ignores it. */
|
|
289
|
+
from?: 'live';
|
|
283
290
|
}
|
|
284
291
|
interface Track<T = unknown> {
|
|
285
292
|
/** Canonical path: '<nodeId>/<prop.path>', e.g. 'circle/position.x'. */
|
|
@@ -289,6 +296,8 @@ interface Track<T = unknown> {
|
|
|
289
296
|
keys: Key<T>[];
|
|
290
297
|
/** Studio may own this track's keys via sidecar (§6.2). */
|
|
291
298
|
editable?: boolean;
|
|
299
|
+
/** Reserved (§2.2/§B.6): v2 additive blending. v1 accepts but ignores it (coalesce stays last-wins). */
|
|
300
|
+
additive?: boolean;
|
|
292
301
|
}
|
|
293
302
|
declare class TrackValidationError extends Error {
|
|
294
303
|
constructor(target: string, message: string);
|
|
@@ -477,6 +486,19 @@ interface TimelineBuilder {
|
|
|
477
486
|
}): TimelineBuilder;
|
|
478
487
|
/** Compiles to a marker; the callback is Player-registered, never serialized (§4.2). */
|
|
479
488
|
call(fn: () => void, at?: Position): TimelineBuilder;
|
|
489
|
+
/**
|
|
490
|
+
* A named cue marker — the composer-signal substrate. Fired on crossing via
|
|
491
|
+
* `player.onCue(kind, …)` and emitted to the render-time `cues.json`. Every
|
|
492
|
+
* cue carries a `data.kind` (default `'cue'`); pass your own to group them
|
|
493
|
+
* (e.g. `{ kind: 'chapter', title: '…' }`). `data.title` becomes the chapter
|
|
494
|
+
* label in `--chapters vtt`.
|
|
495
|
+
*/
|
|
496
|
+
cue(at: Position, name: string, data?: Json): TimelineBuilder;
|
|
497
|
+
/** An ad-break cue: a marker with `data.kind: 'ad-break'` + optional duration (§ad-break). */
|
|
498
|
+
adBreak(at: Position, opts?: {
|
|
499
|
+
id?: string;
|
|
500
|
+
duration?: number;
|
|
501
|
+
}): TimelineBuilder;
|
|
480
502
|
/** Mark the preceding track editable for the studio (§6.2). */
|
|
481
503
|
editable(): TimelineBuilder;
|
|
482
504
|
}
|
|
@@ -571,36 +593,78 @@ interface CheckpointedSim {
|
|
|
571
593
|
declare function bakeCheckpointed<W, S = W>(cfg: CheckpointedBakeConfig<W, S>): CheckpointedSim;
|
|
572
594
|
//#endregion
|
|
573
595
|
//#region src/sidecar.d.ts
|
|
596
|
+
type OrphanReason = 'node-missing' | 'prop-missing' | 'type-changed';
|
|
597
|
+
interface SidecarTrackEntry {
|
|
598
|
+
/** value type, so an editor-created track binds without a code baseline. */
|
|
599
|
+
type: ValueTypeId;
|
|
600
|
+
/** hash of the code baseline this track branched from; null = editor-created. */
|
|
601
|
+
baseHash: string | null;
|
|
602
|
+
keys: Key[];
|
|
603
|
+
}
|
|
604
|
+
interface SidecarOrphan {
|
|
605
|
+
type: ValueTypeId;
|
|
606
|
+
keys: Key[];
|
|
607
|
+
reason: OrphanReason;
|
|
608
|
+
}
|
|
609
|
+
interface SidecarTimelineEntry {
|
|
610
|
+
/** editor-owned tracks, keyed by canonical target. */
|
|
611
|
+
tracks: Record<string, SidecarTrackEntry>;
|
|
612
|
+
/** editor-created labels. */
|
|
613
|
+
labels?: Record<string, number>;
|
|
614
|
+
/** tracks parked off the merge because their target drifted (§6.2 rule 3). */
|
|
615
|
+
orphans?: Record<string, SidecarOrphan>;
|
|
616
|
+
}
|
|
574
617
|
interface SidecarDoc {
|
|
618
|
+
sidecarVersion: 2;
|
|
619
|
+
/** edits namespaced by timeline id; the linear timeline is 'main'. */
|
|
620
|
+
timelines: Record<string, SidecarTimelineEntry>;
|
|
621
|
+
}
|
|
622
|
+
/** The flat v1 shape, accepted on load and migrated forward. */
|
|
623
|
+
interface SidecarDocV1 {
|
|
575
624
|
sidecarVersion: 1;
|
|
576
|
-
/** Editor-owned tracks, replacing same-target code tracks wholesale. */
|
|
577
625
|
tracks: Track[];
|
|
578
|
-
/** Editor-created labels; code labels are authoritative and win on a name collision (§6.2). */
|
|
579
626
|
labels?: Record<string, number>;
|
|
580
627
|
}
|
|
581
628
|
declare class SidecarVersionError extends Error {
|
|
582
629
|
constructor(version: unknown);
|
|
583
630
|
}
|
|
584
631
|
declare function emptySidecar(): SidecarDoc;
|
|
632
|
+
/** Lift a v1 (or already-v2) document to the v2 shape. Throws on unknown versions. */
|
|
633
|
+
declare function migrateSidecar(doc: SidecarDoc | SidecarDocV1 | null | undefined): SidecarDoc | null;
|
|
634
|
+
/** Stable hash of a code track's keys — the baseline an editor branch records. */
|
|
635
|
+
declare function hashKeys(keys: readonly Key[]): string;
|
|
636
|
+
/** Assign stable monotonic `k<N>` ids to keys lacking one; existing ids preserved. */
|
|
637
|
+
declare function assignKeyIds(keys: readonly Key[]): Key[];
|
|
638
|
+
/**
|
|
639
|
+
* Set one editable track's keys in the sidecar (§6.2) — the studio write path.
|
|
640
|
+
* `codeBaselineKeys` is the code track this branches from (null = editor-created
|
|
641
|
+
* with no baseline). Keys get stable `k<N>` ids. Returns a new document.
|
|
642
|
+
*/
|
|
643
|
+
declare function setSidecarTrack(doc: SidecarDoc, timelineId: string, target: string, type: ValueTypeId, keys: Key[], codeBaselineKeys: readonly Key[] | null): SidecarDoc;
|
|
644
|
+
interface MergeResult {
|
|
645
|
+
timeline: Timeline;
|
|
646
|
+
/** targets whose code baseline changed beneath the editor's keys (§6.2 rule 2). */
|
|
647
|
+
drift: string[];
|
|
648
|
+
/** sidecar tracks parked off the merge (§6.2 rule 3). */
|
|
649
|
+
orphans: Record<string, SidecarOrphan>;
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Merge with the full §6.2 report: the bindable Timeline, the list of targets
|
|
653
|
+
* whose code baseline drifted, and the orphaned tracks (type-changed, or whose
|
|
654
|
+
* code track vanished). Orphans are NEVER merged, so a drifted edit can't make
|
|
655
|
+
* the whole overlay fail to bind — the good edits survive. Inputs unmutated.
|
|
656
|
+
*/
|
|
657
|
+
declare function mergeSidecarDetailed(code: Timeline, sidecar: SidecarDoc | SidecarDocV1 | null | undefined): MergeResult;
|
|
658
|
+
/** Merge the sidecar overlay onto the code baseline → a bindable Timeline (§6.2). */
|
|
659
|
+
declare function mergeSidecar(code: Timeline, sidecar: SidecarDoc | SidecarDocV1 | null | undefined): Timeline;
|
|
585
660
|
/**
|
|
586
661
|
* Editor-edit normalization (§2.7 invariant): a spring-eased key's t is
|
|
587
662
|
* intrinsic — prev.t + spring.duration(cfg) — so after any retime, sort and
|
|
588
663
|
* re-pin spring keys to their predecessors. Dragging a spring key itself
|
|
589
664
|
* therefore snaps back; retiming its predecessor carries it along. Returns a
|
|
590
|
-
* new array. Colliding keys are NUDGED apart (+1ms), never deleted — an
|
|
591
|
-
*
|
|
665
|
+
* new array. Colliding keys are NUDGED apart (+1ms), never deleted — an editor
|
|
666
|
+
* must not silently destroy keyframe data on an exact-t collision.
|
|
592
667
|
*/
|
|
593
668
|
declare function normalizeEditedKeys(keys: Key[]): Key[];
|
|
594
|
-
/**
|
|
595
|
-
* Merge rules (§6.2, schema-drift resolution):
|
|
596
|
-
* - a sidecar track whose target exists in code REPLACES that track's keys
|
|
597
|
-
* (the editor owns it; `editable` is preserved on the result);
|
|
598
|
-
* - a sidecar track with no code counterpart is ADDED (editor-created track);
|
|
599
|
-
* - sidecar tracks targeting nothing the scene can bind fail later at
|
|
600
|
-
* bindTimeline with UnboundTargetError — surfaced, never silently dropped;
|
|
601
|
-
* - code tracks without sidecar counterparts pass through untouched.
|
|
602
|
-
* The input documents are not mutated.
|
|
603
|
-
*/
|
|
604
|
-
declare function mergeSidecar(code: Timeline, sidecar: SidecarDoc | null | undefined): Timeline;
|
|
605
669
|
//#endregion
|
|
606
|
-
export { type AssetRef, type AudioClip, type BakeConfig, BakeError, type BindTarget, type BindableSignal, type BoundTimeline, type CheckpointedBakeConfig, type CheckpointedSim, type ChildEntry, CircularDependencyError, ColorParseError, type CompiledTimeline, type CurveSampler, DEFAULT_EASE, type DevWarning, type EaseSpec, type EasingFn, type Equals, type GainEnvelope, type HandoffKind, type Json, type Key, type KeyOpts, type Marker, type OkLab, type PathContour, type PathValue, type Playhead, type Position, PositionError, type ReadonlySignal, type RetargetSpring, type Rgba, type Rng, type SidecarDoc, SidecarVersionError, type Signal, type SignalOptions, type SpringConfig, type SpringEase, TARGET_PATH, type TargetCarrier, type Timeline, type TimelineBuilder, type TimelineInit, TimelineValidationError, type Track, TrackValidationError, type TweenOpts, type TweenTarget, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, type ValueType, type ValueTypeId, ValueTypeInferenceError, type Vec2, type Vec2Signal, WriteDuringEvaluationError, audioOffsetSamples, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, pathType, random, registerValueType, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, signal, spring, springEasing, springEasingDerivative, springPresets, springTo, stagger, stringType, timeline, track, untracked, validateTrack, vec2Equals, vec2Signal, vec2Type, velocityAt };
|
|
670
|
+
export { type AssetRef, type AudioClip, type BakeConfig, BakeError, type BindTarget, type BindableSignal, type BoundTimeline, type CheckpointedBakeConfig, type CheckpointedSim, type ChildEntry, CircularDependencyError, ColorParseError, type CompiledTimeline, type CurveSampler, DEFAULT_EASE, type DevWarning, type EaseSpec, type EasingFn, type Equals, type GainEnvelope, type HandoffKind, type Json, type Key, type KeyOpts, type Marker, type MergeResult, type OkLab, type OrphanReason, type PathContour, type PathValue, type Playhead, type Position, PositionError, type ReadonlySignal, type RetargetSpring, type Rgba, type Rng, type SidecarDoc, type SidecarDocV1, type SidecarOrphan, type SidecarTimelineEntry, type SidecarTrackEntry, SidecarVersionError, type Signal, type SignalOptions, type SpringConfig, type SpringEase, TARGET_PATH, type TargetCarrier, type Timeline, type TimelineBuilder, type TimelineInit, TimelineValidationError, type Track, TrackValidationError, type TweenOpts, type TweenTarget, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, type ValueType, type ValueTypeId, ValueTypeInferenceError, type Vec2, type Vec2Signal, WriteDuringEvaluationError, assignKeyIds, audioOffsetSamples, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, hashKeys, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, mergeSidecarDetailed, migrateSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, pathType, random, registerValueType, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, setSidecarTrack, signal, spring, springEasing, springEasingDerivative, springPresets, springTo, stagger, stringType, timeline, track, untracked, validateTrack, vec2ArcType, vec2Equals, vec2Signal, vec2Type, velocityAt };
|
package/dist/index.js
CHANGED
|
@@ -341,6 +341,24 @@ const vec2Type = {
|
|
|
341
341
|
scale: (a, k) => [a[0] * k, a[1] * k],
|
|
342
342
|
defaultHandoff: "spring"
|
|
343
343
|
};
|
|
344
|
+
/** vec2 swept along a circular arc: polar lerp of radius + shortest-path angle (§2.2). */
|
|
345
|
+
const vec2ArcType = {
|
|
346
|
+
id: "vec2-arc",
|
|
347
|
+
lerp: (a, b, t) => {
|
|
348
|
+
const ra = Math.hypot(a[0], a[1]);
|
|
349
|
+
const rb = Math.hypot(b[0], b[1]);
|
|
350
|
+
const angA = Math.atan2(a[1], a[0]);
|
|
351
|
+
let dAng = Math.atan2(b[1], b[0]) - angA;
|
|
352
|
+
while (dAng > Math.PI) dAng -= 2 * Math.PI;
|
|
353
|
+
while (dAng < -Math.PI) dAng += 2 * Math.PI;
|
|
354
|
+
const r = ra + (rb - ra) * t;
|
|
355
|
+
const ang = angA + dAng * t;
|
|
356
|
+
return [r * Math.cos(ang), r * Math.sin(ang)];
|
|
357
|
+
},
|
|
358
|
+
extrapolates: true,
|
|
359
|
+
equals: vec2Equals,
|
|
360
|
+
defaultHandoff: "blend-from-frozen"
|
|
361
|
+
};
|
|
344
362
|
const colorType = {
|
|
345
363
|
id: "color",
|
|
346
364
|
lerp: lerpColor,
|
|
@@ -435,6 +453,7 @@ function inferValueType(value) {
|
|
|
435
453
|
}
|
|
436
454
|
registerValueType(numberType);
|
|
437
455
|
registerValueType(vec2Type);
|
|
456
|
+
registerValueType(vec2ArcType);
|
|
438
457
|
registerValueType(colorType);
|
|
439
458
|
registerValueType(stringType);
|
|
440
459
|
registerValueType(booleanType);
|
|
@@ -1365,6 +1384,30 @@ function buildTimeline(build, init = {}) {
|
|
|
1365
1384
|
callbacks.set(name, fn);
|
|
1366
1385
|
return builder;
|
|
1367
1386
|
},
|
|
1387
|
+
cue(at, name, data) {
|
|
1388
|
+
const merged = data !== void 0 && data !== null && typeof data === "object" && !Array.isArray(data) ? {
|
|
1389
|
+
kind: "cue",
|
|
1390
|
+
...data
|
|
1391
|
+
} : { kind: "cue" };
|
|
1392
|
+
markers.push({
|
|
1393
|
+
t: resolvePosition(at),
|
|
1394
|
+
name,
|
|
1395
|
+
data: merged
|
|
1396
|
+
});
|
|
1397
|
+
return builder;
|
|
1398
|
+
},
|
|
1399
|
+
adBreak(at, opts = {}) {
|
|
1400
|
+
const data = {
|
|
1401
|
+
kind: "ad-break",
|
|
1402
|
+
...opts.duration !== void 0 ? { duration: opts.duration } : {}
|
|
1403
|
+
};
|
|
1404
|
+
markers.push({
|
|
1405
|
+
t: resolvePosition(at),
|
|
1406
|
+
name: opts.id ?? "ad-break",
|
|
1407
|
+
data
|
|
1408
|
+
});
|
|
1409
|
+
return builder;
|
|
1410
|
+
},
|
|
1368
1411
|
editable() {
|
|
1369
1412
|
const last = insertions[insertions.length - 1];
|
|
1370
1413
|
if (!last) throw new TimelineValidationError(".editable() requires a preceding insertion");
|
|
@@ -1624,85 +1667,204 @@ function bakeCheckpointed(cfg) {
|
|
|
1624
1667
|
//#region src/sidecar.ts
|
|
1625
1668
|
/**
|
|
1626
1669
|
* The editor sidecar (DESIGN.md §6.2): code declares scene structure and
|
|
1627
|
-
* programmatic tracks; the studio owns keyframe data persisted
|
|
1628
|
-
*
|
|
1629
|
-
*
|
|
1670
|
+
* programmatic tracks; the studio owns keyframe data persisted next to the
|
|
1671
|
+
* scene module and merged at track granularity. Versioned independently of the
|
|
1672
|
+
* API (§7.4) — breaking it orphans users' files, so v1 documents migrate.
|
|
1673
|
+
*
|
|
1674
|
+
* v2 namespaces edits by timeline id ('main' for the linear timeline; v2
|
|
1675
|
+
* machines add more), keys tracks by canonical target, records the code
|
|
1676
|
+
* baseline hash for drift detection, parks drifted tracks as `orphans`, and
|
|
1677
|
+
* gives keys stable `k<N>` ids.
|
|
1630
1678
|
*/
|
|
1679
|
+
const MAIN = "main";
|
|
1631
1680
|
var SidecarVersionError = class extends Error {
|
|
1632
1681
|
constructor(version) {
|
|
1633
|
-
super(`unsupported sidecar version ${String(version)}; this build reads sidecarVersion 1`);
|
|
1682
|
+
super(`unsupported sidecar version ${String(version)}; this build reads sidecarVersion 1 or 2`);
|
|
1634
1683
|
this.name = "SidecarVersionError";
|
|
1635
1684
|
}
|
|
1636
1685
|
};
|
|
1637
1686
|
function emptySidecar() {
|
|
1638
1687
|
return {
|
|
1639
|
-
sidecarVersion:
|
|
1640
|
-
|
|
1688
|
+
sidecarVersion: 2,
|
|
1689
|
+
timelines: { [MAIN]: { tracks: {} } }
|
|
1641
1690
|
};
|
|
1642
1691
|
}
|
|
1692
|
+
/** Lift a v1 (or already-v2) document to the v2 shape. Throws on unknown versions. */
|
|
1693
|
+
function migrateSidecar(doc) {
|
|
1694
|
+
if (!doc) return null;
|
|
1695
|
+
if (doc.sidecarVersion === 2) return doc;
|
|
1696
|
+
if (doc.sidecarVersion === 1) {
|
|
1697
|
+
const tracks = {};
|
|
1698
|
+
for (const t of doc.tracks) tracks[t.target] = {
|
|
1699
|
+
type: t.type,
|
|
1700
|
+
baseHash: null,
|
|
1701
|
+
keys: t.keys
|
|
1702
|
+
};
|
|
1703
|
+
const main = { tracks };
|
|
1704
|
+
if (doc.labels) main.labels = doc.labels;
|
|
1705
|
+
return {
|
|
1706
|
+
sidecarVersion: 2,
|
|
1707
|
+
timelines: { [MAIN]: main }
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
throw new SidecarVersionError(doc.sidecarVersion);
|
|
1711
|
+
}
|
|
1712
|
+
/** Stable hash of a code track's keys — the baseline an editor branch records. */
|
|
1713
|
+
function hashKeys(keys) {
|
|
1714
|
+
const s = JSON.stringify(keys.map((k) => [
|
|
1715
|
+
k.t,
|
|
1716
|
+
k.value,
|
|
1717
|
+
k.ease ?? null,
|
|
1718
|
+
k.interp ?? null
|
|
1719
|
+
]));
|
|
1720
|
+
let h = 2166136261;
|
|
1721
|
+
for (let i = 0; i < s.length; i++) {
|
|
1722
|
+
h ^= s.charCodeAt(i);
|
|
1723
|
+
h = Math.imul(h, 16777619);
|
|
1724
|
+
}
|
|
1725
|
+
return (h >>> 0).toString(16);
|
|
1726
|
+
}
|
|
1727
|
+
/** Assign stable monotonic `k<N>` ids to keys lacking one; existing ids preserved. */
|
|
1728
|
+
function assignKeyIds(keys) {
|
|
1729
|
+
let max = -1;
|
|
1730
|
+
for (const k of keys) {
|
|
1731
|
+
const m = k.id ? /^k(\d+)$/.exec(k.id) : null;
|
|
1732
|
+
if (m) max = Math.max(max, Number(m[1]));
|
|
1733
|
+
}
|
|
1734
|
+
return keys.map((k) => k.id ? { ...k } : {
|
|
1735
|
+
...k,
|
|
1736
|
+
id: `k${++max}`
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1643
1739
|
/**
|
|
1644
|
-
*
|
|
1645
|
-
*
|
|
1646
|
-
*
|
|
1647
|
-
* therefore snaps back; retiming its predecessor carries it along. Returns a
|
|
1648
|
-
* new array. Colliding keys are NUDGED apart (+1ms), never deleted — an
|
|
1649
|
-
* editor must not silently destroy keyframe data on an exact-t collision.
|
|
1740
|
+
* Set one editable track's keys in the sidecar (§6.2) — the studio write path.
|
|
1741
|
+
* `codeBaselineKeys` is the code track this branches from (null = editor-created
|
|
1742
|
+
* with no baseline). Keys get stable `k<N>` ids. Returns a new document.
|
|
1650
1743
|
*/
|
|
1651
|
-
function
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1744
|
+
function setSidecarTrack(doc, timelineId, target, type, keys, codeBaselineKeys) {
|
|
1745
|
+
const tl = doc.timelines[timelineId] ?? { tracks: {} };
|
|
1746
|
+
const entry = {
|
|
1747
|
+
type,
|
|
1748
|
+
baseHash: codeBaselineKeys ? hashKeys(codeBaselineKeys) : null,
|
|
1749
|
+
keys: assignKeyIds(keys)
|
|
1750
|
+
};
|
|
1751
|
+
return {
|
|
1752
|
+
...doc,
|
|
1753
|
+
timelines: {
|
|
1754
|
+
...doc.timelines,
|
|
1755
|
+
[timelineId]: {
|
|
1756
|
+
...tl,
|
|
1757
|
+
tracks: {
|
|
1758
|
+
...tl.tracks,
|
|
1759
|
+
[target]: entry
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1657
1762
|
}
|
|
1658
|
-
|
|
1659
|
-
}
|
|
1660
|
-
for (let i = 1; i < out.length; i++) if (out[i].t <= out[i - 1].t) out[i].t = out[i - 1].t + .001;
|
|
1661
|
-
return out;
|
|
1763
|
+
};
|
|
1662
1764
|
}
|
|
1663
1765
|
/**
|
|
1664
|
-
*
|
|
1665
|
-
* -
|
|
1666
|
-
*
|
|
1667
|
-
*
|
|
1668
|
-
* - sidecar tracks targeting nothing the scene can bind fail later at
|
|
1669
|
-
* bindTimeline with UnboundTargetError — surfaced, never silently dropped;
|
|
1670
|
-
* - code tracks without sidecar counterparts pass through untouched.
|
|
1671
|
-
* The input documents are not mutated.
|
|
1766
|
+
* Re-resolve `derived:true` leading keys against the merged track (§2.6): a
|
|
1767
|
+
* derived from-key duplicates the preceding key's held value, so an upstream
|
|
1768
|
+
* edit must flow into it or the segment pops at its start. Build-time derived
|
|
1769
|
+
* keys are already correct; this fixes the ones an edit moved beneath.
|
|
1672
1770
|
*/
|
|
1673
|
-
function
|
|
1674
|
-
if (!
|
|
1675
|
-
|
|
1676
|
-
|
|
1771
|
+
function reresolveDerived(keys) {
|
|
1772
|
+
if (!keys.some((k) => k.derived)) return keys;
|
|
1773
|
+
return keys.map((k, i) => k.derived && i > 0 ? {
|
|
1774
|
+
...k,
|
|
1775
|
+
value: keys[i - 1].value
|
|
1776
|
+
} : k);
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Merge with the full §6.2 report: the bindable Timeline, the list of targets
|
|
1780
|
+
* whose code baseline drifted, and the orphaned tracks (type-changed, or whose
|
|
1781
|
+
* code track vanished). Orphans are NEVER merged, so a drifted edit can't make
|
|
1782
|
+
* the whole overlay fail to bind — the good edits survive. Inputs unmutated.
|
|
1783
|
+
*/
|
|
1784
|
+
function mergeSidecarDetailed(code, sidecar) {
|
|
1785
|
+
const doc = migrateSidecar(sidecar);
|
|
1786
|
+
if (!doc) return {
|
|
1787
|
+
timeline: code,
|
|
1788
|
+
drift: [],
|
|
1789
|
+
orphans: {}
|
|
1790
|
+
};
|
|
1791
|
+
const main = doc.timelines[MAIN] ?? { tracks: {} };
|
|
1792
|
+
const overlay = new Map(Object.entries(main.tracks));
|
|
1793
|
+
const drift = [];
|
|
1794
|
+
const orphans = { ...main.orphans ?? {} };
|
|
1677
1795
|
const tracks = code.tracks.map((t) => {
|
|
1678
|
-
const
|
|
1679
|
-
if (!
|
|
1796
|
+
const entry = overlay.get(t.target);
|
|
1797
|
+
if (!entry) return t;
|
|
1680
1798
|
overlay.delete(t.target);
|
|
1799
|
+
if (entry.type !== t.type) {
|
|
1800
|
+
orphans[t.target] = {
|
|
1801
|
+
type: entry.type,
|
|
1802
|
+
keys: entry.keys,
|
|
1803
|
+
reason: "type-changed"
|
|
1804
|
+
};
|
|
1805
|
+
return t;
|
|
1806
|
+
}
|
|
1807
|
+
if (entry.baseHash !== null && entry.baseHash !== hashKeys(t.keys)) drift.push(t.target);
|
|
1681
1808
|
return {
|
|
1682
1809
|
...t,
|
|
1683
|
-
keys:
|
|
1810
|
+
keys: reresolveDerived(entry.keys.map((k) => ({ ...k }))),
|
|
1684
1811
|
editable: true
|
|
1685
1812
|
};
|
|
1686
1813
|
});
|
|
1687
|
-
for (const
|
|
1688
|
-
|
|
1689
|
-
keys:
|
|
1814
|
+
for (const [target, entry] of overlay) if (entry.baseHash !== null) orphans[target] = {
|
|
1815
|
+
type: entry.type,
|
|
1816
|
+
keys: entry.keys,
|
|
1817
|
+
reason: "prop-missing"
|
|
1818
|
+
};
|
|
1819
|
+
else tracks.push({
|
|
1820
|
+
target,
|
|
1821
|
+
type: entry.type,
|
|
1822
|
+
keys: reresolveDerived(entry.keys.map((k) => ({ ...k }))),
|
|
1690
1823
|
editable: true
|
|
1691
1824
|
});
|
|
1692
1825
|
const merged = {
|
|
1693
1826
|
...code,
|
|
1694
1827
|
tracks
|
|
1695
1828
|
};
|
|
1696
|
-
if (
|
|
1829
|
+
if (main.labels && Object.keys(main.labels).length > 0) {
|
|
1697
1830
|
const codeLabels = code.labels ?? {};
|
|
1698
|
-
const shadowed = Object.keys(
|
|
1831
|
+
const shadowed = Object.keys(main.labels).filter((n) => n in codeLabels);
|
|
1699
1832
|
if (shadowed.length) emitDevWarning(`sidecar label(s) ${shadowed.join(", ")} collide with code labels; code wins (§6.2)`);
|
|
1700
1833
|
merged.labels = {
|
|
1701
|
-
...
|
|
1834
|
+
...main.labels,
|
|
1702
1835
|
...codeLabels
|
|
1703
1836
|
};
|
|
1704
1837
|
}
|
|
1705
|
-
|
|
1838
|
+
if (drift.length) emitDevWarning(`sidecar: code baseline changed beneath edits on ${drift.join(", ")} (§6.2)`);
|
|
1839
|
+
return {
|
|
1840
|
+
timeline: merged,
|
|
1841
|
+
drift,
|
|
1842
|
+
orphans
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
/** Merge the sidecar overlay onto the code baseline → a bindable Timeline (§6.2). */
|
|
1846
|
+
function mergeSidecar(code, sidecar) {
|
|
1847
|
+
return mergeSidecarDetailed(code, sidecar).timeline;
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Editor-edit normalization (§2.7 invariant): a spring-eased key's t is
|
|
1851
|
+
* intrinsic — prev.t + spring.duration(cfg) — so after any retime, sort and
|
|
1852
|
+
* re-pin spring keys to their predecessors. Dragging a spring key itself
|
|
1853
|
+
* therefore snaps back; retiming its predecessor carries it along. Returns a
|
|
1854
|
+
* new array. Colliding keys are NUDGED apart (+1ms), never deleted — an editor
|
|
1855
|
+
* must not silently destroy keyframe data on an exact-t collision.
|
|
1856
|
+
*/
|
|
1857
|
+
function normalizeEditedKeys(keys) {
|
|
1858
|
+
const out = keys.map((k) => ({ ...k })).sort((a, b) => a.t - b.t);
|
|
1859
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
1860
|
+
for (let i = 1; i < out.length; i++) {
|
|
1861
|
+
const ease = out[i].ease;
|
|
1862
|
+
if (ease && typeof ease === "object" && ease.kind === "spring") out[i].t = out[i - 1].t + spring.duration(ease);
|
|
1863
|
+
}
|
|
1864
|
+
out.sort((a, b) => a.t - b.t);
|
|
1865
|
+
}
|
|
1866
|
+
for (let i = 1; i < out.length; i++) if (out[i].t <= out[i - 1].t) out[i].t = out[i - 1].t + .001;
|
|
1867
|
+
return out;
|
|
1706
1868
|
}
|
|
1707
1869
|
//#endregion
|
|
1708
|
-
export { BakeError, CircularDependencyError, ColorParseError, DEFAULT_EASE, PositionError, SidecarVersionError, TARGET_PATH, TimelineValidationError, TrackValidationError, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, ValueTypeInferenceError, WriteDuringEvaluationError, audioOffsetSamples, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, pathType, random, registerValueType, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, signal, spring, springEasing, springEasingDerivative, springPresets, springTo, stagger, stringType, timeline, track, untracked, validateTrack, vec2Equals, vec2Signal, vec2Type, velocityAt };
|
|
1870
|
+
export { BakeError, CircularDependencyError, ColorParseError, DEFAULT_EASE, PositionError, SidecarVersionError, TARGET_PATH, TimelineValidationError, TrackValidationError, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, ValueTypeInferenceError, WriteDuringEvaluationError, assignKeyIds, audioOffsetSamples, bake, bakeCheckpointed, beginReadPhase, bindTimeline, booleanType, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, hashKeys, inReadPhase, inferValueType, key, lerpColor, mergeSidecar, mergeSidecarDetailed, migrateSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, parseColor, pathType, random, registerValueType, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, setSidecarTrack, signal, spring, springEasing, springEasingDerivative, springPresets, springTo, stagger, stringType, timeline, track, untracked, validateTrack, vec2ArcType, vec2Equals, vec2Signal, vec2Type, velocityAt };
|
package/package.json
CHANGED