diff --git a/docs/lane-is-a-view-plan.md b/docs/lane-is-a-view-plan.md index 72fc9fe..7e5ebf4 100644 --- a/docs/lane-is-a-view-plan.md +++ b/docs/lane-is-a-view-plan.md @@ -123,6 +123,82 @@ people, and it does not make the symbol a different kind of thing. The thing to hold the line on is that nothing outside the timeline, and the commit path's overlap check, is allowed to read it. +## Tear out the held cel + +A held cel is a 1-frame symbol shown for many frames. A 1-frame symbol with +`:end :loop`, lengthened, is the same picture by a different route — and the +second route is a case the model already has, so keeping the first one is +keeping a special case for free. + +**What is already true**, so that nothing has to move: `:span` is on the node, +in its own frames; `:time` (`:at`, `:rate`) is on the node; looping is on the +node. The SYMBOL owns only `:frames`, the authored window. So looping and span +are instance properties already, and this change is about deleting a mode, not +relocating a field. + +**What has to be decided and collapsed:** + +- There are two spellings of looping — `:time :loop?` and `:playback :end + :loop` — and both are read, in `node/placed-frame` and in + `nest/audio-tracks`. Keep one. `:playback {:in :speed :end}` already says + what happens at the ends, so `:end :loop` is the one to keep and + `:time :loop?` is the one to delete. +- `:playback :speed 0` stops being produced. Make it illegal in + `node/problems` rather than legal-but-unused, so a frozen clock has exactly + one spelling: a 1-frame loop. +- `lane/extend-hold` exists only because holds were special — it refuses + anything whose speed is not 0 and then edits a span. Once a hold is a loop, + lengthening one IS `resize-out`, and the command collapses into it. +- `cel` survives as the word for the creation policy — a new empty symbol is + one frame — but stops naming a playback mode. + +**What it buys, and this is the point:** one rule for nesting, which settles +the refusal that blocks shift-to-reparent today. `nest/inside` currently has +no `:time` for a hold, for `:end :hold`, or for a loop, and so refuses all +three. The general rule that covers all of them: **resolve the move with the +destination's map at the CURRENT frame — the affine piece the current frame +falls in.** + +- A loop of length L is affine within the period the current frame is in. + Timing is preserved inside that period and repeats after it, which is what + looping means. +- A 1-frame loop — the ex-hold — has a period of length 1, so the map within + it is trivially invertible and lands the moved node on frame 0, aligned to + the current frame. That is exactly the answer `docs/lane-nesting-notes.md` + argues for from first principles, arrived at here as an instance of the + general rule instead of a special case. +- `:end :hold` is affine in the played part and frozen in the tail, which the + same sentence covers. + +**The trap, which must be handled in the same change.** `nest/audio-tracks` +expands a loop into one walk PER PERIOD: + + periods (range (floor (/ (to-local source lo) length)) + (ceil (/ (to-local source hi) length))) + +Today a held cel is skipped entirely — `(pos? speed)` is the guard, and the +comment says a visual freeze does not emit a sustained audio sample. Turn +every drawing into a 1-frame loop and that guard stops firing: a drawing held +for 120 frames becomes 120 recursive walks, and any sound inside it is emitted +120 times. That is both a wrong mix and a performance cliff on the most common +node in the document. Required with this change: a cheap `symbol/audible?` +precheck so a source with no audio anywhere inside it is never period-expanded, +and a cap or a different formulation for the ones that are. + +Also worth knowing before the change: `ui/timeline`'s own docstring already +says a looping instance draws only its first pass. With every drawing a loop, +that sentence now describes every drawing — harmless, since one pass of a +1-frame symbol is the whole of it, but the docstring should stop sounding like +a limitation. + +**Dropping into a 1-frame symbol.** The window is authored and crops what it +holds, so a 10-frame symbol dropped into a 1-frame drawing shows its frame 0 +and nothing else. That is consistent — `:frames` is the shot length and +`:extent :grow-symbol` is the opt-in — but it is probably not what somebody +dragging means. Offer the growth through the `:required-frames` retry the +model already uses: the command reports what it would need, the UI offers one +button. + ## Order of work Each step compiles, passes `npm test`, and leaves the editor usable. @@ -183,7 +259,8 @@ to keep. 2. **Does a lane row's edge drag trim the placing instance's span, or ripple the children?** Same handle, two commands; the row is now an instance, so it has a span of its own for the first time. -3. **The held destination for shift-to-reparent** is still refused — - `nest/inside` has no invertible clock for a hold, which is most of what - anybody would try to nest into. `docs/lane-nesting-notes.md` argues the - refusal is stronger than the facts require and says what would settle it. +3. **Does tearing out the held cel come before or after the lane work?** It + is independent of it — `nest` never knew about lanes — and it is what makes + shift-to-reparent work on the thing people would actually drag onto. Doing + it first means the lane work lands on a model with one playback mode fewer; + doing it after means two changes to `lane_test`'s fixtures instead of one.