aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGene Pasquet <dev@etenil.net>2026-04-17 16:30:34 +0100
committerGene Pasquet <dev@etenil.net>2026-04-17 16:30:34 +0100
commit8251c85a4a588504d38a2fad05e4b0fe1cdccb9d (patch)
treec3fcedb7331caf798f2355c7549b35aa3aaf6ac8
parent5de3b9cf122542f2a0c1c906c8ce8add20e5c8c6 (diff)
Convert entities to alists
-rw-r--r--animation.scm37
-rw-r--r--demo/animation.scm15
-rw-r--r--demo/platformer.scm13
-rw-r--r--demo/sandbox.scm27
-rw-r--r--demo/scaling.scm15
-rw-r--r--demo/shmup.scm15
-rw-r--r--demo/topdown.scm13
-rw-r--r--demo/tweens.scm23
-rw-r--r--downstroke.egg2
-rw-r--r--engine.scm11
-rw-r--r--entity.scm23
-rw-r--r--prefabs.scm149
-rw-r--r--tests/animation-test.scm45
-rw-r--r--tests/entity-test.scm94
-rw-r--r--tests/input-test.scm11
-rw-r--r--tests/physics-test.scm247
-rw-r--r--tests/prefabs-test.scm7
-rw-r--r--tests/renderer-test.scm31
-rw-r--r--tests/scene-loader-test.scm28
-rw-r--r--tests/tween-test.scm71
-rw-r--r--tests/world-test.scm75
21 files changed, 498 insertions, 454 deletions
diff --git a/animation.scm b/animation.scm
index 0d961a1..8843512 100644
--- a/animation.scm
+++ b/animation.scm
@@ -1,18 +1,25 @@
(module downstroke-animation *
(import scheme
(chicken base)
- (chicken keyword)
(chicken pretty-print)
(only srfi-1 filter)
downstroke-entity
downstroke-world)
-;; ---- Animation data accessors ----
+;; Animation definitions are alists (converted from plist form in the user's
+;; prefab data file by load-prefabs). Each animation is an alist with keys
+;; #:name, #:frames, optional #:duration.
-(define (animation-frames anim)
- (get-keyword #:frames anim))
-(define (animation-duration anim)
- (get-keyword #:duration anim))
+;; Look up a key in an animation alist. Mirrors entity-ref:
+;; a non-procedure default is returned as-is on miss; a procedure default
+;; is invoked as a thunk.
+(define (animation-ref anim key #!optional default)
+ (cond ((assq key anim) => cdr)
+ ((procedure? default) (default))
+ (else default)))
+
+(define (animation-frames anim) (animation-ref anim #:frames))
+(define (animation-duration anim) (animation-ref anim #:duration))
(define (frame-by-idx frames frame-idx)
(list-ref frames (modulo frame-idx (length frames))))
@@ -43,8 +50,10 @@
(define (animation-by-name animations name)
- (let ((matching-anims (filter (lambda (anim) (eq? (get-keyword #:name anim) name)) animations)))
- (if matching-anims
+ (let ((matching-anims
+ (filter (lambda (anim) (eq? (animation-ref anim #:name) name))
+ animations)))
+ (if (pair? matching-anims)
(car matching-anims)
#f)))
@@ -61,13 +70,13 @@
(if (>= tick duration)
(let ((new-frame-id (modulo (+ frame 1) (length frames))))
(entity-set-many entity
- (list (cons #:anim-tick 0)
- (cons #:anim-frame new-frame-id)
- (cons #:tile-id (frame->tile-id frames new-frame-id))
- (cons #:duration (frame->duration frames new-frame-id)))))
+ `((#:anim-tick . 0)
+ (#:anim-frame . ,new-frame-id)
+ (#:tile-id . ,(frame->tile-id frames new-frame-id))
+ (#:duration . ,(frame->duration frames new-frame-id)))))
(entity-set-many entity
- (list (cons #:anim-tick tick)
- (cons #:tile-id (frame->tile-id frames frame)))))))
+ `((#:anim-tick . ,tick)
+ (#:tile-id . ,(frame->tile-id frames frame)))))))
(define (animate-entity entity animations)
(let* ((anim-name (entity-ref entity #:anim-name #f))
diff --git a/demo/animation.scm b/demo/animation.scm
index 2379115..f2048d6 100644
--- a/demo/animation.scm
+++ b/demo/animation.scm
@@ -2,6 +2,7 @@
(chicken base)
(chicken pretty-print)
(only srfi-1 iota)
+ (only (list-utils alist) plist->alist)
(prefix sdl2 "sdl2:")
(prefix sdl2-ttf "ttf:")
downstroke-engine
@@ -19,13 +20,13 @@
(define *title-font* #f)
(define (make-demo-entity x y tw th id)
- (list #:type 'demo-bot
- #:x x #:y y #:width tw #:height th
- #:vx 0 #:vy 0
- #:gravity? #t #:on-ground? #f
- #:solid? #t #:immovable? #f
- #:tile-id 1
- #:demo-id id #:demo-since-jump 0))
+ (plist->alist (list #:type 'demo-bot
+ #:x x #:y y #:width tw #:height th
+ #:vx 0 #:vy 0
+ #:gravity? #t #:on-ground? #f
+ #:solid? #t #:immovable? #f
+ #:tile-id 1
+ #:demo-id id #:demo-since-jump 0)))
(define (make-demo-tilemap ts tw th gw gh)
(let* ((ncols (inexact->exact (ceiling (/ gw tw))))
diff --git a/demo/platformer.scm b/demo/platformer.scm
index 1a24a8f..6e854fe 100644
--- a/demo/platformer.scm
+++ b/demo/platformer.scm
@@ -1,6 +1,7 @@
(import scheme
(chicken base)
(chicken process-context)
+ (only (list-utils alist) plist->alist)
(prefix sdl2 "sdl2:")
(prefix sdl2-ttf "ttf:")
(prefix sdl2-image "img:")
@@ -16,12 +17,12 @@
(define +debug?+ (and (member "--debug" (command-line-arguments)) #t))
(define (make-player)
- (list #:type 'player
- #:x 100 #:y 50
- #:width 16 #:height 16
- #:vx 0 #:vy 0
- #:gravity? #t #:on-ground? #f
- #:tile-id 1 #:tags '(player)))
+ (plist->alist (list #:type 'player
+ #:x 100 #:y 50
+ #:width 16 #:height 16
+ #:vx 0 #:vy 0
+ #:gravity? #t #:on-ground? #f
+ #:tile-id 1 #:tags '(player))))
(define (player-vx input)
(cond ((input-held? input 'left) -3)
diff --git a/demo/sandbox.scm b/demo/sandbox.scm
index e23584f..ef71053 100644
--- a/demo/sandbox.scm
+++ b/demo/sandbox.scm
@@ -2,6 +2,7 @@
(chicken base)
(chicken random)
(only srfi-1 iota take)
+ (only (list-utils alist) plist->alist)
(prefix sdl2 "sdl2:")
(prefix sdl2-ttf "ttf:")
(prefix sdl2-image "img:")
@@ -49,12 +50,12 @@
;; ── Entity factories ─────────────────────────────────────────────────────────
(define (make-box x y tw th)
- (list #:type 'box
- #:x x #:y y #:width tw #:height th
- #:vx 0 #:vy 0
- #:gravity? #t #:on-ground? #f
- #:solid? #t #:immovable? #f
- #:tile-id 29))
+ (plist->alist (list #:type 'box
+ #:x x #:y y #:width tw #:height th
+ #:vx 0 #:vy 0
+ #:gravity? #t #:on-ground? #f
+ #:solid? #t #:immovable? #f
+ #:tile-id 29)))
(define (spawn-boxes tw th)
(map (lambda (i)
@@ -64,13 +65,13 @@
(iota 8)))
(define (make-demo-bot x y tw th id)
- (list #:type 'demo-bot
- #:x x #:y y #:width tw #:height th
- #:vx 0 #:vy 0
- #:gravity? #t #:on-ground? #f
- #:solid? #t #:immovable? #f
- #:tile-id 1
- #:demo-id id #:demo-since-jump 0))
+ (plist->alist (list #:type 'demo-bot
+ #:x x #:y y #:width tw #:height th
+ #:vx 0 #:vy 0
+ #:gravity? #t #:on-ground? #f
+ #:solid? #t #:immovable? #f
+ #:tile-id 1
+ #:demo-id id #:demo-since-jump 0)))
;; ── Per-entity intent ───────────────────────────────────────────────────────
diff --git a/demo/scaling.scm b/demo/scaling.scm
index 982817a..1d74e6a 100644
--- a/demo/scaling.scm
+++ b/demo/scaling.scm
@@ -1,5 +1,6 @@
(import scheme
(chicken base)
+ (only (list-utils alist) plist->alist)
(prefix sdl2 "sdl2:")
(prefix sdl2-ttf "ttf:")
downstroke-engine
@@ -25,13 +26,13 @@
create: (lambda (game)
(game-scene-set! game
(make-scene
- entities: (list (list #:type 'box
- #:x (/ +width+ 2)
- #:y (/ +height+ 2)
- #:width +box-size+
- #:height +box-size+
- #:vx 0 #:vy 0
- #:color '(255 200 0)))
+ entities: (list (plist->alist (list #:type 'box
+ #:x (/ +width+ 2)
+ #:y (/ +height+ 2)
+ #:width +box-size+
+ #:height +box-size+
+ #:vx 0 #:vy 0
+ #:color '(255 200 0))))
tilemap: #f
camera: (make-camera x: 0 y: 0)
tileset-texture: #f
diff --git a/demo/shmup.scm b/demo/shmup.scm
index 315069c..15afb71 100644
--- a/demo/shmup.scm
+++ b/demo/shmup.scm
@@ -3,6 +3,7 @@
(chicken random)
(only srfi-1 filter any)
(only srfi-197 chain)
+ (only (list-utils alist) plist->alist)
(prefix sdl2 "sdl2:")
(prefix sdl2-ttf "ttf:")
(prefix sdl2-image "img:")
@@ -27,16 +28,16 @@
;; ── Entity factories ─────────────────────────────────────────────────────────
(define (make-player)
- (list #:type 'player #:x 280 #:y 360
- #:width 16 #:height 16 #:vx 0 #:vy 0))
+ (plist->alist (list #:type 'player #:x 280 #:y 360
+ #:width 16 #:height 16 #:vx 0 #:vy 0)))
(define (make-bullet x y)
- (list #:type 'bullet #:x x #:y y
- #:width 4 #:height 8 #:vx 0 #:vy -6))
+ (plist->alist (list #:type 'bullet #:x x #:y y
+ #:width 4 #:height 8 #:vx 0 #:vy -6)))
(define (make-enemy x)
- (list #:type 'enemy #:x x #:y 0
- #:width 16 #:height 16 #:vx 0 #:vy 2))
+ (plist->alist (list #:type 'enemy #:x x #:y 0
+ #:width 16 #:height 16 #:vx 0 #:vy 2)))
;; ── Collision ────────────────────────────────────────────────────────────────
@@ -79,7 +80,7 @@
(define (update-player player input)
(let ((updated (chain player
(entity-set _ #:vx (player-vx input))
- (apply-velocity-x _ #f 0)
+ (apply-velocity-x #f _ 0)
(clamp-player-x _))))
(when (input-pressed? input 'a)
(play-sound 'shoot))
diff --git a/demo/topdown.scm b/demo/topdown.scm
index 7fa9b7e..21c7b6f 100644
--- a/demo/topdown.scm
+++ b/demo/topdown.scm
@@ -2,6 +2,7 @@
(chicken base)
srfi-8
(only srfi-197 chain)
+ (only (list-utils alist) plist->alist)
(prefix sdl2 "sdl2:")
(prefix sdl2-ttf "ttf:")
(prefix sdl2-image "img:")
@@ -13,12 +14,12 @@
downstroke-scene-loader)
(define (make-player)
- (list #:type 'player
- #:x 100 #:y 100
- #:width 16 #:height 16
- #:vx 0 #:vy 0
- #:gravity? #f
- #:tile-id 1 #:tags '(player)))
+ (plist->alist (list #:type 'player
+ #:x 100 #:y 100
+ #:width 16 #:height 16
+ #:vx 0 #:vy 0
+ #:gravity? #f
+ #:tile-id 1 #:tags '(player))))
(define (input->velocity input)
(values (+ (if (input-held? input 'left) -3 0)
diff --git a/demo/tweens.scm b/demo/tweens.scm
index 609c541..51a72b2 100644
--- a/demo/tweens.scm
+++ b/demo/tweens.scm
@@ -1,12 +1,14 @@
(import scheme
(chicken base)
(only srfi-1 iota)
+ (only (list-utils alist) plist->alist)
(prefix sdl2 "sdl2:")
(prefix sdl2-ttf "ttf:")
downstroke-engine
downstroke-world
downstroke-renderer
- downstroke-entity)
+ downstroke-entity
+ downstroke-tween)
;; ── Constants ────────────────────────────────────────────────────────────────
@@ -31,15 +33,16 @@
(define (make-ease-entity ease-sym y rgb)
(let* ((left 20)
(right (+ left 120))
- (base (list #:x left #:y y)))
- (list #:type 'tween-demo #:x left #:y y
- #:width 14 #:height 14
- #:vx 0 #:vy 0 #:gravity? #f #:solid? #f
- #:color rgb
- #:ease-name ease-sym
- #:tween (make-tween base props: `((#:x . ,right))
- duration: +ease-duration+ ease: ease-sym
- repeat: -1 yoyo?: #t))))
+ (base (plist->alist (list #:x left #:y y))))
+ (plist->alist
+ (list #:type 'tween-demo #:x left #:y y
+ #:width 14 #:height 14
+ #:vx 0 #:vy 0 #:gravity? #f #:solid? #f
+ #:color rgb
+ #:ease-name ease-sym
+ #:tween (make-tween base props: `((#:x . ,right))
+ duration: +ease-duration+ ease: ease-sym
+ repeat: -1 yoyo?: #t)))))
;; ── Rendering ────────────────────────────────────────────────────────────────
diff --git a/downstroke.egg b/downstroke.egg
index ab72665..516eed3 100644
--- a/downstroke.egg
+++ b/downstroke.egg
@@ -3,7 +3,7 @@
(author "Gene Pasquet")
(license "BSD-2-Clause")
(category games)
- (dependencies sdl2 sdl2-image sdl2-ttf expat defstruct srfi-1 srfi-13 srfi-69 srfi-197 matchable simple-logger)
+ (dependencies sdl2 sdl2-image sdl2-ttf expat defstruct srfi-1 srfi-13 srfi-69 srfi-197 matchable simple-logger list-utils)
(components
(extension downstroke-entity
(source "entity.scm"))
diff --git a/engine.scm b/engine.scm
index faa0909..31ddbfc 100644
--- a/engine.scm
+++ b/engine.scm
@@ -2,7 +2,6 @@
(import scheme
(chicken base)
- (chicken keyword)
(prefix sdl2 "sdl2:")
(prefix sdl2-ttf "ttf:")
(prefix sdl2-image "img:")
@@ -110,15 +109,15 @@
;; ── Named scene states ────────────────────────────────────────────────────
-;; Construct a state plist with lifecycle hooks.
+;; Construct a state alist with lifecycle hooks.
(define (make-game-state #!key (create #f) (update #f) (render #f))
- (list #:create create #:update update #:render render))
+ `((#:create . ,create) (#:update . ,update) (#:render . ,render)))
-;; Retrieve a value from a state plist.
+;; Retrieve a value from a state alist.
(define (state-hook state key)
- (get-keyword key state (lambda () #f)))
+ (cond ((assq key state) => cdr) (else #f)))
-;; Register a named state. name is a symbol; state is a make-game-state plist.
+;; Register a named state. name is a symbol; state is a make-game-state alist.
(define (game-add-state! game name state)
(hash-table-set! (game-states game) name state))
diff --git a/entity.scm b/entity.scm
index 7c29bf7..540a2c9 100644
--- a/entity.scm
+++ b/entity.scm
@@ -2,34 +2,27 @@
*
(import scheme
(chicken base)
- (chicken keyword)
- (only srfi-1 fold))
+ (only srfi-1 fold alist-delete))
- ;; Entities = plists with shared keys (#:type, #:x, #:y, #:width, #:height, ...).
+ ;; Entities = alists with shared keys (#:type, #:x, #:y, #:width, #:height, ...).
(define (make-entity x y w h)
- (list #:type 'none #:x x #:y y #:width w #:height h))
+ `((#:type . none) (#:x . ,x) (#:y . ,y) (#:width . ,w) (#:height . ,h)))
(define (entity-ref entity key #!optional default)
- (get-keyword key entity (if (procedure? default) default (lambda () default))))
+ (cond ((assq key entity) => cdr)
+ ((procedure? default) (default))
+ (else default)))
(define (entity-type entity)
(entity-ref entity #:type #f))
(define (entity-set entity key val)
- (let ((cleaned (let loop ((lst entity) (acc '()))
- (if (null? lst)
- (reverse acc)
- (let ((k (car lst))
- (v (cadr lst)))
- (if (eq? k key)
- (loop (cddr lst) acc)
- (loop (cddr lst) (cons v (cons k acc)))))))))
- (cons key (cons val cleaned))))
+ (cons (cons key val) (alist-delete key entity eq?)))
(define (entity-set-many entity pairs)
(fold (lambda (pair working-ent)
- (entity-set working-ent (car pair) (if (list? (cdr pair)) (cadr pair) (cdr pair))))
+ (entity-set working-ent (car pair) (cdr pair)))
entity
pairs))
diff --git a/prefabs.scm b/prefabs.scm
index 24a9ee3..56e9ad1 100644
--- a/prefabs.scm
+++ b/prefabs.scm
@@ -1,10 +1,9 @@
(module downstroke-prefabs *
(import scheme
(chicken base)
- (chicken keyword)
- (chicken port)
- (chicken format)
+ (only (chicken keyword) keyword?)
srfi-1
+ (only (list-utils alist) plist->alist)
defstruct
downstroke-entity)
@@ -12,30 +11,30 @@
(defstruct prefab-registry
prefabs group-prefabs file engine-mixin-table user-hooks hook-table)
-(define (ensure-balanced-plist plist)
- (when (> (modulo (length plist) 2) 0)
- (error "Given plist has odd number of items - not a valid plist!")))
-
-(define (has-keyword? key plist)
- (not (eq? (get-keyword key plist (lambda () 'undefined)) 'undefined)))
-
-(define (plist-merge plist1 . plists)
- "Merge two PLIST1 and PLIST2 into a single one, elements of PLIST1 will overwrite those of PLIST2 etc.."
- (let ((meta-list (concatenate plists)))
- (ensure-balanced-plist plist1)
- (ensure-balanced-plist meta-list)
- (if (= (length meta-list) 0)
- plist1
- (let loop ((key (car meta-list))
- (value (cadr meta-list))
- (rest (if (> (length meta-list) 2) (cddr meta-list) '()))
- (acc plist1))
- (let ((new-acc (if (has-keyword? key acc)
- acc
- (append (list key value) acc))))
- (if (null? rest)
- new-acc
- (loop (car rest) (cadr rest) (if (> (length rest) 2) (cddr rest) '()) new-acc)))))))
+;; Private: internal prefab composition helper.
+;; Merge alists left-to-right; earlier occurrences of a key win.
+;; Returns a fresh alist.
+(define (alist-merge . alists)
+ (fold (lambda (alist acc)
+ (fold (lambda (pair acc)
+ (if (assq (car pair) acc)
+ acc
+ (cons pair acc)))
+ acc
+ alist))
+ '()
+ alists))
+
+;; Keys whose values are lists-of-plists in user data files and must be
+;; deep-converted to lists-of-alists after the top-level plist->alist pass.
+(define +nested-plist-list-keys+ '(#:animations #:parts))
+
+(define (convert-nested-plist-values alist)
+ (map (lambda (pair)
+ (if (memq (car pair) +nested-plist-list-keys+)
+ (cons (car pair) (map plist->alist (cdr pair)))
+ pair))
+ alist))
;; Return engine's built-in mixin table
(define (engine-mixins)
@@ -43,8 +42,10 @@
(has-facing #:facing 1)
(animated #:anim-name idle #:anim-frame 0 #:anim-tick 0 #:tile-id 0 #:animations #t)))
-;; Compose a prefab entry with mixin table
-;; Returns (name . merged-plist)
+;; Compose a prefab entry with a mixin-table of alists.
+;; `entry` is the raw user plist-shaped entry: (name mixin-name ... #:k v #:k v ...)
+;; `mixin-table` maps mixin-name → alist (already converted in load-prefabs).
+;; Returns (name . merged-alist).
(define (compose-prefab entry mixin-table)
(let* ((name (car entry))
(rest (cdr entry))
@@ -54,13 +55,14 @@
(loop (cdr lst) (cons (car lst) mixins)))))
(mixin-names (car split))
(inline-fields (cdr split))
- (mixin-plists
+ (inline-alist (plist->alist inline-fields))
+ (mixin-alists
(map (lambda (mname)
(let ((m (assq mname mixin-table)))
(if m (cdr m) (error "Unknown mixin" mname))))
mixin-names))
- ;; inline-fields prepended → first-match semantics give them highest priority
- (merged (apply plist-merge inline-fields mixin-plists)))
+ ;; inline-alist first → highest priority (earlier-wins)
+ (merged (apply alist-merge (cons inline-alist mixin-alists))))
(cons name merged)))
(define *engine-hooks* '())
@@ -72,29 +74,34 @@
(cdr entry)
(error "Unknown prefab hook" hook-sym))))
-;; Group prefab entry: (name . plist) with #:parts = list of part plists.
-;; Part offsets may use #:local-x / #:local-y or #:group-local-x / #:group-local-y.
-(define (compose-group-prefab entry)
- (cons (car entry) (cdr entry)))
-
;; Optional profiles (enabled per group via #:pose-only-origin? / #:static-parts? in data).
;; Pose-only origin: tweened or scripted leader, invisible, does not run physics pipelines.
(define +pose-only-group-origin-defaults+
- (list #:solid? #f #:gravity? #f #:skip-render #t
- #:skip-pipelines '(jump acceleration gravity velocity-x velocity-y
- tile-collisions-x tile-collisions-y on-solid entity-collisions)))
+ '((#:solid? . #f)
+ (#:gravity? . #f)
+ (#:skip-render . #t)
+ (#:skip-pipelines . (jump acceleration gravity velocity-x velocity-y
+ tile-collisions-x tile-collisions-y on-solid entity-collisions))))
;; Physics-driving origin: invisible point mass; members follow via sync-groups.
(define +physics-group-origin-defaults+
- (list #:solid? #f #:gravity? #t #:skip-render #t
- #:vx 0 #:vy 0 #:on-ground? #f))
+ '((#:solid? . #f)
+ (#:gravity? . #t)
+ (#:skip-render . #t)
+ (#:vx . 0)
+ (#:vy . 0)
+ (#:on-ground? . #f)))
;; Static rigid parts: no integration; world pose comes from the origin each frame.
(define +static-group-member-defaults+
- (list #:gravity? #f #:vx 0 #:vy 0 #:on-ground? #f
- #:solid? #t #:immovable? #t
- #:skip-pipelines '(jump acceleration gravity velocity-x velocity-y
- tile-collisions-x tile-collisions-y on-solid)))
+ '((#:gravity? . #f)
+ (#:vx . 0)
+ (#:vy . 0)
+ (#:on-ground? . #f)
+ (#:solid? . #t)
+ (#:immovable? . #t)
+ (#:skip-pipelines . (jump acceleration gravity velocity-x velocity-y
+ tile-collisions-x tile-collisions-y on-solid))))
(define (part-with-group-locals part)
(let* ((p part)
@@ -109,14 +116,30 @@
(mixin-section (if (assq 'mixins data) (cdr (assq 'mixins data)) '()))
(prefab-section (cdr (assq 'prefabs data)))
(group-section (cond ((assq 'group-prefabs data) => cdr) (else '())))
+ ;; Convert engine mixin-table bodies (plists) to alists.
+ (engine-mixin-alist-table
+ (map (lambda (m) (cons (car m) (plist->alist (cdr m))))
+ engine-mixin-table))
;; user mixins first → user wins on assq lookup (overrides engine mixin by name)
- (user-mixin-table (if (null? mixin-section) '() (map (lambda (m) (cons (car m) (cdr m))) mixin-section)))
- (merged-mixin-table (append user-mixin-table engine-mixin-table))
+ (user-mixin-table
+ (map (lambda (m) (cons (car m) (plist->alist (cdr m))))
+ mixin-section))
+ (merged-mixin-table (append user-mixin-table engine-mixin-alist-table))
;; user-hooks first → user wins on assq lookup (overrides engine hooks by name)
(hook-table (append user-hooks *engine-hooks*))
- (prefab-table (map (lambda (entry) (compose-prefab entry merged-mixin-table))
- prefab-section))
- (group-table (map compose-group-prefab group-section)))
+ (prefab-table
+ (map (lambda (entry)
+ (let* ((composed (compose-prefab entry merged-mixin-table))
+ (converted (convert-nested-plist-values (cdr composed))))
+ (cons (car composed) converted)))
+ prefab-section))
+ (group-table
+ (map (lambda (entry)
+ (let* ((name (car entry))
+ (alist-fields (plist->alist (cdr entry)))
+ (converted (convert-nested-plist-values alist-fields)))
+ (cons name converted)))
+ group-section)))
(make-prefab-registry
prefabs: prefab-table
group-prefabs: group-table
@@ -130,19 +153,8 @@
(prefab-registry-engine-mixin-table registry)
(prefab-registry-user-hooks registry)))
-(define (plist->alist plist)
- (if (> (modulo (length plist) 2) 0)
- (error "Invalid plist!")
- (let loop ((n1 (car plist))
- (n2 (cadr plist))
- (rest (if (> (length plist) 2) (cddr plist) '())))
- (if (null? rest)
- (list (cons n1 n2))
- (cons (cons n1 n2) (loop (car rest) (cadr rest) (cddr rest)))))))
-
(define (do-instantiate-prefab registry entry x y w h)
- (let* ((base (entity-set-many (make-entity x y w h)
- (plist->alist (cdr entry))))
+ (let* ((base (entity-set-many (make-entity x y w h) (cdr entry)))
(hook-val (entity-ref base #:on-instantiate #f))
(handler
(cond
@@ -163,7 +175,7 @@
(define (instantiate-group-member part ox oy gid type-members static-parts?)
(let* ((p0 (part-with-group-locals part))
- (merged (plist-merge p0 (if static-parts? +static-group-member-defaults+ '())))
+ (merged (alist-merge p0 (if static-parts? +static-group-member-defaults+ '())))
(lx (entity-ref merged #:group-local-x 0))
(ly (entity-ref merged #:group-local-y 0))
(typ (entity-ref merged #:type type-members))
@@ -192,9 +204,12 @@
(oh (entity-ref spec #:origin-height 0))
(ot (entity-ref spec #:origin-type 'group-origin))
(origin-fields
- (append
- (list #:type ot #:group-id gid #:group-origin? #t
- #:x ox #:y oy #:width ow #:height oh)
+ (alist-merge
+ `((#:type . ,ot)
+ (#:group-id . ,gid)
+ (#:group-origin? . #t)
+ (#:x . ,ox) (#:y . ,oy)
+ (#:width . ,ow) (#:height . ,oh))
(if pose-only?
+pose-only-group-origin-defaults+
+physics-group-origin-defaults+)))
diff --git a/tests/animation-test.scm b/tests/animation-test.scm
index 87b18e7..aae829a 100644
--- a/tests/animation-test.scm
+++ b/tests/animation-test.scm
@@ -1,10 +1,15 @@
-(import srfi-64)
+(import srfi-64
+ (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"
@@ -24,44 +29,44 @@
)
(test-group "set-animation"
- (let ((entity (list #:type 'player #:anim-name 'idle #:anim-frame 5 #:anim-tick 8)))
- (test-equal "no-op if already active" entity (set-animation entity 'idle))
- (let ((switched (set-animation entity 'walk)))
+ (let ((e (entity #:type 'player #:anim-name 'idle #:anim-frame 5 #:anim-tick 8)))
+ (test-equal "no-op if already active" e (set-animation e 'idle))
+ (let ((switched (set-animation e 'walk)))
(test-equal "switches anim-name" 'walk (entity-ref switched #:anim-name))
(test-equal "resets frame" 0 (entity-ref switched #:anim-frame))
(test-equal "resets tick" 0 (entity-ref switched #:anim-tick)))))
(test-group "animate-entity"
(test-group "Single frames"
- (let* ((anims '((#:name walk #:frames (2 3) #:duration 4)))
- (entity (list #:type 'player #:anim-name 'walk #:anim-frame 0 #:anim-tick 0))
- (stepped (animate-entity entity anims)))
+ (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)))
(test-equal "increments tick" 1 (entity-ref stepped #:anim-tick))
(test-equal "sets tile-id on first tick" 2 (entity-ref stepped #:tile-id)))
- (let* ((anims '((#:name walk #:frames (0 1) #:duration 2)))
- (entity (list #:type 'player #:anim-name 'walk #:anim-frame 0 #:anim-tick 1))
- (advanced (animate-entity entity anims)))
+ (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)))
(test-equal "advances frame when tick reaches duration" 1 (entity-ref advanced #:anim-frame))
(test-equal "resets tick on frame advance" 0 (entity-ref advanced #:anim-tick))))
(test-group "Frames with duration"
- (let* ((anims '((#:name walk #:frames ((0 10) (1 20)) #:duration 4)))
- (entity (list #:type 'player #:anim-name 'walk #:anim-frame 0 #:anim-tick 9))
- (stepped (animate-entity entity anims)))
+ (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)))
(test-equal "ticks resets on frame switch" 0 (entity-ref stepped #:anim-tick))
(test-equal "sets tile-id on 10th tick" 1 (entity-ref stepped #:tile-id))
(test-equal "sets duration to frame duration" 20 (entity-ref stepped #:duration))))
(test-group "Empty"
- (let* ((entity (list #:type 'player)))
- (test-equal "unchanged entity without anim-name" entity (animate-entity entity '())))))
+ (let* ((e (entity #:type 'player)))
+ (test-equal "unchanged entity without anim-name" e (animate-entity e '())))))
(test-group "animation pipeline"
(test-group "animated entity"
- (let* ((anims '((#:name walk #:frames (2 3) #:duration 4)))
- (entity (list #:type 'player #:anim-name 'walk #:anim-frame 0 #:anim-tick 0 #:animations anims))
- (stepped-entity (apply-animation #f entity 10)))
+ (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 10)))
(test-equal "Updated animated entity" 1 (entity-ref stepped-entity #:anim-tick)))
- (let* ((entity (list #:type 'static))
- (stepped-entity (apply-animation #f entity 10)))
+ (let* ((e (entity #:type 'static))
+ (stepped-entity (apply-animation #f e 10)))
(test-equal "unchanged static entity" #f (entity-ref stepped-entity #:anim-tick)))))
(test-end "animation")
diff --git a/tests/entity-test.scm b/tests/entity-test.scm
index 3b83dff..d686acf 100644
--- a/tests/entity-test.scm
+++ b/tests/entity-test.scm
@@ -4,9 +4,13 @@
(test-begin "entity")
-;; Test: entity-ref retrieves values from entity plists
+;; Test: entity-ref retrieves values from entity alists
(test-group "entity-ref"
- (let ((entity '(#:type player #:x 100 #:y 200 #:width 16 #:height 16)))
+ (let ((entity '((#:type . player)
+ (#:x . 100)
+ (#:y . 200)
+ (#:width . 16)
+ (#:height . 16))))
(test-equal "retrieves type" 'player (entity-ref entity #:type))
(test-equal "retrieves x" 100 (entity-ref entity #:x))
(test-equal "retrieves y" 200 (entity-ref entity #:y))
@@ -14,7 +18,7 @@
(test-equal "retrieves height" 16 (entity-ref entity #:height)))
;; Test with default value
- (let ((entity '(#:type player)))
+ (let ((entity '((#:type . player))))
(test-equal "returns default for missing key"
99
(entity-ref entity #:x 99))
@@ -24,7 +28,7 @@
;; Test: entity-ref with procedure as default
(test-group "entity-ref-with-procedure-default"
- (let ((entity '(#:type player)))
+ (let ((entity '((#:type . player))))
(test-equal "calls procedure default when key missing"
42
(entity-ref entity #:x (lambda () 42)))))
@@ -40,26 +44,26 @@
;; Test: entity-type extracts type from entity
(test-group "entity-type"
- (let ((player '(#:type player #:x 100))
- (enemy '(#:type enemy #:x 200)))
+ (let ((player '((#:type . player) (#:x . 100)))
+ (enemy '((#:type . enemy) (#:x . 200))))
(test-equal "extracts player type" 'player (entity-type player))
(test-equal "extracts enemy type" 'enemy (entity-type enemy)))
- (let ((no-type '(#:x 100 #:y 200)))
+ (let ((no-type '((#:x . 100) (#:y . 200))))
(test-equal "returns #f for entity without type"
#f
(entity-type no-type))))
;; Test: complex entity with multiple properties
(test-group "complex-entity"
- (let ((entity '(#:type enemy
- #:x 100
- #:y 200
- #:width 16
- #:height 16
- #:health 50
- #:speed 2.5
- #:ai-state patrol)))
+ (let ((entity '((#:type . enemy)
+ (#:x . 100)
+ (#:y . 200)
+ (#:width . 16)
+ (#:height . 16)
+ (#:health . 50)
+ (#:speed . 2.5)
+ (#:ai-state . patrol))))
(test-equal "retrieves numeric property" 50 (entity-ref entity #:health))
(test-equal "retrieves float property" 2.5 (entity-ref entity #:speed))
(test-equal "retrieves symbol property" 'patrol (entity-ref entity #:ai-state))))
@@ -67,79 +71,75 @@
;; Test: entity-set updates entity properties
(test-group "entity-set"
(test-group "existing key is replaced"
- (let ((e (entity-set '(#:x 10 #:y 20) #:x 15)))
+ (let ((e (entity-set '((#:x . 10) (#:y . 20)) #:x 15)))
(test-equal "value updated" 15 (entity-ref e #:x))
(test-equal "other key untouched" 20 (entity-ref e #:y))
- ;; plist length should shrink from 4 to 4 (same — one pair removed, one added)
- ;; stronger: verify the list length stays at 4, not 6
- (test-equal "no duplicate key: list length unchanged" 4 (length e))))
+ ;; alist length stays at 2 (one pair removed, one added) — not 3.
+ (test-equal "no duplicate key: list length unchanged" 2 (length e))))
(test-group "new key is added"
- (let ((e (entity-set '(#:x 10) #:vx 3)))
+ (let ((e (entity-set '((#:x . 10)) #:vx 3)))
(test-equal "new key present" 3 (entity-ref e #:vx))
(test-equal "existing key untouched" 10 (entity-ref e #:x))
- (test-equal "list grows by one pair" 4 (length e)))))
+ (test-equal "list grows by one pair" 2 (length e)))))
(test-group "entity-set-many"
- (test-group "Set multiple entities with lists"
- (let ((e (entity-set-many '(#:x 10 #:y 20) '((#:x 15) (#:y 25)))))
- (test-equal "value x updated" 15 (entity-ref e #:x))
- (test-equal "value y updated" 25 (entity-ref e #:y))))
(test-group "Set multiple entities with cons"
- (let ((e (entity-set-many '(#:x 10 #:y 20) (list (cons #:x 15) (cons #:y 25)))))
+ (let ((e (entity-set-many '((#:x . 10) (#:y . 20))
+ '((#:x . 15) (#:y . 25)))))
(test-equal "value x updated" 15 (entity-ref e #:x))
(test-equal "value y updated" 25 (entity-ref e #:y)))))
;; Test: entity-update applies transformations
(test-group "entity-update"
(test-group "transform existing value"
- (let ((e (entity-update '(#:x 10 #:y 20) #:x (lambda (v) (+ v 5)))))
+ (let ((e (entity-update '((#:x . 10) (#:y . 20)) #:x (lambda (v) (+ v 5)))))
(test-equal "#:x is 15" 15 (entity-ref e #:x))
(test-equal "#:y is 20" 20 (entity-ref e #:y))))
(test-group "missing key uses default"
- (let ((e (entity-update '(#:x 10) #:health (lambda (v) (+ v 1)) 0)))
+ (let ((e (entity-update '((#:x . 10)) #:health (lambda (v) (+ v 1)) 0)))
(test-equal "#:health is 1" 1 (entity-ref e #:health))))
(test-group "missing key without default"
- (let ((e (entity-update '(#:x 10) #:z (lambda (v) v))))
+ (let ((e (entity-update '((#:x . 10)) #:z (lambda (v) v))))
(test-equal "#:z is #f" #f (entity-ref e #:z))))
(test-group "no duplicate keys"
- (let ((e (entity-update '(#:x 10 #:y 20) #:x (lambda (v) (* v 2)))))
- (test-equal "length is 4" 4 (length e)))))
+ (let ((e (entity-update '((#:x . 10) (#:y . 20)) #:x (lambda (v) (* v 2)))))
+ (test-equal "length is 2" 2 (length e)))))
(test-group "entity-skips-pipeline?"
(test-assert "absent skip list"
- (not (entity-skips-pipeline? '(#:type a) 'gravity)))
+ (not (entity-skips-pipeline? '((#:type . a)) 'gravity)))
(test-assert "empty skip list"
- (not (entity-skips-pipeline? '(#:skip-pipelines ()) 'gravity)))
+ (not (entity-skips-pipeline? '((#:skip-pipelines . ())) 'gravity)))
(test-assert "member"
- (entity-skips-pipeline? '(#:skip-pipelines (gravity velocity-x)) 'gravity))
+ (entity-skips-pipeline? '((#:skip-pipelines . (gravity velocity-x))) 'gravity))
(test-assert "not member"
- (not (entity-skips-pipeline? '(#:skip-pipelines (gravity)) 'velocity-x))))
+ (not (entity-skips-pipeline? '((#:skip-pipelines . (gravity))) 'velocity-x))))
-(define-pipeline (fixture-pipeline fixture-skip) (scene_ ent)
+(define-pipeline (fixture-pipeline fixture-skip) (scene_ ent _dt)
(entity-set ent #:x 42))
(test-group "define-pipeline"
- (let ((e '(#:type t #:x 0)))
- (test-equal "runs body" 42 (entity-ref (fixture-pipeline e) #:x)))
- (let ((e '(#:type t #:x 0 #:skip-pipelines (fixture-skip))))
- (test-equal "skipped" 0 (entity-ref (fixture-pipeline e) #:x))))
+ (let ((e '((#:type . t) (#:x . 0))))
+ (test-equal "runs body" 42 (entity-ref (fixture-pipeline #f e 0) #:x)))
+ (let ((e '((#:type . t) (#:x . 0) (#:skip-pipelines . (fixture-skip)))))
+ (test-equal "skipped" 0 (entity-ref (fixture-pipeline #f e 0) #:x))))
-(define-pipeline (guarded-pipeline guarded-skip) (scene_ ent)
+(define-pipeline (guarded-pipeline guarded-skip) (scene_ ent _dt)
guard: (entity-ref ent #:active? #f)
(entity-set ent #:x 99))
(test-group "define-pipeline with guard:"
- (let ((e '(#:type t #:x 0 #:active? #t)))
+ (let ((e '((#:type . t) (#:x . 0) (#:active? . #t))))
(test-equal "runs body when guard passes" 99
- (entity-ref (guarded-pipeline e) #:x)))
- (let ((e '(#:type t #:x 0)))
+ (entity-ref (guarded-pipeline #f e 0) #:x)))
+ (let ((e '((#:type . t) (#:x . 0))))
(test-equal "returns entity unchanged when guard fails" 0
- (entity-ref (guarded-pipeline e) #:x)))
- (let ((e '(#:type t #:x 0 #:active? #t #:skip-pipelines (guarded-skip))))
+ (entity-ref (guarded-pipeline #f e 0) #:x)))
+ (let ((e '((#:type . t) (#:x . 0) (#:active? . #t) (#:skip-pipelines . (guarded-skip)))))
(test-equal "skip-pipelines takes precedence over guard" 0
- (entity-ref (guarded-pipeline e) #:x))))
+ (entity-ref (guarded-pipeline #f e 0) #:x))))
(test-end "entity")
diff --git a/tests/input-test.scm b/tests/input-test.scm
index 0d1e4b5..bbc5599 100644
--- a/tests/input-test.scm
+++ b/tests/input-test.scm
@@ -14,6 +14,11 @@
(include "entity.scm")
(import downstroke-entity)
+(import (only (list-utils alist) plist->alist))
+
+;; Test helper: build an alist entity from plist-style keyword args.
+(define (entity . kws) (plist->alist kws))
+
;; Load the module source directly
(include "input.scm")
;; Now import it to access the exported functions
@@ -119,13 +124,13 @@
(define (make-physics-entity)
(entity-set-many (make-entity 0 0 16 16)
- '((#:vx 0) (#:vy 0)
- (#:input-map ((left . (-2 . 0)) (right . (2 . 0)))))))
+ `((#:vx . 0) (#:vy . 0)
+ (#:input-map . ((left . (-2 . 0)) (right . (2 . 0)))))))
;; Test: apply-input-to-entity applies input to entity
(test-group "apply-input-to-entity"
(test-group "no input-map: entity unchanged"
- (let* ((e '(#:type player #:x 5 #:y 10))
+ (let* ((e (entity #:type 'player #:x 5 #:y 10))
(out (apply-input-to-entity e (lambda (a) #f))))
(test-equal "entity returned as-is" e out)))
diff --git a/tests/physics-test.scm b/tests/physics-test.scm
index 88ddcce..4ab4b17 100644
--- a/tests/physics-test.scm
+++ b/tests/physics-test.scm
@@ -41,6 +41,11 @@
(include "entity.scm")
(import downstroke-entity)
+(import (only (list-utils alist) plist->alist))
+
+;; Test helper: build an alist entity from plist-style keyword args.
+(define (entity . kws) (plist->alist kws))
+
;; Load world module first
(include "world.scm")
(import downstroke-world)
@@ -74,82 +79,82 @@
;; Integration helper: simulate one frame of physics
(define (tick e tm held?)
(let* ((e (apply-input-to-entity e held?))
- (e (apply-gravity e #f 0))
- (e (apply-velocity-x e #f 0))
- (e (resolve-tile-collisions-x e (test-scene tilemap: tm) 0))
- (e (apply-velocity-y e #f 0))
- (e (resolve-tile-collisions-y e (test-scene tilemap: tm) 0))
- (e (detect-on-solid e (test-scene tilemap: tm) 0)))
+ (e (apply-gravity #f e 0))
+ (e (apply-velocity-x #f e 0))
+ (e (resolve-tile-collisions-x (test-scene tilemap: tm) e 0))
+ (e (apply-velocity-y #f e 0))
+ (e (resolve-tile-collisions-y (test-scene tilemap: tm) e 0))
+ (e (detect-on-solid (test-scene tilemap: tm) e 0)))
e))
;; Test: apply-gravity
(test-group "apply-gravity"
(test-group "gravity? true, vy starts at 0"
- (let* ((e '(#:type rock #:x 0 #:y 0 #:vx 0 #:vy 0 #:gravity? #t))
- (result (apply-gravity e #f 0)))
+ (let* ((e (entity #:type 'rock #:x 0 #:y 0 #:vx 0 #:vy 0 #:gravity? #t))
+ (result (apply-gravity #f e 0)))
(test-equal "vy increased by gravity" *gravity* (entity-ref result #:vy))
(test-equal "x unchanged" 0 (entity-ref result #:x))
(test-equal "y unchanged" 0 (entity-ref result #:y))
(test-equal "vx unchanged" 0 (entity-ref result #:vx))))
(test-group "gravity? true, vy already has value"
- (let* ((e '(#:type rock #:x 0 #:y 0 #:vx 0 #:vy 3 #:gravity? #t))
- (result (apply-gravity e #f 0)))
+ (let* ((e (entity #:type 'rock #:x 0 #:y 0 #:vx 0 #:vy 3 #:gravity? #t))
+ (result (apply-gravity #f e 0)))
(test-equal "vy increased by gravity" 4 (entity-ref result #:vy))))
(test-group "gravity? false"
- (let* ((e '(#:type static #:x 0 #:y 0 #:vx 0 #:vy 0 #:gravity? #f))
- (result (apply-gravity e #f 0)))
+ (let* ((e (entity #:type 'static #:x 0 #:y 0 #:vx 0 #:vy 0 #:gravity? #f))
+ (result (apply-gravity #f e 0)))
(test-equal "vy unchanged" 0 (entity-ref result #:vy))))
(test-group "no gravity? field at all"
- (let* ((e '(#:type static #:x 5 #:y 5))
- (result (apply-gravity e #f 0)))
+ (let* ((e (entity #:type 'static #:x 5 #:y 5))
+ (result (apply-gravity #f e 0)))
(test-equal "entity unchanged" e result))))
(test-group "apply-velocity-x"
(test-group "basic horizontal movement"
- (let* ((e '(#:type rock #:x 10 #:y 20 #:vx 5 #:vy -2))
- (result (apply-velocity-x e #f 0)))
+ (let* ((e (entity #:type 'rock #:x 10 #:y 20 #:vx 5 #:vy -2))
+ (result (apply-velocity-x #f e 0)))
(test-equal "x moved by vx" 15 (entity-ref result #:x))
(test-equal "y unchanged" 20 (entity-ref result #:y))
(test-equal "vy unchanged" -2 (entity-ref result #:vy))))
(test-group "zero vx"
- (let* ((e '(#:type rock #:x 10 #:y 20 #:vx 0 #:vy 3))
- (result (apply-velocity-x e #f 0)))
+ (let* ((e (entity #:type 'rock #:x 10 #:y 20 #:vx 0 #:vy 3))
+ (result (apply-velocity-x #f e 0)))
(test-equal "x unchanged" 10 (entity-ref result #:x))
(test-equal "y unchanged" 20 (entity-ref result #:y)))))
(test-group "apply-velocity-y"
(test-group "basic vertical movement"
- (let* ((e '(#:type rock #:x 10 #:y 20 #:vx 3 #:vy -5))
- (result (apply-velocity-y e #f 0)))
+ (let* ((e (entity #:type 'rock #:x 10 #:y 20 #:vx 3 #:vy -5))
+ (result (apply-velocity-y #f e 0)))
(test-equal "x unchanged" 10 (entity-ref result #:x))
(test-equal "y moved by vy" 15 (entity-ref result #:y))
(test-equal "vx unchanged" 3 (entity-ref result #:vx))))
(test-group "zero vy"
- (let* ((e '(#:type rock #:x 10 #:y 20 #:vx 3 #:vy 0))
- (result (apply-velocity-y e #f 0)))
+ (let* ((e (entity #:type 'rock #:x 10 #:y 20 #:vx 3 #:vy 0))
+ (result (apply-velocity-y #f e 0)))
(test-equal "x unchanged" 10 (entity-ref result #:x))
(test-equal "y unchanged" 20 (entity-ref result #:y)))))
(test-group "apply-velocity"
(test-group "basic movement"
- (let* ((e '(#:type rock #:x 10 #:y 20 #:vx 3 #:vy -2))
+ (let* ((e (entity #:type 'rock #:x 10 #:y 20 #:vx 3 #:vy -2))
(result (apply-velocity e)))
(test-equal "x moved by vx" 13 (entity-ref result #:x))
(test-equal "y moved by vy" 18 (entity-ref result #:y))))
(test-group "zero velocity"
- (let* ((e '(#:type rock #:x 10 #:y 20 #:vx 0 #:vy 0))
+ (let* ((e (entity #:type 'rock #:x 10 #:y 20 #:vx 0 #:vy 0))
(result (apply-velocity e)))
(test-equal "x unchanged" 10 (entity-ref result #:x))
(test-equal "y unchanged" 20 (entity-ref result #:y))))
(test-group "no velocity fields (defaults to 0)"
- (let* ((e '(#:type static #:x 5 #:y 5))
+ (let* ((e (entity #:type 'static #:x 5 #:y 5))
(result (apply-velocity e)))
(test-equal "x unchanged" 5 (entity-ref result #:x))
(test-equal "y unchanged" 5 (entity-ref result #:y)))))
@@ -201,100 +206,100 @@
(test-group "resolve-tile-collisions-x"
(test-group "no collision: entity unchanged"
(let* ((tm (make-test-tilemap '((0 0 0) (0 0 0) (0 0 0))))
- (e '(#:type player #:x 0 #:y 0 #:width 16 #:height 16 #:vx 2 #:vy 0)))
- (let ((result (resolve-tile-collisions-x e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16 #:vx 2 #:vy 0)))
+ (let ((result (resolve-tile-collisions-x (test-scene tilemap: tm) e 0)))
(test-equal "x unchanged" 0 (entity-ref result #:x))
(test-equal "vx unchanged" 2 (entity-ref result #:vx)))))
(test-group "zero vx: skipped entirely"
(let* ((tm (make-test-tilemap '((0 1 0) (0 0 0) (0 0 0))))
- (e '(#:type player #:x 0 #:y 0 #:width 16 #:height 16 #:vx 0 #:vy 0)))
- (test-equal "entity eq? when vx=0" e (resolve-tile-collisions-x e (test-scene tilemap: tm) 0))))
+ (e (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16 #:vx 0 #:vy 0)))
+ (test-equal "entity eq? when vx=0" e (resolve-tile-collisions-x (test-scene tilemap: tm) e 0))))
(test-group "collision moving right: push left"
;; solid at col=1 (x=16..31); entity at x=20 overlaps it, vx>0
(let* ((tm (make-test-tilemap '((0 0 0) (0 1 0) (0 0 0))))
- (e '(#:type player #:x 20 #:y 16 #:width 16 #:height 16 #:vx 5 #:vy 0)))
- (let ((result (resolve-tile-collisions-x e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 20 #:y 16 #:width 16 #:height 16 #:vx 5 #:vy 0)))
+ (let ((result (resolve-tile-collisions-x (test-scene tilemap: tm) e 0)))
(test-equal "pushed left of solid tile" 0 (entity-ref result #:x))
(test-equal "vx zeroed" 0 (entity-ref result #:vx)))))
(test-group "collision moving left: push right"
;; solid at col=1 (x=16..31); entity at x=16 overlaps it, vx<0
(let* ((tm (make-test-tilemap '((0 0 0) (0 1 0) (0 0 0))))
- (e '(#:type player #:x 16 #:y 16 #:width 16 #:height 16 #:vx -5 #:vy 0)))
- (let ((result (resolve-tile-collisions-x e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 16 #:y 16 #:width 16 #:height 16 #:vx -5 #:vy 0)))
+ (let ((result (resolve-tile-collisions-x (test-scene tilemap: tm) e 0)))
(test-equal "pushed right of solid tile" 32 (entity-ref result #:x))
(test-equal "vx zeroed" 0 (entity-ref result #:vx)))))
(test-group "floating-point x position"
;; solid at col=1; entity at x=20.5 (float), vx>0
(let* ((tm (make-test-tilemap '((0 0 0) (0 1 0) (0 0 0))))
- (e '(#:type player #:x 20.5 #:y 16 #:width 16 #:height 16 #:vx 2 #:vy 0)))
- (let ((result (resolve-tile-collisions-x e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 20.5 #:y 16 #:width 16 #:height 16 #:vx 2 #:vy 0)))
+ (let ((result (resolve-tile-collisions-x (test-scene tilemap: tm) e 0)))
(test-equal "pushed left of solid tile" 0 (entity-ref result #:x))
(test-equal "vx zeroed" 0 (entity-ref result #:vx)))))
(test-group "entity spanning two columns: both checked"
;; wall at col=3; 20px-wide entity at x=28 spans cols 1 and 2, no collision
(let* ((tm (make-test-tilemap '((0 0 0 1) (0 0 0 1) (0 0 0 1))))
- (e '(#:type player #:x 28 #:y 0 #:width 20 #:height 16 #:vx 3 #:vy 0)))
- (let ((result (resolve-tile-collisions-x e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 28 #:y 0 #:width 20 #:height 16 #:vx 3 #:vy 0)))
+ (let ((result (resolve-tile-collisions-x (test-scene tilemap: tm) e 0)))
(test-equal "no collision yet" 28 (entity-ref result #:x))))
;; entity moved to x=34 now spans cols 2 and 3 (solid), pushed left
(let* ((tm (make-test-tilemap '((0 0 0 1) (0 0 0 1) (0 0 0 1))))
- (e '(#:type player #:x 34 #:y 0 #:width 20 #:height 16 #:vx 3 #:vy 0)))
- (let ((result (resolve-tile-collisions-x e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 34 #:y 0 #:width 20 #:height 16 #:vx 3 #:vy 0)))
+ (let ((result (resolve-tile-collisions-x (test-scene tilemap: tm) e 0)))
(test-equal "pushed left of wall" 28 (entity-ref result #:x))
(test-equal "vx zeroed" 0 (entity-ref result #:vx))))))
(test-group "resolve-tile-collisions-y"
(test-group "no collision: entity unchanged"
(let* ((tm (make-test-tilemap '((0 0 0) (0 0 0) (0 0 0))))
- (e '(#:type player #:x 0 #:y 0 #:width 16 #:height 16 #:vx 0 #:vy 2)))
- (let ((result (resolve-tile-collisions-y e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16 #:vx 0 #:vy 2)))
+ (let ((result (resolve-tile-collisions-y (test-scene tilemap: tm) e 0)))
(test-equal "y unchanged" 0 (entity-ref result #:y))
(test-equal "vy unchanged" 2 (entity-ref result #:vy)))))
(test-group "zero vy: skipped entirely"
(let* ((tm (make-test-tilemap '((1 0 0) (0 0 0) (0 0 0))))
- (e '(#:type player #:x 0 #:y 0 #:width 16 #:height 16 #:vx 0 #:vy 0)))
- (test-equal "entity eq? when vy=0" e (resolve-tile-collisions-y e (test-scene tilemap: tm) 0))))
+ (e (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16 #:vx 0 #:vy 0)))
+ (test-equal "entity eq? when vy=0" e (resolve-tile-collisions-y (test-scene tilemap: tm) e 0))))
(test-group "collision moving down: push up"
;; solid at row=1 (y=16..31); entity at y=20 overlaps it, vy>0
(let* ((tm (make-test-tilemap '((0 0 0) (1 0 0) (0 0 0))))
- (e '(#:type player #:x 0 #:y 20 #:width 16 #:height 16 #:vx 0 #:vy 5)))
- (let ((result (resolve-tile-collisions-y e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 0 #:y 20 #:width 16 #:height 16 #:vx 0 #:vy 5)))
+ (let ((result (resolve-tile-collisions-y (test-scene tilemap: tm) e 0)))
(test-equal "pushed above solid tile" 0 (entity-ref result #:y))
(test-equal "vy zeroed" 0 (entity-ref result #:vy)))))
(test-group "collision moving up: push down"
;; solid at row=1 (y=16..31); entity at y=16 overlaps it from below, vy<0
(let* ((tm (make-test-tilemap '((0 0 0) (0 1 0) (0 0 0))))
- (e '(#:type player #:x 16 #:y 16 #:width 16 #:height 16 #:vx 0 #:vy -5)))
- (let ((result (resolve-tile-collisions-y e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 16 #:y 16 #:width 16 #:height 16 #:vx 0 #:vy -5)))
+ (let ((result (resolve-tile-collisions-y (test-scene tilemap: tm) e 0)))
(test-equal "pushed below solid tile" 32 (entity-ref result #:y))
(test-equal "vy zeroed" 0 (entity-ref result #:vy)))))
(test-group "floating-point y position"
;; solid at row=1; entity at y=20.5 (float), vy>0
(let* ((tm (make-test-tilemap '((0 0 0) (1 0 0) (0 0 0))))
- (e '(#:type player #:x 0 #:y 20.5 #:width 16 #:height 16 #:vx 0 #:vy 3)))
- (let ((result (resolve-tile-collisions-y e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 0 #:y 20.5 #:width 16 #:height 16 #:vx 0 #:vy 3)))
+ (let ((result (resolve-tile-collisions-y (test-scene tilemap: tm) e 0)))
(test-equal "pushed above solid tile" 0 (entity-ref result #:y))
(test-equal "vy zeroed" 0 (entity-ref result #:vy)))))
(test-group "entity spanning two rows: both checked"
;; floor at row=3; 20px-tall entity at y=28 spans rows 1 and 2, no collision
(let* ((tm (make-test-tilemap '((0 0 0) (0 0 0) (0 0 0) (1 1 1))))
- (e '(#:type player #:x 0 #:y 28 #:width 16 #:height 20 #:vx 0 #:vy 3)))
- (let ((result (resolve-tile-collisions-y e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 0 #:y 28 #:width 16 #:height 20 #:vx 0 #:vy 3)))
+ (let ((result (resolve-tile-collisions-y (test-scene tilemap: tm) e 0)))
(test-equal "no collision yet" 28 (entity-ref result #:y))))
;; entity at y=34 now spans rows 2 and 3 (solid), pushed up
(let* ((tm (make-test-tilemap '((0 0 0) (0 0 0) (0 0 0) (1 1 1))))
- (e '(#:type player #:x 0 #:y 34 #:width 16 #:height 20 #:vx 0 #:vy 3)))
- (let ((result (resolve-tile-collisions-y e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 0 #:y 34 #:width 16 #:height 20 #:vx 0 #:vy 3)))
+ (let ((result (resolve-tile-collisions-y (test-scene tilemap: tm) e 0)))
(test-equal "pushed above floor" 28 (entity-ref result #:y))
(test-equal "vy zeroed" 0 (entity-ref result #:vy))))))
@@ -304,8 +309,8 @@
;; After apply-velocity-y the entity lands at y=34 (overlapping both rows 2 and 3).
;; Correct: snap to top of row 2 → y=16. Bug was: fold overwrote row 2 snap with row 3 snap → y=32 (inside row 2).
(let* ((tm (make-test-tilemap '((0 0 0) (0 0 0) (1 0 0) (1 0 0) (0 0 0))))
- (e '(#:type player #:x 0 #:y 34 #:width 16 #:height 16 #:vx 0 #:vy 20)))
- (let ((result (resolve-tile-collisions-y e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 0 #:y 34 #:width 16 #:height 16 #:vx 0 #:vy 20)))
+ (let ((result (resolve-tile-collisions-y (test-scene tilemap: tm) e 0)))
(test-equal "snapped to first solid row" 16 (entity-ref result #:y))
(test-equal "vy zeroed" 0 (entity-ref result #:vy)))))
@@ -315,8 +320,8 @@
;; 3x4 tilemap: air on rows 0-2, solid floor on row 3
;; Player starts at y=0, 16px tall; floor is at y=48
(let* ((tm (make-test-tilemap '((0 0 0) (0 0 0) (0 0 0) (1 1 1))))
- (e0 '(#:type player #:x 0 #:y 0 #:width 16 #:height 16
- #:vx 0 #:vy 0 #:gravity? #t #:input-map ())))
+ (e0 (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16
+ #:vx 0 #:vy 0 #:gravity? #t #:input-map '())))
(let loop ((e e0) (n 10))
(if (= n 0)
(begin
@@ -328,8 +333,8 @@
;; Player already on floor, should stay there
(let* ((tm (make-test-tilemap '((0 0 0) (0 0 0) (1 1 1))))
;; Floor at row 2 (y=32); player at y=16, height=16: bottom at y=32
- (e0 '(#:type player #:x 0 #:y 16 #:width 16 #:height 16
- #:vx 0 #:vy 0 #:gravity? #t #:input-map ())))
+ (e0 (entity #:type 'player #:x 0 #:y 16 #:width 16 #:height 16
+ #:vx 0 #:vy 0 #:gravity? #t #:input-map '())))
(let loop ((e e0) (n 10))
(if (= n 0)
(test-assert "player stays on floor" (<= (entity-ref e #:y) 16))
@@ -350,7 +355,7 @@
(test-group "resolve-entity-collisions"
(define (make-solid x y w h)
- (list #:type 'block #:x x #:y y #:width w #:height h #:solid? #t))
+ (entity #:type 'block #:x x #:y y #:width w #:height h #:solid? #t))
(test-group "no overlap: entities unchanged"
(let* ((a (make-solid 0 0 16 16))
@@ -383,9 +388,9 @@
(test-group "immovable: landing uses vertical separation when horizontal overlap is shallower"
;; Without the landing rule, ovx < ovy would pick horizontal separation and shove the
;; mover sideways off a narrow platform. Box center remains above shelf center → snap on top.
- (let* ((shelf (list #:type 'static #:x 100 #:y 200 #:width 16 #:height 16
- #:solid? #t #:immovable? #t))
- (box (list #:type 'box #:x 92 #:y 196 #:width 16 #:height 16
+ (let* ((shelf (entity #:type 'static #:x 100 #:y 200 #:width 16 #:height 16
+ #:solid? #t #:immovable? #t))
+ (box (entity #:type 'box #:x 92 #:y 196 #:width 16 #:height 16
#:solid? #t #:immovable? #f #:vx 0 #:vy 0))
(result (resolve-entity-collisions (list shelf box)))
(box2 (list-ref result 1)))
@@ -394,7 +399,7 @@
(test-group "non-solid entity ignored"
(let* ((a (make-solid 0 0 16 16))
- (b (list #:type 'goal #:x 5 #:y 5 #:width 16 #:height 16))
+ (b (entity #:type 'goal #:x 5 #:y 5 #:width 16 #:height 16))
(result (resolve-entity-collisions (list a b))))
(test-equal "a x unchanged" 0 (entity-ref (list-ref result 0) #:x 0))
(test-equal "b x unchanged" 5 (entity-ref (list-ref result 1) #:x 0)))))
@@ -406,72 +411,72 @@
;; tilewidth=tileheight=16
;; Entity standing: y=16, h=16 → bottom at y=32, probe at y=33 → row=2 → solid
(let* ((tm (make-test-tilemap '((0 0 0) (0 0 0) (1 1 1))))
- (e (list #:type 'player #:x 0 #:y 16 #:width 16 #:height 16
- #:vx 0 #:vy 0 #:gravity? #t #:on-ground? #f))
- (result (detect-on-solid e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 0 #:y 16 #:width 16 #:height 16
+ #:vx 0 #:vy 0 #:gravity? #t #:on-ground? #f))
+ (result (detect-on-solid (test-scene tilemap: tm) e 0)))
(test-assert "on-ground? is #t" (entity-ref result #:on-ground? #f))))
(test-group "entity in mid-air"
;; Entity in mid-air: y=0, h=16 → bottom at 16, probe at 17 → row=1 → empty
(let* ((tm (make-test-tilemap '((0 0 0) (0 0 0) (1 1 1))))
- (e (list #:type 'player #:x 0 #:y 0 #:width 16 #:height 16
- #:vx 0 #:vy 0 #:gravity? #t #:on-ground? #t))
- (result (detect-on-solid e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16
+ #:vx 0 #:vy 0 #:gravity? #t #:on-ground? #t))
+ (result (detect-on-solid (test-scene tilemap: tm) e 0)))
(test-assert "on-ground? is #f" (not (entity-ref result #:on-ground? #f)))))
(test-group "entity probe spans two tiles, left is solid"
;; Entity at x=0, w=16: left foot at col 0; probe below
;; Row with solid at col 0, empty at col 1: should be on-ground
(let* ((tm (make-test-tilemap '((0 0 0) (0 0 0) (1 0 0))))
- (e (list #:type 'player #:x 0 #:y 16 #:width 16 #:height 16
- #:vx 0 #:vy 0 #:gravity? #t #:on-ground? #f))
- (result (detect-on-solid e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 0 #:y 16 #:width 16 #:height 16
+ #:vx 0 #:vy 0 #:gravity? #t #:on-ground? #f))
+ (result (detect-on-solid (test-scene tilemap: tm) e 0)))
(test-assert "on-ground? is #t (left foot on solid)" (entity-ref result #:on-ground? #f))))
(test-group "entity probe spans two tiles, right is solid"
;; Entity at x=8, w=16: left foot at col 0, right foot at col 1; probe below
;; Row with empty at col 0, solid at col 1: should be on-ground (right foot on solid)
(let* ((tm (make-test-tilemap '((0 0 0) (0 0 0) (0 1 0))))
- (e (list #:type 'player #:x 8 #:y 16 #:width 16 #:height 16
- #:vx 0 #:vy 0 #:gravity? #t #:on-ground? #f))
- (result (detect-on-solid e (test-scene tilemap: tm) 0)))
+ (e (entity #:type 'player #:x 8 #:y 16 #:width 16 #:height 16
+ #:vx 0 #:vy 0 #:gravity? #t #:on-ground? #f))
+ (result (detect-on-solid (test-scene tilemap: tm) e 0)))
(test-assert "on-ground? is #t (right foot on solid)" (entity-ref result #:on-ground? #f))))
(test-group "standing on solid entity (no tile): moving platform / crate"
;; All-air tilemap; wide platform top at y=32; player feet (bottom) at y=32
(let* ((tm (make-test-tilemap '((0 0 0) (0 0 0) (0 0 0))))
- (platform (list #:type 'platform #:x 0 #:y 32 #:width 64 #:height 16
- #:solid? #t #:vx 0 #:vy 0 #:gravity? #f))
- (player (list #:type 'player #:x 8 #:y 16 #:width 16 #:height 16
- #:vx 0 #:vy 0 #:gravity? #t #:on-ground? #f))
+ (platform (entity #:type 'platform #:x 0 #:y 32 #:width 64 #:height 16
+ #:solid? #t #:vx 0 #:vy 0 #:gravity? #f))
+ (player (entity #:type 'player #:x 8 #:y 16 #:width 16 #:height 16
+ #:vx 0 #:vy 0 #:gravity? #t #:on-ground? #f))
(ents (list platform player))
- (result (detect-on-solid player (test-scene tilemap: tm entities: ents) 0)))
+ (result (detect-on-solid (test-scene tilemap: tm entities: ents) player 0)))
(test-assert "on-ground? from entity top" (entity-ref result #:on-ground? #f))))
(test-group "scene with empty entity list: no entity below"
(let* ((tm (make-test-tilemap '((0 0 0) (0 0 0) (0 0 0))))
- (platform (list #:type 'platform #:x 0 #:y 32 #:width 64 #:height 16 #:solid? #t))
- (player (list #:type 'player #:x 8 #:y 16 #:width 16 #:height 16
- #:gravity? #t #:on-ground? #f))
- (result (detect-on-solid player (test-scene tilemap: tm) 0)))
+ (platform (entity #:type 'platform #:x 0 #:y 32 #:width 64 #:height 16 #:solid? #t))
+ (player (entity #:type 'player #:x 8 #:y 16 #:width 16 #:height 16
+ #:gravity? #t #:on-ground? #f))
+ (result (detect-on-solid (test-scene tilemap: tm) player 0)))
(test-assert "empty entity list → not on ground" (not (entity-ref result #:on-ground? #f))))))
(test-group "apply-acceleration"
(test-group "gravity? #t, ay set: consumed into vy and cleared"
- (let* ((e '(#:type player #:x 0 #:y 0 #:vy 3 #:ay 5 #:gravity? #t))
- (result (apply-acceleration e #f 0)))
+ (let* ((e (entity #:type 'player #:x 0 #:y 0 #:vy 3 #:ay 5 #:gravity? #t))
+ (result (apply-acceleration #f e 0)))
(test-equal "vy += ay" 8 (entity-ref result #:vy 0))
(test-equal "ay cleared" 0 (entity-ref result #:ay 0))))
(test-group "gravity? #t, ay is 0: vy unchanged"
- (let* ((e '(#:type player #:x 0 #:y 0 #:vy 3 #:ay 0 #:gravity? #t))
- (result (apply-acceleration e #f 0)))
+ (let* ((e (entity #:type 'player #:x 0 #:y 0 #:vy 3 #:ay 0 #:gravity? #t))
+ (result (apply-acceleration #f e 0)))
(test-equal "vy unchanged" 3 (entity-ref result #:vy 0))
(test-equal "ay still 0" 0 (entity-ref result #:ay 0))))
(test-group "gravity? #f: entity unchanged"
- (let* ((e '(#:type player #:x 0 #:y 0 #:vy 3 #:ay 5 #:gravity? #f))
- (result (apply-acceleration e #f 0)))
+ (let* ((e (entity #:type 'player #:x 0 #:y 0 #:vy 3 #:ay 5 #:gravity? #f))
+ (result (apply-acceleration #f e 0)))
(test-equal "entity unchanged" e result))))
(test-group "pixel->tile"
@@ -485,14 +490,14 @@
(test-group "entity-tile-cells"
(test-group "entity aligned to one tile"
(let* ((tm (make-test-tilemap '((0 0) (0 0))))
- (e '(#:type player #:x 0 #:y 0 #:width 16 #:height 16))
+ (e (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16))
(cells (entity-tile-cells e tm)))
(test-equal "one cell" 1 (length cells))
(test-equal "cell is (0 . 0)" '(0 . 0) (car cells))))
(test-group "entity spanning 2 cols and 2 rows"
(let* ((tm (make-test-tilemap '((0 0 0) (0 0 0) (0 0 0))))
- (e '(#:type player #:x 8 #:y 8 #:width 16 #:height 16))
+ (e (entity #:type 'player #:x 8 #:y 8 #:width 16 #:height 16))
(cells (entity-tile-cells e tm)))
(test-equal "four cells" 4 (length cells)))))
@@ -531,43 +536,43 @@
(test-group "push-entity"
(test-group "push right (sign=1): x += overlap/2, vx=1"
- (let* ((e '(#:type player #:x 10 #:y 0 #:vx 0 #:vy 0))
+ (let* ((e (entity #:type 'player #:x 10 #:y 0 #:vx 0 #:vy 0))
(result (push-entity e #:x #:vx 10 6 1)))
(test-equal "x = 10 + 3" 13 (entity-ref result #:x 0))
(test-equal "vx = 1" 1 (entity-ref result #:vx 0))))
(test-group "push left (sign=-1): x -= overlap/2, vx=-1"
- (let* ((e '(#:type player #:x 10 #:y 0 #:vx 0 #:vy 0))
+ (let* ((e (entity #:type 'player #:x 10 #:y 0 #:vx 0 #:vy 0))
(result (push-entity e #:x #:vx 10 6 -1)))
(test-equal "x = 10 - 3" 7 (entity-ref result #:x 0))
(test-equal "vx = -1" -1 (entity-ref result #:vx 0)))))
(test-group "entity-center-on-axis"
- (let ((e '(#:type player #:x 10 #:y 20 #:width 16 #:height 24)))
+ (let ((e (entity #:type 'player #:x 10 #:y 20 #:width 16 #:height 24)))
(test-equal "center-x = 10 + 8 = 18" 18 (entity-center-on-axis e #:x))
(test-equal "center-y = 20 + 12 = 32" 32 (entity-center-on-axis e #:y))))
(test-group "aabb-overlap-on-axis"
(test-group "x overlap: a at x=0 w=16, b at x=10 w=16 → overlap=6"
;; half-sum of widths = 16, center dist = |18 - 8| = 10, overlap = 16 - 10 = 6
- (let ((a '(#:type player #:x 0 #:y 0 #:width 16 #:height 16))
- (b '(#:type player #:x 10 #:y 0 #:width 16 #:height 16)))
+ (let ((a (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16))
+ (b (entity #:type 'player #:x 10 #:y 0 #:width 16 #:height 16)))
(test-equal "x overlap = 6" 6 (aabb-overlap-on-axis #:x a b))))
(test-group "y overlap: a at y=0 h=16, b at y=10 h=16 → overlap=6"
- (let ((a '(#:type player #:x 0 #:y 0 #:width 16 #:height 16))
- (b '(#:type player #:x 0 #:y 10 #:width 16 #:height 16)))
+ (let ((a (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16))
+ (b (entity #:type 'player #:x 0 #:y 10 #:width 16 #:height 16)))
(test-equal "y overlap = 6" 6 (aabb-overlap-on-axis #:y a b))))
(test-group "no overlap: negative value"
- (let ((a '(#:type player #:x 0 #:y 0 #:width 16 #:height 16))
- (b '(#:type player #:x 100 #:y 0 #:width 16 #:height 16)))
+ (let ((a (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16))
+ (b (entity #:type 'player #:x 100 #:y 0 #:width 16 #:height 16)))
(test-assert "x overlap is negative" (< (aabb-overlap-on-axis #:x a b) 0)))))
(test-group "push-along-axis"
(test-group "x axis: a left of b, pushed apart"
- (let* ((a '(#:type player #:x 0 #:y 0 #:width 16 #:height 16))
- (b '(#:type player #:x 10 #:y 0 #:width 16 #:height 16))
+ (let* ((a (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16))
+ (b (entity #:type 'player #:x 10 #:y 0 #:width 16 #:height 16))
(result (push-along-axis #:x a b 6))
(ra (car result))
(rb (cdr result)))
@@ -577,8 +582,8 @@
(test-equal "b vx = 1" 1 (entity-ref rb #:vx 0))))
(test-group "y axis: a above b, pushed apart"
- (let* ((a '(#:type player #:x 0 #:y 0 #:width 16 #:height 16))
- (b '(#:type player #:x 0 #:y 10 #:width 16 #:height 16))
+ (let* ((a (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16))
+ (b (entity #:type 'player #:x 0 #:y 10 #:width 16 #:height 16))
(result (push-along-axis #:y a b 6))
(ra (car result))
(rb (cdr result)))
@@ -588,42 +593,42 @@
(test-group "push-apart"
(test-group "x overlap smaller: pushes on x axis"
;; a at (0,0), b at (10,0), both 16x16: ovx=6, ovy=16 → push on x
- (let* ((a '(#:type player #:x 0 #:y 0 #:width 16 #:height 16))
- (b '(#:type player #:x 10 #:y 0 #:width 16 #:height 16))
+ (let* ((a (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16))
+ (b (entity #:type 'player #:x 10 #:y 0 #:width 16 #:height 16))
(result (push-apart a b)))
(test-equal "a pushed left" -3 (entity-ref (car result) #:x 0))
(test-equal "b pushed right" 13 (entity-ref (cdr result) #:x 0))))
(test-group "y overlap smaller: pushes on y axis"
;; a at (0,0), b at (0,10), both 16x16: ovx=16, ovy=6 → push on y
- (let* ((a '(#:type player #:x 0 #:y 0 #:width 16 #:height 16))
- (b '(#:type player #:x 0 #:y 10 #:width 16 #:height 16))
+ (let* ((a (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16))
+ (b (entity #:type 'player #:x 0 #:y 10 #:width 16 #:height 16))
(result (push-apart a b)))
(test-equal "a pushed up" -3 (entity-ref (car result) #:y 0))
(test-equal "b pushed down" 13 (entity-ref (cdr result) #:y 0)))))
(test-group "skip-pipelines"
(test-group "apply-gravity"
- (let* ((e '(#:type t #:vy 0 #:gravity? #t #:skip-pipelines (gravity)))
- (r (apply-gravity e #f 0)))
+ (let* ((e (entity #:type 't #:vy 0 #:gravity? #t #:skip-pipelines '(gravity)))
+ (r (apply-gravity #f e 0)))
(test-equal "skipped: vy unchanged" 0 (entity-ref r #:vy))))
(test-group "apply-velocity-x"
- (let* ((e '(#:type t #:x 10 #:vx 5 #:skip-pipelines (velocity-x)))
- (r (apply-velocity-x e #f 0)))
+ (let* ((e (entity #:type 't #:x 10 #:vx 5 #:skip-pipelines '(velocity-x)))
+ (r (apply-velocity-x #f e 0)))
(test-equal "skipped: x unchanged" 10 (entity-ref r #:x))))
(test-group "resolve-pair with entity-collisions skip"
- (define (make-solid x y) (list #:type 'block #:x x #:y y #:width 16 #:height 16 #:solid? #t))
- (let* ((a (list #:type 'ghost #:x 0 #:y 0 #:width 16 #:height 16 #:solid? #t
- #:skip-pipelines '(entity-collisions)))
+ (define (make-solid x y) (entity #:type 'block #:x x #:y y #:width 16 #:height 16 #:solid? #t))
+ (let* ((a (entity #:type 'ghost #:x 0 #:y 0 #:width 16 #:height 16 #:solid? #t
+ #:skip-pipelines '(entity-collisions)))
(b (make-solid 10 0)))
(test-assert "no resolution" (not (resolve-pair a b))))))
(test-group "resolve-pair"
- (define (make-solid x y) (list #:type 'block #:x x #:y y #:width 16 #:height 16 #:solid? #t))
+ (define (make-solid x y) (entity #:type 'block #:x x #:y y #:width 16 #:height 16 #:solid? #t))
(test-group "one entity not solid: returns #f"
(let ((a (make-solid 0 0))
- (b '(#:type ghost #:x 5 #:y 5 #:width 16 #:height 16)))
+ (b (entity #:type 'ghost #:x 5 #:y 5 #:width 16 #:height 16)))
(test-assert "returns #f" (not (resolve-pair a b)))))
(test-group "no overlap: returns #f"
@@ -641,9 +646,9 @@
(test-group "immovable"
(define (make-static x y)
- (list #:type 'wall #:x x #:y y #:width 16 #:height 16 #:solid? #t #:immovable? #t))
+ (entity #:type 'wall #:x x #:y y #:width 16 #:height 16 #:solid? #t #:immovable? #t))
(define (make-box x y)
- (list #:type 'box #:x x #:y y #:width 16 #:height 16 #:solid? #t))
+ (entity #:type 'box #:x x #:y y #:width 16 #:height 16 #:solid? #t))
(test-group "both immovable and overlapping: #f"
(let* ((a (make-static 0 0))
(b (make-static 8 0)))
diff --git a/tests/prefabs-test.scm b/tests/prefabs-test.scm
index 3578b35..ab7b5f1 100644
--- a/tests/prefabs-test.scm
+++ b/tests/prefabs-test.scm
@@ -67,7 +67,7 @@
;; Inline #:vx 99 beats mixin #:vx 5
(let ((e (instantiate-prefab reg 'runner 0 0 16 16)))
(pp e)
- (test-equal "entity should have squashed properties" 14 (length e))
+ (test-equal "entity should have squashed properties" 7 (length e))
(test-equal "inline field beats mixin field for same key"
99
(entity-ref e #:vx))
@@ -143,9 +143,8 @@
;; (Data files only contain symbols; this tests the procedure? branch directly.)
(let* ((hook-proc (lambda (e) (entity-set e #:proc-fired #t)))
(reg (make-prefab-registry
- prefabs: (list (cons 'proc-hooked
- (list #:type 'proc-hooked
- #:on-instantiate hook-proc)))
+ prefabs: `((proc-hooked . ((#:type . proc-hooked)
+ (#:on-instantiate . ,hook-proc))))
group-prefabs: '()
file: "/dev/null"
engine-mixin-table: '()
diff --git a/tests/renderer-test.scm b/tests/renderer-test.scm
index 2829348..9f6ce55 100644
--- a/tests/renderer-test.scm
+++ b/tests/renderer-test.scm
@@ -42,6 +42,11 @@
(include "entity.scm")
(import downstroke-entity)
+(import (only (list-utils alist) plist->alist))
+
+;; Test helper: build an alist entity from plist-style keyword args.
+(define (entity . kws) (plist->alist kws))
+
;; Load world module
(include "world.scm")
(import downstroke-world)
@@ -54,7 +59,7 @@
(test-group "entity-screen-coords"
(let* ((cam (make-camera x: 10 y: 20))
- (e (list #:x 50 #:y 80 #:width 16 #:height 16)))
+ (e (entity #:x 50 #:y 80 #:width 16 #:height 16)))
(test-equal "subtracts camera offset from x"
40
(car (entity-screen-coords e cam)))
@@ -69,7 +74,7 @@
(cadddr (entity-screen-coords e cam))))
(let* ((cam (make-camera x: 0 y: 0))
- (e (list #:x 100.7 #:y 200.3 #:width 16 #:height 16)))
+ (e (entity #:x 100.7 #:y 200.3 #:width 16 #:height 16)))
(test-equal "floors fractional x"
100
(car (entity-screen-coords e cam)))
@@ -78,7 +83,7 @@
(cadr (entity-screen-coords e cam))))
(let* ((cam (make-camera x: 0 y: 0))
- (e (list #:x 0 #:y 0 #:width 32 #:height 32)))
+ (e (entity #:x 0 #:y 0 #:width 32 #:height 32)))
(test-equal "zero camera, zero position"
'(0 0 32 32)
(entity-screen-coords e cam))))
@@ -86,13 +91,13 @@
(test-group "entity-flip"
(test-equal "facing 1: no flip"
'()
- (entity-flip (list #:facing 1)))
+ (entity-flip (entity #:facing 1)))
(test-equal "facing -1: horizontal flip"
'(horizontal)
- (entity-flip (list #:facing -1)))
+ (entity-flip (entity #:facing -1)))
(test-equal "no facing key: defaults to no flip"
'()
- (entity-flip (list #:x 0))))
+ (entity-flip (entity #:x 0))))
(test-group "render-scene!"
(let* ((cam (make-camera x: 0 y: 0))
@@ -116,7 +121,7 @@
(begin (render-scene! #f scene) #t)))
(let* ((cam (make-camera x: 0 y: 0))
- (box (list #:x 4 #:y 8 #:width 10 #:height 12 #:color '(200 40 90)))
+ (box (entity #:x 4 #:y 8 #:width 10 #:height 12 #:color '(200 40 90)))
(scene (make-scene entities: (list box)
tilemap: #f
camera: cam
@@ -243,8 +248,8 @@
tileset: tileset
layers: (list layer)
objects: '()))
- (player (list #:type 'player #:x 10 #:y 20 #:width 16 #:height 16 #:facing 1))
- (enemy (list #:type 'enemy #:x 50 #:y 60 #:width 16 #:height 16 #:facing -1))
+ (player (entity #:type 'player #:x 10 #:y 20 #:width 16 #:height 16 #:facing 1))
+ (enemy (entity #:type 'enemy #:x 50 #:y 60 #:width 16 #:height 16 #:facing -1))
(scene (make-scene entities: (list player enemy)
tilemap: tilemap
camera: cam
@@ -267,7 +272,7 @@
tileset: tileset
layers: (list layer)
objects: '()))
- (player (list #:type 'player #:x 10 #:y 20 #:width 16 #:height 16 #:facing 1))
+ (player (entity #:type 'player #:x 10 #:y 20 #:width 16 #:height 16 #:facing 1))
(scene (make-scene entities: (list player)
tilemap: tilemap
camera: cam
@@ -287,9 +292,9 @@
tileset-source: "" tileset: tileset
layers: (list layer) objects: '()))
(tex 'mock-texture)
- (entity (list #:type 'box #:x 10 #:y 20 #:width 16 #:height 16 #:tile-id 1))
- (cell (vector entity 'extra-data 0 100 'linear #t))
- (scene-ok (make-scene entities: (list entity)
+ (ent (entity #:type 'box #:x 10 #:y 20 #:width 16 #:height 16 #:tile-id 1))
+ (cell (vector ent 'extra-data 0 100 'linear #t))
+ (scene-ok (make-scene entities: (list ent)
tilemap: tilemap camera: cam
tileset-texture: tex camera-target: #f))
(scene-bad (make-scene entities: (list cell)
diff --git a/tests/scene-loader-test.scm b/tests/scene-loader-test.scm
index 5c85ede..88fb544 100644
--- a/tests/scene-loader-test.scm
+++ b/tests/scene-loader-test.scm
@@ -20,25 +20,15 @@
(define (load-tileset filename) (make-tileset tilewidth: 16 tileheight: 16 spacing: 0 tilecount: 256 columns: 16 image-source: "" image: #f)))
(import downstroke-tilemap)
-;; Mock entity module (minimal)
-(module downstroke-entity *
- (import scheme (chicken base))
- (define (entity-ref entity key #!optional (default #f))
- (let loop ((plist entity))
- (cond
- ((null? plist) (if (procedure? default) (default) default))
- ((eq? (car plist) key) (cadr plist))
- (else (loop (cddr plist))))))
- (define (entity-set entity key val)
- (let loop ((plist entity) (acc '()))
- (cond
- ((null? plist) (reverse (cons val (cons key acc))))
- ((eq? (car plist) key) (append (reverse acc) (cons key (cons val (cddr plist)))))
- (else (loop (cddr plist) (cons (cadr plist) (cons (car plist) acc)))))))
- (define (entity-type entity)
- (entity-ref entity #:type #f)))
+;; Load the real entity module (alist-based)
+(include "entity.scm")
(import downstroke-entity)
+(import (only (list-utils alist) plist->alist))
+
+;; Test helper: build an alist entity from plist-style keyword args.
+(define (entity . kws) (plist->alist kws))
+
;; Mock world module
(module downstroke-world *
(import scheme (chicken base) defstruct)
@@ -100,8 +90,8 @@
objects: (list obj1 obj2 obj3)))
;; mock registry: alist of (type . constructor)
(registry
- (list (cons 'player (lambda (x y w h) (list #:type 'player #:x x #:y y #:width w #:height h)))
- (cons 'enemy (lambda (x y w h) (list #:type 'enemy #:x x #:y y #:width w #:height h)))))
+ (list (cons 'player (lambda (x y w h) (entity #:type 'player #:x x #:y y #:width w #:height h)))
+ (cons 'enemy (lambda (x y w h) (entity #:type 'enemy #:x x #:y y #:width w #:height h)))))
(result (tilemap-objects->entities tm registry)))
(test-equal "filters #f results: 2 entities from 3 objects"
2 (length result))
diff --git a/tests/tween-test.scm b/tests/tween-test.scm
index f0622fb..962d325 100644
--- a/tests/tween-test.scm
+++ b/tests/tween-test.scm
@@ -4,6 +4,11 @@
(include "tween.scm")
(import downstroke-entity downstroke-tween)
+(import (only (list-utils alist) plist->alist))
+
+;; Test helper: build an alist entity from plist-style keyword args.
+(define (entity . kws) (plist->alist kws))
+
(test-begin "tween")
(test-group "ease functions"
@@ -35,7 +40,7 @@
(test-group "make-tween / tween-step"
(test-group "linear completes to target"
- (let* ((ent (list #:type 'a #:x 0 #:y 10))
+ (let* ((ent (entity #:type 'a #:x 0 #:y 10))
(tw (make-tween ent props: '((#:x . 100)) duration: 100 delay: 0 ease: 'linear)))
(receive (tw2 e2) (tween-step tw ent 100)
(test-assert "finished" (tween-finished? tw2))
@@ -43,7 +48,7 @@
(test-equal "y preserved" 10 (entity-ref e2 #:y)))))
(test-group "delay holds props"
- (let* ((ent (list #:type 'a #:x 0))
+ (let* ((ent (entity #:type 'a #:x 0))
(tw (make-tween ent props: '((#:x . 50)) duration: 100 delay: 40 ease: 'linear)))
(receive (tw2 e2) (tween-step tw ent 30)
(test-assert "not finished" (not (tween-finished? tw2)))
@@ -55,14 +60,14 @@
(test-assert "past delay, moved" (> (entity-ref e4 #:x) 0)))))))
(test-group "midpoint linear"
- (let* ((ent (list #:type 'a #:x 0))
+ (let* ((ent (entity #:type 'a #:x 0))
(tw (make-tween ent props: '((#:x . 100)) duration: 100 delay: 0 ease: 'linear)))
(receive (_ e2) (tween-step tw ent 50)
(test-equal "halfway x" 50.0 (entity-ref e2 #:x)))))
(test-group "on-complete runs once"
(let ((calls 0))
- (let* ((ent (list #:type 'a #:x 0))
+ (let* ((ent (entity #:type 'a #:x 0))
(tw (make-tween ent props: '((#:x . 10)) duration: 10 delay: 0 ease: 'linear
on-complete: (lambda (_) (set! calls (+ calls 1))))))
(receive (tw2 e2) (tween-step tw ent 10)
@@ -72,7 +77,7 @@
(test-equal "entity stable" e3 e2))))))
(test-group "idempotent after finish"
- (let* ((ent (list #:type 'a #:x 0))
+ (let* ((ent (entity #:type 'a #:x 0))
(tw (make-tween ent props: '((#:x . 20)) duration: 10 delay: 0 ease: 'linear)))
(receive (tw2 e2) (tween-step tw ent 10)
(receive (tw3 e3) (tween-step tw2 e2 999)
@@ -81,7 +86,7 @@
(test-group "repeat"
(test-group "repeat: 1 plays twice"
- (let* ((ent (list #:type 'a #:x 0))
+ (let* ((ent (entity #:type 'a #:x 0))
(tw (make-tween ent props: '((#:x . 100)) duration: 100
ease: 'linear repeat: 1)))
(receive (tw2 e2) (tween-step tw ent 100)
@@ -92,7 +97,7 @@
(test-equal "x at target again" 100.0 (entity-ref e3 #:x))))))
(test-group "repeat: -1 never finishes"
- (let* ((ent (list #:type 'a #:x 0))
+ (let* ((ent (entity #:type 'a #:x 0))
(tw (make-tween ent props: '((#:x . 10)) duration: 10
ease: 'linear repeat: -1)))
(let loop ((tw tw) (ent ent) (i 0))
@@ -102,14 +107,14 @@
(loop tw2 e2 (+ i 1)))))))
(test-group "repeat: 0 is default (no repeat)"
- (let* ((ent (list #:type 'a #:x 0))
+ (let* ((ent (entity #:type 'a #:x 0))
(tw (make-tween ent props: '((#:x . 50)) duration: 50 ease: 'linear)))
(receive (tw2 _e2) (tween-step tw ent 50)
(test-assert "finished immediately" (tween-finished? tw2)))))
(test-group "on-complete fires after last repeat"
(let ((calls 0))
- (let* ((ent (list #:type 'a #:x 0))
+ (let* ((ent (entity #:type 'a #:x 0))
(tw (make-tween ent props: '((#:x . 10)) duration: 10
ease: 'linear repeat: 1
on-complete: (lambda (_) (set! calls (+ calls 1))))))
@@ -120,7 +125,7 @@
(test-group "on-complete does not fire with repeat: -1"
(let ((calls 0))
- (let* ((ent (list #:type 'a #:x 0))
+ (let* ((ent (entity #:type 'a #:x 0))
(tw (make-tween ent props: '((#:x . 10)) duration: 10
ease: 'linear repeat: -1
on-complete: (lambda (_) (set! calls (+ calls 1))))))
@@ -131,7 +136,7 @@
(test-group "yoyo"
(test-group "yoyo: #t with repeat: 1 reverses"
- (let* ((ent (list #:type 'a #:x 0))
+ (let* ((ent (entity #:type 'a #:x 0))
(tw (make-tween ent props: '((#:x . 100)) duration: 100
ease: 'linear repeat: 1 yoyo?: #t)))
(receive (tw2 e2) (tween-step tw ent 100)
@@ -143,7 +148,7 @@
(test-equal "x back to start" 0.0 (entity-ref e4 #:x)))))))
(test-group "yoyo: #t with repeat: -1 ping-pongs forever"
- (let* ((ent (list #:type 'a #:x 0))
+ (let* ((ent (entity #:type 'a #:x 0))
(tw (make-tween ent props: '((#:x . 100)) duration: 100
ease: 'linear repeat: -1 yoyo?: #t)))
;; Forward
@@ -158,7 +163,7 @@
(test-assert "still active" (tween-active? tw4)))))))
(test-group "yoyo: #f with repeat: 1 replays same direction"
- (let* ((ent (list #:type 'a #:x 0))
+ (let* ((ent (entity #:type 'a #:x 0))
(tw (make-tween ent props: '((#:x . 100)) duration: 100
ease: 'linear repeat: 1 yoyo?: #f)))
(receive (tw2 e2) (tween-step tw ent 100)
@@ -169,7 +174,7 @@
(test-assert "not finished mid-repeat" (not (tween-finished? tw3)))))))
(test-group "yoyo: #t without repeat has no effect"
- (let* ((ent (list #:type 'a #:x 0))
+ (let* ((ent (entity #:type 'a #:x 0))
(tw (make-tween ent props: '((#:x . 100)) duration: 100
ease: 'linear repeat: 0 yoyo?: #t)))
(receive (tw2 e2) (tween-step tw ent 100)
@@ -178,40 +183,40 @@
(test-group "step-tweens pipeline"
(test-group "advances #:tween on entity"
- (let* ((ent (list #:type 'a #:x 0
- #:tween (make-tween (list #:x 0) props: '((#:x . 100))
- duration: 100 ease: 'linear)))
- (e2 (step-tweens ent #f 50)))
+ (let* ((ent (entity #:type 'a #:x 0
+ #:tween (make-tween (entity #:x 0) props: '((#:x . 100))
+ duration: 100 ease: 'linear)))
+ (e2 (step-tweens #f ent 50)))
(test-equal "x moved to midpoint" 50.0 (entity-ref e2 #:x))
(test-assert "tween still attached" (entity-ref e2 #:tween #f))))
(test-group "removes #:tween when finished"
- (let* ((ent (list #:type 'a #:x 0
- #:tween (make-tween (list #:x 0) props: '((#:x . 100))
- duration: 100 ease: 'linear)))
- (e2 (step-tweens ent #f 100)))
+ (let* ((ent (entity #:type 'a #:x 0
+ #:tween (make-tween (entity #:x 0) props: '((#:x . 100))
+ duration: 100 ease: 'linear)))
+ (e2 (step-tweens #f ent 100)))
(test-equal "x at target" 100.0 (entity-ref e2 #:x))
(test-equal "tween removed" #f (entity-ref e2 #:tween #f))))
(test-group "no-op without #:tween"
- (let* ((ent (list #:type 'a #:x 42))
- (e2 (step-tweens ent #f 100)))
+ (let* ((ent (entity #:type 'a #:x 42))
+ (e2 (step-tweens #f ent 100)))
(test-equal "x unchanged" 42 (entity-ref e2 #:x))))
(test-group "keeps repeating tween attached"
- (let* ((ent (list #:type 'a #:x 0
- #:tween (make-tween (list #:x 0) props: '((#:x . 100))
- duration: 100 ease: 'linear repeat: -1 yoyo?: #t)))
- (e2 (step-tweens ent #f 100)))
+ (let* ((ent (entity #:type 'a #:x 0
+ #:tween (make-tween (entity #:x 0) props: '((#:x . 100))
+ duration: 100 ease: 'linear repeat: -1 yoyo?: #t)))
+ (e2 (step-tweens #f ent 100)))
(test-equal "x at target" 100.0 (entity-ref e2 #:x))
(test-assert "tween still attached (repeating)" (entity-ref e2 #:tween #f))))
(test-group "respects #:skip-pipelines"
- (let* ((ent (list #:type 'a #:x 0
- #:skip-pipelines '(tweens)
- #:tween (make-tween (list #:x 0) props: '((#:x . 100))
- duration: 100 ease: 'linear)))
- (e2 (step-tweens ent #f 100)))
+ (let* ((ent (entity #:type 'a #:x 0
+ #:skip-pipelines '(tweens)
+ #:tween (make-tween (entity #:x 0) props: '((#:x . 100))
+ duration: 100 ease: 'linear)))
+ (e2 (step-tweens #f ent 100)))
(test-equal "x unchanged (skipped)" 0 (entity-ref e2 #:x))
(test-assert "tween still there" (entity-ref e2 #:tween #f)))))
diff --git a/tests/world-test.scm b/tests/world-test.scm
index bfbb336..a66103e 100644
--- a/tests/world-test.scm
+++ b/tests/world-test.scm
@@ -41,6 +41,11 @@
(include "entity.scm")
(import downstroke-entity)
+(import (only (list-utils alist) plist->alist))
+
+;; Test helper: build an alist entity from plist-style keyword args.
+(define (entity . kws) (plist->alist kws))
+
;; Load the module source directly
(include "world.scm")
;; Now import it to access the exported functions
@@ -111,7 +116,7 @@
;; Test: scene with entities and tilemap
(test-group "scene-with-data"
(let* ((player (entity-set (make-entity 100 100 16 16) #:type 'player))
- (enemy '(#:type enemy #:x 200 #:y 200))
+ (enemy (entity #:type 'enemy #:x 200 #:y 200))
(tilemap "mock-tilemap")
(scene (make-scene entities: (list player enemy)
tilemap: tilemap
@@ -130,7 +135,7 @@
(test-group "scene-add-entity"
(let* ((player (make-entity 100 100 16 16))
(scene (make-scene entities: (list player) tilemap: #f camera-target: #f))
- (enemy '(#:type enemy #:x 200 #:y 200)))
+ (enemy (entity #:type 'enemy #:x 200 #:y 200)))
(test-equal "initial entity count" 1 (length (scene-entities scene)))
@@ -143,9 +148,9 @@
;; Test: scene-add-entity appends to end
(test-group "scene-add-entity-order"
- (let* ((e1 '(#:type a #:x 1))
- (e2 '(#:type b #:x 2))
- (e3 '(#:type c #:x 3))
+ (let* ((e1 (entity #:type 'a #:x 1))
+ (e2 (entity #:type 'b #:x 2))
+ (e3 (entity #:type 'c #:x 3))
(scene (make-scene entities: (list e1) tilemap: #f camera-target: #f))
(scene (scene-add-entity scene e2))
(scene (scene-add-entity scene e3)))
@@ -156,14 +161,14 @@
;; Test: scene-map-entities applies function to all entities
(test-group "scene-map-entities"
- (let* ((e1 '(#:type player #:x 100 #:y 100))
- (e2 '(#:type enemy #:x 200 #:y 200))
+ (let* ((e1 (entity #:type 'player #:x 100 #:y 100))
+ (e2 (entity #:type 'enemy #:x 200 #:y 200))
(scene (make-scene entities: (list e1 e2) tilemap: #f camera-target: #f))
- (move-right (lambda (scene entity)
- (let ((x (entity-ref entity #:x))
- (y (entity-ref entity #:y))
- (type (entity-ref entity #:type)))
- (list #:type type #:x (+ x 10) #:y y))))
+ (move-right (lambda (scene ent)
+ (let ((x (entity-ref ent #:x))
+ (y (entity-ref ent #:y))
+ (type (entity-ref ent #:type)))
+ (entity #:type type #:x (+ x 10) #:y y))))
(scene2 (scene-map-entities scene move-right)))
(test-equal "original scene unchanged"
@@ -181,8 +186,8 @@
;; Test: scene-map-entities with identity function
(test-group "scene-map-entities-identity"
- (let* ((e1 '(#:type player #:x 100))
- (e2 '(#:type enemy #:x 200))
+ (let* ((e1 (entity #:type 'player #:x 100))
+ (e2 (entity #:type 'enemy #:x 200))
(scene (make-scene entities: (list e1 e2) tilemap: #f camera-target: #f))
(scene2 (scene-map-entities scene (lambda (scene e) e))))
@@ -204,8 +209,8 @@
(let ((x (entity-ref e #:x))
(y (entity-ref e #:y))
(type (entity-type e)))
- (list #:type type #:x (* x 2) #:y (* y 2)
- #:width 16 #:height 16))))))
+ (entity #:type type #:x (* x 2) #:y (* y 2)
+ #:width 16 #:height 16))))))
(test-equal "entity x doubled" 20 (entity-ref (car (scene-entities scene)) #:x))
(test-equal "entity y doubled" 40 (entity-ref (car (scene-entities scene)) #:y)))))
@@ -227,8 +232,8 @@
;; Test: scene-filter-entities
(test-group "scene-filter-entities"
- (let* ((e1 (list #:type 'player #:x 0 #:y 0 #:width 16 #:height 16))
- (e2 (list #:type 'enemy #:x 0 #:y 0 #:width 16 #:height 16))
+ (let* ((e1 (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16))
+ (e2 (entity #:type 'enemy #:x 0 #:y 0 #:width 16 #:height 16))
(scene (make-scene entities: (list e1 e2)
tilemap: test-tilemap
camera: (make-camera x: 0 y: 0)
@@ -244,20 +249,20 @@
(test-group "camera-follow"
(let* ((cam (make-camera x: 0 y: 0))
- (entity (list #:type 'player #:x 400 #:y 300 #:width 16 #:height 16))
- (cam2 (camera-follow cam entity 600 400)))
+ (ent (entity #:type 'player #:x 400 #:y 300 #:width 16 #:height 16))
+ (cam2 (camera-follow cam ent 600 400)))
(test-equal "original camera unchanged" 0 (camera-x cam))
(test-equal "centers camera x on entity" 100 (camera-x cam2))
(test-equal "centers camera y on entity" 100 (camera-y cam2)))
(let* ((cam (make-camera x: 0 y: 0))
- (entity (list #:type 'player #:x 50 #:y 30 #:width 16 #:height 16))
- (cam2 (camera-follow cam entity 600 400)))
+ (ent (entity #:type 'player #:x 50 #:y 30 #:width 16 #:height 16))
+ (cam2 (camera-follow cam ent 600 400)))
(test-equal "clamps camera x to 0 when entity near origin" 0 (camera-x cam2))
(test-equal "clamps camera y to 0 when entity near origin" 0 (camera-y cam2))))
(test-group "scene-find-tagged"
- (let* ((p (list #:type 'player #:x 0 #:y 0 #:width 16 #:height 16 #:tags '(player)))
- (e (list #:type 'enemy #:x 0 #:y 0 #:width 16 #:height 16 #:tags '(enemy npc)))
+ (let* ((p (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16 #:tags '(player)))
+ (e (entity #:type 'enemy #:x 0 #:y 0 #:width 16 #:height 16 #:tags '(enemy npc)))
(s (make-scene entities: (list p e) tilemap: #f
camera: (make-camera x: 0 y: 0) tileset-texture: #f camera-target: #f)))
(test-equal "finds entity with matching tag" p (scene-find-tagged s 'player))
@@ -266,9 +271,9 @@
(test-equal "returns #f when tag not found" #f (scene-find-tagged s 'boss))))
(test-group "scene-find-all-tagged"
- (let* ((p1 (list #:type 'player #:x 0 #:y 0 #:width 16 #:height 16 #:tags '(player friendly)))
- (p2 (list #:type 'ally #:x 0 #:y 0 #:width 16 #:height 16 #:tags '(ally friendly)))
- (e (list #:type 'enemy #:x 0 #:y 0 #:width 16 #:height 16 #:tags '(enemy)))
+ (let* ((p1 (entity #:type 'player #:x 0 #:y 0 #:width 16 #:height 16 #:tags '(player friendly)))
+ (p2 (entity #:type 'ally #:x 0 #:y 0 #:width 16 #:height 16 #:tags '(ally friendly)))
+ (e (entity #:type 'enemy #:x 0 #:y 0 #:width 16 #:height 16 #:tags '(enemy)))
(s (make-scene entities: (list p1 p2 e) tilemap: #f
camera: (make-camera x: 0 y: 0) tileset-texture: #f camera-target: #f)))
(test-equal "returns all friendly entities" 2 (length (scene-find-all-tagged s 'friendly)))
@@ -276,12 +281,12 @@
(test-group "sync-groups"
(let* ((gid 'g1)
- (origin (list #:type 'group-origin #:group-origin? #t #:group-id gid
- #:x 100 #:y 200 #:width 0 #:height 0))
- (m1 (list #:type 'part #:group-id gid #:group-local-x 5 #:group-local-y 0
- #:x 0 #:y 0 #:width 8 #:height 8))
- (m2 (list #:type 'part #:group-id gid #:group-local-x 0 #:group-local-y 7
- #:x 0 #:y 0 #:width 8 #:height 8))
+ (origin (entity #:type 'group-origin #:group-origin? #t #:group-id gid
+ #:x 100 #:y 200 #:width 0 #:height 0))
+ (m1 (entity #:type 'part #:group-id gid #:group-local-x 5 #:group-local-y 0
+ #:x 0 #:y 0 #:width 8 #:height 8))
+ (m2 (entity #:type 'part #:group-id gid #:group-local-x 0 #:group-local-y 7
+ #:x 0 #:y 0 #:width 8 #:height 8))
(entities (list origin m1 m2))
(result (sync-groups entities)))
(test-equal "original list unchanged" 0 (entity-ref (list-ref entities 1) #:x))
@@ -291,8 +296,8 @@
(test-equal "member 2 y" 207 (entity-ref (list-ref result 2) #:y))))
(test-group "scene-transform-entities"
- (let* ((e1 '(#:type a #:x 1))
- (e2 '(#:type b #:x 2))
+ (let* ((e1 (entity #:type 'a #:x 1))
+ (e2 (entity #:type 'b #:x 2))
(scene (make-scene entities: (list e1 e2) tilemap: #f camera-target: #f))
(scene2 (scene-transform-entities scene reverse)))
(test-equal "transforms entity list" 'b