* Downstroke A 2D tile-driven game engine for Chicken Scheme, built on SDL2. Targets old-school platformer and arcade games — NES-style and beyond. The API is inspired by Phaser 2: one call to start, lifecycle hooks to fill in. A minimal game is ~20 lines of Scheme. #+begin_src scheme (define my-game (make-game title: "My Game" width: 320 height: 240 preload: (lambda (game) ...) ; load assets create: (lambda (game) ...) ; set up scene update: (lambda (game dt) ...))) ; per-frame logic (physics runs first) (game-run! my-game) #+end_src ** Features - Tile-based physics: gravity, velocity, AABB collision against TMX tilemaps - Built-in update pipeline: input → acceleration → gravity → x-collision → y-collision → ground detection → entity collisions - Entities as plists — purely functional, no classes - Data-driven prefab/mixin system for composing entity types - Configurable input: keyboard, joystick, and controller - Asset registry with =preload:= lifecycle hook - Scene and camera management - Sprite animation with frame/tick tracking - SDL2_mixer audio via a thin FFI binding ** Status Early extraction phase. The engine logic is fully working — it powers [[https://genepasquet.itch.io/macroknight][macroknight]], built for Spring Lisp Game Jam 2025. Work is underway to extract it into a standalone, installable Chicken egg. | Milestone | Description | Status | |-----------+------------------------------------+----------| | 1 | Zero-risk module extraction | DONE | | 2 | Configurable input system | pending | | 3 | Data-driven entity rendering | pending | | 4 | Renderer abstraction | pending | | 5 | Asset preloading | pending | | 6 | Scene loading via =create:= hook | pending | | 7 | =make-game= / =game-run!= API | pending | | 8 | Camera follow | pending | | 9 | Named scene states | pending | | 10 | AI tag-based lookup | pending | | 11 | Package as Chicken egg (v1.0.0rc1) | pending | | 12 | Macroknight ported to use the egg | pending | Milestones 1–6 are pure refactoring. Milestone 7 is the design pivot where the public API stabilises. Milestones 11–12 produce the first installable egg and validate it against macroknight. ** Dependencies System libraries: =SDL2=, =SDL2_image=, =SDL2_mixer=, =SDL2_ttf= Chicken eggs: #+begin_example chicken-install sdl2 sdl2-image expat matchable defstruct \ srfi-197 simple-logger srfi-64 #+end_example Games that ship their own FSM logic (for example with the =states= egg) declare that egg themselves; Downstroke does not depend on it. ** Building #+begin_src sh make # compile all modules to bin/ make test # run SRFI-64 test suites make demos # build demo games (verifies they compile) make clean # remove bin/ #+end_src Single test module: #+begin_src sh csi -s tests/physics-test.scm #+end_src The canonical version string is =VERSION= at the top of the =Makefile=; it must match =((version "…")= in =downstroke.egg= and the =v…= marker in the milestone table. Bump all three with =make bump-version NEW=1.2.3= (POSIX =sed=; =NEW= must not contain =&=, backslash, or =#=). ** Architecture Modules live at the project root. Each compiles as a Chicken unit (=csc -c -J -unit=). Compile order follows the dependency graph: : entity, tilemap → world → animation, physics → input → prefabs → mixer → sound → engine | Module | Responsibility | |----------------+---------------------------------------------------| | =entity= | Plist accessors: =entity-ref=, =entity-set= | | =tilemap= | TMX/TSX parsing (expat), tileset loading | | =world= | Scene struct, entity list ops, camera | | =physics= | Gravity, velocity, AABB tile + entity collisions | | =input= | SDL2 events → action mapping, configurable binds | | =animation= | Frame/tick tracking, sprite ID mapping | | =prefabs= | Mixin composition, entity instantiation | | =renderer= | =draw-sprite=, =draw-tilemap-layer=, =draw-text= | | =assets= | Asset registry for the =preload:= hook | | =scene-loader= | =game-load-scene!=, =instantiate-prefab= | | =mixer= | SDL_mixer FFI (no Scheme deps) | | =sound= | Sound registry, music playback | | =engine= | =make-game=, =game-run!=, lifecycle orchestration | Entities are plists — no classes, purely functional: #+begin_src scheme (list #:type 'player #:x 100 #:y 200 #:width 16 #:height 16 #:vx 0 #:vy 0 #:gravity? #t #:on-ground? #f #:tile-id 29 #:tags '(player) #:anim-name 'walk #:animations '((idle #:frames (28) #:duration 10) (walk #:frames (27 28) #:duration 10))) #+end_src Entity types are composed from mixins declared in a data file: #+begin_src scheme (mixins (physics-body #:vx 0 #:vy 0 #:gravity? #t #:on-ground? #f) (has-facing #:facing 1)) (prefabs (player physics-body has-facing #:type player #:tile-id 29)) #+end_src ** LLM (AI) disclosure Some engine code was written or refactored with an LLM (aka AI) prior to the initial release. I don't intend to keep using AI for engine code after that. The =docs/= tree is LLM-generated, and that is expected to continue. ** Credits Engine extracted from [[https://genepasquet.itch.io/macroknight][macroknight]] (Spring Lisp Game Jam 2025). Code: Gene Pasquet — Levels: Owen Pasquet — Art: [[https://kenney.nl][Kenney]] (1-bit pack)