From 526e6cdcdf1025d5e29680bc99ab910c79789764 Mon Sep 17 00:00:00 2001 From: Gene Pasquet Date: Sun, 5 Apr 2026 14:17:51 +0100 Subject: Initial port of macroknight to an engine --- CLAUDE.md | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 CLAUDE.md (limited to 'CLAUDE.md') diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fb196e5 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,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 `-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. -- cgit v1.2.3