Key what an empty timeline row shows on a double-click (#412)

A double-click into a timeline row without keys set the property's
default, while the field in the row header showed the value the target
actually holds. A sprite standing on x 40 got a key with 0, so the
first key moved it. The double-click now keys the value the row shows.

- trackHeld in src/core/model/animation.ts says what a row without keys
  holds. The field in the row header and SetAnimationKeyCommand both
  ask it, so they cannot drift apart again.
- A relative row still gets 0, a row with keys still takes the value of
  its curve at that frame, and a row whose target is gone still only
  gives a notice and no key.
- README and the wiki page Development name 2047 checks.

Tests:
- Unit animation: 87 green, 1 new. A sprite on x 40 with the override
  scale 2 gets keys with 40 and 2. Red with the old def.default.
- e2e 25 on melektaus-atx: ALL PASSED, 35 checks, 25i-ii new. The arm
  stands on x 40 and a double-click on frame 3 keys { frame: 3,
  value: 40 }.
- Gate on melektaus-atx: green, 304 files, 4703 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-12 18:09:02 +02:00
1 parent d4fc425233
commit c425eea004
5 files changed
+51 -6

No files matched your search

+17
View File
@@ -366,6 +366,23 @@ describe('editing the timeline', () => {
expect(rowOf(walk, x).keys).toEqual(keys([0, 0]));
});
it('starts an empty row at what its header shows, the value the object holds (#412)', () => {
const f = makeSpriteScene();
const a = place(f, f.spriteId, { x: 40, y: 0 });
f.stack.execute(new SetOverrideCommand(f.sceneId, a, 'scale', 2));
const walk = addAnimation(f, f.sceneId);
const x = addRow(f, walk, 'x', [], a);
const scale = addRow(f, walk, 'scale', [], a);
const shown = timelineLines(f.project, current(f, walk)).flatMap((l) =>
l.kind === 'row' ? [lineValueAt(l, 0)] : [],
);
f.stack.execute(new SetAnimationKeyCommand(walk.id, x, 0));
f.stack.execute(new SetAnimationKeyCommand(walk.id, scale, 0));
expect(shown).toEqual([40, 2]);
expect(rowOf(current(f, walk), x).keys).toEqual(keys([0, 40]));
expect(rowOf(current(f, walk), scale).keys).toEqual(keys([0, 2]));
});
it('moves keys together; one landing on a key left behind replaces it, and undo brings both back', () => {
const f = makeSpriteScene();
const walk = addAnimation(f, f.spriteId);