diff --git a/docs/lane-handoff.md b/docs/lane-handoff.md new file mode 100644 index 0000000..72d3a1a --- /dev/null +++ b/docs/lane-handoff.md @@ -0,0 +1,190 @@ +# Lane and cel handoff + +Status (2026-09-30): the lane model is implemented through its commands. Cels are +ordinary nodes with their own playback clock, a lane draws them as one row, the +cel commands all exist, and correction layers evaluate and survive regeneration. +What is missing is a second view and the commands that make a correction. + +Seven commits on branch `lanes`, off `624242b`. `master` is untouched and can be +fast-forwarded. Each commit message is the argument for its change and is worth +reading before touching what it did — they are the design record, more than this +file is. + + 3d3c1bb An occurrence is a node, with a clock of its own + 9446829 Reuse, duplicate and make unique: deciding what is shared + 26517af A position is an argument, not another command + 94c0a21 A correction is a layer, and a layer's values are a channel + 72b57e3 Regenerate the base, keep the hand work, and say when you cannot + 76106d3 The shot is as long as somebody said it was + 598c186 One word for one thing: it is a cel + +## Read first, in this order + +1. [The Lane Model](lane-model.md) — the design, and the status note under + *Proof obligations* says what is built. It supersedes `animation-model.md`, + `timing-model.md` and `architecture.md` wherever they overlap. +2. `frontend/src/arthur/domain/lane.cljs` — every command, and the reasoning in + its docstrings. +3. `frontend/test/arthur/domain/lane_test.cljs` — what the model is asserted to + do. It is the fastest way to see the shapes. +4. `frontend/src/arthur/domain/channel.cljs`, the correction-layer section. + +## Vocabulary — one word for one thing + +Renamed in `598c186`, after four words had accumulated for one object. Use these +and do not reintroduce the others. + +| word | means | +| --- | --- | +| instance | the `:kind`. The general thing, anywhere in a document | +| cel | an instance in a lane. One drawing, held for some duration | +| lane | a group with `:layout :sequence` | +| drawing | the content a cel names — an ordinary symbol | +| placement | ONLY where a node sits: `nest/placement`, and the transform that puts a face on the stage. Never the node itself | + +`occurrence` and `exposure` are not words for a cel. **`exposure` means something +else and still does**: `:time :expose` is how many frames each step of a subtree +lasts, which is what shooting on twos is — `node/expose`, `clock/exposed-frame`, +`subs/render ::exposure`. Keeping these apart is why the block is called a cel. + +`:layout :sequence` stays as the field, and is the one place two words are kept +on purpose: the layout names the RULE — children follow one another and may not +overlap — and a group carrying it is called a lane. `node/lane?` is where they +meet. + +The second view is the CEL SHEET, not the exposure sheet. + +## Decisions already made — do not re-litigate + +These were each argued out and are load-bearing. Changing one is a design +decision, not a cleanup. + +- **The shot length is authored.** `:frames` is the symbol's window; the + occupied extent of its lanes is a different fact derived from the cels. A + command grows the window only when the caller passes `:extent :grow-symbol`, + and never shrinks it. Blanking the end of a shot leaves empty frames at the + end, because deriving the window from the extent would make deleting the last + drawing silently shorten the film. `lane/finish`. +- **Placing ripples; overwrite is `blank` then `place`.** Overwrite is not a + policy argument and should not become one until there is a reason `blank` + cannot serve. An argument whose second value is unimplemented is worse than + no argument. +- **A position inside a cel refuses and names `split`.** One command must not + quietly perform two. The UI offers the retry. +- **A correction has no time space of its own.** 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 in lane frames and reaches across the drawings under + it; one on a cel travels with that cel. Ownership already answered it. +- **A layer's values are a channel.** Constant, ramp and return motion are one + mechanism. Do not add a second way to say what a value is over time. +- **A conflict is not a `problem`.** A document whose topology outgrew a + correction loads, evaluates and saves; `clip/conflicts` lists the decisions + waiting for a person. `problems` means the document will not load. +- **Refuse rather than guess.** Every command returns `{:clip :selection}` or + `{:refused why}`, never a half-applied edit. Where the model needs a choice + nobody has made, refusing and saying why is the behaviour, not a placeholder. +- **A cel is not a row.** Rows, expansion and selection are editor state. The + document has never known about rows and must not learn. + +## Next steps, in order + +1. **The cel sheet.** The doc's own test that the document is separate from its + presentation: frames down, a column per lane, one cell per frame naming the + drawing. It needs NO new model — `symbol/lane-cels` and the existing commands + are the whole API. The obligation is that the same command issued from the + sheet and from the timeline produces identical document changes and the same + selection. This is the recommended next piece: it is self-contained, it is + specified, and it is the first real evidence the model is not shaped by the + timeline that grew up with it. +2. **The commands that make a correction** — Constant adjustment, Ramp, Return + motion over a selected range, per `lane-model.md`. The evaluator is done and + has no opinion about how a range or a motion shape is chosen, which is now a + view question. A panel also needs to offer `clip/conflicts` for resolution. + Note the one open question: a correction needs a stable `:id` from somewhere, + and cel ids come from the caller because this namespace is pure. +3. **Slip source and retime.** Both have real design questions open and the doc + says to refuse rather than approximate: retime needs a defined warp and + interpolation behaviour, and is not moving keys whose numbers happen to fall + inside a selection. +4. **Deleting reused content.** Reference discovery exists (`node/sources`, + `clip/places`, `clip/contains-symbol?`); the policy does not. +5. **Collaboration.** `lane-model.md` is explicit that one leaf per channel does + NOT solve two people editing different keys of the same channel. No conflict + policy exists for that. + +## Mechanisms to reuse — these keep paying out + +- **`:span` is in the node's OWN frames** and `:time` says where they land in + the lane. Moving an edge of a cel is therefore one write to `:span`, with + `:time` and `:playback` untouched. This is why split costs nothing, why the + two halves of a split go on meaning what the one cel meant, why trimming the + front of a playing insert starts it later into its animation instead of + restarting it, and why extending a hold leaves lane keys alone. `lane/local` + and `lane/edged` are the whole geometry; trim, split and blank are all it. +- **`lane/finish`** is the one commit path: it validates, applies the shot-length + policy, and returns the refusal. New commands go through it. +- **`:required-frames` plus the retry event** is the pattern for "this needs a + decision you have not made": the domain reports what it would need, the UI + offers one button. `events/ui/lane-retry`. +- **`lane/lane-frame`** converts a symbol frame to a lane frame, or returns nil + through a stepped or looping lane where there is no single answer. Nil refuses; + it never snaps. +- **`channel/conflict-with`** is the single rule for "can this layer apply to + this base", used by the validator, by `conflicts`, and by the regeneration + that marks them. +- **Two test patterns worth copying.** `the-cursor-agrees-with-the-specification-in-any-frame-order` + holds the optimized cursor to `value-at` in forward, backward and random order + — add a case to it for any new channel shape. And `drawn` in `lane_test` + samples every frame before and after an edit, which is how split and trim are + proved to change nothing: state a claim as "the same picture" rather than as + numbers computed by hand. + +## Known gaps and traps + +- **Audio lanes do not work.** `symbol/lane-problems` requires `:instance` + children, so an audio node in a lane is rejected outright. `lane-model.md` + says a lane may hold visual OR audio cels and should reject only a mixture. +- **`:z` is required on cels and means nothing there.** A lane never has two + cels on one frame, so draw order between them cannot matter. `node/problems` + requires `:z` on every node uniformly, which is its own kind of simplicity — + but the field is noise on a cel. +- **`channel/offset-onto` throws** on a shape mismatch that no regeneration has + recorded as a conflict. That is deliberate — a correction that silently does + not take is the failure the design exists to prevent, and `channel/problems` + catches the authored case — but it is a throw in the read path, so any new + producer of layers must not create a mismatched one. +- **`docs/timing-handoff.md` is a separate, unreconciled thread.** Performance- + pose selection and plate drawings/tracing, instance-specific picture-rate + requests, `pose/put-cut` addressing only `:main`. It predates the lane model + and nobody has squared the two. +- **The button row in the timeline pane is a test harness, not a design.** It is + how the commands were made reachable and provable. `lane-model.md` describes + the real cel action strip, the breadcrumb and the location bar; none exist. +- **`shadow-cljs release app` clobbers the dev bundle.** Both builds write + `../static/arthur/js`, which Django serves, and the optimized build does not + export the `arthur` global — so after a release the browser tests fail with + `ReferenceError: arthur is not defined`. Run `npx shadow-cljs compile app` to + restore it. A running `watch app` does not notice; it rebuilds on the next + source change. + +## Running it + +From `frontend/`: + + npx shadow-cljs compile test && node out/node-tests.js # 424 tests, 5,749 assertions + npx shadow-cljs compile app # the bundle Django serves + npx shadow-cljs release app # then `compile app` again — see above + +The browser tests need the Django dev server up (`mise exec -- python manage.py +runserver 8778` from the repo root) and a compiled dev bundle: + + node --experimental-websocket test/browser/lane.mjs # the lane/cel flow + CHROME=/usr/bin/chromium node --experimental-websocket test/browser/take.mjs + +`take.mjs` defaults to a macOS Chrome path, hence `CHROME=`. It writes a real +project to the local server by design; `lane.mjs` never writes to the server. + +From the repo root: `mise exec -- python manage.py test clips` — 56 tests. + +Documents are schema 3. A version 2 document is not read and nothing converts +one; there is no backward compatibility to preserve anywhere in this work. diff --git a/docs/lane-model.md b/docs/lane-model.md index 9496cd6..c00fd7d 100644 --- a/docs/lane-model.md +++ b/docs/lane-model.md @@ -7,6 +7,9 @@ correction, overwrite, the retiming commands and the remaining views 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