A correction is a layer, and a layer's values are a channel

`:over` was specified in animation-model.md, refused in two places, and
produced by nothing: `check-unimplemented!` threw on read and `channel/problems`
reported it. It reads now. This is the part of the model the rotoscoping half
depends on — generate motion, correct it by hand, turn the knob, keep the
correction — and it was the last thing in the design that had never been tried.

The shape that made it small: A LAYER'S VALUES ARE A CHANNEL.

  {:id :nudge :support [88 98] :op :offset
   :values {:animated? true :interp :linear :keys {88 [2 0], 96 [0 0]}}}

So the three commands the lane model asks for over a selected range — a
constant adjustment, a ramp, a return motion — are one mechanism and not three:
framed values say the same thing on every frame they cover, keyed values move,
and neither needs a new way to say what a value is over time. A layer reads
through `value-at` and `cursor` like any channel, which is also what stopped
blending from becoming two implementations: `over-at` is shared, and the
specification and the playback path differ only in how they READ a layer —
recursively through `value-at`, or through a reading head of its own. One level
deep; a layer's values may not carry layers, which the stack already orders.

That was the risk worth spiking for. A cursor that drifts produces the wrong
pose rather than an error, and a stack means several reading heads per channel
where there was one. The agreement test that holds the cursor to the
specification in forward, backward and random frame order now covers stacked
channels too — including a layer whose head is asked for nothing across the long
stretches outside its support and then asked again, which is where drift would
hide.

`:support` is half-open and explicit. Outside it the base evaluates exactly as
it did before, which is the whole difference between a bounded correction and
inserting boundary keys: the latter alters the neighbouring segments, and the
lane model says so.

A LAYER HAS NO TIME SPACE OF ITS OWN, and this is the design question the doc
left open. Its support and its values' keys are in the frames the base channel's
keys are in — the node's. A correction on a lane is therefore in lane frames and
reaches across the drawings exposed beneath it; one on a single occurrence is in
that occurrence's frames and travels with it when the exposure moves. Ownership
had already answered it, so there is no field to disagree with, and both halves
are under test at lane level.

Two things cost nothing, which is worth recording. A channel is ONE LEAF, so a
correction persists inside it with no codec change at all. And `node/problems`
already reports every channel's problems, so a malformed layer surfaces at the
document level and in the sequence commands' post-check without plumbing.

What is still missing is a command that MAKES one, and with it the question of
how a view offers a constant, a ramp and a return over a selected range. The
evaluator no longer has an opinion about that, which was the point.

`offset` adds component-wise and never writes into a dense value, which is a
view onto the block itself; a shape mismatch throws rather than being dropped,
since a correction that silently does not take is the failure this design exists
to prevent. `replace` can supply a value over an absent base and `offset`
cannot, as animation-model.md required.

408 tests, 5,655 assertions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-09-30 16:07:38 -04:00
parent 26517af2fd
commit 94c0a21de1
5 changed files with 365 additions and 71 deletions

View file

@ -1,9 +1,10 @@
# The Lane Model
Revised 2026-09-30. Target design. Occurrence ownership, source playback, the
content and exposure commands, placement anywhere in a lane, and a one-row cel
strip are implemented; correction layers, overwrite and the retiming commands,
and the remaining views are not. See the status note under
content and exposure commands, placement anywhere in a lane, a one-row cel strip
and the correction-layer evaluator are implemented; the commands that produce a
correction, overwrite, the retiming commands and the remaining views are not.
See the status note under
[Proof obligations](#proof-obligations-and-implementation-order).
This revises the Claude artifact [The Lane Model](https://claude.ai/code/artifact/cd42981d-ed08-493f-94df-b7dd6657f0e6).
@ -471,14 +472,32 @@ 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.
Correction layers, overwrite, and the range and retiming commands (blank, trim,
move, slip source, retime) and the exposure-sheet view are not implemented; a
refusal is the current behavior where the model demands an explicit choice
nobody has made yet. The suite stands at 401 tests and 5,612 assertions, with
`frontend/test/browser/sequence.mjs` driving the editor through create, hold,
overflow, undo, reuse, make unique, duplicate, split and insert. Rewrite tests
that encode superseded behavior rather than preserving behavior to keep them
green.
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
exposure travels with that exposure when a hold before it grows.
What is NOT implemented is a command that produces a layer — the doc's Constant
adjustment, Ramp and Return motion — and with it the question of how a view
offers those three over a selected range. Overwrite, the range and retiming
commands (blank, trim, move, slip source, retime) and the exposure-sheet view
are also not implemented; a refusal is the current behavior where the model
demands an explicit choice nobody has made yet. The suite stands at 408 tests
and 5,655 assertions, with `frontend/test/browser/sequence.mjs` driving the
editor through create, hold, overflow, undo, reuse, make unique, duplicate,
split and insert. 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: