591 lines
34 KiB
Markdown
591 lines
34 KiB
Markdown
# The Lane Model
|
||
|
||
Revised 2026-10-01. Clip ownership, source playback, one-row generic lanes,
|
||
direct clip movement and edge editing, correction evaluation, and correction
|
||
authoring for rotation and position are implemented. The former cel-sheet
|
||
projection was removed: the timeline is the single timing interface. Sections
|
||
below that describe a cel sheet are retained as design history and are superseded
|
||
by this revision. Retiming commands are not.
|
||
See the status note under
|
||
[Proof obligations](#proof-obligations-and-implementation-order).
|
||
|
||
[Lane and cel handoff](lane-handoff.md) records what is built, the decisions
|
||
that are settled, and what to do next.
|
||
|
||
This revises the Claude artifact [The Lane Model](https://claude.ai/code/artifact/cd42981d-ed08-493f-94df-b7dd6657f0e6).
|
||
Its prose and diagram source were recovered from session
|
||
`1c603f71-84eb-498e-aeaf-4c0346f1f513`; the live artifact was not accessible for
|
||
reading or editing here. This repository document is the revised design. The
|
||
original artifact has not been updated, and edits made there outside the recorded
|
||
session may not be represented here.
|
||
|
||
For the subjects covered here, this document supersedes the original artifact
|
||
and conflicting proposals in `animation-model.md`, `timing-model.md`, and
|
||
`architecture.md`. Those documents retain useful detail about the existing system.
|
||
|
||
## Goal and compatibility policy
|
||
|
||
Arthur is one animation document with several ways to see and edit it: drawing
|
||
on the stage, arranging clips, timing cels, editing curves, and generating
|
||
motion from footage. Each view exposes relevant facts and invokes shared editing
|
||
operations. Switching views must preserve the meaning of the work.
|
||
|
||
The user explicitly requires no backward compatibility. Replace obsolete shapes,
|
||
APIs, and tests when a better model requires it. Do not retain compatibility
|
||
branches, adapters, or migrations solely to preserve the current document format.
|
||
A format marker can reject unsupported files clearly; it does not promise to
|
||
convert them. This policy does not authorize deleting existing user assets.
|
||
|
||
Simplicity means predictable composition, clear ownership, and few independent
|
||
rules. Minimizing field count is secondary to representing independent choices.
|
||
|
||
## What stays
|
||
|
||
- A symbol is the one container for authored scene nodes. A drawing can be a
|
||
one-frame symbol; an animation uses the same container over more frames.
|
||
- Nodes have stable identities and flat parent references. Shared content is
|
||
referenced rather than copied implicitly.
|
||
- Animatable properties are addressed by channel paths. Generated and authored
|
||
values participate in the same evaluation machinery.
|
||
- Authored data, generated blocks, and source media remain separate. Documents
|
||
reference immutable blocks; caches and resolver indexes remain derived.
|
||
- A pure reference evaluator specifies the result. Playback, seeking, preview,
|
||
export, and optimized cursors must agree with it.
|
||
- Existence, visibility, and missing measured data remain distinct facts.
|
||
|
||
## Content, cels, lanes, and rows
|
||
|
||
These have different identities and responsibilities:
|
||
|
||
| Concept | Owns | Example |
|
||
| --- | --- | --- |
|
||
| Content | Reusable nodes and their animation | Drawing `a2`, an animated head, or a sound asset |
|
||
| Cel | One use of content, its interval, source playback, and local treatment | `a2` exposed on frames 12–16 |
|
||
| Lane | A sequence of cels and shared properties | The girl's drawings and the girl's overall transform |
|
||
| View row or column | Presentation and editor state | Timeline row, cel-sheet column, or property curve |
|
||
|
||
Use the existing instance/node identity mechanism for cels. A cel
|
||
should not acquire a second identity system just because it is shown as a cel.
|
||
Lanes group cels; they do not introduce another node-holding content type.
|
||
The concrete candidate below uses existing group and instance nodes; its ownership
|
||
boundaries are part of the design. It is now the implemented shape, and the field
|
||
spellings below are the ones the runtime reads.
|
||
|
||
Each cel has a stable ID. Moving it, changing its hold, swapping its source,
|
||
or trimming it preserves that ID. Repeating it creates a new cel that may
|
||
reference the same content. A split retains the original ID on the left and gives
|
||
the right piece a new ID; commands return the resulting selection explicitly.
|
||
|
||
Cels are the canonical authored arrangement. A source-at-time channel or
|
||
interval index may be compiled from them for evaluation, but is not a second
|
||
editable copy of the schedule. This replaces the earlier proposal that every cel
|
||
must be represented solely as a source key. Ordinary property animation still
|
||
uses lightweight keys; it does not need cel objects.
|
||
|
||
A lane has non-overlapping half-open cel intervals `[start,end)`
|
||
in its own time space. Uncovered intervals are gaps. Empty lanes are valid.
|
||
Compositing and simultaneous sounds are represented by multiple lanes or ordinary
|
||
scene composition; an accidental overlap never silently selects a winner.
|
||
Transitions, if added, need explicit overlap and mixing semantics.
|
||
|
||
Properties can belong to content, one cel, or the lane. For example:
|
||
|
||
- Rotate the reusable drawing: all its uses change.
|
||
- Rotate one cel: only that cel changes.
|
||
- Animate the lane's rotation: whichever drawing is showing follows it.
|
||
|
||
A cel can have its own transform, gain, corrections, and source timing
|
||
while remaining a block in the same timeline row. Independent treatment never
|
||
requires a new row or an otherwise unnecessary wrapper symbol.
|
||
|
||
### Concrete candidate: a lane and ordinary instances
|
||
|
||
A lane is a group node with `:layout :sequence`. Its cels are ordinary
|
||
instance nodes whose `:parent` points to the group. All remain in their symbol's
|
||
flat node map. The sequence constraint is document semantics; which rows the UI
|
||
expands remains editor state. Ordinary groups retain unconstrained composition.
|
||
|
||
This example is a document the runtime accepts, built and evaluated by
|
||
`frontend/test/arthur/domain/lane_test.cljs`. Times here are zero-based. Channels
|
||
use the existing representation; cel source references and playback have
|
||
replaced the `[:source]` channel, which no longer exists.
|
||
|
||
```clojure
|
||
;; Within :main's :nodes; referenced drawings/animations live in :symbols.
|
||
{:girl
|
||
{:id :girl :kind :group :layout :sequence :z "b"
|
||
:channels {[:xform :rot]
|
||
{:animated? true :interp :linear :keys {0 0, 6 30, 12 0}}}}
|
||
|
||
:cel-a
|
||
{:id :cel-a :kind :instance :parent :girl :z "a"
|
||
:time {:at 0 :rate 1} :span [0 4]
|
||
:source {:symbol :drawing-a}
|
||
:playback {:in 0 :speed 0 :end :stop}}
|
||
|
||
:cel-b
|
||
{:id :cel-b :kind :instance :parent :girl :z "b"
|
||
:time {:at 4 :rate 1} :span [0 4]
|
||
:source {:symbol :drawing-b}
|
||
:playback {:in 0 :speed 0 :end :stop}
|
||
:channels {[:xform :pos]
|
||
{:animated? true :interp :hold :keys {0 [0 0], 1 [2 0]}}}}
|
||
|
||
:animated-insert
|
||
{:id :animated-insert :kind :instance :parent :girl :z "c"
|
||
:time {:at 8 :rate 1} :span [0 4]
|
||
:source {:symbol :wave}
|
||
:playback {:in 3 :speed 1 :end :stop}}}
|
||
```
|
||
|
||
The lane's rotation reads lane time. Each cel's channels read cel
|
||
time. Its content reads source time. The stills sample frame 0, while the insert
|
||
samples source frames 3, 4, 5, and 6. The group transform composes with the
|
||
cel transform and then the content's own transform.
|
||
|
||
`:span` remains in the node's own coordinates, consistent with ordinary nodes.
|
||
The interval in lane time is derived through `:time`; do not also store parent
|
||
start/end values. Sequence children require finite intervals and positive
|
||
placement rates. Ordering and overlap checks use the mapped intervals, not `:z`.
|
||
The current sequence group contains visual symbol clips. Audio remains an
|
||
independent root node (and can be linked to picture); if audio lanes are added,
|
||
their capability must be explicit rather than inferred per frame.
|
||
|
||
A source reference is fixed within a cel. The lane changes content when
|
||
another cel becomes active. This is a deliberate revision of the original
|
||
diagnosis that making `:of` a channel was necessary to avoid vertical growth:
|
||
multiple instances can occupy one row when the view presents their containing
|
||
sequence. A lane-level source schedule is therefore derived, not authored twice.
|
||
|
||
## Source selection and source playback are independent
|
||
|
||
The current implementation makes framed sources play and keyed sources hold.
|
||
Retire that rule. Channel storage shape must not determine playback behavior.
|
||
Adding or removing a key must not turn a still into an animation or vice versa.
|
||
|
||
A cel names content and describes how its source time is sampled. In the
|
||
basic case, after mapping lane time into cel time:
|
||
|
||
```text
|
||
source_time = in_point + speed × cel_time
|
||
```
|
||
|
||
A newly created cel starts at local time zero. Moving it preserves this
|
||
origin relative to its content. Trimming can narrow its local support without
|
||
resetting that origin; split pieces likewise preserve the source and property
|
||
values at the cut. Trimming, slipping, and retiming are distinct operations with
|
||
explicitly different effects on the interval and the source map.
|
||
|
||
| Intent | Source playback |
|
||
| --- | --- |
|
||
| Hold a drawing | Constant source frame, equivalently speed 0 |
|
||
| Play an animated symbol | Advancing source time, normally speed 1 |
|
||
| Cut between animations | Several cels, each with its own in-point and speed |
|
||
| Mix stills and animation in a lane | Constant and advancing maps in the same sequence |
|
||
|
||
The source reference itself is discrete and never numerically interpolated.
|
||
Interpolation belongs to properties that support it; a property registry should
|
||
declare value types, defaults, and permitted interpolation and correction modes.
|
||
Generic key toggles must consult those capabilities rather than assume every
|
||
non-boolean value can be tweened.
|
||
|
||
Define source bounds and end behavior explicitly: stop contributing outside the
|
||
source, hold an endpoint, or loop an explicit range. A still uses a valid constant
|
||
frame. A loop uses a nonempty half-open range and a defined modulo rule. Playback
|
||
never guesses these policies from whether a channel happens to have keys.
|
||
|
||
Audio shares cel arrangement, trimming, gain ownership, and clock mapping.
|
||
It does not inherit visual frame-hold semantics: holding one audio sample is not
|
||
an audio freeze effect. Validate supported playback policies by media capability.
|
||
Actual audio scheduling must follow active cels, including gaps and cuts,
|
||
rather than playing every sound reachable through a structural reference.
|
||
|
||
## Time spaces and sampling
|
||
|
||
Name the relevant space whenever an API accepts a time or range: project,
|
||
symbol/lane, cel, or source. Store authored frame coordinates exactly;
|
||
avoid cumulative rounding when moving through nested mappings. Quantize at a
|
||
declared sampling boundary, not at every traversal step. Audio also needs its
|
||
continuous clock/sample space rather than visual frame quantization.
|
||
|
||
A hold is an evaluable time map with no unique inverse. A loop can map many
|
||
displayed cels to one source time. APIs must distinguish forward sampling
|
||
from inverse editing, and expose enough context to resolve a cel or
|
||
explicitly refuse an ambiguous operation. Do not report a missing time map merely
|
||
because inversion is unavailable.
|
||
|
||
Separate invertible placement timing from source sampling. A zero source speed
|
||
can mean hold without making the cel's own edit clock non-invertible.
|
||
Reparenting through changing transforms or non-invertible timing must either
|
||
preserve the full result by an explicit bake or return a reason it cannot; a
|
||
matrix captured at one frame does not prove preservation across the animation.
|
||
|
||
The source's frame step, generated-pose sampling, and the lane's transform clock
|
||
are independent scopes. Drawing on twos must not accidentally step a smooth lane
|
||
transform. An explicit whole-subtree stepping operation can exist separately.
|
||
|
||
Share the quantization primitive where possible, but retain its units, phase,
|
||
rounding policy, and order relative to retiming and lead. The original suggestion
|
||
that cel and picture-rate sampling are simply one floor is insufficient:
|
||
noninteger grids and source-frame quantization require specified behavior.
|
||
Identity timing can be implicit; remove `:time :mode` if it only duplicates that.
|
||
|
||
A cel interval is authored. Lane content extent is derived from its
|
||
cels, including the explicit end of the last one. A separately authored
|
||
container trim/window is legitimate when it intentionally gates children. Do not
|
||
conflate that window with occupied extent or infer a final hold from the next key
|
||
when no next key exists. A range of frame numbers alone cannot encode visibility
|
||
or a missing measurement.
|
||
|
||
## Shared editing operations
|
||
|
||
Every view issues the same domain commands. A command accepts an explicit target
|
||
and edit policy, computes a valid change, and returns the change, resulting
|
||
selection, and any refusal reason. A button and a drag must not implement two
|
||
versions of cel extension.
|
||
|
||
An edit target identifies the symbol, cel path, selected entities or
|
||
properties, and the time range with its space. Navigation also distinguishes
|
||
editing shared content directly from editing it through a particular cel.
|
||
Crossing a source cut must not silently redirect an active drawing edit to a
|
||
different symbol: retain the explicit content target until navigation changes it.
|
||
|
||
Core commands include new drawing, reuse drawing, duplicate drawing, make unique,
|
||
blank range, split, trim, move, extend cel, slip source, retime, and apply a
|
||
bounded property edit. Ripple/overwrite policy and the set of affected lanes are
|
||
explicit command arguments. Preview consequences before committing a gesture.
|
||
|
||
New drawing creates fresh empty content and a cel. Blank range removes
|
||
content coverage without inventing a hidden drawing. These are different actions.
|
||
Reuse creates another cel pointing at existing content. Duplicate creates
|
||
a new content identity. Make unique rebinds the selected cel only.
|
||
|
||
Copy semantics must specify nested sharing. A normal content copy duplicates its
|
||
owned nodes and channels while preserving references to other reusable symbols.
|
||
For a fully independent drawing assembled from nested symbols, provide an
|
||
explicit deep-copy operation with ID remapping. Never promise decoupling while
|
||
leaving the relevant edited object shared. Immutable media blocks may remain shared.
|
||
|
||
Commands are atomic undo transactions, even when they touch several leaves.
|
||
Pointer movement and keyboard invocation use explicit begin/preview/commit or
|
||
cancel boundaries; a timing heuristic alone must not decide user intent.
|
||
Collaboration applies a transaction consistently, validates affected references,
|
||
and detects conflicts at the owned data being changed. One leaf per channel does
|
||
not solve simultaneous edits to different keys of that same channel; define a
|
||
conflict policy rather than claiming that granularity solves all collaboration.
|
||
|
||
### Default timing behavior: cel edits preserve lane keys
|
||
|
||
Working default from the follow-up discussion: extending a drawing's hold changes
|
||
cel timing, leaving lane animation at its authored times. The user raised
|
||
keeping keyframes in place as a possibility; this is the proposed predictable
|
||
default, not a claim that they selected every timing policy below.
|
||
|
||
Ownership supplies the remaining rule: properties attached to a cel
|
||
travel with it. Extending its end does not stretch those properties; moving it
|
||
changes where their existing local times land. No per-key attachment flag is
|
||
needed to recover ownership that the document already expresses.
|
||
|
||
For the concrete example, extend `:cel-a` by two lane frames with ripple:
|
||
|
||
| Fact | Before | After |
|
||
| --- | --- | --- |
|
||
| Cel A's lane interval | `[0,4)` | `[0,6)` |
|
||
| Cel B's lane interval | `[4,8)` | `[6,10)` |
|
||
| Animated insert's lane interval | `[8,12)` | `[10,14)` |
|
||
| Girl's rotation peak | Lane frame 6 | Lane frame 6 |
|
||
| B's position change | B frame 1, lane frame 5 | B frame 1, lane frame 7 |
|
||
| Insert's first source frame | Source frame 3 | Source frame 3 |
|
||
|
||
The rotation peak now coincides with a different point in the drawing sequence.
|
||
That is the intended consequence of changing cels underneath timed motion.
|
||
The position correction stays attached to drawing B's cel. Neither the
|
||
background's keys nor audio on another lane moves.
|
||
|
||
The command contract for this edit names the symbol and cel, a delta in
|
||
lane frames, `:ripple` behavior, and an explicit scope of cel timing. It
|
||
extends A's local support by the delta converted through A's placement rate,
|
||
and shifts subsequent cel placements by that delta in lane time. It does
|
||
not modify any channel's key map, source in-point, or playback speed. Reject a
|
||
nonpositive resulting duration. Validate and commit the entire change together.
|
||
|
||
The symbol's authored end is another explicit boundary: preview an overflow and
|
||
offer to extend the symbol or cancel. A command can request that extension as
|
||
part of its transaction; it must not silently truncate later cels or grow
|
||
other uses of a shared symbol. In the example, a 12-frame symbol needs an explicit
|
||
extension to 14 frames or the edit must be refused without partial changes.
|
||
|
||
Retime performance is a separate operation over explicitly selected cels
|
||
and channels. It applies the same time transformation to their relevant clocks,
|
||
keys, and correction supports. Stretching an interval requires a defined warp
|
||
and interpolation behavior; it is not merely moving keys whose frame numbers
|
||
happen to lie inside the selection. Until supported, refuse this operation
|
||
rather than approximating it with a cel ripple.
|
||
|
||
The initial UI should default stage transforms to the lane when drawing in a cel
|
||
workflow, so movement usually remains independent of cel timing. The
|
||
inspector names the target: lane motion, this cel, or shared drawing.
|
||
Changing that scope is explicit. It changes what the edit means, not just which
|
||
panel happens to be open.
|
||
|
||
## Three-frame rotation and correction layers
|
||
|
||
A range says where an edit applies; it does not specify the motion. Offer distinct
|
||
commands for a constant adjustment, a ramp, and a return-to-start motion. For UI
|
||
frames 10–12, the internal range contains exactly three frame samples after
|
||
conversion from the displayed numbering convention.
|
||
|
||
- Constant adjustment: the same offset throughout those three samples.
|
||
- Ramp: interpolate from the specified start value to the target over the range.
|
||
- Return motion: interpolate from the starting value to a peak and back.
|
||
|
||
For a return motion sampled on three frames, the values can be `0, angle, 0`.
|
||
Outside the selected range, the underlying animation must evaluate exactly as it
|
||
did before. A range-scoped correction layer expresses this directly; blindly
|
||
inserting boundary keys can alter neighboring segments or destroy existing motion.
|
||
|
||
Implement corrections as an ordered stack over the base channel. Each correction
|
||
has stable identity, explicit support interval, blend operation, and values in a
|
||
named time space. Outside its support it is inactive. `replace` can supply a value
|
||
over an absent base; `offset` cannot offset a nonexistent value. Blend capability
|
||
depends on property type, and geometry corrections require compatible topology.
|
||
|
||
Regeneration replaces the generated base and preserves corrections. If changed
|
||
topology or removed targets make a correction incompatible, report a resolvable
|
||
conflict instead of silently dropping or misapplying it. Provenance explains
|
||
where the base came from; explicit sampling policy determines its playback.
|
||
|
||
This is core to the workflow: generate motion, correct it by hand, adjust the
|
||
generator, and keep the corrections. It should be proven before adding many views.
|
||
|
||
## Other unifications worth keeping
|
||
|
||
Pose choices, tracing-frame choices, and ordinary held values should share the
|
||
channel evaluator and cursor infrastructure. Preserve their different ownership,
|
||
fallback behavior, and sampling scope. A pose choice must address the relevant
|
||
content/feature explicitly; switching to another symbol must not accidentally
|
||
reuse a track just because both symbols contain a node with the same local name.
|
||
|
||
Keep the two animation idioms distinct: keyed geometry modifies one mark over
|
||
time; drawing substitution selects content that may have different structure.
|
||
Linear geometry interpolation requires compatible vertex correspondence, not
|
||
merely two drawings that happen to look related.
|
||
|
||
Derived library grouping may collect drawings used by a single lane. This is a
|
||
convenience, not ownership or deletion authority. Reference discovery for cycle
|
||
validation, copying, and deletion examines all structural references, including
|
||
currently inactive cels. Authored folders, favorites, and labels remain
|
||
legitimate user data even when the UI could have suggested defaults.
|
||
|
||
## UX: location, selection, and controls
|
||
|
||
The breadcrumb sits above the timeline and states the editing location, shared
|
||
content identity, and cel context when applicable. Show local time and
|
||
its project context where a useful mapping exists. Holds and loops need an honest
|
||
description instead of a fictitious unique global frame.
|
||
|
||
Creation follows the primary active row, resolved at the playhead as specified in
|
||
[`creating-in.md`](creating-in.md). The wider selection set still names what copy,
|
||
delete and transform affect; it is not a second list of creation destinations. A
|
||
shared drawing indicates its reuse and offers Make this cel unique. Names help identify content;
|
||
linked-use indicators must rely on IDs, because different drawings can share names.
|
||
|
||
| Surface | Primary scope and controls |
|
||
| --- | --- |
|
||
| Topbar | Project name, save/open/export, project rate and stage size |
|
||
| Location bar | Breadcrumb, add lane/content, shared-content context |
|
||
| Cel action strip | New drawing, duplicate drawing, hold longer/shorter, blank range |
|
||
| Lane header | Lane selection, lock, mute/solo where applicable, onion settings, expansion |
|
||
| Stage tools | Drawing and transform modes, active target and scope |
|
||
| Inspector | Selected content/cel/lane properties and valid key controls |
|
||
|
||
Cel actions have visible contextual buttons, shortcuts, a context menu, and
|
||
command-palette entries. These are different entrances to the same commands.
|
||
Shortcut names from the original sketch (`N`, `D`, `H`, `B`, `K`) are provisional;
|
||
their meanings must match the visible labels and avoid tool conflicts.
|
||
|
||
The inspector normally edits values and the timeline normally edits timing, but
|
||
this is an organizational default. Numeric duration and in-point controls are
|
||
useful inspector edits to the same domain facts. Do not ban a convenient control
|
||
just to preserve a visual division.
|
||
|
||
Default nesting navigation enters content; expanding a lane reveals properties.
|
||
Other views may show hierarchies differently without changing the document.
|
||
Tabs can pin explicit locations. Zoom, expansion, onion preferences, and current
|
||
selection are editor state rather than animation content. Persistent workspace
|
||
preferences can be saved separately.
|
||
|
||
## A session, revised
|
||
|
||
1. In `main`, create a girl lane and a new drawing. Draw; use New drawing (`N`)
|
||
to create the next one with the previous cel ghosted behind it.
|
||
2. Use Duplicate drawing (`D`) when the current shapes are the starting point.
|
||
Use Reuse drawing for a deliberately linked cel. The UI shows the
|
||
difference before an edit can change other uses.
|
||
3. Time the performance. Hold longer (`H`) extends the selected cel and
|
||
ripples later cels in the explicitly targeted lane. A trim gesture
|
||
can use overwrite instead. The preview shows which boundaries will move.
|
||
4. Choose a two-frame default cel for newly created drawings, or run a
|
||
separate Retime cels command on a selected range. This does not quantize
|
||
lane transforms or silently retime already authored cels.
|
||
5. Place the background in a lane below. Its source holds one frame throughout
|
||
its cel. Key the lane's X position at the beginning and end and choose
|
||
linear interpolation. The background slides while the girl's drawings cut.
|
||
6. Select three frames on the girl's lane, choose Return motion, and rotate to
|
||
the desired peak. A bounded rotation correction affects the girl across any
|
||
drawing boundaries in that range. Existing motion survives outside it.
|
||
7. Insert a playing animated symbol among the girl's held drawings. Set that
|
||
cel's source playback to advance. No lane conversion is required.
|
||
|
||
The timeline shows named cel blocks with property marks and optional curve
|
||
subrows. The cel sheet shows the same cels by frame and lane. The
|
||
graph editor edits the same properties; the stage resolves the same document.
|
||
Onion skin is configurable and counts neighboring cel events, skipping gaps
|
||
by default; a long hold does not consume the budget. Repeated uses of the same
|
||
drawing remain distinct events. Deduplicating identical ghosts is a display option.
|
||
|
||
## Proof obligations and implementation order
|
||
|
||
The source-channel prototype has been removed: a cel names one symbol
|
||
and carries its own playback clock, and `node/problems` rejects the old
|
||
`[:source]` channel. What a lane IS lives in `arthur.domain.symbol` beside the
|
||
other rules about a node map; `arthur.domain.lane` holds the commands over
|
||
one — add lane, place a drawing (new, reused or duplicated), make unique, split,
|
||
trim, move, blank and extend hold. Each is one history step, and each refuses rather than
|
||
half-applying. The timeline draws a lane's cels as cel blocks on the
|
||
lane's own row, and offers Make unique only where the selected cel actually
|
||
shares its drawing.
|
||
|
||
There is ONE placement function and a position argument, so appending is not a
|
||
different operation from inserting: `:end` is a position like any other, the one
|
||
where nothing has to move. Placing ripples — cels at or after the
|
||
position move later by the new cel's duration — and `:keep` versus
|
||
`:grow-symbol` still decides what happens at the shot's end. OVERWRITE is not a
|
||
policy argument yet, deliberately: taking frames away from the cel
|
||
already there is trimming, and until `trim` exists, placement that would need it
|
||
refuses instead of approximating it. A position inside an existing cel
|
||
refuses too, and names `split` — one command does not quietly perform two.
|
||
|
||
Splitting turned out to cost almost nothing, which is evidence for the
|
||
representation rather than for the command. The two pieces keep ONE `:time` and
|
||
differ only in `:span`, so the right piece's own frames carry on where the
|
||
left's stopped and its source clock, keys and corrections go on meaning what
|
||
they meant — a held drawing holds the same frame either side, a playing insert
|
||
plays through the cut without a seam, and the test for it samples every frame
|
||
before and after and asserts the picture is identical. That falls out of `:span`
|
||
being in the node's own coordinates; it is not something split arranges.
|
||
|
||
Content copies are shallow by default and keep their references to other
|
||
symbols; `:deep? true` is the explicit copy that shares nothing, so the promise
|
||
of independence is only made where it is kept.
|
||
|
||
THE SHOT LENGTH IS AUTHORED, which is the decision the range commands forced.
|
||
`:frames` is the symbol's window — how long the shot IS — and the occupied
|
||
extent of its lanes is a different fact derived from the cels. A command
|
||
grows the window only when the caller says `:grow-symbol`, and never shrinks it:
|
||
blanking the end of a shot leaves a shot with empty frames at the end, because
|
||
that is a true statement about what somebody authored, and deriving the window
|
||
from the extent would make deleting the last drawing quietly shorten the film.
|
||
`finish` keeps the two numbers apart by name now rather than by a `max` that
|
||
read like an accident.
|
||
|
||
Trim NARROWS one edge and moves nothing else; lengthening is `extend-hold`,
|
||
which carries the ripple and shot-length policies because it needs them.
|
||
Move is one write to `:time :at` and REFUSES a destination that would overlap,
|
||
because moving a drawing and re-timing the ones around it are different
|
||
intentions — clear the room with `blank` or `trim` first, which is the
|
||
composition. Blank leaves a gap and does not close it; a cel wholly inside
|
||
the range goes, one overlapping an end is trimmed to it, and the one spanning
|
||
the range is split. Their drawings stay in the library, since a lane does not
|
||
own its content.
|
||
|
||
All three are the same geometry as `split`: a `:span` is in the cel's own
|
||
frames, so moving an edge is one write and `:time` and `:playback` are never
|
||
touched. That is why trimming the front of a playing insert starts it later into
|
||
its animation instead of restarting it — the difference between trimming and
|
||
slipping, and the reason they stay separate commands.
|
||
|
||
Correction layers EVALUATE. `channel/problems` used to refuse an `:over` stack
|
||
and `value-at`/`cursor` used to throw on one; both now read it, and the
|
||
agreement test that holds the optimized cursor to the specification covers
|
||
stacked channels in forward, backward and random frame order. A layer's values
|
||
are themselves a channel, so a constant adjustment, a ramp and a return motion
|
||
are one mechanism; `:support` is half-open and a layer is inactive outside it;
|
||
and a layer has no time space of its own, because the node its channel is on
|
||
already has one. Nothing had to change in the codec — a channel is one leaf, so
|
||
a correction persists inside it — and nothing had to change in validation
|
||
plumbing, since `node/problems` already reports every channel's problems.
|
||
|
||
Both halves of ownership are under test at lane level: a three-frame correction
|
||
on the girl's lane reaches across the drawing boundary beneath it and leaves
|
||
every frame outside its support identical, and a correction owned by one
|
||
cel travels with that cel when a hold before it grows.
|
||
|
||
Regeneration keeps them, which is the obligation the layer design exists to
|
||
meet: `rebased` replaces a base and carries its corrections across, and a
|
||
correction the new base no longer fits is MARKED rather than dropped or
|
||
misapplied — `clip/conflicts` lists those for a view to offer, separately from
|
||
`problems`, because a conflict is a decision nobody has made yet and not a
|
||
document that will not load. Turning the mouth's `:verts` knob is a real
|
||
topology change and is what the test uses. Two latent faults turned up there and
|
||
are fixed: `regenerate-head` compared authored channels to measured ones
|
||
directly, so the first correction on the head would have stopped it following
|
||
re-measurement for good; and an incompatible offset threw in the read path,
|
||
which would have taken the stage down on exactly the case the model says to
|
||
report.
|
||
|
||
Overwrite is `blank` followed by non-rippling placement, composed inside one
|
||
transaction; insertion keeps its ripple rule. Still unbuilt: slip source,
|
||
retime, and deleting reused content. A lane cannot hold AUDIO cels — `lane-problems`
|
||
requires visual ones, though this document says a lane may hold either and
|
||
should reject only a mixture.
|
||
|
||
`domain/correction.cljs` now produces Constant adjustment, Ramp, and Return
|
||
motion layers for rotation and position. The inspector exposes them on a selected
|
||
lane or cel using an explicit range in that owner's frames; this deliberately
|
||
leaves displayed-range dragging through nested or retimed owners for later. One
|
||
Apply is one undo step. Conflicted layers are listed, can be removed, and can be
|
||
retried when the complete ordered stack is compatible again. Validation,
|
||
conflict reporting, and regeneration share that ordered-stack rule, including
|
||
coverage by adjacent replacement layers. Slip source and retime are still not
|
||
implemented; a refusal is the current behavior where the model demands an
|
||
explicit choice nobody has made yet.
|
||
The cel sheet is the same projected cels and selection addresses with its axes
|
||
turned: frames down and lanes across, so commands selected there and in the
|
||
timeline have identical targets; a gap selects its column's lane rather than
|
||
retaining a stale selection from another column. The suite stands at 437 tests and 5,804
|
||
assertions, with `frontend/test/browser/lane.mjs` driving the editor through
|
||
create, hold, overflow, undo, reuse, make unique, duplicate, split, insert,
|
||
trim, move, blank, correction authoring, and two-lane sheet targeting. Rewrite tests that encode superseded
|
||
behavior rather than preserving behavior to keep them green.
|
||
|
||
Build small adversarial documents and test their domain operations before
|
||
expanding the interface:
|
||
|
||
| Scenario | Required invariant |
|
||
| --- | --- |
|
||
| Same drawing exposed twice, then one made unique | Linked edits affect both before copying and only the selected content after |
|
||
| Holds, playing inserts, nonzero in-points, and gaps on one lane | Source behavior is independent of property key count and channel encoding |
|
||
| Adjacent cels, final hold, split, trim, ripple, and overwrite | Exact boundaries, stable IDs, deterministic collision handling |
|
||
| Extend a hold under lane keys and cel-local corrections | Lane key times remain fixed; later cel corrections travel with their owners; source playback origins survive |
|
||
| Ripple beyond the symbol end | Explicit extent policy; refusal leaves the document unchanged; resizing and retiming undo together |
|
||
| Girl on twos over a moving background | Drawing cadence does not quantize either lane's continuous properties |
|
||
| Three-frame correction crossing a drawing boundary | Exact support, same result outside it, one undo step |
|
||
| Nested retiming, holds, loops, and fractional sampling | Explicit time spaces; ambiguous inverse edits cannot silently choose a target |
|
||
| Audio inside changing source cels | Only active intervals sound, with correct trim and source timing |
|
||
| Regenerate with corrections and a topology change | Compatible edits survive; incompatible ones produce actionable conflicts |
|
||
| Reference cycles and deletion of reused content | Inactive references are validated too; no dangling references |
|
||
| Save/load and command undo/redo | Identity, source maps, corrections, and evaluation round-trip |
|
||
| Timeline and cel-sheet invocation of one command | Identical document changes and selection targets |
|
||
| Random forward/backward seeks and export | Reference and optimized evaluation agree, including defaults and absence |
|
||
| Concurrent commands on overlapping and disjoint targets | Transactions remain valid; conflicts are explicit and undo preserves others' work |
|
||
|
||
Implementation order: cel ownership and playback semantics; shared
|
||
commands and validation; correction layers and time-addressing contracts; then
|
||
breadcrumb, cel strip, and a cel-sheet projection. Use those two temporal
|
||
views plus direct stage editing to prove the model before broadening the UI.
|
||
|
||
A new presentation should not require duplicate animation state. A genuinely new
|
||
authoring capability may require new domain data. The model is successful when
|
||
such additions have a clear owner and compose with existing operations, not when
|
||
it can claim that no future feature will ever need another field.
|