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:
parent
9446829774
commit
26517af2fd
6 changed files with 296 additions and 42 deletions
|
|
@ -149,8 +149,33 @@ try {
|
|||
assert.equal(new Set(cels.map(n => n.source.symbol)).size, 4,
|
||||
'four exposures of four drawings: nothing is shared once every copy is made');
|
||||
assert.equal(s.history.done.length, before.history.done.length + 3, 'three more commands, three more steps');
|
||||
|
||||
// A drawing into the middle of a hold: split, then insert. Both act at the
|
||||
// playhead, and neither guesses what the other one is for.
|
||||
const placed = s => instances(s)
|
||||
.map(n => [n.time.at + n.span[0] / (n.time.rate ?? 1), n.time.at + n.span[1] / (n.time.rate ?? 1)])
|
||||
.sort((a, b) => a[0] - b[0]);
|
||||
assert.deepEqual(placed(s), [[0, 4], [4, 5], [5, 6], [6, 7]]);
|
||||
await evaluate(`document.querySelector('.tl-cel').click()`);
|
||||
await sleep(200);
|
||||
assert.equal(await enabled('split'), false, 'the start of an exposure is not inside it');
|
||||
await click('+1');
|
||||
await click('+1');
|
||||
assert.equal(await enabled('split'), true);
|
||||
await click('split');
|
||||
s = await shot();
|
||||
assert.deepEqual(placed(s), [[0, 2], [2, 4], [4, 5], [5, 6], [6, 7]],
|
||||
'one exposure became two, over the frames it had');
|
||||
await click('insert');
|
||||
s = await shot();
|
||||
assert.deepEqual(placed(s), [[0, 2], [2, 3], [3, 5], [5, 6], [6, 7], [7, 8]],
|
||||
'the new drawing took frame 2 and everything from there rippled later');
|
||||
assert.equal(await evaluate('document.querySelectorAll(".tl-cel").length'), 6);
|
||||
assert.equal(await evaluate('document.querySelectorAll(".tl-label:not(.tl-corner)").length'), 1,
|
||||
'six exposures, still one row');
|
||||
assert.equal(s.history.done.length, before.history.done.length + 5);
|
||||
assert.equal(errors.length, 0, JSON.stringify(errors));
|
||||
console.log('PASS: create lane/drawings, one-row cels, hold ripple, seek, explicit overflow, atomic undo, reuse/make unique/duplicate; no server writes');
|
||||
console.log('PASS: create lane/drawings, one-row cels, hold ripple, seek, explicit overflow, atomic undo, reuse/make unique/duplicate, split and insert; no server writes');
|
||||
} finally {
|
||||
if (ws?.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ id: 999999, method: 'Browser.close' }));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue