aboutsummaryrefslogtreecommitdiff
path: root/docs/superpowers/specs/2026-04-05-milestone-8-game-object-lifecycle-design.md
blob: 2588761c43632d5b25c58caf5a0eace6ae7bcf65 (plain)
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
# 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)