# anim.ludic — Anim.* spritesheet frame animation and Tween.* value tweening, # both pure and deterministic off the fixed frame clock. Each assertion that # holds prints its number, so a full run prints: # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 # A `timer` is elapsed seconds as a fixed; fps / frame counts are ints; tween # amounts are a fixed in 0..1. Timers are exact-in-fixed so floor(timer*fps) # never lands ambiguously on a frame boundary. program Anim { property Tag { v: int = 0 } model Marker { Tag } handler Boot phase Start { let F0 = fixed(0) let F1 = fixed(1) let HALF = fixed(1) / fixed(2) # 0.5 let QTR = fixed(1) / fixed(4) # 0.25 let E18 = fixed(1) / fixed(8) # 0.125 let E78 = fixed(7) / fixed(8) # 0.875 let F125 = fixed(5) / fixed(4) # 1.25 let F15 = fixed(3) / fixed(2) # 1.5 let TWO = fixed(2) let FOUR = fixed(4) let WHITE = 16777215 # 0xFFFFFF let MID = 8355711 # 0x7F7F7F # --- Anim.frame: looping frame index at 8 fps over a 4-frame clip --- if Anim.frame(F0, 8, 4) == 0 { print(1) } # elapsed 0 if Anim.frame(QTR, 8, 4) == 2 { print(2) } # elapsed 2 if Anim.frame(HALF, 8, 4) == 0 { print(3) } # elapsed 4 -> 4%4 if Anim.frame(E78, 8, 4) == 3 { print(4) } # elapsed 7 -> 7%4 # --- Anim.once: clamped one-shot (stops on the last frame) --- if Anim.once(HALF, 8, 4) == 3 { print(5) } # elapsed 4 -> min(4,3) if Anim.once(QTR, 8, 4) == 2 { print(6) } # elapsed 2 # --- Anim.pingpong: bounce 0..3..0 --- if Anim.pingpong(HALF, 8, 4) == 2 { print(7) } # elapsed 4 -> 6-4 if Anim.pingpong(E18, 8, 4) == 1 { print(8) } # elapsed 1 # --- Anim.finished: has a one-shot run past its last frame? --- if Anim.finished(HALF, 8, 4) { print(9) } # elapsed 4 >= 4 if not Anim.finished(QTR, 8, 4) { print(10) } # elapsed 2 < 4 # --- Anim.duration + spritesheet cell geometry --- if Anim.duration(8, 4) == HALF { print(11) } # 4/8 = 0.5s if Anim.cell_x(5, 4, 16) == 16 { print(12) } # (5%4)*16 if Anim.cell_y(5, 4, 16) == 16 { print(13) } # (5/4)*16 if Anim.cell_x(6, 4, 16) == 32 { print(14) } # (6%4)*16 # --- Tween.progress: clamped 0..1 over a duration --- if Tween.progress(QTR, F1) == QTR { print(15) } # 0.25/1 if Tween.progress(TWO, F1) == F1 { print(16) } # 2/1 -> clamp 1 if Tween.progress(F0 - F1, F1) == F0 { print(17) } # -1 -> clamp 0 # --- Tween.done --- if Tween.done(F1, F1) { print(18) } if not Tween.done(HALF, F1) { print(19) } # --- Tween.loop / Tween.yoyo --- if Tween.loop(F125, F1) == QTR { print(20) } # frac(1.25) if Tween.yoyo(HALF, F1) == HALF { print(21) } # up-leg if Tween.yoyo(F15, F1) == HALF { print(22) } # down-leg 2-1.5 # --- Tween.number / Tween.round scalar interpolation --- if Tween.number(F0, FOUR, HALF) == TWO { print(23) } # lerp fixed if Tween.round(0, 10, HALF) == 5 { print(24) } # round(5.0) if Tween.round(0, 10, QTR) == 3 { print(25) } # round(2.5) half-up # --- Tween.tint: per-channel color blend --- if Tween.tint(0, WHITE, F0) == 0 { print(26) } if Tween.tint(0, WHITE, F1) == WHITE { print(27) } if Tween.tint(0, WHITE, HALF) == MID { print(28) } # 0x7F7F7F # --- Tween.point: 2D vector interpolation --- let p = Tween.point(Vector.make(F0, F0), Vector.make(FOUR, fixed(8)), HALF) if Vector.x(p) == TWO { print(29) } if Vector.y(p) == FOUR { print(30) } # --- Tween.ease: curve selection, and consistency with Ease.* --- if Tween.ease(HALF, 0) == HALF { print(31) } # 0 = linear if Tween.ease(HALF, 1) == QTR { print(32) } # 1 = ease-in: 0.5^2 if Tween.ease(HALF, 1) == Ease.in(HALF) { print(33) } # same curve as Ease.in if Tween.ease(F1, 2) == F1 { print(34) } # 2 = ease-out at 1.0 } handler Run phase Update { quit() } }