namespace
Anim
Spritesheet frame animation off the fixed frame clock. Store an elapsed timer (seconds, a fixed) on a component and each frame ask Anim.frame / Anim.once / Anim.pingpong which cell to draw; Anim.cell_x/Anim.cell_y turn a frame index into a source rectangle on the sheet. Everything is integer/fixed and deterministic — the same timer reproduces the same frame every run, so replays and lockstep netcode match exactly. For the ECS engine-owned SpriteAnim component there is also an ergonomic layer: register named clips with Anim.clip and play them by name with Anim.play, and arm frame events with Anim.on_frame / Anim.fired.
Anim.frame(timer, fps, count) -> intThe looping frame index for an elapsed timer at a given fps.Anim.once(timer, fps, count) -> intA non-looping frame index that clamps on the last frame.Anim.pingpong(timer, fps, count) -> intA frame index that bounces 0..count-1..0 and repeats.Anim.finished(timer, fps, count) -> boolTrue once a one-shot clip has run past its last frame.Anim.duration(fps, count) -> fixedSeconds for one full cycle of a clip: count / fps.Anim.cell_x(frame, cols, cell_w) -> intThe source x (pixels) of a frame on a grid spritesheet.Anim.cell_y(frame, cols, cell_h) -> intThe source y (pixels) of a frame on a grid spritesheet.Anim.play(entity, clip) | Anim.play(entity, fps, frames, mode)Start (or restart) a spritesheet clip on an entity in one call.Anim.clip(name, frames, fps, mode)Register a named spritesheet clip so Anim.play can play it by name.Anim.on_frame(entity, frame)Arm a frame event — the engine flags the tick a clip lands on this frame.Anim.fired(entity) -> boolDid the entity's clip land on its armed frame event this tick?