Follow-up to #5: animation sugar — named clips, Anim.play, fluent Tween handles, auto-advance system #43

Closed
opened 2026-08-31 12:06:47 +02:00 by orkun · 1 comment
Owner

Follow-up to #5, which shipped the deterministic math core of 2D animation in e4d1e95: the Anim.* (frame/once/pingpong/finished/duration/cell_x/cell_y) and Tween.* (progress/loop/yoyo/done/ease/number/round/point/tint) namespaces, all pure Q16.16/integer and replay-exact.

This tracks the remaining stateful / ergonomic layer the proposal sketched, deliberately kept separate because it needs new runtime state and/or codegen rather than pure inline math:

  • Named spritesheet clips: Sprite.sheet(...) / Sprite.clip(name, frames, fps, loop) and a SpriteAnim component, so a game plays by name (Anim.play(self(), "run")) instead of tracking a timer + frame math by hand.
  • Frame events / callbacks: fire an event on a given frame (footstep, hitbox-active).
  • Fluent Tween handles: one-shot Tween.to/by, Tween.chain/parallel, delay, loop, yoyo — a disposable handle advanced by the engine, plus an entity-bound Motion component.
  • Auto-advance system: a built-in system that ticks SpriteAnim / Motion timers each frame. Ludic has no auto-injected systems over user components yet — this is the real dependency.

The math core from #5 is what all of the above builds on: named clips resolve to Anim.frame, tween handles evaluate through Tween.ease + the typed blends. Sequence this after the ECS gains a way to register an engine-owned system over user components.

Related: #5 (the shipped math core).

Follow-up to #5, which shipped the deterministic **math core** of 2D animation in e4d1e95: the `Anim.*` (frame/once/pingpong/finished/duration/cell_x/cell_y) and `Tween.*` (progress/loop/yoyo/done/ease/number/round/point/tint) namespaces, all pure Q16.16/integer and replay-exact. This tracks the remaining **stateful / ergonomic layer** the proposal sketched, deliberately kept separate because it needs new runtime state and/or codegen rather than pure inline math: - **Named spritesheet clips**: `Sprite.sheet(...)` / `Sprite.clip(name, frames, fps, loop)` and a `SpriteAnim` component, so a game plays by name (`Anim.play(self(), "run")`) instead of tracking a timer + frame math by hand. - **Frame events / callbacks**: fire an `event` on a given frame (footstep, hitbox-active). - **Fluent Tween handles**: one-shot `Tween.to/by`, `Tween.chain/parallel`, `delay`, `loop`, `yoyo` — a disposable handle advanced by the engine, plus an entity-bound `Motion` component. - **Auto-advance system**: a built-in system that ticks `SpriteAnim` / `Motion` timers each frame. Ludic has no auto-injected systems over user components yet — this is the real dependency. The math core from #5 is what all of the above builds on: named clips resolve to `Anim.frame`, tween handles evaluate through `Tween.ease` + the typed blends. Sequence this after the ECS gains a way to register an engine-owned system over user components. Related: #5 (the shipped math core).
Author
Owner

Shipped the piece this issue named as its real dependency — "Ludic has no auto-injected systems over user components yet" — in b014333.

Engine-owned systems. The compiler now inserts a system it owns into the frame loop over a component a game merely declares and carries, no handler wired:

  • SpriteAnim { ticks, fps, frames, mode, frame } — spritesheet frame animation, the engine advances frame (mode 0 loop / 1 once / 2 ping-pong).
  • Motion { ticks, dur, from, to, ease, value, done } — value tween, the engine advances value (ease 0 linear / 1 in / 2 out / 3 in-out) and latches done.

Both stand on the by-name reflection ABI (resolve fields by name, no-op when absent), are pure integer off the fixed 60 Hz clock — so replay and lockstep reproduce motion exactly — and are spliced only when the component is declared, leaving every other game byte-for-byte unchanged. Wired in emit_engine_systems_for_phase; runtime in runtime/native/systems.ludic; documented under Engine-owned systems in LANGUAGE.md.

Proof. examples/library/anim_ecs.ludic declares the components, carries them on models, ticks the sim from entry, and asserts the auto-advanced fields (1 5 2 10 0 2 10 1 1) — now in the regression suite (73 passed, self-host C-free bootstrap fixpoint intact).

The remaining ergonomic layer — named clips + Anim.play(e, "run"), Anim.play/Motion.to sugar, frame events, and fluent Tween.chain/parallel handles — builds on this hook and is tracked in #48, mirroring how #5 shipped the math core and opened this issue. Closing as the dependency + component layer are delivered.

Shipped the piece this issue named as its **real dependency** — "Ludic has no auto-injected systems over user components yet" — in b014333. **Engine-owned systems.** The compiler now inserts a system it owns into the frame loop over a component a game merely *declares and carries*, no `handler` wired: - `SpriteAnim { ticks, fps, frames, mode, frame }` — spritesheet frame animation, the engine advances `frame` (`mode` 0 loop / 1 once / 2 ping-pong). - `Motion { ticks, dur, from, to, ease, value, done }` — value tween, the engine advances `value` (`ease` 0 linear / 1 in / 2 out / 3 in-out) and latches `done`. Both stand on the by-name reflection ABI (resolve fields by name, no-op when absent), are pure integer off the fixed 60 Hz clock — so replay and lockstep reproduce motion exactly — and are spliced only when the component is declared, leaving every other game byte-for-byte unchanged. Wired in `emit_engine_systems_for_phase`; runtime in `runtime/native/systems.ludic`; documented under *Engine-owned systems* in LANGUAGE.md. **Proof.** `examples/library/anim_ecs.ludic` declares the components, carries them on models, ticks the sim from `entry`, and asserts the auto-advanced fields (`1 5 2 10 0 2 10 1 1`) — now in the regression suite (73 passed, self-host C-free bootstrap fixpoint intact). The remaining **ergonomic layer** — named clips + `Anim.play(e, "run")`, `Anim.play`/`Motion.to` sugar, frame events, and fluent `Tween.chain`/`parallel` handles — builds on this hook and is tracked in #48, mirroring how #5 shipped the math core and opened this issue. Closing as the dependency + component layer are delivered.
orkun closed this issue 2026-08-31 14:43:16 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: workshopsoft/ludic#43
No description provided.