A composition is a timeline of scene objects for cuts and cutscenes.
This first step brings the model and the pure evaluation behind it.
The tab and placing it in a scene follow in the next steps.
- CompositionAsset holds objects, tracks, items and clips. Each item
owns exactly one object with overrides and container edits.
- The composition is a third object holder next to scene and
container. Delete cascade, undo, usages and references work with it.
- Duplicating gives the copy new object ids and moves the item ids
along with them.
- compositionAt() tells which items live at a frame. The top track is
drawn in front.
- A clip acts on the item of its own track that it overlaps. After its
end it holds the last value until the item ends. The animation's
play mode is ignored, a clip is one pass.
- Clip targets are found by template path, else by name path, like
the runtime's animation system.
- The root sprite of an item plays its video driven at offset plus
time. Children follow unless they are already driven by hand.
- fittedLength() gives the video or clip length minus the offset, or
3 s without media.
- fps (60) and loop live in their own properties file. Keeping them in
PropertySchema would close an import cycle.
- The type is listed under Visuals with its own icon. It is not
placeable yet.
Tests: the gate passed with 300 files and 4625 tests. That is 25 new
tests in tests/core/composition.test.ts and 2 icon checks that run
for every icon. Four counter-checks (track order, overlap rule, child
videos, ids on duplicate) each turned exactly their test red. e2e 04
passed with 67 checks and now counts 33 asset types.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The built-in eases cover the usual shapes, but a game often wants its
own: a coin that bounces twice, a door that overshoots and settles.
A new asset type holds such a curve, and keys and the Lerp node can
pick it instead of a built-in ease.
- New asset type "curve": points like in Vector art, x is t and y is
the value. A new curve is the straight line from (0, 0) to (1, 1).
- One pure evaluation in src/core/model/curve.ts serves the timeline
preview, the running game and the Lerp node.
- The curve always has one value per t. The end points stay at t 0
and 1, points cannot pass each other, and a handle that reaches past
its neighbour is shortened along its direction. Loading a hand-edited
file applies the same rules.
- Curve tab: click on the line to insert a point that keeps the shape,
click on free space to put one there, drag points and handles,
double-click or Delete removes, Alt-click toggles round and sharp.
Every step is one undo step.
- Presets: all 30 built-in eases plus Linear, fitted with adaptive
subdivision. Each stays within 0.4 % of the built-in ease.
- Preview: a dot runs along the curve once a second, a second dot
shows only the value. The Preview button stops it.
- Animation keys get "Curve" under Easing with a curve picker. Lerp
gets "Curve" under Ease, and its result may overshoot b.
- A missing or unpicked curve runs linear. The inspector says so, the
node shows it as a problem, and deleting a used curve warns with the
number of keys and Lerp nodes.
Tests:
- ./check.sh: 180 files, 2612 tests, CHECK EXIT 0
- GM_STEPS=1-6,25,53: ALL PASSED, 309 checks
- New e2e module 53 with 13 checks, all with the real mouse. Expected
values come from its own Bezier evaluation.
- Counter-checks bite: without Bezier evaluation 4 unit tests fail,
without handle clamping 2. In the browser, a key that cannot find its
curve, a click that sets no point and a handle drag that moves the
point all turn checks red.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A key could only ease quadratically, which makes quick moves like a
sword swing feel soft. Animators know the Penner curves from CSS,
GSAP and Unity, so a key now offers all of them.
- Ten curves (Sine, Quad, Cubic, Quart, Quint, Expo, Circ, Back,
Elastic, Bounce), each in, out and in-out, next to Linear and Step.
- The inspector shows two fields instead of one long list: Easing,
and Direction only when a curve is picked. Switching the curve
keeps the direction. Coming from Linear or Step starts at in-out.
- Old projects keep working. ease-in/out/in-out now read as Quad and
are still saved under that name. New curves save as cubic-out,
back-in-out and so on.
- Back and Elastic overshoot on purpose. A number property with min
or max is clamped in play and preview, so a Back in from scale 0
does not flip the sprite. trackWriteAt now takes the property
definition, and animatedKind* is renamed to animatedDef*.
- The timeline marker still shows only the direction.
- In-out is built for every curve from its in-curve and its mirror.
easings.net uses own constants for Back and Elastic in-out, so our
Back in-out winds up about 5 % instead of 10 %.
Tests:
- ./check.sh: 165 files, 2413 tests, CHECK EXIT 0.
- 12 new unit tests in tests/core/easing-curves.test.ts: round trip
of all 30 values, start/end/midpoint of every curve, reference
values, only Back and Elastic leave 0..1, clamping in play and
preview, unknown values are rejected.
- e2e 25: ALL PASSED, 28 checks. New 25b-ii (3 checks) clicks both
fields and undoes the four choices. Check sum 1393 -> 1396.
- Counter-checks, each fails its test: no clamping (2), Quad saved
as quad-* (2), mirrored in-out wrong (2), curve lookup with `in`
instead of Object.hasOwn (1), no Direction field (e2e 25).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Animation tab shows the root asset over the timeline, with every row's value at the
playhead written onto a copy of it – the sprite, text or container placed at the origin as a
scene would place it, a scene as itself. Rows reach the root through that placed object and its
children through the object's instance edits, the route a placed container's content takes,
so the ids match what the runtime gives them. The runtime player and the preview evaluate a row
through one function (trackWriteAt), relative rows included; past the end of a once-reset pass
the preview shows the starting values, as the player would. Play, pause and the ruler drive the
preview through the timeline's playhead; a drag pans, the wheel zooms, Centre frames it. The
document is never touched and undo never hears of scrubbing.
On animation finished no longer describes itself as hearing "a sprite of the owner".
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>