aboutsummaryrefslogtreecommitdiff
path: root/README.org
diff options
context:
space:
mode:
authorGene Pasquet <dev@etenil.net>2026-04-05 14:17:51 +0100
committerGene Pasquet <dev@etenil.net>2026-04-05 14:17:51 +0100
commit526e6cdcdf1025d5e29680bc99ab910c79789764 (patch)
tree2a91b3e96f2b97cfc81169627f222a5393982830 /README.org
Initial port of macroknight to an engine
Diffstat (limited to 'README.org')
-rw-r--r--README.org125
1 files changed, 125 insertions, 0 deletions
diff --git a/README.org b/README.org
new file mode 100644
index 0000000..45db408
--- /dev/null
+++ b/README.org
@@ -0,0 +1,125 @@
+* 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
+- FSM-based enemy AI via the =states= egg
+- 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 (v0.1.0) | 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 states \
+ srfi-197 simple-logger srfi-64
+#+end_example
+
+** 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
+
+** 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, ai → 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 |
+| =ai= | FSM enemy AI: idle → patrol → chase → attack |
+| =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
+
+** 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)