A position is an argument, not another command

Everything could only be added to the end, because `append` computed its own
position — the max end of the lane — and so had no opinion to state. Insert is
not a new command; it is the argument that function was missing. `:at` takes a
lane frame or `:end`, `:end` is the position where nothing has to move, and
appending stops being a separate operation from inserting. New, reused and
duplicated drawings all take it, because there was only ever one placement rule.

Placing ripples: occurrences at or after the position move later by the new
exposure's duration, and `:keep` against `:grow-symbol` still decides what
happens at the shot's end. OVERWRITE is deliberately not a policy argument yet.
Taking frames away from the occurrence already there is TRIMMING, and an
argument whose second value is unimplemented is worse than an argument that is
not there. A position strictly inside an existing exposure refuses and names
`split`, rather than splitting on the quiet: one command performing two is how
a command stops being predictable.

Then split, which turned out to cost almost nothing, and that is the
interesting part. The two pieces keep ONE `:time` and differ only in `:span`.
The right piece's own frames therefore carry on exactly where the left's
stopped, so its source clock, its keys and its corrections go on meaning what
they meant: a held drawing holds the same frame either side of the cut, and a
playing insert plays through it without a seam. There is no arithmetic on
in-points to get wrong, and no shot-length question, since the pieces occupy
the frames the one exposure occupied. The test samples every frame before and
after and asserts the picture is identical — for a hold, for an exposure with a
correction of its own, and for a playing insert.

That is not a clever split. It is `:span` being in the node's OWN coordinates,
which was decided long before there were lanes, paying for something it was not
designed for. The same property is why extending a hold leaves lane keys alone.

Both new commands act at the playhead, which needed `lane-frame` — the symbol's
frame as a frame of the lane's own time, nil through a stepped or looping lane
where one is not the other. Nil refuses; it does not snap to a nearby frame.

Two smaller things found while doing it. `placeable` promised "a whole lane
frame" in its refusal and then accepted 2.5, so both it and `split` now require
an integer, as `extend-hold` already did for its delta. And `lane-end` is
private: `:end` is the only way to ask for it.

401 tests, 5,612 assertions. The browser flow now splits an exposure at the
playhead and puts a drawing in the gap, and checks that six exposures are still
one row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-09-30 15:56:26 -04:00
parent 9446829774
commit 26517af2fd
6 changed files with 296 additions and 42 deletions

View file

@ -1,9 +1,9 @@
# The Lane Model
Revised 2026-09-30. Target design. Occurrence ownership, source playback, the
content and exposure commands and a one-row cel strip are implemented;
correction layers, the range and retiming commands, and the remaining views are
not. See the status note under
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
[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).
@ -442,23 +442,43 @@ The source-channel prototype has been removed: an occurrence 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.sequence` holds the commands over
one — add lane, append drawing, reuse drawing, duplicate drawing, make unique,
and extend hold with explicit ripple and shot-length policy. Each is one history
step, and each refuses rather than half-applying. The timeline draws a lane's
occurrences as cel blocks on the lane's own row, and offers Make unique only
where the selected exposure actually shares its drawing.
one — add lane, place a drawing (new, reused or duplicated), make unique, split,
and extend hold. Each is one history step, and each refuses rather than
half-applying. The timeline draws a lane's occurrences as cel blocks on the
lane's own row, and offers Make unique only where the selected exposure 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 — occurrences at or after the
position move later by the new exposure'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 occurrence
already there is trimming, and until `trim` exists, placement that would need it
refuses instead of approximating it. A position inside an existing exposure
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.
Correction layers, the range and retiming commands (blank, split, 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 397 tests and 5,561 assertions, with
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 and duplicate. Rewrite tests that encode
superseded behavior rather than preserving behavior to keep them green.
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: