Symbols, not timelines; no symbol is special

Everything that holds nodes is a symbol (domain/timeline -> domain/symbol,
:timelines -> :symbols) and a node that places one is :kind :instance. The
reserved :main root is gone: which symbol is on screen is editor state
([:ui :open]), every domain function that needs a symbol is told which, and
a document opens on the longest symbol nothing else places.

Saved projects move to schema 2 through migration 0007, which rewrites leaf
paths, instance kinds and the feature :symbol key; the client refuses a
schema it does not read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Olive Vaughn 2026-09-29 12:46:42 -04:00
parent 179770d7d4
commit 5dff490162
61 changed files with 1587 additions and 1431 deletions

View file

@ -415,9 +415,11 @@ different rules:
*not* to the plate, which is the whole point of it — so the offset genuinely
belongs at the node, not the clip.
## Timelines, and why a scene is one
## Symbols, and why a scene is one
A **timeline** is an ordered bag of nodes in its own frame space:
A **symbol** is an ordered bag of nodes in its own frame space. (Earlier drafts
and code called this a *timeline*; that word now means only the UI pane that
shows one.)
```clojure
{:frames 91
@ -427,9 +429,10 @@ A **timeline** is an ordered bag of nodes in its own frame space:
That is the whole type, and **everything that holds nodes is one of these**:
- a clip's **scene** is its root timeline,
- a **symbol** in the library is a timeline,
- a node with `:kind :symbol` is an **instance** of one.
- what a document opens on is a symbol, and **no symbol is reserved** — a new
document's is called `main` only because it has to be called something,
- anything placed inside another symbol is a symbol,
- a node with `:kind :instance` is an **instance** of one.
An earlier draft of this document had a scene and a `:kind :timeline` symbol as
two structures with the same fields and never said they were the same thing.
@ -474,7 +477,7 @@ for all three is the same — **their own**:
### Instances
A node with `:kind :symbol` and `:of :sym/blink` places one. Its own channels
A node with `:kind :instance` and `:of :sym/blink` places one. Its own channels
compose *over* the symbol's, so one definition is placed many times and tinted,
offset or retimed at each placement — that is how a three-frame blink is reused
at frames 40, 88 and 200 without copying it.

View file

@ -639,10 +639,10 @@ is what step 9 implemented, for the subset that exists:
clip/<cid>/name clip/<cid>/subject/<sid>
clip/<cid>/timing clip/<cid>/feature/<fid>
clip/<cid>/stage clip/<cid>/group/<gid>
clip/<cid>/source clip/<cid>/timeline/<tid>
clip/<cid>/timeline/<tid>/node/<nid>
clip/<cid>/timeline/<tid>/measured/<nid>
clip/<cid>/timeline/<tid>/channel/<nid>/<prop>
clip/<cid>/source clip/<cid>/symbol/<sid>
clip/<cid>/symbol/<sid>/node/<nid>
clip/<cid>/symbol/<sid>/measured/<nid>
clip/<cid>/symbol/<sid>/channel/<nid>/<prop>
```
Settings live on subject, feature and group leaves. Each feature has one area, so
@ -785,12 +785,12 @@ clip/:cid/timing clip rate
clip/:cid/subject/:sid tracked subject and settings
clip/:cid/feature/:fid tracked feature and settings
clip/:cid/group/:gid shared settings for an eye pair
clip/:cid/timeline/:tid frame count, palette
clip/:cid/timeline/:tid/node/:nid one node: parent, stencil, z, time
clip/:cid/timeline/:tid/channel/:nid/:prop
clip/:cid/timeline/:tid/measured/:nid
clip/:cid/timeline/:tid/cel/:nid/:frame
clip/:cid/timeline/:tid/overrides/:nid/:prop
clip/:cid/symbol/:sid frame count, palette
clip/:cid/symbol/:sid/node/:nid one node: parent, stencil, z, time
clip/:cid/symbol/:sid/channel/:nid/:prop
clip/:cid/symbol/:sid/measured/:nid
clip/:cid/symbol/:sid/cel/:nid/:frame
clip/:cid/symbol/:sid/overrides/:nid/:prop
```
Each feature and node has its own leaf, so tuning separate features and adding

View file

@ -8,11 +8,11 @@ symbol instance. Timelines already provide local node names, independent playbac
and persistence. No new kind of scene container is needed.
```clojure
:timelines
:symbols
{:main {:nodes {:root {:time {:mode :map :expose 2}}
:face {:parent :root :channels <source-to-stage placement>}
:face-1 {:kind :symbol :of :face-1 :parent :face :z "a0"}
:face-2 {:kind :symbol :of :face-2 :parent :face :z "a1"}}}
:face-1 {:kind :instance :of :face-1 :parent :face :z "a0"}
:face-2 {:kind :instance :of :face-2 :parent :face :z "a1"}}}
:face-1 {:nodes {:head {...} :mouth {:parent :head ...} ...}}
:face-2 {:nodes {:head {...} :mouth {:parent :head ...} ...}}}

View file

@ -70,7 +70,7 @@ handling and the relevant key whitelist if its storage location requires it.
- `freeze/performance-nodes` marks generated animated channels with
`:pose-sampled?` and local `:pose-group` names. This includes keyed visibility
as well as dense geometry. `:generated` remains provenance for regeneration.
- `timeline/channel-frame` already applies explicit pose choices and default
- `symbol/channel-frame` already applies explicit pose choices and default
picture sampling to marked channels. Playback and export both use
`clip/resolver` with `:picture-fps`; there is no need for a second sampling
implementation. Export's pose count is still a rate-based estimate.