# 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 `-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//`) - 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.