1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
|
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this is
**Downstroke** is a 2D tile-driven game engine for Chicken Scheme, built on SDL2. API inspired by Phaser 2: a minimal game is ~20 lines of Scheme.
The engine is being extracted from the testbed game **macroknight** (`/home/gene/src/macroknight`). Milestones 1–6 are pure refactoring (no behavior changes); Milestone 7 is the design pivot where the public API stabilizes.
**Detailed extraction plan**: `/home/gene/src/macroknight/TODO-engine.org`
**Project milestones**: `/home/gene/Documents/Perso/Projects/downstroke.org` (also in README.org)
## Target API
```scheme
(define my-game
(make-game
title: "My Game" width: 600 height: 400
preload: (lambda (game) ...) ; load assets
create: (lambda (game) ...) ; init scene
update: (lambda (game dt) ...))) ; game-specific logic (physics runs first)
(game-run! my-game)
```
Built-in physics pipeline (runs before user `update:` hook):
```
input → acceleration → gravity → velocity-x → tile-collision-x →
velocity-y → tile-collision-y → ground-detection → entity-collisions
```
## Build & Test (macroknight — source of truth)
All engine code currently lives in `/home/gene/src/macroknight`. Until Milestone 1 is complete, build and test from there.
```bash
cd /home/gene/src/macroknight
make # compile all modules + link bin/game
make test # run all 8 SRFI-64 test suites via csi
make clean # remove bin/ and .import.scm files
# Run a single test module:
csi -s tests/physics-test.scm
csi -s tests/entity-test.scm
# etc.
```
Once extraction begins, the downstroke Makefile must also build all demos:
```bash
make # compile engine + all demos in demo/
make test # run all SRFI-64 test suites
make demos # build demo games only (verify they compile)
```
**Module compile order** (dependency order, must be respected in Makefile):
`tilemap → entity → world → animation → physics → ai → input → prefabs → mixer → sound`
Modules are compiled as **units** (`csc -c -J -unit $*`) to avoid C toplevel name collisions when linking multiple `.o` files. Each module generates both a `.o` and a `.import.scm` in `bin/`.
## Test-Driven Development
**Tests are mandatory for all engine code.** Write tests before or alongside implementation — never after. The test suite is the primary correctness guarantee for the engine, since behavior regressions are easy to introduce during extraction.
- Test files live in `tests/`, named `<module>-test.scm`
- Framework: SRFI-64 (`test-begin`, `test-equal`, `test-assert`, `test-end`)
- Tests run via `csi -s` (interpreter, not compiled) and must not require SDL2 — mock or stub any SDL2-dependent code
- Each engine module must have a corresponding test module before the module is considered done
## Documentation
End-user documentation lives in `docs/` as **org-mode files** and must be kept up to date as the API evolves. This is not optional — docs ship with the egg.
- `docs/api.org` — public API reference (`make-game`, `game-run!`, all accessors and hooks)
- `docs/guide.org` — getting started guide with the minimal ~20-line game example
- `docs/entities.org` — entity model, plist keys, prefab/mixin system
- `docs/physics.org` — physics pipeline, collision model, gravity/velocity API
When adding or changing any public-facing function or keyword argument, update the relevant doc file in the same commit.
## Demo Games
`demo/` contains small self-contained example games that exercise the engine API. They serve as living documentation and integration tests.
- Each demo is a single `.scm` file (plus any assets in `demo/<name>/`)
- The Makefile must build all demos as part of `make` or `make demos` — a demo that fails to compile is a build failure
- Demos should be minimal: one mechanic per demo (gravity+jump, tilemap rendering, animation, etc.)
- Do not add game-specific logic to the engine to make a demo work; if a demo needs something, it belongs in the engine's public API
## Engine Module Architecture
| Module | File | Responsibility |
|---|---|---|
| `engine` | engine.scm | `make-game`, `game-run!`, lifecycle orchestration |
| `world` | world.scm | Scene struct, entity list ops, camera |
| `entity` | entity.scm | Entity plist accessors (`entity-ref`, `entity-set`, `entity-type`) |
| `physics` | physics.scm | Gravity, velocity, AABB tile + entity collisions, ground detection |
| `tilemap` | tilemap.scm | TMX/TSX XML parsing (expat), tileset loading, tile rect calculations |
| `input` | input.scm | SDL2 event → action mapping, keyboard/joystick/controller |
| `animation` | animation.scm | Frame/tick tracking, sprite ID mapping, animation state machine |
| `prefabs` | prefabs.scm | Mixin composition, prefab data loading, entity instantiation + hooks |
| `ai` | ai.scm | FSM-based enemy AI (idle/patrol/chase) via `states` egg |
| `renderer` | renderer.scm | SDL2 drawing abstraction: `draw-sprite`, `draw-tilemap-layer`, `draw-text` |
| `assets` | assets.scm | Asset registry for `preload:` lifecycle hook |
| `scene-loader` | scene-loader.scm | `game-load-scene!`, `instantiate-prefab` |
| `sound` | sound.scm | Sound registry, music playback |
| `mixer` | mixer.scm | SDL_mixer FFI bindings (no Scheme dependencies) |
## Entity Model
Entities are **plists** (property lists) — no classes, pure data + functions:
```scheme
(list #:type 'player
#:x 100 #:y 200
#:width 16 #:height 16
#:vx 0 #:vy 0
#:gravity? #t
#:on-ground? #f
#:tile-id 29 ; sprite index in tileset
#:tags '(player)
#:anim-name 'walk
#:animations ((idle #:frames (28) #:duration 10)
(walk #:frames (27 28) #:duration 10)))
```
Key shared entity keys: `#:type`, `#:x`, `#:y`, `#:width`, `#:height`, `#:vx`, `#:vy`, `#:tile-id`, `#:tags`.
Access via `entity-ref`, `entity-set` (returns new plist — functional/immutable), `entity-type`.
## Scene & Camera
```scheme
(make-scene
entities: (list ...)
tilemap: tm
camera: (make-camera x: 0 y: 0)
tileset-texture: tex)
```
## Prefab / Mixin System
```scheme
(mixins
(physics-body #:vx 0 #:vy 0 ...)
(has-facing #:facing 1))
(prefabs
(player physics-body has-facing #:type player #:tile-id 29 ...))
```
Prefabs are loaded from a data file (`assets/prefabs.scm`). `instantiate-prefab` merges mixins + overrides into a fresh entity plist.
## Tile Collision Model
Tiles come from TMX maps (Tiled editor). The tilemap module parses TMX XML via expat into a struct with layers, tile GIDs, and a tileset. Collision tiles are identified by metadata in the TSX tileset. The physics module checks all tile cells overlapping an entity's AABB and snaps the entity to the nearest edge (velocity-direction-aware).
## AI State Machine
Uses the `states` egg. States: `idle → patrol → chase → patrol` (cycles back). Guards: `player-in-range?`, `player-in-attack-range?`, `chase-give-up?`. After Milestone 10, hardcoded `'player` type checks are replaced with `(scene-find-tagged scene 'player)`.
## Dependencies
System: `SDL2`, `SDL2_mixer`, `SDL2_ttf`, `SDL2_image`
Chicken eggs: `sdl2`, `sdl2-image`, `expat`, `matchable`, `defstruct`, `states`, `srfi-64` (tests), `srfi-197`
## Egg Packaging
Downstroke is distributed as a **Chicken egg** named `downstroke`. The egg spec (`downstroke.egg`) declares all modules as installable units. Once published, games depend on it via `chicken-install downstroke`.
Module namespacing follows the egg convention: `downstroke/physics`, `downstroke/world`, etc. All module source files live at the project root; the egg spec maps them to the namespaced identifiers.
## Macroknight Port
As soon as the first installable egg exists (Milestone 11), macroknight must be ported to depend on it. This is an ongoing obligation — macroknight is the primary validation that the engine API is usable. Any engine change that requires macroknight to be updated should update macroknight in the same work session.
The ported macroknight `game.scm` is the definition of "Milestone 12 done": it should be ~20–30 lines, containing only `make-game` + lifecycle hooks + `game-run!`.
## Tracking Progress
As milestones and tasks are completed, update the TODO items in `/home/gene/Documents/Perso/Projects/downstroke.org`. Mark tasks with `DONE` as they are finished — this file is the authoritative project tracker.
## Milestone Status
Milestones 1–6: pure refactoring — extract modules into the project root, no behavior changes.
Milestone 7: design pivot — `make-game` + `game-run!` public API becomes stable.
Milestones 8–10: camera follow, scene state machine, AI tag lookup.
Milestones 11–12: package as Chicken egg, macroknight uses it as dependency.
Current status: all milestones at 0/N — extraction has not yet begun.
|