@glissade/core 0.19.0 → 0.20.0-pre.3

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 CHANGED
@@ -1,7 +1,6 @@
1
1
  import { $ as cubicBezier, A as getValueType, B as vec2Equals, C as UnknownValueTypeError, D as Vec2, E as ValueTypeInferenceError, F as pathType, G as spring, H as RetargetSpring, I as registerValueType, J as springPresets, K as springEasing, L as reprOf, M as listValueTypes, N as numberType, O as booleanType, P as paintType, Q as UnknownEasingError, R as stringType, S as PathValue, T as ValueTypeId, U as SpringConfig, V as vec2Type, W as SpringEase, X as EaseSpec, Y as DEFAULT_EASE, Z as EasingFn, _ as MeshInterpolation, a as key, b as Paint, c as sampleTrack, d as track, et as cubicBezierDerivative, f as validateTrack, g as HandoffKind, h as GradientInterpolation, i as TrackValidationError, j as inferValueType, k as colorType, l as springTo, m as ColorStop, n as KeyOpts, nt as easings, o as resolveEase, p as velocityAt, q as springEasingDerivative, r as Track, rt as namedEasing, s as resolveEaseDerivative, t as Key, tt as easingDerivatives, u as stagger, v as MeshPaint, w as ValueType, x as PathContour, y as MeshPoint, z as vec2ArcType } from "./track.js";
2
2
  import { a as isEditableNodeId, i as UnresolvableTargetError, n as TargetCarrier, o as resolveTweenTarget, r as TweenTarget, s as targetNodeId, t as TARGET_PATH } from "./targetRef.js";
3
3
  import { _ as setDevWarning, a as FontFaceRef, c as Marker, d as TimelineValidationError, f as audioOffsetSamples, g as emitDevWarning, h as DevWarning, i as CompiledTimeline, l as Timeline, m as isDurationEditable, n as AudioClip, o as GainEnvelope, p as compileTimeline, r as ChildEntry, s as Json, t as AssetRef, u as TimelineInit } from "./timeline.js";
4
- import { _ as setSidecarTrack, a as SidecarOrphan, c as SidecarVersionError, d as emptySidecar, f as hashKeys, g as normalizeEditedKeys, h as migrateSidecar, i as SidecarDocV1, l as assignKeyIds, m as mergeSidecarDetailed, n as OrphanReason, o as SidecarTimelineEntry, p as mergeSidecar, r as SidecarDoc, s as SidecarTrackEntry, t as MergeResult, u as deleteSidecarTrack } from "./sidecar.js";
5
4
 
6
5
  //#region src/signal.d.ts
7
6
 
@@ -374,7 +373,13 @@ declare function timeline(build: (tl: TimelineBuilder) => void, init?: Omit<Time
374
373
  type Playhead = BindableSignal<number>;
375
374
  declare function createPlayhead(initial?: number): Playhead;
376
375
  declare class UnboundTargetError extends Error {
377
- constructor(target: string);
376
+ /**
377
+ * `message` overrides the default generic text — the seam a layer with more
378
+ * context (e.g. `scene`, which knows the node's construction-prop schema) uses
379
+ * to throw a friendlier, more specific reason while keeping the same error
380
+ * type (so existing `instanceof UnboundTargetError` catches still fire).
381
+ */
382
+ constructor(target: string, message?: string);
378
383
  }
379
384
  /**
380
385
  * A track's value type doesn't match the shape of the property it targets — the
@@ -421,12 +426,25 @@ interface BoundTimeline {
421
426
  /** Detach every track binding, freezing signals at their last values. */
422
427
  unbind(): void;
423
428
  }
429
+ /**
430
+ * Optional knobs for {@link bindTimeline}.
431
+ */
432
+ interface BindOptions {
433
+ /**
434
+ * Asked for a friendlier message when a target fails to resolve. `core` knows
435
+ * nothing about node types or construction props; a caller with that context
436
+ * (e.g. `scene`) can return a more-specific reason string — used verbatim as
437
+ * the {@link UnboundTargetError} message — or `undefined` to fall through to
438
+ * the generic "no property signal resolves to it".
439
+ */
440
+ unboundMessage?: (target: string) => string | undefined;
441
+ }
424
442
  /**
425
443
  * Bind a compiled timeline's tracks to property signals. `resolve` returns
426
444
  * the signal for a target path, or undefined — which is a compile-time-style
427
445
  * error (§2.2: unbound tracks are build errors, not silent no-ops).
428
446
  */
429
- declare function bindTimeline(compiled: CompiledTimeline, resolve: (target: string) => BindTarget | undefined, playhead?: Playhead): BoundTimeline;
447
+ declare function bindTimeline(compiled: CompiledTimeline, resolve: (target: string) => BindTarget | undefined, playhead?: Playhead, options?: BindOptions): BoundTimeline;
430
448
  /**
431
449
  * The evaluation entry discipline (§2.5): the playhead write is the sanctioned
432
450
  * entry mutation, then the read phase begins and `read` must be pure.
@@ -477,4 +495,4 @@ interface CheckpointedSim {
477
495
  }
478
496
  declare function bakeCheckpointed<W, S = W>(cfg: CheckpointedBakeConfig<W, S>): CheckpointedSim;
479
497
  //#endregion
480
- export { type AssetRef, type AudioClip, type BakeConfig, BakeError, type BindTarget, BindTypeMismatchError, type BindableSignal, type BoundTimeline, type CheckpointedBakeConfig, type CheckpointedSim, type ChildEntry, CircularDependencyError, ColorParseError, type ColorStop, type CompiledTimeline, type CoverageReport, type CurveSampler, DEFAULT_EASE, type DevWarning, type EaseSpec, type EasingFn, type Equals, type FontFaceRef, type FontMode, type FontRegistry, type FontUsage, FontValidationError, type GainEnvelope, type GradientInterpolation, type HandoffKind, type Json, type Key, type KeyOpts, type Marker, type MergeResult, type MeshInterpolation, type MeshPaint, type MeshPoint, type MissingGlyphs, type OkLab, type OrphanReason, type Paint, type PathContour, type PathValue, type Playhead, type Position, PositionError, type ReadonlySignal, type ResolvedFace, type RetargetSpring, type Rgba, type Rng, type Scheduler, type SidecarDoc, type SidecarDocV1, type SidecarOrphan, type SidecarTimelineEntry, type SidecarTrackEntry, SidecarVersionError, type Signal, type SignalOptions, type SpringConfig, type SpringEase, type StaggerOpts, type StaggerSpec, TARGET_PATH, type TargetCarrier, type Timeline, type TimelineBuilder, type TimelineInit, TimelineValidationError, type Track, TrackValidationError, type TweenOpts, type TweenTarget, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, type ValidateFontsOptions, type ValueType, type ValueTypeId, ValueTypeInferenceError, type Vec2, type Vec2Component, type Vec2Signal, WriteDuringEvaluationError, assignKeyIds, audioOffsetSamples, bake, bakeCheckpointed, batch, beginReadPhase, bindTimeline, booleanType, buildFontRegistry, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, deleteSidecarTrack, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, hashKeys, inReadPhase, inferValueType, isDurationEditable, isEditableNodeId, isExemptFamily, key, lerpColor, listValueTypes, mergeSidecar, mergeSidecarDetailed, migrateSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, paintType, parseCmap, parseColor, pathType, random, registerValueType, reprOf, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, setScheduler, setSidecarTrack, signal, spring, springEasing, springEasingDerivative, springPresets, springTo, stagger, stringType, synchronousScheduler, targetNodeId, timeline, track, untracked, validateFonts, validateTrack, vec2ArcType, vec2Equals, vec2Signal, vec2Type, velocityAt };
498
+ export { type AssetRef, type AudioClip, type BakeConfig, BakeError, type BindOptions, type BindTarget, BindTypeMismatchError, type BindableSignal, type BoundTimeline, type CheckpointedBakeConfig, type CheckpointedSim, type ChildEntry, CircularDependencyError, ColorParseError, type ColorStop, type CompiledTimeline, type CoverageReport, type CurveSampler, DEFAULT_EASE, type DevWarning, type EaseSpec, type EasingFn, type Equals, type FontFaceRef, type FontMode, type FontRegistry, type FontUsage, FontValidationError, type GainEnvelope, type GradientInterpolation, type HandoffKind, type Json, type Key, type KeyOpts, type Marker, type MeshInterpolation, type MeshPaint, type MeshPoint, type MissingGlyphs, type OkLab, type Paint, type PathContour, type PathValue, type Playhead, type Position, PositionError, type ReadonlySignal, type ResolvedFace, type RetargetSpring, type Rgba, type Rng, type Scheduler, type Signal, type SignalOptions, type SpringConfig, type SpringEase, type StaggerOpts, type StaggerSpec, TARGET_PATH, type TargetCarrier, type Timeline, type TimelineBuilder, type TimelineInit, TimelineValidationError, type Track, TrackValidationError, type TweenOpts, type TweenTarget, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, type ValidateFontsOptions, type ValueType, type ValueTypeId, ValueTypeInferenceError, type Vec2, type Vec2Component, type Vec2Signal, WriteDuringEvaluationError, audioOffsetSamples, bake, bakeCheckpointed, batch, beginReadPhase, bindTimeline, booleanType, buildFontRegistry, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, isDurationEditable, isEditableNodeId, isExemptFamily, key, lerpColor, listValueTypes, namedEasing, numberType, oklabToRgba, paintType, parseCmap, parseColor, pathType, random, registerValueType, reprOf, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, setScheduler, signal, spring, springEasing, springEasingDerivative, springPresets, springTo, stagger, stringType, synchronousScheduler, targetNodeId, timeline, track, untracked, validateFonts, validateTrack, vec2ArcType, vec2Equals, vec2Signal, vec2Type, velocityAt };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { A as vec2Equals, B as spring, C as numberType, D as reprOf, E as registerValueType, F as oklabToRgba, G as UnknownEasingError, H as springEasingDerivative, I as parseColor, J as easingDerivatives, K as cubicBezier, L as rgbaToOklab, M as ColorParseError, N as formatColor, O as stringType, P as lerpColor, R as emitDevWarning, S as listValueTypes, T as pathType, U as springPresets, V as springEasing, W as DEFAULT_EASE, X as namedEasing, Y as easings, _ as ValueTypeInferenceError, a as targetNodeId, b as getValueType, c as resolveEase, d as springTo, f as stagger, g as UnknownValueTypeError, h as velocityAt, i as resolveTweenTarget, j as vec2Type, k as vec2ArcType, l as resolveEaseDerivative, m as validateTrack, n as UnresolvableTargetError, o as TrackValidationError, p as track, q as cubicBezierDerivative, r as isEditableNodeId, s as key, t as TARGET_PATH, u as sampleTrack, v as booleanType, w as paintType, x as inferValueType, y as colorType, z as setDevWarning } from "./targetRef.js";
2
2
  import { t as parseCmap } from "./cmap.js";
3
- import { _ as timeline$1, a as hashKeys, c as migrateSidecar, d as TimelineValidationError, f as audioOffsetSamples, g as namespaceCallName, h as isDurationEditable, i as emptySidecar, l as normalizeEditedKeys, m as compileTimeline, n as assignKeyIds, o as mergeSidecar, p as callMarkerPrefix, r as deleteSidecarTrack, s as mergeSidecarDetailed, t as SidecarVersionError, u as setSidecarTrack } from "./sidecar.js";
3
+ import { a as isDurationEditable, i as compileTimeline, n as audioOffsetSamples, o as namespaceCallName, r as callMarkerPrefix, s as timeline$1, t as TimelineValidationError } from "./timeline.js";
4
4
  //#region src/ticker.ts
5
5
  /** The default scheduler: flush synchronously, preserving pre-ticker timing. */
6
6
  const synchronousScheduler = (flush) => flush();
@@ -914,8 +914,14 @@ function createPlayhead(initial = 0) {
914
914
  return signal(initial);
915
915
  }
916
916
  var UnboundTargetError = class extends Error {
917
- constructor(target) {
918
- super(`timeline targets '${target}' but no property signal resolves to it`);
917
+ /**
918
+ * `message` overrides the default generic text the seam a layer with more
919
+ * context (e.g. `scene`, which knows the node's construction-prop schema) uses
920
+ * to throw a friendlier, more specific reason while keeping the same error
921
+ * type (so existing `instanceof UnboundTargetError` catches still fire).
922
+ */
923
+ constructor(target, message) {
924
+ super(message ?? `timeline targets '${target}' but no property signal resolves to it`);
919
925
  this.name = "UnboundTargetError";
920
926
  }
921
927
  };
@@ -948,12 +954,12 @@ var BindTypeMismatchError = class extends Error {
948
954
  * the signal for a target path, or undefined — which is a compile-time-style
949
955
  * error (§2.2: unbound tracks are build errors, not silent no-ops).
950
956
  */
951
- function bindTimeline(compiled, resolve, playhead = createPlayhead()) {
957
+ function bindTimeline(compiled, resolve, playhead = createPlayhead(), options = {}) {
952
958
  const bound = [];
953
959
  const samplers = /* @__PURE__ */ new Map();
954
960
  for (const [target, tr] of compiled.tracks) {
955
961
  const sig = resolve(target);
956
- if (!sig) throw new UnboundTargetError(target);
962
+ if (!sig) throw new UnboundTargetError(target, options.unboundMessage?.(target));
957
963
  const got = tr.type;
958
964
  const expects = sig.expects;
959
965
  if (expects !== void 0) {
@@ -1109,4 +1115,4 @@ function bakeCheckpointed(cfg) {
1109
1115
  };
1110
1116
  }
1111
1117
  //#endregion
1112
- export { BakeError, BindTypeMismatchError, CircularDependencyError, ColorParseError, DEFAULT_EASE, FontValidationError, PositionError, SidecarVersionError, TARGET_PATH, TimelineValidationError, TrackValidationError, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, ValueTypeInferenceError, WriteDuringEvaluationError, assignKeyIds, audioOffsetSamples, bake, bakeCheckpointed, batch, beginReadPhase, bindTimeline, booleanType, buildFontRegistry, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, deleteSidecarTrack, easingDerivatives, easings, emitDevWarning, emptySidecar, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, hashKeys, inReadPhase, inferValueType, isDurationEditable, isEditableNodeId, isExemptFamily, key, lerpColor, listValueTypes, mergeSidecar, mergeSidecarDetailed, migrateSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, paintType, parseCmap, parseColor, pathType, random, registerValueType, reprOf, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, setScheduler, setSidecarTrack, signal, spring, springEasing, springEasingDerivative, springPresets, springTo, stagger, stringType, synchronousScheduler, targetNodeId, timeline, track, untracked, validateFonts, validateTrack, vec2ArcType, vec2Equals, vec2Signal, vec2Type, velocityAt };
1118
+ export { BakeError, BindTypeMismatchError, CircularDependencyError, ColorParseError, DEFAULT_EASE, FontValidationError, PositionError, TARGET_PATH, TimelineValidationError, TrackValidationError, UnboundTargetError, UnknownEasingError, UnknownValueTypeError, UnresolvableTargetError, ValueTypeInferenceError, WriteDuringEvaluationError, audioOffsetSamples, bake, bakeCheckpointed, batch, beginReadPhase, bindTimeline, booleanType, buildFontRegistry, buildTimeline, colorType, compileTimeline, computed, createPlayhead, cubicBezier, cubicBezierDerivative, easingDerivatives, easings, emitDevWarning, endReadPhase, evaluateAt, formatColor, getTimelineCallbacks, getValueType, inReadPhase, inferValueType, isDurationEditable, isEditableNodeId, isExemptFamily, key, lerpColor, listValueTypes, namedEasing, numberType, oklabToRgba, paintType, parseCmap, parseColor, pathType, random, registerValueType, reprOf, resolveEase, resolveEaseDerivative, resolveTweenTarget, rgbaToOklab, sampleTrack, setDevWarning, setScheduler, signal, spring, springEasing, springEasingDerivative, springPresets, springTo, stagger, stringType, synchronousScheduler, targetNodeId, timeline, track, untracked, validateFonts, validateTrack, vec2ArcType, vec2Equals, vec2Signal, vec2Type, velocityAt };
package/dist/sidecar.d.ts CHANGED
@@ -85,4 +85,4 @@ declare function mergeSidecar(code: Timeline, sidecar: SidecarDoc | SidecarDocV1
85
85
  */
86
86
  declare function normalizeEditedKeys(keys: Key[]): Key[];
87
87
  //#endregion
88
- export { setSidecarTrack as _, SidecarOrphan as a, SidecarVersionError as c, emptySidecar as d, hashKeys as f, normalizeEditedKeys as g, migrateSidecar as h, SidecarDocV1 as i, assignKeyIds as l, mergeSidecarDetailed as m, OrphanReason as n, SidecarTimelineEntry as o, mergeSidecar as p, SidecarDoc as r, SidecarTrackEntry as s, MergeResult as t, deleteSidecarTrack as u };
88
+ export { MergeResult, OrphanReason, SidecarDoc, SidecarDocV1, SidecarOrphan, SidecarTimelineEntry, SidecarTrackEntry, SidecarVersionError, assignKeyIds, deleteSidecarTrack, emptySidecar, hashKeys, mergeSidecar, mergeSidecarDetailed, migrateSidecar, normalizeEditedKeys, setSidecarTrack };
package/dist/sidecar.js CHANGED
@@ -1,190 +1,4 @@
1
- import { B as spring, R as emitDevWarning, a as targetNodeId, m as validateTrack, o as TrackValidationError, r as isEditableNodeId } from "./targetRef.js";
2
- //#region src/timeline.ts
3
- /**
4
- * The Timeline document (DESIGN.md §2.3) — the serializable animation source
5
- * of truth — and its compiler: child flattening (add/sync), same-target
6
- * coalescing (last-insertion-wins, §2.2), duration computation, validation.
7
- */
8
- function timeline(init) {
9
- const doc = {
10
- version: 1,
11
- tracks: init.tracks ?? []
12
- };
13
- if (init.duration !== void 0) doc.duration = init.duration;
14
- if (init.editableDuration !== void 0) doc.editableDuration = init.editableDuration;
15
- if (init.fps !== void 0) doc.fps = init.fps;
16
- if (init.posterTime !== void 0) doc.posterTime = init.posterTime;
17
- if (init.labels !== void 0) doc.labels = init.labels;
18
- if (init.markers !== void 0) doc.markers = init.markers;
19
- if (init.children !== void 0) doc.children = init.children;
20
- if (init.audio !== void 0) doc.audio = init.audio;
21
- if (init.assets !== void 0) doc.assets = init.assets;
22
- return doc;
23
- }
24
- var TimelineValidationError = class extends Error {
25
- constructor(message) {
26
- super(message);
27
- this.name = "TimelineValidationError";
28
- }
29
- };
30
- /** Spring key rule (§2.7): a spring-eased key's t must equal prev.t + spring.duration(cfg). */
31
- function validateSpringKeys(tr) {
32
- for (let i = 1; i < tr.keys.length; i++) {
33
- const k = tr.keys[i];
34
- if (k.ease && typeof k.ease === "object" && k.ease.kind === "spring") {
35
- const expected = tr.keys[i - 1].t + spring.duration(k.ease);
36
- if (Math.abs(k.t - expected) > 1e-6) throw new TimelineValidationError(`track '${tr.target}': spring-eased key at t=${k.t} must sit at prev.t + spring.duration (${expected.toFixed(6)}); the builder computes this automatically`);
37
- }
38
- }
39
- }
40
- /**
41
- * Sample-indexed clip offset — the single A/V-sync source of truth (§5.3),
42
- * `round(at * sampleRate)`. Both the CLI (FFmpeg) and the browser
43
- * (OfflineAudioContext) mixers derive their delay from this, so A/V offsets
44
- * agree across paths by construction rather than one rounding to milliseconds
45
- * and the other to raw float seconds. Default rate is the canonical mix grid.
46
- */
47
- function audioOffsetSamples(at, sampleRate = 48e3) {
48
- return Math.round(at * sampleRate);
49
- }
50
- function rebaseKeys(keys, at, timeScale) {
51
- return keys.map((k) => ({
52
- ...k,
53
- t: at + k.t / timeScale
54
- }));
55
- }
56
- /**
57
- * `.call()` markers carry an auto-assigned `call:N` name whose counter resets
58
- * per document, so two sibling sub-timelines that each define a `.call()` would
59
- * collide on `call:0` when rebased into one parent (one callback dropped, the
60
- * other double-firing). We namespace a child's `call:*` markers by the child's
61
- * position PATH in the parent (`c<index>/…`), and the builder's `add()` applies
62
- * the EXACT same prefix when forwarding the child's name→fn map — so the rebased
63
- * marker name and the registered callback key agree by construction. Only
64
- * `call:*` names are rewritten; author-named cues keep their names. Both surfaces
65
- * call these so the convention lives in one place.
66
- */
67
- function callMarkerPrefix(childIndex) {
68
- return `c${childIndex}/`;
69
- }
70
- const CALL = /(^|\/)call:\d+$/;
71
- function namespaceCallName(name, prefix) {
72
- return prefix !== "" && CALL.test(name) ? prefix + name : name;
73
- }
74
- function flatten(doc, at, timeScale, unit, out, counter) {
75
- for (const tr of doc.tracks) {
76
- validateTrack(tr);
77
- validateSpringKeys(tr);
78
- out.push({
79
- track: {
80
- ...tr,
81
- keys: rebaseKeys(tr.keys, at, timeScale)
82
- },
83
- unit
84
- });
85
- }
86
- for (const child of doc.children ?? []) {
87
- const scale = child.mode === "sync" ? child.timeScale ?? 1 : 1;
88
- if (child.mode === "add" && child.timeScale !== void 0) throw new TimelineValidationError("timeScale is only valid on mode:'sync' children (§2.3)");
89
- if (scale <= 0) throw new TimelineValidationError("sync timeScale must be > 0");
90
- const childUnit = child.mode === "sync" ? ++counter.n : unit;
91
- flatten(child.timeline, at + child.at / timeScale, timeScale * scale, childUnit, out, counter);
92
- }
93
- }
94
- /**
95
- * Coalesce same-target tracks: later insertion wins where key ranges overlap
96
- * (§2.2/§2.6 decided rule), with a dev warning. Earlier keys inside the later
97
- * track's [first.t, last.t] span are dropped.
98
- */
99
- function coalesce(entries) {
100
- const byTarget = /* @__PURE__ */ new Map();
101
- for (const { track: tr, unit } of entries) {
102
- const existing = byTarget.get(tr.target);
103
- if (!existing) {
104
- byTarget.set(tr.target, {
105
- track: {
106
- ...tr,
107
- keys: [...tr.keys]
108
- },
109
- unit
110
- });
111
- continue;
112
- }
113
- if (existing.unit !== unit) throw new TimelineValidationError(`target '${tr.target}' is animated by a sync (opaque) child and another timeline unit; sync children must own disjoint targets (no last-writer-wins across the sync boundary, §2.3)`);
114
- if (existing.track.type !== tr.type) throw new TimelineValidationError(`target '${tr.target}' has conflicting value types '${existing.track.type}' and '${tr.type}'`);
115
- const start = tr.keys[0].t;
116
- const end = tr.keys[tr.keys.length - 1].t;
117
- const existingStart = existing.track.keys[0].t;
118
- const existingEnd = existing.track.keys[existing.track.keys.length - 1].t;
119
- const kept = existing.track.keys.filter((k) => k.t < start || k.t > end);
120
- if (existingStart <= end && start <= existingEnd) emitDevWarning(`overlapping tracks for '${tr.target}' in [${start}, ${end}]: later insertion wins (${existing.track.keys.length - kept.length} earlier key(s) dropped)`);
121
- existing.track.keys = [...kept, ...tr.keys].sort((a, b) => a.t - b.t);
122
- }
123
- const result = /* @__PURE__ */ new Map();
124
- for (const [target, { track }] of byTarget) result.set(target, track);
125
- return result;
126
- }
127
- /**
128
- * Studio reader (§6.2 rule 4): is this timeline's duration opted into editor
129
- * editing? Code-owned and read-only by default; `editableDuration()` flips it.
130
- */
131
- function isDurationEditable(doc) {
132
- return doc.editableDuration === true;
133
- }
134
- function childExtent(child) {
135
- const scale = child.mode === "sync" ? child.timeScale ?? 1 : 1;
136
- return child.at + computeDuration(child.timeline) / scale;
137
- }
138
- function computeDuration(doc) {
139
- if (doc.duration !== void 0) return doc.duration;
140
- let max = 0;
141
- for (const tr of doc.tracks) {
142
- const last = tr.keys[tr.keys.length - 1];
143
- if (last) max = Math.max(max, last.t);
144
- }
145
- for (const m of doc.markers ?? []) max = Math.max(max, m.t);
146
- for (const child of doc.children ?? []) max = Math.max(max, childExtent(child));
147
- return max;
148
- }
149
- function compileTimeline(doc) {
150
- if (doc.version !== 1) throw new TimelineValidationError(`unsupported timeline document version ${String(doc.version)}`);
151
- const flat = [];
152
- flatten(doc, 0, 1, 0, flat, { n: 0 });
153
- const tracks = coalesce(flat);
154
- const labels = { ...doc.labels };
155
- const markers = [...doc.markers ?? []];
156
- const audio = [...doc.audio ?? []];
157
- const visitChildren = (children, at, scale, prefix) => {
158
- (children ?? []).forEach((child, index) => {
159
- const base = at + child.at / scale;
160
- const childScale = scale * (child.mode === "sync" ? child.timeScale ?? 1 : 1);
161
- const childPrefix = prefix + callMarkerPrefix(index);
162
- for (const [name, t] of Object.entries(child.timeline.labels ?? {})) if (!(name in labels)) labels[name] = base + t / childScale;
163
- for (const m of child.timeline.markers ?? []) markers.push({
164
- ...m,
165
- name: namespaceCallName(m.name, childPrefix),
166
- t: base + m.t / childScale
167
- });
168
- for (const clip of child.timeline.audio ?? []) audio.push({
169
- ...clip,
170
- at: base + clip.at / childScale,
171
- ...childScale !== 1 ? { playbackRate: (clip.playbackRate ?? 1) * childScale } : {}
172
- });
173
- visitChildren(child.timeline.children, base, childScale, childPrefix);
174
- });
175
- };
176
- visitChildren(doc.children, 0, 1, "");
177
- markers.sort((a, b) => a.t - b.t);
178
- audio.sort((a, b) => a.at - b.at);
179
- return {
180
- duration: computeDuration(doc),
181
- labels,
182
- markers,
183
- tracks,
184
- audio
185
- };
186
- }
187
- //#endregion
1
+ import { B as spring, R as emitDevWarning, a as targetNodeId, o as TrackValidationError, r as isEditableNodeId } from "./targetRef.js";
188
2
  //#region src/sidecar.ts
189
3
  /**
190
4
  * The editor sidecar (DESIGN.md §6.2): code declares scene structure and
@@ -411,4 +225,4 @@ function normalizeEditedKeys(keys) {
411
225
  return out;
412
226
  }
413
227
  //#endregion
414
- export { timeline as _, hashKeys as a, migrateSidecar as c, TimelineValidationError as d, audioOffsetSamples as f, namespaceCallName as g, isDurationEditable as h, emptySidecar as i, normalizeEditedKeys as l, compileTimeline as m, assignKeyIds as n, mergeSidecar as o, callMarkerPrefix as p, deleteSidecarTrack as r, mergeSidecarDetailed as s, SidecarVersionError as t, setSidecarTrack as u };
228
+ export { SidecarVersionError, assignKeyIds, deleteSidecarTrack, emptySidecar, hashKeys, mergeSidecar, mergeSidecarDetailed, migrateSidecar, normalizeEditedKeys, setSidecarTrack };
@@ -1,7 +1,7 @@
1
1
  import { T as ValueTypeId, X as EaseSpec, t as Key } from "./track.js";
2
2
  import { a as isEditableNodeId, s as targetNodeId } from "./targetRef.js";
3
3
  import { l as Timeline } from "./timeline.js";
4
- import { a as SidecarOrphan, r as SidecarDoc } from "./sidecar.js";
4
+ import { SidecarDoc, SidecarOrphan } from "./sidecar.js";
5
5
 
6
6
  //#region src/timelinePatch.d.ts
7
7
 
@@ -1,5 +1,5 @@
1
1
  import { a as targetNodeId, r as isEditableNodeId } from "./targetRef.js";
2
- import { a as hashKeys, l as normalizeEditedKeys, n as assignKeyIds } from "./sidecar.js";
2
+ import { assignKeyIds, hashKeys, normalizeEditedKeys } from "./sidecar.js";
3
3
  //#region src/timelinePatch.ts
4
4
  /** Unambiguous composite map key (no control-char separators). */
5
5
  function refKey(timelineId, name) {
@@ -0,0 +1,188 @@
1
+ import { B as spring, R as emitDevWarning, m as validateTrack } from "./targetRef.js";
2
+ //#region src/timeline.ts
3
+ /**
4
+ * The Timeline document (DESIGN.md §2.3) — the serializable animation source
5
+ * of truth — and its compiler: child flattening (add/sync), same-target
6
+ * coalescing (last-insertion-wins, §2.2), duration computation, validation.
7
+ */
8
+ function timeline(init) {
9
+ const doc = {
10
+ version: 1,
11
+ tracks: init.tracks ?? []
12
+ };
13
+ if (init.duration !== void 0) doc.duration = init.duration;
14
+ if (init.editableDuration !== void 0) doc.editableDuration = init.editableDuration;
15
+ if (init.fps !== void 0) doc.fps = init.fps;
16
+ if (init.posterTime !== void 0) doc.posterTime = init.posterTime;
17
+ if (init.labels !== void 0) doc.labels = init.labels;
18
+ if (init.markers !== void 0) doc.markers = init.markers;
19
+ if (init.children !== void 0) doc.children = init.children;
20
+ if (init.audio !== void 0) doc.audio = init.audio;
21
+ if (init.assets !== void 0) doc.assets = init.assets;
22
+ return doc;
23
+ }
24
+ var TimelineValidationError = class extends Error {
25
+ constructor(message) {
26
+ super(message);
27
+ this.name = "TimelineValidationError";
28
+ }
29
+ };
30
+ /** Spring key rule (§2.7): a spring-eased key's t must equal prev.t + spring.duration(cfg). */
31
+ function validateSpringKeys(tr) {
32
+ for (let i = 1; i < tr.keys.length; i++) {
33
+ const k = tr.keys[i];
34
+ if (k.ease && typeof k.ease === "object" && k.ease.kind === "spring") {
35
+ const expected = tr.keys[i - 1].t + spring.duration(k.ease);
36
+ if (Math.abs(k.t - expected) > 1e-6) throw new TimelineValidationError(`track '${tr.target}': spring-eased key at t=${k.t} must sit at prev.t + spring.duration (${expected.toFixed(6)}); the builder computes this automatically`);
37
+ }
38
+ }
39
+ }
40
+ /**
41
+ * Sample-indexed clip offset — the single A/V-sync source of truth (§5.3),
42
+ * `round(at * sampleRate)`. Both the CLI (FFmpeg) and the browser
43
+ * (OfflineAudioContext) mixers derive their delay from this, so A/V offsets
44
+ * agree across paths by construction rather than one rounding to milliseconds
45
+ * and the other to raw float seconds. Default rate is the canonical mix grid.
46
+ */
47
+ function audioOffsetSamples(at, sampleRate = 48e3) {
48
+ return Math.round(at * sampleRate);
49
+ }
50
+ function rebaseKeys(keys, at, timeScale) {
51
+ return keys.map((k) => ({
52
+ ...k,
53
+ t: at + k.t / timeScale
54
+ }));
55
+ }
56
+ /**
57
+ * `.call()` markers carry an auto-assigned `call:N` name whose counter resets
58
+ * per document, so two sibling sub-timelines that each define a `.call()` would
59
+ * collide on `call:0` when rebased into one parent (one callback dropped, the
60
+ * other double-firing). We namespace a child's `call:*` markers by the child's
61
+ * position PATH in the parent (`c<index>/…`), and the builder's `add()` applies
62
+ * the EXACT same prefix when forwarding the child's name→fn map — so the rebased
63
+ * marker name and the registered callback key agree by construction. Only
64
+ * `call:*` names are rewritten; author-named cues keep their names. Both surfaces
65
+ * call these so the convention lives in one place.
66
+ */
67
+ function callMarkerPrefix(childIndex) {
68
+ return `c${childIndex}/`;
69
+ }
70
+ const CALL = /(^|\/)call:\d+$/;
71
+ function namespaceCallName(name, prefix) {
72
+ return prefix !== "" && CALL.test(name) ? prefix + name : name;
73
+ }
74
+ function flatten(doc, at, timeScale, unit, out, counter) {
75
+ for (const tr of doc.tracks) {
76
+ validateTrack(tr);
77
+ validateSpringKeys(tr);
78
+ out.push({
79
+ track: {
80
+ ...tr,
81
+ keys: rebaseKeys(tr.keys, at, timeScale)
82
+ },
83
+ unit
84
+ });
85
+ }
86
+ for (const child of doc.children ?? []) {
87
+ const scale = child.mode === "sync" ? child.timeScale ?? 1 : 1;
88
+ if (child.mode === "add" && child.timeScale !== void 0) throw new TimelineValidationError("timeScale is only valid on mode:'sync' children (§2.3)");
89
+ if (scale <= 0) throw new TimelineValidationError("sync timeScale must be > 0");
90
+ const childUnit = child.mode === "sync" ? ++counter.n : unit;
91
+ flatten(child.timeline, at + child.at / timeScale, timeScale * scale, childUnit, out, counter);
92
+ }
93
+ }
94
+ /**
95
+ * Coalesce same-target tracks: later insertion wins where key ranges overlap
96
+ * (§2.2/§2.6 decided rule), with a dev warning. Earlier keys inside the later
97
+ * track's [first.t, last.t] span are dropped.
98
+ */
99
+ function coalesce(entries) {
100
+ const byTarget = /* @__PURE__ */ new Map();
101
+ for (const { track: tr, unit } of entries) {
102
+ const existing = byTarget.get(tr.target);
103
+ if (!existing) {
104
+ byTarget.set(tr.target, {
105
+ track: {
106
+ ...tr,
107
+ keys: [...tr.keys]
108
+ },
109
+ unit
110
+ });
111
+ continue;
112
+ }
113
+ if (existing.unit !== unit) throw new TimelineValidationError(`target '${tr.target}' is animated by a sync (opaque) child and another timeline unit; sync children must own disjoint targets (no last-writer-wins across the sync boundary, §2.3)`);
114
+ if (existing.track.type !== tr.type) throw new TimelineValidationError(`target '${tr.target}' has conflicting value types '${existing.track.type}' and '${tr.type}'`);
115
+ const start = tr.keys[0].t;
116
+ const end = tr.keys[tr.keys.length - 1].t;
117
+ const existingStart = existing.track.keys[0].t;
118
+ const existingEnd = existing.track.keys[existing.track.keys.length - 1].t;
119
+ const kept = existing.track.keys.filter((k) => k.t < start || k.t > end);
120
+ if (existingStart <= end && start <= existingEnd) emitDevWarning(`overlapping tracks for '${tr.target}' in [${start}, ${end}]: later insertion wins (${existing.track.keys.length - kept.length} earlier key(s) dropped)`);
121
+ existing.track.keys = [...kept, ...tr.keys].sort((a, b) => a.t - b.t);
122
+ }
123
+ const result = /* @__PURE__ */ new Map();
124
+ for (const [target, { track }] of byTarget) result.set(target, track);
125
+ return result;
126
+ }
127
+ /**
128
+ * Studio reader (§6.2 rule 4): is this timeline's duration opted into editor
129
+ * editing? Code-owned and read-only by default; `editableDuration()` flips it.
130
+ */
131
+ function isDurationEditable(doc) {
132
+ return doc.editableDuration === true;
133
+ }
134
+ function childExtent(child) {
135
+ const scale = child.mode === "sync" ? child.timeScale ?? 1 : 1;
136
+ return child.at + computeDuration(child.timeline) / scale;
137
+ }
138
+ function computeDuration(doc) {
139
+ if (doc.duration !== void 0) return doc.duration;
140
+ let max = 0;
141
+ for (const tr of doc.tracks) {
142
+ const last = tr.keys[tr.keys.length - 1];
143
+ if (last) max = Math.max(max, last.t);
144
+ }
145
+ for (const m of doc.markers ?? []) max = Math.max(max, m.t);
146
+ for (const child of doc.children ?? []) max = Math.max(max, childExtent(child));
147
+ return max;
148
+ }
149
+ function compileTimeline(doc) {
150
+ if (doc.version !== 1) throw new TimelineValidationError(`unsupported timeline document version ${String(doc.version)}`);
151
+ const flat = [];
152
+ flatten(doc, 0, 1, 0, flat, { n: 0 });
153
+ const tracks = coalesce(flat);
154
+ const labels = { ...doc.labels };
155
+ const markers = [...doc.markers ?? []];
156
+ const audio = [...doc.audio ?? []];
157
+ const visitChildren = (children, at, scale, prefix) => {
158
+ (children ?? []).forEach((child, index) => {
159
+ const base = at + child.at / scale;
160
+ const childScale = scale * (child.mode === "sync" ? child.timeScale ?? 1 : 1);
161
+ const childPrefix = prefix + callMarkerPrefix(index);
162
+ for (const [name, t] of Object.entries(child.timeline.labels ?? {})) if (!(name in labels)) labels[name] = base + t / childScale;
163
+ for (const m of child.timeline.markers ?? []) markers.push({
164
+ ...m,
165
+ name: namespaceCallName(m.name, childPrefix),
166
+ t: base + m.t / childScale
167
+ });
168
+ for (const clip of child.timeline.audio ?? []) audio.push({
169
+ ...clip,
170
+ at: base + clip.at / childScale,
171
+ ...childScale !== 1 ? { playbackRate: (clip.playbackRate ?? 1) * childScale } : {}
172
+ });
173
+ visitChildren(child.timeline.children, base, childScale, childPrefix);
174
+ });
175
+ };
176
+ visitChildren(doc.children, 0, 1, "");
177
+ markers.sort((a, b) => a.t - b.t);
178
+ audio.sort((a, b) => a.at - b.at);
179
+ return {
180
+ duration: computeDuration(doc),
181
+ labels,
182
+ markers,
183
+ tracks,
184
+ audio
185
+ };
186
+ }
187
+ //#endregion
188
+ export { isDurationEditable as a, compileTimeline as i, audioOffsetSamples as n, namespaceCallName as o, callMarkerPrefix as r, timeline as s, TimelineValidationError as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glissade/core",
3
- "version": "0.19.0",
3
+ "version": "0.20.0-pre.3",
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
  "engines": {
@@ -21,6 +21,10 @@
21
21
  "types": "./dist/clips.d.ts",
22
22
  "default": "./dist/clips.js"
23
23
  },
24
+ "./sidecar": {
25
+ "types": "./dist/sidecar.d.ts",
26
+ "default": "./dist/sidecar.js"
27
+ },
24
28
  "./i18n": {
25
29
  "types": "./dist/i18n.d.ts",
26
30
  "default": "./dist/i18n.js"