(import test (only (list-utils alist) plist->alist)) (include "entity.scm") (include "tilemap.scm") (include "world.scm") (include "animation.scm") (import (downstroke entity) (downstroke world) (downstroke animation)) ;; Test helpers: construct alist entities / animation defs from readable plist kwargs. (define (entity . kws) (plist->alist kws)) (define (anim . kws) (plist->alist kws)) (test-begin "animation") (test-group "frame->tile-id" (test-group "tile IDs only" (test "first frame, frames (0)" 0 (frame->tile-id '(0) 0)) (test "wraps around" 0 (frame->tile-id '(0 1) 2)) (test "frame 1 of (27 28)" 28 (frame->tile-id '(27 28) 1))) (test-group "tile IDs and durations" (test "first frame, frames (0)" 0 (frame->tile-id '((0 10)) 0)) (test "wraps around" 0 (frame->tile-id '((0 10) (1 10)) 2)) (test "frame 1 of (27 28)" 28 (frame->tile-id '((27 10) (28 10)) 1)))) (test-group "animation-frame-duration" (test "first frame, frames (0)" 100 (animation-frame-duration '((#:frames . ((0 100)))) 0)) (test "wraps around" 100 (animation-frame-duration '((#:frames . ((0 100) (1 200)))) 0)) (test "frame 1 of (27 28)" 200 (animation-frame-duration '((#:frames . ((27 100) (28 200)))) 1)) (test "bare frame uses animation #:duration" 4 (animation-frame-duration (anim #:name 'x #:frames '(0 1) #:duration 4) 0)) (test "bare frame falls back to global default without animation #:duration" 100 (animation-frame-duration (anim #:name 'x #:frames '(0 1)) 0)) (test "timed frame ignores animation #:duration" 10 (animation-frame-duration (anim #:name 'x #:frames '((0 10) (1 20)) #:duration 4) 0)) ) (test-group "set-animation" (let ((e (entity #:type 'player #:anim-name 'idle #:anim-frame 5 #:anim-tick 8))) (test "no-op if already active" e (set-animation e 'idle)) (let ((switched (set-animation e 'walk))) (test "switches anim-name" 'walk (entity-ref switched #:anim-name)) (test "resets frame" 0 (entity-ref switched #:anim-frame)) (test "resets tick" 0 (entity-ref switched #:anim-tick))))) (test-group "animate-entity" (test-group "Single frames" (let* ((anims (list (anim #:name 'walk #:frames '(2 3) #:duration 4))) (e (entity #:type 'player #:anim-name 'walk #:anim-frame 0 #:anim-tick 0)) (stepped (animate-entity e anims 1))) (test "increments tick" 1 (entity-ref stepped #:anim-tick)) (test "sets tile-id on first tick" 2 (entity-ref stepped #:tile-id))) (let* ((anims (list (anim #:name 'walk #:frames '(0 1) #:duration 2))) (e (entity #:type 'player #:anim-name 'walk #:anim-frame 0 #:anim-tick 1)) (advanced (animate-entity e anims 200))) (test "advances frame when tick reaches duration" 1 (entity-ref advanced #:anim-frame)) (test "resets tick on frame advance" 0 (entity-ref advanced #:anim-tick)))) (test-group "Frames with duration" (let* ((anims (list (anim #:name 'walk #:frames '((0 10) (1 20)) #:duration 4))) (e (entity #:type 'player #:anim-name 'walk #:anim-frame 0 #:anim-tick 9)) (stepped (animate-entity e anims 200))) (test "ticks resets on frame switch" 0 (entity-ref stepped #:anim-tick)) (test "sets tile-id on 10th tick" 1 (entity-ref stepped #:tile-id)) (test "sets anim-duration to frame duration" 20 (entity-ref stepped #:anim-duration)))) (test-group "Empty" (let* ((e (entity #:type 'player))) (test "unchanged entity without anim-name" e (animate-entity e '() 1))))) (test-group "animation pipeline" (test-group "animated entity" (let* ((anims (list (anim #:name 'walk #:frames '(2 3) #:duration 4))) (e (entity #:type 'player #:anim-name 'walk #:anim-frame 0 #:anim-tick 0 #:animations anims)) (stepped-entity (apply-animation #f e 3))) (test "accumulates anim-tick against frame duration" 3 (entity-ref stepped-entity #:anim-tick)) (test "mirrors current frame duration on entity" 4 (entity-ref stepped-entity #:anim-duration)))) (let* ((e (entity #:type 'static)) (stepped-entity (apply-animation #f e 10))) (test "unchanged static entity" #f (entity-ref stepped-entity #:anim-tick)))) (test-end "animation") (test-exit)