aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorGene Pasquet <dev@etenil.net>2026-04-05 16:19:39 +0100
committerGene Pasquet <dev@etenil.net>2026-04-05 16:19:39 +0100
commit188d2240b390467c4b08d6d2dad4e6d134112585 (patch)
tree4de1aaefc0d600d930a185144a10857b7aff64a4 /docs
parent2a75c88de470a173067feee4df80cd8e3fb7a641 (diff)
docs: add Milestone 8 design spec — game object and lifecycle API
Captures the approved design for make-game, game-run!, assets.scm, and the macroknight port target. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'docs')
-rw-r--r--docs/superpowers/specs/2026-04-05-milestone-8-game-object-lifecycle-design.md169
1 files changed, 169 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-04-05-milestone-8-game-object-lifecycle-design.md b/docs/superpowers/specs/2026-04-05-milestone-8-game-object-lifecycle-design.md
new file mode 100644
index 0000000..2588761
--- /dev/null
+++ b/docs/superpowers/specs/2026-04-05-milestone-8-game-object-lifecycle-design.md
@@ -0,0 +1,169 @@
+# Milestone 8 — The Game Object and Lifecycle API
+
+**Date:** 2026-04-05
+**Status:** Approved
+**Scope:** `engine.scm`, `assets.scm`, macroknight port
+
+---
+
+## Goal
+
+Introduce `make-game` and `game-run!` as the public entry point for downstroke. A minimal game becomes ~20 lines of Scheme. This is the "Phaser moment" — the API stabilises around a game struct with lifecycle hooks.
+
+---
+
+## Architecture
+
+Two new files:
+
+- **`engine.scm`** — `make-game` struct, `game-run!`, the frame loop, and all public accessors.
+- **`assets.scm`** — minimal key→value asset registry. No asset-type-specific logic; that is Milestone 6's domain.
+
+`engine.scm` imports: `renderer.scm`, `input.scm`, `world.scm`, `assets.scm`.
+
+`physics.scm` is **not** imported by the engine. Physics is a library; the user requires it if needed and calls physics functions from their `update:` hook.
+
+### Build order (Makefile)
+
+```
+tilemap → entity → world → animation → physics → ai → input → prefabs → mixer → sound → assets → engine
+```
+
+---
+
+## Data Structures
+
+### `game` struct (`engine.scm`)
+
+```scheme
+(defstruct game
+ title width height
+ window renderer
+ input-state
+ scene camera
+ assets ;; asset registry hash table (from assets.scm)
+ frame-delay
+ preload-hook ;; (lambda (game) ...)
+ create-hook ;; (lambda (game) ...)
+ update-hook ;; (lambda (game dt) ...)
+ render-hook) ;; (lambda (game) ...) — post-render overlay
+```
+
+### `make-game` keyword constructor
+
+```scheme
+(define (make-game #!key
+ (title "Downstroke Game")
+ (width 640) (height 480)
+ (frame-delay 16)
+ (preload #f) (create #f) (update #f) (render #f))
+ (make-game* title: title width: width height: height
+ frame-delay: frame-delay
+ assets: (make-asset-registry)
+ preload-hook: preload
+ create-hook: create
+ update-hook: update
+ render-hook: render))
+```
+
+### Asset registry (`assets.scm`)
+
+```scheme
+(define (make-asset-registry) (make-hash-table))
+(define (asset-set! reg key val) (hash-table-set! reg key val))
+(define (asset-ref reg key) (hash-table-ref reg key (lambda () #f)))
+```
+
+---
+
+## Lifecycle: `game-run!`
+
+```
+SDL2 init
+ └─ create window + renderer → stored in game struct
+ └─ call preload-hook (user loads assets)
+ └─ call create-hook (user builds initial scene)
+ └─ frame loop:
+ input-handle-events! → update input-state
+ call update-hook game dt
+ render-clear!
+ render-scene! renderer scene camera ;; engine draws world
+ call render-hook game ;; user overlay
+ render-present!
+ sdl2:delay! frame-delay
+ └─ cleanup: destroy window, sdl2:quit!
+```
+
+The loop exits when `input-quit?` is true (SDL_QUIT event). There is no user-settable `game-running?` flag in this milestone.
+
+`render-scene!` lives in `renderer.scm` and draws tilemap layers then entities — the logic already present in macroknight, extracted into the engine.
+
+---
+
+## Public Accessors
+
+All generated by `defstruct` and exported:
+
+| Accessor | Description |
+|---|---|
+| `game-renderer` | SDL2 renderer — available in `preload:` for texture upload |
+| `game-window` | SDL2 window |
+| `game-scene` / `game-scene-set!` | Current scene struct; user sets this in `create:` |
+| `game-camera` / `game-camera-set!` | Current camera struct |
+| `game-assets` | Full asset registry hash table |
+| `game-input` | Current `input-state` struct |
+| `(game-asset game key)` | Convenience: `(asset-ref (game-assets game) key)` |
+| `(game-asset-set! game key val)` | Convenience setter |
+
+---
+
+## `render:` Hook Semantics
+
+Following Phaser 2.x: the engine draws the full scene first (tilemap + entities), then calls the user's `render:` hook. The hook is for post-render overlays only — debug info, HUD elements, custom visuals. It does not replace or suppress the engine's render pass.
+
+---
+
+## Physics
+
+Physics (`physics.scm`) is a library, not a pipeline. `game-run!` does not call any physics function automatically. Users who want physics `(require-extension downstroke/physics)` and call functions from their `update:` hook.
+
+---
+
+## Scene-Level Preloading
+
+Out of scope for this milestone. A `preload:` slot will be reserved in the scene struct when `scene-loader.scm` is implemented (Milestone 7), and the two-level asset registry (global + per-scene) will be designed then.
+
+---
+
+## macroknight Port
+
+macroknight's `game.scm` is ported to use `make-game` + `game-run!` as part of this milestone. macroknight is the primary integration test; any engine change that requires a macroknight update must update macroknight in the same session.
+
+Target shape for macroknight `game.scm`:
+
+```scheme
+(define my-game
+ (make-game
+ title: "MacroKnight" width: 600 height: 400
+ preload: (lambda (game)
+ ;; load fonts, sounds, tilemaps
+ ...)
+ create: (lambda (game)
+ ;; build initial scene
+ ...)
+ update: (lambda (game dt)
+ ;; game logic, input handling, physics calls
+ ...)))
+
+(game-run! my-game)
+```
+
+---
+
+## Out of Scope
+
+- Scene-level preloading (Milestone 7)
+- Asset-type-specific load functions (`game-load-tilemap!`, `game-load-font!`, etc.) — Milestone 6
+- `game-running?` quit flag
+- Scene state machine (Milestone 9)
+- AI tag lookup (Milestone 10)