diff options
| author | Gene Pasquet <dev@etenil.net> | 2026-04-07 23:36:12 +0100 |
|---|---|---|
| committer | Gene Pasquet <dev@etenil.net> | 2026-04-07 23:36:12 +0100 |
| commit | 19a5db8606a82830a5ccd0ed46d8e0cf3c95db0a (patch) | |
| tree | 241e7376014068ab9fc7a1bc8fa7a29cc1b62490 /docs/superpowers/plans/2026-04-05-milestone-7-scene-loader.md | |
| parent | 618ed5fd6f5ae9c9f275c1e3cfb74762d7d51a01 (diff) | |
Work on demos
Diffstat (limited to 'docs/superpowers/plans/2026-04-05-milestone-7-scene-loader.md')
| -rw-r--r-- | docs/superpowers/plans/2026-04-05-milestone-7-scene-loader.md | 201 |
1 files changed, 0 insertions, 201 deletions
diff --git a/docs/superpowers/plans/2026-04-05-milestone-7-scene-loader.md b/docs/superpowers/plans/2026-04-05-milestone-7-scene-loader.md deleted file mode 100644 index 2b7f86f..0000000 --- a/docs/superpowers/plans/2026-04-05-milestone-7-scene-loader.md +++ /dev/null @@ -1,201 +0,0 @@ -# Scene Loader Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Create `scene-loader.scm` -- a module that encapsulates the repeated tilemap-load / texture-create / scene-build pattern currently duplicated across platformer, topdown, and sandbox demos. - -**Architecture:** A new `downstroke/scene-loader` module provides `game-load-scene!` as the single entry point for the common pattern (load TMX, create texture, build scene, set on game). Two pure helpers (`tilemap-objects->entities` and `make-prefab-registry` / `instantiate-prefab`) handle object-layer entity instantiation with a simple hash-table registry. The module sits between `assets`/`world`/`tilemap` and `engine` in the dependency graph; demos import it and replace ~10 lines of boilerplate with a single call. - -**Tech Stack:** Chicken Scheme 5, `defstruct`, `srfi-1` (filter-map), `srfi-69` (hash-tables), `sdl2` (texture creation), existing downstroke modules (`world`, `tilemap`, `assets`). - -**Rejected alternatives:** -- Putting scene-loading logic directly into `engine.scm` -- rejected because engine should stay lifecycle-only; scene loading is an opt-in convenience. -- Making `game-load-scene!` accept an entity list parameter -- rejected because callers should add entities after via `scene-add-entity` (which already exists and mutates in place); keeps the loader focused on the tilemap/texture concern. - ---- - -### Task 1: Tests for pure helpers (TDD -- red phase) - -**Files:** -- Create: `tests/scene-loader-test.scm` - -- [ ] **Step 1:** Create `tests/scene-loader-test.scm` with `(import srfi-64)` and mock modules. Mock `downstroke/tilemap` inline (same pattern as `tests/renderer-test.scm` lines 10-18) defining the `object` defstruct (`object-type`, `object-x`, `object-y`, `object-width`, `object-height`) and `tilemap-objects` accessor. Mock `downstroke/world` with the `scene`, `camera` defstructs. Mock `sdl2` with a stub `create-texture-from-surface`. Mock `downstroke/assets` with stubs for `asset-set!` and `asset-ref`. -- [ ] **Step 2:** Write tests for `make-prefab-registry` + `instantiate-prefab`: - - `make-prefab-registry` with two type/constructor pairs returns a registry. - - `instantiate-prefab` with a known type calls the constructor and returns the entity plist. - - `instantiate-prefab` with an unknown type returns `#f`. -- [ ] **Step 3:** Write tests for `tilemap-objects->entities`: - - Given a mock tilemap whose `tilemap-objects` returns a list of 3 fake objects (with `object-type`, `object-x`, `object-y`, `object-width`, `object-height`), and an `instantiate-fn` that returns a plist for type `"player"` but `#f` for type `"decoration"`, verify the result is a list containing only the matched entity (i.e., `#f` results are filtered out). - - Given a tilemap with zero objects, verify the result is `'()`. -- [ ] **Step 4:** Add a placeholder `(include "scene-loader.scm")` and `(import downstroke/scene-loader)` after the mocks. Run `csi -s tests/scene-loader-test.scm` and confirm it fails (module not found). This is the red phase. - ---- - -### Task 2: Implement `scene-loader.scm` (green phase) - -**Files:** -- Create: `scene-loader.scm` - -- [ ] **Step 1:** Create `scene-loader.scm` with the module declaration: - ```scheme - (module downstroke/scene-loader * - (import scheme - (chicken base) - (only srfi-1 filter-map) - (srfi 69) - (prefix sdl2 "sdl2:") - defstruct - downstroke/world - downstroke/tilemap - downstroke/assets) - ...) - ``` -- [ ] **Step 2:** Implement `make-prefab-registry` -- takes alternating `symbol constructor` pairs, returns an `srfi-69` hash-table mapping each symbol to its constructor lambda: - ```scheme - (define (make-prefab-registry . pairs) - (let ((ht (make-hash-table))) - (let loop ((p pairs)) - (if (null? p) ht - (begin - (hash-table-set! ht (car p) (cadr p)) - (loop (cddr p))))))) - ``` -- [ ] **Step 3:** Implement `instantiate-prefab` -- looks up `type` (a symbol) in registry, calls the constructor with `(x y w h)`, returns entity plist or `#f`: - ```scheme - (define (instantiate-prefab registry type x y w h) - (let ((ctor (hash-table-ref/default registry type #f))) - (and ctor (ctor x y w h)))) - ``` -- [ ] **Step 4:** Implement `tilemap-objects->entities`: - ```scheme - (define (tilemap-objects->entities tilemap instantiate-fn) - (filter-map - (lambda (obj) - (instantiate-fn (string->symbol (object-type obj)) - (object-x obj) (object-y obj) - (object-width obj) (object-height obj))) - (tilemap-objects tilemap))) - ``` - Note: `object-type` returns a string (from TMX XML); convert to symbol so callers work with symbols. -- [ ] **Step 5:** Implement `create-tileset-texture`: - ```scheme - (define (create-tileset-texture renderer tilemap) - (sdl2:create-texture-from-surface - renderer - (tileset-image (tilemap-tileset tilemap)))) - ``` -- [ ] **Step 6:** Implement `game-load-scene!`: - ```scheme - (define (game-load-scene! game filename) - (let* ((tm (load-tilemap filename)) - (tex (create-tileset-texture (game-renderer game) tm)) - (scene (make-scene entities: '() - tilemap: tm - camera: (make-camera x: 0 y: 0) - tileset-texture: tex))) - (game-asset-set! game 'tilemap tm) - (game-scene-set! game scene) - scene)) - ``` - Note: `game-load-scene!` needs `game-renderer`, `game-asset-set!`, and `game-scene-set!` from `downstroke/engine`. However, importing engine from scene-loader would create a circular dependency (engine depends on world, scene-loader depends on world+tilemap, engine should not depend on scene-loader). **Resolution:** `game-load-scene!` must accept `renderer` explicitly, or scene-loader must also import `downstroke/engine`. Check: engine exports `game-renderer`, `game-asset-set!`, `game-scene-set!`. Scene-loader depends on engine; engine does NOT need to depend on scene-loader. This is safe -- add `downstroke/engine` to imports. -- [ ] **Step 7:** Run `csi -s tests/scene-loader-test.scm`. All tests should pass (green phase). - ---- - -### Task 3: Build system integration - -**Files:** -- Modify: `/home/gene/src/downstroke/Makefile` -- Modify: `/home/gene/src/downstroke/downstroke.egg` - -- [ ] **Step 1:** In `Makefile`, add `scene-loader` to `MODULE_NAMES` after `engine` (it depends on engine, world, tilemap, assets): - ``` - MODULE_NAMES := entity tilemap world input physics renderer assets engine mixer sound animation ai scene-loader - ``` -- [ ] **Step 2:** Add explicit dependency line: - ```makefile - bin/scene-loader.o: bin/world.o bin/tilemap.o bin/assets.o bin/engine.o - ``` -- [ ] **Step 3:** Add the test to the `test:` target: - ```makefile - @csi -s tests/scene-loader-test.scm - ``` -- [ ] **Step 4:** In `downstroke.egg`, add the extension after the `downstroke/engine` entry: - ```scheme - (extension downstroke/scene-loader - (source "scene-loader.scm") - (component-dependencies downstroke/world downstroke/tilemap downstroke/assets downstroke/engine)) - ``` -- [ ] **Step 5:** Run `make clean && make && make test` to verify engine compiles and all tests pass. - ---- - -### Task 4: Update demos to use scene-loader - -**Files:** -- Modify: `/home/gene/src/downstroke/demo/platformer.scm` -- Modify: `/home/gene/src/downstroke/demo/topdown.scm` -- Modify: `/home/gene/src/downstroke/demo/sandbox.scm` - -- [ ] **Step 1:** In `demo/platformer.scm`: - - Add `downstroke/scene-loader` to the imports. - - In `preload:`, replace the `game-asset-set!` call for tilemap with nothing (remove it; `game-load-scene!` handles this). Keep the `init-audio!` and `load-sounds!` calls. - - In `create:`, replace the entire `let*` body with: - ```scheme - (let* ((scene (game-load-scene! game "demo/assets/level-0.tmx")) - (player (list #:type 'player - #:x 100 #:y 50 - #:width 16 #:height 16 - #:vx 0 #:vy 0 - #:gravity? #t - #:on-ground? #f - #:tile-id 1))) - (scene-add-entity scene player)) - ``` - Note: `scene-add-entity` (from `world.scm` line 41) mutates and returns the scene, which is already set on the game by `game-load-scene!`, so no further `game-scene-set!` needed. - -- [ ] **Step 2:** In `demo/topdown.scm`: - - Add `downstroke/scene-loader` to imports. - - Remove the `preload:` tilemap loading entirely (the `preload:` lambda can just be `(lambda (game) #f)` or removed if `make-game` allows it -- check engine.scm; if preload is optional, omit it). - - In `create:`, replace with: - ```scheme - (let* ((scene (game-load-scene! game "demo/assets/level-0.tmx")) - (player (list #:type 'player - #:x 100 #:y 100 - #:width 16 #:height 16 - #:vx 0 #:vy 0 - #:gravity? #f - #:tile-id 1))) - (scene-add-entity scene player)) - ``` - -- [ ] **Step 3:** In `demo/sandbox.scm`: - - Add `downstroke/scene-loader` to imports. - - Remove the `preload:` tilemap loading. - - In `create:`, replace with: - ```scheme - (let ((scene (game-load-scene! game "demo/assets/level-0.tmx"))) - (scene-entities-set! scene (spawn-entities))) - ``` - Note: sandbox replaces the entire entity list rather than adding one, so use `scene-entities-set!` directly. - -- [ ] **Step 4:** Run `make clean && make && make demos` to verify all demos compile. - ---- - -### Task 5: Verify and clean up - -**Files:** -- All modified files - -- [ ] **Step 1:** Run the full build and test suite: `make clean && make && make demos && make test`. All must pass. -- [ ] **Step 2:** Review that no demo still imports `(prefix sdl2 "sdl2:")` solely for `create-texture-from-surface`. If that was the only sdl2 usage in the demo's `create:` hook, the `sdl2` import can remain (it may be needed for other SDL2 calls in `update:` or elsewhere) -- do not remove imports that are still used. -- [ ] **Step 3:** Verify the three demos no longer contain the duplicated tilemap-load/texture-create/scene-build pattern. Each demo's `create:` hook should be noticeably shorter. - ---- - -## Gaps -- follow-up investigation needed - -- **`game-load-scene!` importing engine:** The plan assumes `scene-loader.scm` can import `downstroke/engine` without creating a circular dependency. Verify by checking that `engine.scm` does NOT import `scene-loader`. From the Makefile deps (`bin/engine.o: bin/renderer.o bin/world.o bin/input.o bin/assets.o`), this is confirmed safe. -- **`preload:` optionality:** The plan suggests removing `preload:` from topdown and sandbox demos. Need to verify whether `make-game` requires the `preload:` keyword or treats it as optional. If required, keep it as `(lambda (game) #f)`. -- **`object-type` return type:** The plan assumes `object-type` returns a string (from TMX XML parsing). If it already returns a symbol, remove the `string->symbol` call in `tilemap-objects->entities`. Check the TMX parser in `tilemap.scm` to confirm. |
