aboutsummaryrefslogtreecommitdiff
path: root/docs/superpowers/plans/2026-04-05-milestone-7-scene-loader.md
diff options
context:
space:
mode:
authorGene Pasquet <dev@etenil.net>2026-04-07 23:36:12 +0100
committerGene Pasquet <dev@etenil.net>2026-04-07 23:36:12 +0100
commit19a5db8606a82830a5ccd0ed46d8e0cf3c95db0a (patch)
tree241e7376014068ab9fc7a1bc8fa7a29cc1b62490 /docs/superpowers/plans/2026-04-05-milestone-7-scene-loader.md
parent618ed5fd6f5ae9c9f275c1e3cfb74762d7d51a01 (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.md201
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.