@glissade/core 0.7.0-pre.0 → 0.8.0-pre.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/dist/index.d.ts +74 -16
- package/dist/index.js +206 -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,13 @@ 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
|
+
/** A named cue marker (serialized, fired on crossing) — the composer-signal substrate. */
|
|
490
|
+
cue(at: Position, name: string, data?: Json): TimelineBuilder;
|
|
491
|
+
/** An ad-break cue: a marker with `data.kind: 'ad-break'` + optional duration (§ad-break). */
|
|
492
|
+
adBreak(at: Position, opts?: {
|
|
493
|
+
id?: string;
|
|
494
|
+
duration?: number;
|
|
495
|
+
}): TimelineBuilder;
|
|
480
496
|
/** Mark the preceding track editable for the studio (§6.2). */
|
|
481
497
|
editable(): TimelineBuilder;
|
|
482
498
|
}
|
|
@@ -571,36 +587,78 @@ interface CheckpointedSim {
|
|
|
571
587
|
declare function bakeCheckpointed<W, S = W>(cfg: CheckpointedBakeConfig<W, S>): CheckpointedSim;
|
|
572
588
|
//#endregion
|
|
573
589
|
//#region src/sidecar.d.ts
|
|
590
|
+
type OrphanReason = 'node-missing' | 'prop-missing' | 'type-changed';
|
|
591
|
+
interface SidecarTrackEntry {
|
|
592
|
+
/** value type, so an editor-created track binds without a code baseline. */
|
|
593
|
+
type: ValueTypeId;
|
|
594
|
+
/** hash of the code baseline this track branched from; null = editor-created. */
|
|
595
|
+
baseHash: string | null;
|
|
596
|
+
keys: Key[];
|
|
597
|
+
}
|
|
598
|
+
interface SidecarOrphan {
|
|
599
|
+
type: ValueTypeId;
|
|
600
|
+
keys: Key[];
|
|
601
|
+
reason: OrphanReason;
|
|
602
|
+
}
|
|
603
|
+
interface SidecarTimelineEntry {
|
|
604
|
+
/** editor-owned tracks, keyed by canonical target. */
|
|
605
|
+
tracks: Record<string, SidecarTrackEntry>;
|
|
606
|
+
/** editor-created labels. */
|
|
607
|
+
labels?: Record<string, number>;
|
|
608
|
+
/** tracks parked off the merge because their target drifted (§6.2 rule 3). */
|
|
609
|
+
orphans?: Record<string, SidecarOrphan>;
|
|
610
|
+
}
|
|
574
611
|
interface SidecarDoc {
|
|
612
|
+
sidecarVersion: 2;
|
|
613
|
+
/** edits namespaced by timeline id; the linear timeline is 'main'. */
|
|
614
|
+
timelines: Record<string, SidecarTimelineEntry>;
|
|
615
|
+
}
|
|
616
|
+
/** The flat v1 shape, accepted on load and migrated forward. */
|
|
617
|
+
interface SidecarDocV1 {
|
|
575
618
|
sidecarVersion: 1;
|
|
576
|
-
/** Editor-owned tracks, replacing same-target code tracks wholesale. */
|
|
577
619
|
tracks: Track[];
|
|
578
|
-
/** Editor-created labels; code labels are authoritative and win on a name collision (§6.2). */
|
|
579
620
|
labels?: Record<string, number>;
|
|
580
621
|
}
|
|
581
622
|
declare class SidecarVersionError extends Error {
|
|
582
623
|
constructor(version: unknown);
|
|
583
624
|
}
|
|
584
625
|
declare function emptySidecar(): SidecarDoc;
|
|
626
|
+
/** Lift a v1 (or already-v2) document to the v2 shape. Throws on unknown versions. */
|
|
627
|
+
declare function migrateSidecar(doc: SidecarDoc | SidecarDocV1 | null | undefined): SidecarDoc | null;
|
|
628
|
+
/** Stable hash of a code track's keys — the baseline an editor branch records. */
|
|
629
|
+
declare function hashKeys(keys: readonly Key[]): string;
|
|
630
|
+
/** Assign stable monotonic `k<N>` ids to keys lacking one; existing ids preserved. */
|
|
631
|
+
declare function assignKeyIds(keys: readonly Key[]): Key[];
|
|
632
|
+
/**
|
|
633
|
+
* Set one editable track's keys in the sidecar (§6.2) — the studio write path.
|
|
634
|
+
* `codeBaselineKeys` is the code track this branches from (null = editor-created
|
|
635
|
+
* with no baseline). Keys get stable `k<N>` ids. Returns a new document.
|
|
636
|
+
*/
|
|
637
|
+
declare function setSidecarTrack(doc: SidecarDoc, timelineId: string, target: string, type: ValueTypeId, keys: Key[], codeBaselineKeys: readonly Key[] | null): SidecarDoc;
|
|
638
|
+
interface MergeResult {
|
|
639
|
+
timeline: Timeline;
|
|
640
|
+
/** targets whose code baseline changed beneath the editor's keys (§6.2 rule 2). */
|
|
641
|
+
drift: string[];
|
|
642
|
+
/** sidecar tracks parked off the merge (§6.2 rule 3). */
|
|
643
|
+
orphans: Record<string, SidecarOrphan>;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Merge with the full §6.2 report: the bindable Timeline, the list of targets
|
|
647
|
+
* whose code baseline drifted, and the orphaned tracks (type-changed, or whose
|
|
648
|
+
* code track vanished). Orphans are NEVER merged, so a drifted edit can't make
|
|
649
|
+
* the whole overlay fail to bind — the good edits survive. Inputs unmutated.
|
|
650
|
+
*/
|
|
651
|
+
declare function mergeSidecarDetailed(code: Timeline, sidecar: SidecarDoc | SidecarDocV1 | null | undefined): MergeResult;
|
|
652
|
+
/** Merge the sidecar overlay onto the code baseline → a bindable Timeline (§6.2). */
|
|
653
|
+
declare function mergeSidecar(code: Timeline, sidecar: SidecarDoc | SidecarDocV1 | null | undefined): Timeline;
|
|
585
654
|
/**
|
|
586
655
|
* Editor-edit normalization (§2.7 invariant): a spring-eased key's t is
|
|
587
656
|
* intrinsic — prev.t + spring.duration(cfg) — so after any retime, sort and
|
|
588
657
|
* re-pin spring keys to their predecessors. Dragging a spring key itself
|
|
589
658
|
* therefore snaps back; retiming its predecessor carries it along. Returns a
|
|
590
|
-
* new array. Colliding keys are NUDGED apart (+1ms), never deleted — an
|
|
591
|
-
*
|
|
659
|
+
* new array. Colliding keys are NUDGED apart (+1ms), never deleted — an editor
|
|
660
|
+
* must not silently destroy keyframe data on an exact-t collision.
|
|
592
661
|
*/
|
|
593
662
|
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
663
|
//#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 };
|
|
664
|
+
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,29 @@ function buildTimeline(build, init = {}) {
|
|
|
1365
1384
|
callbacks.set(name, fn);
|
|
1366
1385
|
return builder;
|
|
1367
1386
|
},
|
|
1387
|
+
cue(at, name, data) {
|
|
1388
|
+
markers.push(data !== void 0 ? {
|
|
1389
|
+
t: resolvePosition(at),
|
|
1390
|
+
name,
|
|
1391
|
+
data
|
|
1392
|
+
} : {
|
|
1393
|
+
t: resolvePosition(at),
|
|
1394
|
+
name
|
|
1395
|
+
});
|
|
1396
|
+
return builder;
|
|
1397
|
+
},
|
|
1398
|
+
adBreak(at, opts = {}) {
|
|
1399
|
+
const data = {
|
|
1400
|
+
kind: "ad-break",
|
|
1401
|
+
...opts.duration !== void 0 ? { duration: opts.duration } : {}
|
|
1402
|
+
};
|
|
1403
|
+
markers.push({
|
|
1404
|
+
t: resolvePosition(at),
|
|
1405
|
+
name: opts.id ?? "ad-break",
|
|
1406
|
+
data
|
|
1407
|
+
});
|
|
1408
|
+
return builder;
|
|
1409
|
+
},
|
|
1368
1410
|
editable() {
|
|
1369
1411
|
const last = insertions[insertions.length - 1];
|
|
1370
1412
|
if (!last) throw new TimelineValidationError(".editable() requires a preceding insertion");
|
|
@@ -1624,85 +1666,204 @@ function bakeCheckpointed(cfg) {
|
|
|
1624
1666
|
//#region src/sidecar.ts
|
|
1625
1667
|
/**
|
|
1626
1668
|
* The editor sidecar (DESIGN.md §6.2): code declares scene structure and
|
|
1627
|
-
* programmatic tracks; the studio owns keyframe data persisted
|
|
1628
|
-
*
|
|
1629
|
-
*
|
|
1669
|
+
* programmatic tracks; the studio owns keyframe data persisted next to the
|
|
1670
|
+
* scene module and merged at track granularity. Versioned independently of the
|
|
1671
|
+
* API (§7.4) — breaking it orphans users' files, so v1 documents migrate.
|
|
1672
|
+
*
|
|
1673
|
+
* v2 namespaces edits by timeline id ('main' for the linear timeline; v2
|
|
1674
|
+
* machines add more), keys tracks by canonical target, records the code
|
|
1675
|
+
* baseline hash for drift detection, parks drifted tracks as `orphans`, and
|
|
1676
|
+
* gives keys stable `k<N>` ids.
|
|
1630
1677
|
*/
|
|
1678
|
+
const MAIN = "main";
|
|
1631
1679
|
var SidecarVersionError = class extends Error {
|
|
1632
1680
|
constructor(version) {
|
|
1633
|
-
super(`unsupported sidecar version ${String(version)}; this build reads sidecarVersion 1`);
|
|
1681
|
+
super(`unsupported sidecar version ${String(version)}; this build reads sidecarVersion 1 or 2`);
|
|
1634
1682
|
this.name = "SidecarVersionError";
|
|
1635
1683
|
}
|
|
1636
1684
|
};
|
|
1637
1685
|
function emptySidecar() {
|
|
1638
1686
|
return {
|
|
1639
|
-
sidecarVersion:
|
|
1640
|
-
|
|
1687
|
+
sidecarVersion: 2,
|
|
1688
|
+
timelines: { [MAIN]: { tracks: {} } }
|
|
1641
1689
|
};
|
|
1642
1690
|
}
|
|
1691
|
+
/** Lift a v1 (or already-v2) document to the v2 shape. Throws on unknown versions. */
|
|
1692
|
+
function migrateSidecar(doc) {
|
|
1693
|
+
if (!doc) return null;
|
|
1694
|
+
if (doc.sidecarVersion === 2) return doc;
|
|
1695
|
+
if (doc.sidecarVersion === 1) {
|
|
1696
|
+
const tracks = {};
|
|
1697
|
+
for (const t of doc.tracks) tracks[t.target] = {
|
|
1698
|
+
type: t.type,
|
|
1699
|
+
baseHash: null,
|
|
1700
|
+
keys: t.keys
|
|
1701
|
+
};
|
|
1702
|
+
const main = { tracks };
|
|
1703
|
+
if (doc.labels) main.labels = doc.labels;
|
|
1704
|
+
return {
|
|
1705
|
+
sidecarVersion: 2,
|
|
1706
|
+
timelines: { [MAIN]: main }
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
throw new SidecarVersionError(doc.sidecarVersion);
|
|
1710
|
+
}
|
|
1711
|
+
/** Stable hash of a code track's keys — the baseline an editor branch records. */
|
|
1712
|
+
function hashKeys(keys) {
|
|
1713
|
+
const s = JSON.stringify(keys.map((k) => [
|
|
1714
|
+
k.t,
|
|
1715
|
+
k.value,
|
|
1716
|
+
k.ease ?? null,
|
|
1717
|
+
k.interp ?? null
|
|
1718
|
+
]));
|
|
1719
|
+
let h = 2166136261;
|
|
1720
|
+
for (let i = 0; i < s.length; i++) {
|
|
1721
|
+
h ^= s.charCodeAt(i);
|
|
1722
|
+
h = Math.imul(h, 16777619);
|
|
1723
|
+
}
|
|
1724
|
+
return (h >>> 0).toString(16);
|
|
1725
|
+
}
|
|
1726
|
+
/** Assign stable monotonic `k<N>` ids to keys lacking one; existing ids preserved. */
|
|
1727
|
+
function assignKeyIds(keys) {
|
|
1728
|
+
let max = -1;
|
|
1729
|
+
for (const k of keys) {
|
|
1730
|
+
const m = k.id ? /^k(\d+)$/.exec(k.id) : null;
|
|
1731
|
+
if (m) max = Math.max(max, Number(m[1]));
|
|
1732
|
+
}
|
|
1733
|
+
return keys.map((k) => k.id ? { ...k } : {
|
|
1734
|
+
...k,
|
|
1735
|
+
id: `k${++max}`
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1643
1738
|
/**
|
|
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.
|
|
1739
|
+
* Set one editable track's keys in the sidecar (§6.2) — the studio write path.
|
|
1740
|
+
* `codeBaselineKeys` is the code track this branches from (null = editor-created
|
|
1741
|
+
* with no baseline). Keys get stable `k<N>` ids. Returns a new document.
|
|
1650
1742
|
*/
|
|
1651
|
-
function
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1743
|
+
function setSidecarTrack(doc, timelineId, target, type, keys, codeBaselineKeys) {
|
|
1744
|
+
const tl = doc.timelines[timelineId] ?? { tracks: {} };
|
|
1745
|
+
const entry = {
|
|
1746
|
+
type,
|
|
1747
|
+
baseHash: codeBaselineKeys ? hashKeys(codeBaselineKeys) : null,
|
|
1748
|
+
keys: assignKeyIds(keys)
|
|
1749
|
+
};
|
|
1750
|
+
return {
|
|
1751
|
+
...doc,
|
|
1752
|
+
timelines: {
|
|
1753
|
+
...doc.timelines,
|
|
1754
|
+
[timelineId]: {
|
|
1755
|
+
...tl,
|
|
1756
|
+
tracks: {
|
|
1757
|
+
...tl.tracks,
|
|
1758
|
+
[target]: entry
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1657
1761
|
}
|
|
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;
|
|
1762
|
+
};
|
|
1662
1763
|
}
|
|
1663
1764
|
/**
|
|
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.
|
|
1765
|
+
* Re-resolve `derived:true` leading keys against the merged track (§2.6): a
|
|
1766
|
+
* derived from-key duplicates the preceding key's held value, so an upstream
|
|
1767
|
+
* edit must flow into it or the segment pops at its start. Build-time derived
|
|
1768
|
+
* keys are already correct; this fixes the ones an edit moved beneath.
|
|
1672
1769
|
*/
|
|
1673
|
-
function
|
|
1674
|
-
if (!
|
|
1675
|
-
|
|
1676
|
-
|
|
1770
|
+
function reresolveDerived(keys) {
|
|
1771
|
+
if (!keys.some((k) => k.derived)) return keys;
|
|
1772
|
+
return keys.map((k, i) => k.derived && i > 0 ? {
|
|
1773
|
+
...k,
|
|
1774
|
+
value: keys[i - 1].value
|
|
1775
|
+
} : k);
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Merge with the full §6.2 report: the bindable Timeline, the list of targets
|
|
1779
|
+
* whose code baseline drifted, and the orphaned tracks (type-changed, or whose
|
|
1780
|
+
* code track vanished). Orphans are NEVER merged, so a drifted edit can't make
|
|
1781
|
+
* the whole overlay fail to bind — the good edits survive. Inputs unmutated.
|
|
1782
|
+
*/
|
|
1783
|
+
function mergeSidecarDetailed(code, sidecar) {
|
|
1784
|
+
const doc = migrateSidecar(sidecar);
|
|
1785
|
+
if (!doc) return {
|
|
1786
|
+
timeline: code,
|
|
1787
|
+
drift: [],
|
|
1788
|
+
orphans: {}
|
|
1789
|
+
};
|
|
1790
|
+
const main = doc.timelines[MAIN] ?? { tracks: {} };
|
|
1791
|
+
const overlay = new Map(Object.entries(main.tracks));
|
|
1792
|
+
const drift = [];
|
|
1793
|
+
const orphans = { ...main.orphans ?? {} };
|
|
1677
1794
|
const tracks = code.tracks.map((t) => {
|
|
1678
|
-
const
|
|
1679
|
-
if (!
|
|
1795
|
+
const entry = overlay.get(t.target);
|
|
1796
|
+
if (!entry) return t;
|
|
1680
1797
|
overlay.delete(t.target);
|
|
1798
|
+
if (entry.type !== t.type) {
|
|
1799
|
+
orphans[t.target] = {
|
|
1800
|
+
type: entry.type,
|
|
1801
|
+
keys: entry.keys,
|
|
1802
|
+
reason: "type-changed"
|
|
1803
|
+
};
|
|
1804
|
+
return t;
|
|
1805
|
+
}
|
|
1806
|
+
if (entry.baseHash !== null && entry.baseHash !== hashKeys(t.keys)) drift.push(t.target);
|
|
1681
1807
|
return {
|
|
1682
1808
|
...t,
|
|
1683
|
-
keys:
|
|
1809
|
+
keys: reresolveDerived(entry.keys.map((k) => ({ ...k }))),
|
|
1684
1810
|
editable: true
|
|
1685
1811
|
};
|
|
1686
1812
|
});
|
|
1687
|
-
for (const
|
|
1688
|
-
|
|
1689
|
-
keys:
|
|
1813
|
+
for (const [target, entry] of overlay) if (entry.baseHash !== null) orphans[target] = {
|
|
1814
|
+
type: entry.type,
|
|
1815
|
+
keys: entry.keys,
|
|
1816
|
+
reason: "prop-missing"
|
|
1817
|
+
};
|
|
1818
|
+
else tracks.push({
|
|
1819
|
+
target,
|
|
1820
|
+
type: entry.type,
|
|
1821
|
+
keys: reresolveDerived(entry.keys.map((k) => ({ ...k }))),
|
|
1690
1822
|
editable: true
|
|
1691
1823
|
});
|
|
1692
1824
|
const merged = {
|
|
1693
1825
|
...code,
|
|
1694
1826
|
tracks
|
|
1695
1827
|
};
|
|
1696
|
-
if (
|
|
1828
|
+
if (main.labels && Object.keys(main.labels).length > 0) {
|
|
1697
1829
|
const codeLabels = code.labels ?? {};
|
|
1698
|
-
const shadowed = Object.keys(
|
|
1830
|
+
const shadowed = Object.keys(main.labels).filter((n) => n in codeLabels);
|
|
1699
1831
|
if (shadowed.length) emitDevWarning(`sidecar label(s) ${shadowed.join(", ")} collide with code labels; code wins (§6.2)`);
|
|
1700
1832
|
merged.labels = {
|
|
1701
|
-
...
|
|
1833
|
+
...main.labels,
|
|
1702
1834
|
...codeLabels
|
|
1703
1835
|
};
|
|
1704
1836
|
}
|
|
1705
|
-
|
|
1837
|
+
if (drift.length) emitDevWarning(`sidecar: code baseline changed beneath edits on ${drift.join(", ")} (§6.2)`);
|
|
1838
|
+
return {
|
|
1839
|
+
timeline: merged,
|
|
1840
|
+
drift,
|
|
1841
|
+
orphans
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
/** Merge the sidecar overlay onto the code baseline → a bindable Timeline (§6.2). */
|
|
1845
|
+
function mergeSidecar(code, sidecar) {
|
|
1846
|
+
return mergeSidecarDetailed(code, sidecar).timeline;
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Editor-edit normalization (§2.7 invariant): a spring-eased key's t is
|
|
1850
|
+
* intrinsic — prev.t + spring.duration(cfg) — so after any retime, sort and
|
|
1851
|
+
* re-pin spring keys to their predecessors. Dragging a spring key itself
|
|
1852
|
+
* therefore snaps back; retiming its predecessor carries it along. Returns a
|
|
1853
|
+
* new array. Colliding keys are NUDGED apart (+1ms), never deleted — an editor
|
|
1854
|
+
* must not silently destroy keyframe data on an exact-t collision.
|
|
1855
|
+
*/
|
|
1856
|
+
function normalizeEditedKeys(keys) {
|
|
1857
|
+
const out = keys.map((k) => ({ ...k })).sort((a, b) => a.t - b.t);
|
|
1858
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
1859
|
+
for (let i = 1; i < out.length; i++) {
|
|
1860
|
+
const ease = out[i].ease;
|
|
1861
|
+
if (ease && typeof ease === "object" && ease.kind === "spring") out[i].t = out[i - 1].t + spring.duration(ease);
|
|
1862
|
+
}
|
|
1863
|
+
out.sort((a, b) => a.t - b.t);
|
|
1864
|
+
}
|
|
1865
|
+
for (let i = 1; i < out.length; i++) if (out[i].t <= out[i - 1].t) out[i].t = out[i - 1].t + .001;
|
|
1866
|
+
return out;
|
|
1706
1867
|
}
|
|
1707
1868
|
//#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 };
|
|
1869
|
+
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-pre.0",
|
|
4
4
|
"description": "glissade core: signals, tracks, timeline document, evaluation, easing, springs, seeded RNG. Zero DOM/Node dependencies.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|