# 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)