@glissade/core 0.14.0 → 0.15.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/clips.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { J as EaseSpec, T as ValueTypeId, r as Track, t as Key } from "./track.js";
1
+ import { T as ValueTypeId, Y as EaseSpec, r as Track, t as Key } from "./track.js";
2
2
  import { r as TweenTarget } from "./targetRef.js";
3
3
 
4
4
  //#region src/clip.d.ts
package/dist/i18n.d.ts CHANGED
@@ -30,8 +30,13 @@ interface LocalizeOptions {
30
30
  * Message ids ALREADY consumed outside the doc — the free-standing `t()` ids
31
31
  * resolved at module-eval / createScene() time (`getConsumedMessageIds()`).
32
32
  * `localize` folds these into the "matched keys" set so the orphaned-key check
33
- * (FIX 5) doesn't flag a legitimate `t()` key as unmatched. Omit when there is
34
- * no ambient `t()` usage (then only node-id keys count as matched).
33
+ * (0.14 FIX 5) doesn't flag a legitimate `t()` key as unmatched. Omit when there
34
+ * is no ambient `t()` usage (then only node-id keys count as matched).
35
+ *
36
+ * 0.15 FIX 2: this set ALSO drives the key-collision guard — a table key that is
37
+ * BOTH a `t()` id (in here) AND a node-id with a string track is ambiguous (the
38
+ * two id-spaces would silently fight over the same key, with the node-track swap
39
+ * winning), so `localize` throws a `LocalizationError` rather than guess.
35
40
  */
36
41
  consumedIds?: ReadonlySet<string> | undefined;
37
42
  }
@@ -49,27 +54,66 @@ declare class LocalizationError extends Error {
49
54
  * Pure: no playhead, no time, no filesystem. The base-locale table (or an empty
50
55
  * table) leaves the doc structurally identical to the input.
51
56
  *
52
- * FIX 5 (0.14 canary): a table key matching NO consumed id (neither a string-track
57
+ * 0.14 FIX 5: a table key matching NO consumed id (neither a string-track
53
58
  * node-id NOR a `t()` id passed via `opts.consumedIds`) is a stale/typo'd key that
54
59
  * would silently never localize anything — so `localize` throws a `LocalizationError`
55
60
  * naming every orphaned key (the inverse of `t()`'s hard-fail). A fully-matched
56
61
  * table is silent.
62
+ *
63
+ * 0.15 FIX 1 (multi-cue collapse): broadcasting one table string across EVERY key
64
+ * of a string track freezes a single caption over a multi-cue track (>1 distinct
65
+ * keyed value). A multi-cue caption must be regenerated per-locale (locale-tagged
66
+ * narration), never collapsed — so a matched track with >1 distinct key value
67
+ * HARD-THROWS, naming the id. A single-value / single-key track localizes fine.
68
+ *
69
+ * 0.15 FIX 2 (key collision): a table key that is BOTH a node-id-with-a-string-
70
+ * track AND a `t()` id (`opts.consumedIds`) is ambiguous — the two id-spaces would
71
+ * silently fight over the same flat key. `localize` HARD-THROWS on any such key
72
+ * rather than let the node-track swap silently win. The flat `messages.<locale>.json`
73
+ * shape is UNCHANGED; this is a pure additive guard.
57
74
  */
58
75
  declare function localize(doc: Timeline, table: MessageTable, opts: LocalizeOptions): Timeline;
59
76
  /**
60
- * Install the ambient message table consulted by `t()`. Called ONCE before
61
- * scene construction (the render entry injects it from `--locale`). Pass
62
- * `undefined` to clear it (the base-locale / no-flag path leaves it unset).
63
- * Resets the consumed-id set (a fresh table = a fresh resolution pass).
77
+ * Install the ambient message table consulted by `t()` on the PROCESS-GLOBAL
78
+ * scope. Called ONCE before scene construction (the render entry injects it from
79
+ * `--locale`). Pass `undefined` to clear it (the base-locale / no-flag path leaves
80
+ * it unset). Resets the consumed-id set (a fresh table = a fresh resolution pass).
81
+ *
82
+ * 0.15 FIX 3: for CONCURRENT renders of different locales, prefer
83
+ * `runWithMessageTable` — this global setter races across interleaved flows.
64
84
  */
65
85
  declare function setMessageTable(table: MessageTable | undefined): void;
86
+ /**
87
+ * Run `fn` with `table` installed as the ambient message table for the DURATION
88
+ * of `fn`'s async flow only — isolated from the process-global scope and from any
89
+ * other concurrent flow (0.15 FIX 3). `t()` inside `fn` (and anything it awaits)
90
+ * resolves against `table`; `t()` outside it is unaffected. Resolves to `fn`'s
91
+ * result. The render entry can scope the whole `loadSceneModule`→`localize` flow
92
+ * per locale with this so parallel embedders never cross-contaminate. The
93
+ * consumed-id set is collected per scope and is readable with
94
+ * `getConsumedMessageIds()` inside `fn`.
95
+ *
96
+ * Async because the AsyncLocalStorage class is loaded via a dynamic `node` import
97
+ * on first use (so this prepare/render-path module never statically pulls in
98
+ * `node:async_hooks`); the ALS context still spans `fn` and everything it awaits.
99
+ */
100
+ declare function runWithMessageTable<T>(table: MessageTable | undefined, fn: () => T | Promise<T>): Promise<T>;
66
101
  /** The currently installed ambient message table (undefined when none is set). */
67
102
  declare function getMessageTable(): MessageTable | undefined;
103
+ /**
104
+ * Run `fn` while PRESERVING the process-global ambient scope across it (0.15 FIX 3).
105
+ * `fn` may freely call `setMessageTable` / `loadSceneModule` (which clobbers the
106
+ * global table) — the table + consumed-id set are snapshotted before and restored
107
+ * after, so a no-locale helper (the audio-mix gather) can't leave a leaked or
108
+ * cleared table visible to a concurrent locale's flow. Awaits/returns `fn`'s result.
109
+ */
110
+ declare function preservingMessageTable<T>(fn: () => Promise<T>): Promise<T>;
68
111
  /**
69
112
  * The set of ids `t()` has resolved against the ambient table since the last
70
- * `setMessageTable`. The render entry passes this to `localize` so a key consumed
71
- * by a free-standing `t()` (which `localize` can't see it isn't a doc track)
72
- * isn't reported as an orphaned table key (FIX 5).
113
+ * `setMessageTable` (or, inside a `runWithMessageTable` flow, since that scope
114
+ * began). The render entry passes this to `localize` so a key consumed by a
115
+ * free-standing `t()` (which `localize` can't see it isn't a doc track) isn't
116
+ * reported as an orphaned table key (0.14 FIX 5).
73
117
  */
74
118
  declare function getConsumedMessageIds(): ReadonlySet<string>;
75
119
  /**
@@ -84,4 +128,4 @@ declare function getConsumedMessageIds(): ReadonlySet<string>;
84
128
  */
85
129
  declare function t(id: string): string;
86
130
  //#endregion
87
- export { LocaleManifest, LocalizationError, LocalizeOptions, MessageTable, ParityError, getConsumedMessageIds, getMessageTable, localize, requireParity, setMessageTable, t };
131
+ export { LocaleManifest, LocalizationError, LocalizeOptions, MessageTable, ParityError, getConsumedMessageIds, getMessageTable, localize, preservingMessageTable, requireParity, runWithMessageTable, setMessageTable, t };
package/dist/i18n.js CHANGED
@@ -17,6 +17,14 @@ var ParityError = class extends Error {
17
17
  * (or zero) is trivially in parity.
18
18
  */
19
19
  function requireParity(...manifests) {
20
+ for (const m of manifests) if (new Set(m.ids).size !== m.ids.length) {
21
+ const seen = /* @__PURE__ */ new Set();
22
+ const dups = [];
23
+ for (const id of m.ids) if (seen.has(id)) dups.push(id);
24
+ else seen.add(id);
25
+ const uniqueDups = [...new Set(dups)].sort();
26
+ throw new ParityError(`locale '${m.locale}' declares duplicate id(s): ${uniqueDups.map((id) => `'${id}'`).join(", ")} — each message id must appear once per manifest.`);
27
+ }
20
28
  if (manifests.length < 2) return;
21
29
  const union = /* @__PURE__ */ new Set();
22
30
  for (const m of manifests) for (const id of m.ids) union.add(id);
@@ -56,11 +64,23 @@ function nodeIdOf(target) {
56
64
  * Pure: no playhead, no time, no filesystem. The base-locale table (or an empty
57
65
  * table) leaves the doc structurally identical to the input.
58
66
  *
59
- * FIX 5 (0.14 canary): a table key matching NO consumed id (neither a string-track
67
+ * 0.14 FIX 5: a table key matching NO consumed id (neither a string-track
60
68
  * node-id NOR a `t()` id passed via `opts.consumedIds`) is a stale/typo'd key that
61
69
  * would silently never localize anything — so `localize` throws a `LocalizationError`
62
70
  * naming every orphaned key (the inverse of `t()`'s hard-fail). A fully-matched
63
71
  * table is silent.
72
+ *
73
+ * 0.15 FIX 1 (multi-cue collapse): broadcasting one table string across EVERY key
74
+ * of a string track freezes a single caption over a multi-cue track (>1 distinct
75
+ * keyed value). A multi-cue caption must be regenerated per-locale (locale-tagged
76
+ * narration), never collapsed — so a matched track with >1 distinct key value
77
+ * HARD-THROWS, naming the id. A single-value / single-key track localizes fine.
78
+ *
79
+ * 0.15 FIX 2 (key collision): a table key that is BOTH a node-id-with-a-string-
80
+ * track AND a `t()` id (`opts.consumedIds`) is ambiguous — the two id-spaces would
81
+ * silently fight over the same flat key. `localize` HARD-THROWS on any such key
82
+ * rather than let the node-track swap silently win. The flat `messages.<locale>.json`
83
+ * shape is UNCHANGED; this is a pure additive guard.
64
84
  */
65
85
  function localize(doc, table, opts) {
66
86
  let changed = false;
@@ -69,6 +89,9 @@ function localize(doc, table, opts) {
69
89
  if (tr.type !== "string") return tr;
70
90
  const id = nodeIdOf(tr.target);
71
91
  if (!(id in table)) return tr;
92
+ if (opts.consumedIds?.has(id)) throw new LocalizationError(`message table for locale '${opts.locale}' has an AMBIGUOUS key '${id}' — it matches BOTH a string track on node '${id}' AND a free-standing t('${id}') id. The flat table can't serve both id-spaces from one key. Rename one of them so the key resolves a single target.`);
93
+ const distinctValues = new Set(tr.keys.map((k) => k.value));
94
+ if (distinctValues.size > 1) throw new LocalizationError(`message table for locale '${opts.locale}' targets node '${id}', whose string track has ${distinctValues.size} distinct keyed values (a multi-cue caption). Broadcasting one table string would freeze a single caption over the whole track. Regenerate this locale's narration (a locale-tagged '<base>.${opts.locale}.narration.timing.json') instead of localizing the multi-cue track via the message table.`);
72
95
  consumed.add(id);
73
96
  const localized = table[id];
74
97
  changed = true;
@@ -88,30 +111,88 @@ function localize(doc, table, opts) {
88
111
  tracks
89
112
  };
90
113
  }
91
- let ambientTable;
92
- let consumedIds = /* @__PURE__ */ new Set();
114
+ const globalScope = {
115
+ table: void 0,
116
+ consumedIds: /* @__PURE__ */ new Set()
117
+ };
118
+ let ambientStore;
119
+ /** The scope `t()` should read: the ALS-scoped one if active, else the global one. */
120
+ function activeScope() {
121
+ return ambientStore?.getStore() ?? globalScope;
122
+ }
93
123
  /**
94
- * Install the ambient message table consulted by `t()`. Called ONCE before
95
- * scene construction (the render entry injects it from `--locale`). Pass
96
- * `undefined` to clear it (the base-locale / no-flag path leaves it unset).
97
- * Resets the consumed-id set (a fresh table = a fresh resolution pass).
124
+ * Install the ambient message table consulted by `t()` on the PROCESS-GLOBAL
125
+ * scope. Called ONCE before scene construction (the render entry injects it from
126
+ * `--locale`). Pass `undefined` to clear it (the base-locale / no-flag path leaves
127
+ * it unset). Resets the consumed-id set (a fresh table = a fresh resolution pass).
128
+ *
129
+ * 0.15 FIX 3: for CONCURRENT renders of different locales, prefer
130
+ * `runWithMessageTable` — this global setter races across interleaved flows.
98
131
  */
99
132
  function setMessageTable(table) {
100
- ambientTable = table;
101
- consumedIds = /* @__PURE__ */ new Set();
133
+ globalScope.table = table;
134
+ globalScope.consumedIds = /* @__PURE__ */ new Set();
135
+ }
136
+ /**
137
+ * Run `fn` with `table` installed as the ambient message table for the DURATION
138
+ * of `fn`'s async flow only — isolated from the process-global scope and from any
139
+ * other concurrent flow (0.15 FIX 3). `t()` inside `fn` (and anything it awaits)
140
+ * resolves against `table`; `t()` outside it is unaffected. Resolves to `fn`'s
141
+ * result. The render entry can scope the whole `loadSceneModule`→`localize` flow
142
+ * per locale with this so parallel embedders never cross-contaminate. The
143
+ * consumed-id set is collected per scope and is readable with
144
+ * `getConsumedMessageIds()` inside `fn`.
145
+ *
146
+ * Async because the AsyncLocalStorage class is loaded via a dynamic `node` import
147
+ * on first use (so this prepare/render-path module never statically pulls in
148
+ * `node:async_hooks`); the ALS context still spans `fn` and everything it awaits.
149
+ */
150
+ async function runWithMessageTable(table, fn) {
151
+ if (ambientStore === void 0) {
152
+ const { AsyncLocalStorage } = await import("node:async_hooks");
153
+ ambientStore ??= new AsyncLocalStorage();
154
+ }
155
+ return ambientStore.run({
156
+ table,
157
+ consumedIds: /* @__PURE__ */ new Set()
158
+ }, fn);
102
159
  }
103
160
  /** The currently installed ambient message table (undefined when none is set). */
104
161
  function getMessageTable() {
105
- return ambientTable;
162
+ return activeScope().table;
163
+ }
164
+ /**
165
+ * Run `fn` while PRESERVING the process-global ambient scope across it (0.15 FIX 3).
166
+ * `fn` may freely call `setMessageTable` / `loadSceneModule` (which clobbers the
167
+ * global table) — the table + consumed-id set are snapshotted before and restored
168
+ * after, so a no-locale helper (the audio-mix gather) can't leave a leaked or
169
+ * cleared table visible to a concurrent locale's flow. Awaits/returns `fn`'s result.
170
+ */
171
+ function preservingMessageTable(fn) {
172
+ const savedTable = globalScope.table;
173
+ const savedConsumed = globalScope.consumedIds;
174
+ const restore = () => {
175
+ globalScope.table = savedTable;
176
+ globalScope.consumedIds = savedConsumed;
177
+ };
178
+ let promise;
179
+ try {
180
+ promise = fn();
181
+ } catch (err) {
182
+ restore();
183
+ throw err;
184
+ }
185
+ return promise.finally(restore);
106
186
  }
107
187
  /**
108
188
  * The set of ids `t()` has resolved against the ambient table since the last
109
- * `setMessageTable`. The render entry passes this to `localize` so a key consumed
110
- * by a free-standing `t()` (which `localize` can't see it isn't a doc track)
111
- * isn't reported as an orphaned table key (FIX 5).
189
+ * `setMessageTable` (or, inside a `runWithMessageTable` flow, since that scope
190
+ * began). The render entry passes this to `localize` so a key consumed by a
191
+ * free-standing `t()` (which `localize` can't see it isn't a doc track) isn't
192
+ * reported as an orphaned table key (0.14 FIX 5).
112
193
  */
113
194
  function getConsumedMessageIds() {
114
- return consumedIds;
195
+ return activeScope().consumedIds;
115
196
  }
116
197
  /**
117
198
  * Resolve a free-standing message `id` against the ambient table — build-time
@@ -124,11 +205,13 @@ function getConsumedMessageIds() {
124
205
  * so an un-localized build renders its keys as authored and stays byte-identical.
125
206
  */
126
207
  function t(id) {
208
+ const scope = activeScope();
209
+ const ambientTable = scope.table;
127
210
  if (ambientTable === void 0) return id;
128
211
  const value = ambientTable[id];
129
- if (value !== void 0) consumedIds.add(id);
212
+ if (value !== void 0) scope.consumedIds.add(id);
130
213
  if (value === void 0) throw new LocalizationError(`t('${id}'): no message for id '${id}' in the active message table (have: ${Object.keys(ambientTable).map((k) => `'${k}'`).join(", ") || "<empty>"})`);
131
214
  return value;
132
215
  }
133
216
  //#endregion
134
- export { LocalizationError, ParityError, getConsumedMessageIds, getMessageTable, localize, requireParity, setMessageTable, t };
217
+ export { LocalizationError, ParityError, getConsumedMessageIds, getMessageTable, localize, preservingMessageTable, requireParity, runWithMessageTable, setMessageTable, t };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as easingDerivatives, A as getValueType, B as RetargetSpring, C as UnknownValueTypeError, D as Vec2, E as ValueTypeInferenceError, F as registerValueType, G as springEasingDerivative, H as SpringEase, I as stringType, J as EaseSpec, K as springPresets, L as vec2ArcType, M as numberType, N as paintType, O as booleanType, P as pathType, Q as cubicBezierDerivative, R as vec2Equals, S as PathValue, T as ValueTypeId, U as spring, V as SpringConfig, W as springEasing, X as UnknownEasingError, Y as EasingFn, Z as cubicBezier, _ as MeshInterpolation, a as key, b as Paint, c as sampleTrack, d as track, et as easings, 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, o as resolveEase, p as velocityAt, q as DEFAULT_EASE, r as Track, s as resolveEaseDerivative, t as Key, tt as namedEasing, u as stagger, v as MeshPaint, w as ValueType, x as PathContour, y as MeshPoint, z as vec2Type } from "./track.js";
1
+ import { $ as cubicBezierDerivative, A as getValueType, B as vec2Type, C as UnknownValueTypeError, D as Vec2, E as ValueTypeInferenceError, F as registerValueType, G as springEasing, H as SpringConfig, I as reprOf, J as DEFAULT_EASE, K as springEasingDerivative, L as stringType, M as numberType, N as paintType, O as booleanType, P as pathType, Q as cubicBezier, R as vec2ArcType, S as PathValue, T as ValueTypeId, U as SpringEase, V as RetargetSpring, W as spring, X as EasingFn, Y as EaseSpec, Z as UnknownEasingError, _ as MeshInterpolation, a as key, b as Paint, c as sampleTrack, d as track, et as easingDerivatives, 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 namedEasing, o as resolveEase, p as velocityAt, q as springPresets, r as Track, s as resolveEaseDerivative, t as Key, tt as easings, u as stagger, v as MeshPaint, w as ValueType, x as PathContour, y as MeshPoint, z as vec2Equals } 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
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";
@@ -310,12 +310,14 @@ interface BindTarget {
310
310
  bindSource(fn: () => unknown): void;
311
311
  unbindSource(): void;
312
312
  /**
313
- * The value type this target accepts; a track of any other type is a bind
314
- * error. An ARRAY for a polymorphic prop that admits more than one type
315
- * (e.g. a Shape `fill` accepts both `color` and `paint`, or a vec2 prop that
316
- * accepts both `vec2` and `vec2-arc`). UNDEFINED means the target opted OUT
317
- * of the guard (an untagged custom-node prop back-compat with 0.13, which
318
- * had no guard): any track binds without a type check.
313
+ * The value type this target accepts; a track whose REPRESENTATION (its
314
+ * `repr`, single-hop) differs is a bind error. So a `vec2-arc` track (repr
315
+ * 'vec2') binds to a plain `'vec2'` target, and a custom `cents` (repr
316
+ * 'number') binds to a `'number'` target the extension door (0.15). An
317
+ * ARRAY is for GENUINE polymorphism distinct reprs admitted at one prop
318
+ * (e.g. a Shape `fill` accepts both `color` and `paint`). UNDEFINED means the
319
+ * target opted OUT of the guard (an untagged custom-node prop — back-compat
320
+ * with 0.13, which had no guard): any track binds without a type check.
319
321
  */
320
322
  readonly expects: ValueTypeId | readonly ValueTypeId[] | undefined;
321
323
  }
@@ -390,4 +392,4 @@ interface CheckpointedSim {
390
392
  }
391
393
  declare function bakeCheckpointed<W, S = W>(cfg: CheckpointedBakeConfig<W, S>): CheckpointedSim;
392
394
  //#endregion
393
- 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, 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, mergeSidecar, mergeSidecarDetailed, migrateSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, paintType, parseCmap, parseColor, pathType, random, registerValueType, 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 };
395
+ 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, 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, 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 };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as ColorParseError, B as springEasingDerivative, C as paintType, D as vec2ArcType, E as stringType, F as rgbaToOklab, G as cubicBezierDerivative, H as DEFAULT_EASE, I as emitDevWarning, J as namedEasing, K as easingDerivatives, L as setDevWarning, M as lerpColor, N as oklabToRgba, O as vec2Equals, P as parseColor, R as spring, S as numberType, T as registerValueType, U as UnknownEasingError, V as springPresets, W as cubicBezier, _ 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 formatColor, k as vec2Type, l as resolveEaseDerivative, m as validateTrack, n as UnresolvableTargetError, o as TrackValidationError, p as track, q as easings, r as isEditableNodeId, s as key, t as TARGET_PATH, u as sampleTrack, v as booleanType, w as pathType, x as inferValueType, y as colorType, z as springEasing } from "./targetRef.js";
1
+ import { A as vec2Type, B as springEasing, C as paintType, D as stringType, E as reprOf, F as parseColor, G as cubicBezier, H as springPresets, I as rgbaToOklab, J as easings, K as cubicBezierDerivative, L as emitDevWarning, M as formatColor, N as lerpColor, O as vec2ArcType, P as oklabToRgba, R as setDevWarning, S as numberType, T as registerValueType, U as DEFAULT_EASE, V as springEasingDerivative, W as UnknownEasingError, Y as namedEasing, _ 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 ColorParseError, k as vec2Equals, l as resolveEaseDerivative, m as validateTrack, n as UnresolvableTargetError, o as TrackValidationError, p as track, q as easingDerivatives, r as isEditableNodeId, s as key, t as TARGET_PATH, u as sampleTrack, v as booleanType, w as pathType, x as inferValueType, y as colorType, z as spring } from "./targetRef.js";
2
2
  import { t as parseCmap } from "./cmap.js";
3
3
  import { a as hashKeys, c as migrateSidecar, d as TimelineValidationError, f as audioOffsetSamples, h as timeline$1, i as emptySidecar, l as normalizeEditedKeys, m as isDurationEditable, n as assignKeyIds, o as mergeSidecar, p as compileTimeline, r as deleteSidecarTrack, s as mergeSidecarDetailed, t as SidecarVersionError, u as setSidecarTrack } from "./sidecar.js";
4
4
  //#region src/ticker.ts
@@ -855,7 +855,8 @@ function bindTimeline(compiled, resolve, playhead = createPlayhead()) {
855
855
  const got = tr.type;
856
856
  const expects = sig.expects;
857
857
  if (expects !== void 0) {
858
- if (!(Array.isArray(expects) ? expects.includes(got) : got === expects)) throw new BindTypeMismatchError(target, got, expects);
858
+ const gotRepr = reprOf(got);
859
+ if (!(Array.isArray(expects) ? expects : [expects]).some((e) => reprOf(e) === gotRepr)) throw new BindTypeMismatchError(target, got, expects);
859
860
  }
860
861
  sig.bindSource(() => sampleTrack(tr, playhead()));
861
862
  bound.push(sig);
@@ -1006,4 +1007,4 @@ function bakeCheckpointed(cfg) {
1006
1007
  };
1007
1008
  }
1008
1009
  //#endregion
1009
- 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, mergeSidecar, mergeSidecarDetailed, migrateSidecar, namedEasing, normalizeEditedKeys, numberType, oklabToRgba, paintType, parseCmap, parseColor, pathType, random, registerValueType, 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 };
1010
+ 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, 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 };
package/dist/sidecar.js CHANGED
@@ -1,4 +1,4 @@
1
- import { I as emitDevWarning, R as spring, a as targetNodeId, m as validateTrack, o as TrackValidationError, r as isEditableNodeId } from "./targetRef.js";
1
+ import { L as emitDevWarning, a as targetNodeId, m as validateTrack, o as TrackValidationError, r as isEditableNodeId, z as spring } from "./targetRef.js";
2
2
  //#region src/timeline.ts
3
3
  /**
4
4
  * The Timeline document (DESIGN.md §2.3) — the serializable animation source
@@ -1,4 +1,4 @@
1
- import { J as EaseSpec, T as ValueTypeId, t as Key } from "./track.js";
1
+ import { T as ValueTypeId, Y 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
4
  import { a as SidecarOrphan, r as SidecarDoc } from "./sidecar.js";
package/dist/targetRef.js CHANGED
@@ -490,6 +490,16 @@ function getValueType(id) {
490
490
  if (!vt) throw new UnknownValueTypeError(id);
491
491
  return vt;
492
492
  }
493
+ /**
494
+ * Resolve a value-type id to its REPRESENTATION (the bind guard's compatibility
495
+ * key, §2.2): a type's `repr` (the built-in it's representationally compatible
496
+ * with), else the id itself. SINGLE-HOP — `repr` is never chained, so a custom
497
+ * type's repr must be a base type. An UNREGISTERED id resolves to itself (the
498
+ * guard's job is repr-compat, not existence — `getValueType` enforces that).
499
+ */
500
+ function reprOf(id) {
501
+ return registry.get(id)?.repr ?? id;
502
+ }
493
503
  const numberType = {
494
504
  id: "number",
495
505
  lerp: (a, b, t) => a + (b - a) * t,
@@ -514,6 +524,7 @@ const vec2Type = {
514
524
  /** vec2 swept along a circular arc: polar lerp of radius + shortest-path angle (§2.2). */
515
525
  const vec2ArcType = {
516
526
  id: "vec2-arc",
527
+ repr: "vec2",
517
528
  lerp: (a, b, t) => {
518
529
  const ra = Math.hypot(a[0], a[1]);
519
530
  const rb = Math.hypot(b[0], b[1]);
@@ -1019,4 +1030,4 @@ function resolveTweenTarget(target) {
1019
1030
  return path;
1020
1031
  }
1021
1032
  //#endregion
1022
- export { ColorParseError as A, springEasingDerivative as B, paintType as C, vec2ArcType as D, stringType as E, rgbaToOklab as F, cubicBezierDerivative as G, DEFAULT_EASE as H, emitDevWarning as I, namedEasing as J, easingDerivatives as K, setDevWarning as L, lerpColor as M, oklabToRgba as N, vec2Equals as O, parseColor as P, spring as R, numberType as S, registerValueType as T, UnknownEasingError as U, springPresets as V, cubicBezier as W, ValueTypeInferenceError as _, targetNodeId as a, getValueType as b, resolveEase as c, springTo as d, stagger as f, UnknownValueTypeError as g, velocityAt as h, resolveTweenTarget as i, formatColor as j, vec2Type as k, resolveEaseDerivative as l, validateTrack as m, UnresolvableTargetError as n, TrackValidationError as o, track as p, easings as q, isEditableNodeId as r, key as s, TARGET_PATH as t, sampleTrack as u, booleanType as v, pathType as w, inferValueType as x, colorType as y, springEasing as z };
1033
+ export { vec2Type as A, springEasing as B, paintType as C, stringType as D, reprOf as E, parseColor as F, cubicBezier as G, springPresets as H, rgbaToOklab as I, easings as J, cubicBezierDerivative as K, emitDevWarning as L, formatColor as M, lerpColor as N, vec2ArcType as O, oklabToRgba as P, setDevWarning as R, numberType as S, registerValueType as T, DEFAULT_EASE as U, springEasingDerivative as V, UnknownEasingError as W, namedEasing as Y, ValueTypeInferenceError as _, targetNodeId as a, getValueType as b, resolveEase as c, springTo as d, stagger as f, UnknownValueTypeError as g, velocityAt as h, resolveTweenTarget as i, ColorParseError as j, vec2Equals as k, resolveEaseDerivative as l, validateTrack as m, UnresolvableTargetError as n, TrackValidationError as o, track as p, easingDerivatives as q, isEditableNodeId as r, key as s, TARGET_PATH as t, sampleTrack as u, booleanType as v, pathType as w, inferValueType as x, colorType as y, spring as z };
package/dist/track.d.ts CHANGED
@@ -145,6 +145,16 @@ type PathValue = PathContour[];
145
145
  type HandoffKind = 'cut' | 'decay' | 'spring' | 'blend-from-frozen';
146
146
  interface ValueType<T> {
147
147
  id: string;
148
+ /**
149
+ * The built-in value type this type is REPRESENTATIONALLY compatible with —
150
+ * the bind guard (binding.ts) matches a track to a target when their reprs
151
+ * agree, not just their ids. A custom type that is "a number under a different
152
+ * id" (e.g. a `cents` type) sets `repr: 'number'` so it binds to `number`
153
+ * props (and vice versa); `vec2-arc` sets `repr: 'vec2'`. SINGLE-HOP only:
154
+ * `repr` must name a base type that has no `repr` of its own (no chaining).
155
+ * Omitted ⇒ the type is its own repr.
156
+ */
157
+ repr?: ValueTypeId;
148
158
  lerp(a: T, b: T, t: number): T;
149
159
  /** Accepts easedT outside [0,1] (spring overshoot)? Otherwise clamped. */
150
160
  extrapolates: boolean;
@@ -165,6 +175,14 @@ declare class UnknownValueTypeError extends Error {
165
175
  constructor(id: string);
166
176
  }
167
177
  declare function getValueType<T = unknown>(id: ValueTypeId): ValueType<T>;
178
+ /**
179
+ * Resolve a value-type id to its REPRESENTATION (the bind guard's compatibility
180
+ * key, §2.2): a type's `repr` (the built-in it's representationally compatible
181
+ * with), else the id itself. SINGLE-HOP — `repr` is never chained, so a custom
182
+ * type's repr must be a base type. An UNREGISTERED id resolves to itself (the
183
+ * guard's job is repr-compat, not existence — `getValueType` enforces that).
184
+ */
185
+ declare function reprOf(id: ValueTypeId): ValueTypeId;
168
186
  declare const numberType: ValueType<number>;
169
187
  declare const vec2Equals: (a: Vec2, b: Vec2) => boolean;
170
188
  declare const vec2Type: ValueType<Vec2>;
@@ -326,4 +344,4 @@ declare function velocityAt<T>(tr: Track<T>, t: number): T | null;
326
344
  /** Pure sample of a track at time t (§2.4). */
327
345
  declare function sampleTrack<T>(tr: Track<T>, t: number): T;
328
346
  //#endregion
329
- export { easingDerivatives as $, getValueType as A, RetargetSpring as B, UnknownValueTypeError as C, Vec2 as D, ValueTypeInferenceError as E, registerValueType as F, springEasingDerivative as G, SpringEase as H, stringType as I, EaseSpec as J, springPresets as K, vec2ArcType as L, numberType as M, paintType as N, booleanType as O, pathType as P, cubicBezierDerivative as Q, vec2Equals as R, PathValue as S, ValueTypeId as T, spring as U, SpringConfig as V, springEasing as W, UnknownEasingError as X, EasingFn as Y, cubicBezier as Z, MeshInterpolation as _, key as a, Paint as b, sampleTrack as c, track as d, easings as et, validateTrack as f, HandoffKind as g, GradientInterpolation as h, TrackValidationError as i, inferValueType as j, colorType as k, springTo as l, ColorStop as m, KeyOpts as n, resolveEase as o, velocityAt as p, DEFAULT_EASE as q, Track as r, resolveEaseDerivative as s, Key as t, namedEasing as tt, stagger as u, MeshPaint as v, ValueType as w, PathContour as x, MeshPoint as y, vec2Type as z };
347
+ export { cubicBezierDerivative as $, getValueType as A, vec2Type as B, UnknownValueTypeError as C, Vec2 as D, ValueTypeInferenceError as E, registerValueType as F, springEasing as G, SpringConfig as H, reprOf as I, DEFAULT_EASE as J, springEasingDerivative as K, stringType as L, numberType as M, paintType as N, booleanType as O, pathType as P, cubicBezier as Q, vec2ArcType as R, PathValue as S, ValueTypeId as T, SpringEase as U, RetargetSpring as V, spring as W, EasingFn as X, EaseSpec as Y, UnknownEasingError as Z, MeshInterpolation as _, key as a, Paint as b, sampleTrack as c, track as d, easingDerivatives as et, validateTrack as f, HandoffKind as g, GradientInterpolation as h, TrackValidationError as i, inferValueType as j, colorType as k, springTo as l, ColorStop as m, KeyOpts as n, namedEasing as nt, resolveEase as o, velocityAt as p, springPresets as q, Track as r, resolveEaseDerivative as s, Key as t, easings as tt, stagger as u, MeshPaint as v, ValueType as w, PathContour as x, MeshPoint as y, vec2Equals as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glissade/core",
3
- "version": "0.14.0",
3
+ "version": "0.15.0-pre.1",
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": {