aboutsummaryrefslogtreecommitdiff
path: root/docs/api.org
diff options
context:
space:
mode:
Diffstat (limited to 'docs/api.org')
-rw-r--r--docs/api.org846
1 files changed, 846 insertions, 0 deletions
diff --git a/docs/api.org b/docs/api.org
new file mode 100644
index 0000000..0a443d6
--- /dev/null
+++ b/docs/api.org
@@ -0,0 +1,846 @@
+#+TITLE: Downstroke API Reference
+
+This document describes the public API for the Downstroke game engine. All exported functions are organized by module.
+
+* Engine (~downstroke/engine~)
+
+#+begin_src scheme
+(import downstroke/engine)
+#+end_src
+
+The engine module provides the top-level game lifecycle and state management.
+
+** ~make-game~
+
+#+begin_src scheme
+(make-game #!key
+ (title "Downstroke Game")
+ (width 640)
+ (height 480)
+ (frame-delay 16)
+ (input-config *default-input-config*)
+ (preload #f)
+ (create #f)
+ (update #f)
+ (render #f))
+#+end_src
+
+Creates and initializes a game object. All parameters are optional keywords.
+
+| Parameter | Type | Default | Description |
+|-----------+------+---------+-------------|
+| ~title~ | string | "Downstroke Game" | Window title |
+| ~width~ | integer | 640 | Game window width in pixels |
+| ~height~ | integer | 480 | Game window height in pixels |
+| ~frame-delay~ | integer | 16 | Delay between frames in milliseconds (30 FPS ≈ 33) |
+| ~input-config~ | input-config | *default-input-config* | Keyboard/controller mappings |
+| ~preload~ | procedure/false | #f | Hook: ~(lambda (game) ...)~ called once before create |
+| ~create~ | procedure/false | #f | Hook: ~(lambda (game) ...)~ called once at startup |
+| ~update~ | procedure/false | #f | Hook: ~(lambda (game dt) ...)~ called each frame |
+| ~render~ | procedure/false | #f | Hook: ~(lambda (game) ...)~ called after render-scene! |
+
+The game object is the central hub. Use it to store/retrieve assets, manage scenes, and access the current input state.
+
+** ~game-run!~
+
+#+begin_src scheme
+(game-run! game)
+#+end_src
+
+Starts the main event loop. Initializes SDL2, opens the window, and runs the frame loop indefinitely until the user quits or the ~quit~ action is pressed. Never returns.
+
+Lifecycle order within each frame:
+1. Collect SDL2 events
+2. Update input state
+3. Call ~update:~ hook (or active state's ~update~)
+4. Clear renderer
+5. Call ~render-scene!~ (if scene is set)
+6. Call ~render:~ hook (or active state's ~render~)
+7. Present renderer
+8. Apply frame delay
+
+** ~game-camera~
+
+#+begin_src scheme
+(game-camera game)
+#+end_src
+
+Returns the current scene's camera struct. Only valid after ~create:~ runs. Returns a camera record with ~x~ and ~y~ fields for the top-left viewport corner in world coordinates.
+
+** ~game-asset~
+
+#+begin_src scheme
+(game-asset game key)
+#+end_src
+
+Retrieves an asset from the game's registry by key. Returns ~#f~ if the key is not found.
+
+** ~game-asset-set!~
+
+#+begin_src scheme
+(game-asset-set! game key value)
+#+end_src
+
+Stores an asset in the game's registry. Overwrites any existing value at the key.
+
+** ~make-game-state~
+
+#+begin_src scheme
+(make-game-state #!key (create #f) (update #f) (render #f))
+#+end_src
+
+Creates a state record (plist) with optional lifecycle hooks. Used with ~game-add-state!~ and ~game-start-state!~ to build a state machine within the game.
+
+| Parameter | Type | Default | Description |
+|-----------+------+---------+-------------|
+| ~create~ | procedure/false | #f | Called when entering this state |
+| ~update~ | procedure/false | #f | Called each frame while active |
+| ~render~ | procedure/false | #f | Called each frame after rendering (overlay) |
+
+** ~game-add-state!~
+
+#+begin_src scheme
+(game-add-state! game name state)
+#+end_src
+
+Registers a named state (created with ~make-game-state~) in the game's state table. ~name~ must be a symbol.
+
+** ~game-start-state!~
+
+#+begin_src scheme
+(game-start-state! game name)
+#+end_src
+
+Transitions to a named state, activating its lifecycle hooks. Calls the state's ~create:~ hook (if present) immediately.
+
+** Example: Game with State Machine
+
+#+begin_src scheme
+(define my-game (make-game title: "My Game"))
+
+(game-add-state! my-game 'playing
+ (make-game-state
+ create: (lambda (game) (print "Game started"))
+ update: (lambda (game dt) (print "Updating..."))
+ render: (lambda (game) (print "Rendering overlay"))))
+
+(game-add-state! my-game 'paused
+ (make-game-state
+ create: (lambda (game) (print "Paused"))))
+
+; Start the game in 'playing state
+(game-start-state! my-game 'playing)
+
+; Later, transition to paused
+(game-start-state! my-game 'paused)
+#+end_src
+
+* World (~downstroke/world~)
+
+#+begin_src scheme
+(import downstroke/world)
+#+end_src
+
+The world module provides the scene (level) abstraction and camera management.
+
+** ~make-scene~
+
+Auto-generated by defstruct. Use keyword arguments:
+
+#+begin_src scheme
+(make-scene #!key
+ (entities '())
+ (tilemap #f)
+ (camera #f)
+ (tileset-texture #f))
+#+end_src
+
+Creates a scene record representing the current level state.
+
+| Parameter | Type | Default | Description |
+|-----------+------+---------+-------------|
+| ~entities~ | list | ~'()~ | List of entity plists |
+| ~tilemap~ | tilemap/false | #f | Tile grid and collisions |
+| ~camera~ | camera/false | #f | Viewport position |
+| ~tileset-texture~ | SDL2 texture/false | #f | Rendered tileset image |
+
+** ~make-camera~
+
+Auto-generated by defstruct. Use keyword arguments:
+
+#+begin_src scheme
+(make-camera #!key (x 0) (y 0))
+#+end_src
+
+Creates a camera record. ~x~ and ~y~ are the pixel coordinates of the viewport's top-left corner in world space.
+
+** ~camera-x~, ~camera-y~
+
+#+begin_src scheme
+(camera-x camera)
+(camera-y camera)
+#+end_src
+
+Accessors for camera position.
+
+** ~camera-x-set!~, ~camera-y-set!~
+
+#+begin_src scheme
+(camera-x-set! camera x)
+(camera-y-set! camera y)
+#+end_src
+
+Mutate camera position (in-place).
+
+** ~camera-follow!~
+
+#+begin_src scheme
+(camera-follow! camera entity viewport-w viewport-h)
+#+end_src
+
+Centers the camera on an entity, clamping to stay within world bounds (never negative). ~viewport-w~ and ~viewport-h~ are the game window dimensions.
+
+** ~scene-add-entity~
+
+#+begin_src scheme
+(scene-add-entity scene entity)
+#+end_src
+
+Appends an entity to the scene's entity list. Returns the modified scene.
+
+** ~scene-update-entities~
+
+#+begin_src scheme
+(scene-update-entities scene proc1 proc2 ...)
+#+end_src
+
+Applies each procedure in sequence to all entities in the scene. Each procedure takes a single entity and returns a modified entity. The scene's entity list is updated once with the final result. Returns the modified scene.
+
+Example:
+
+#+begin_src scheme
+(define (increment-x entity)
+ (entity-set entity #:x (+ 1 (entity-ref entity #:x 0))))
+
+(define (apply-gravity entity)
+ (entity-set entity #:vy (+ 1 (entity-ref entity #:vy 0))))
+
+(scene-update-entities scene increment-x apply-gravity)
+; Each entity is passed through increment-x, then through apply-gravity
+#+end_src
+
+** ~scene-filter-entities~
+
+#+begin_src scheme
+(scene-filter-entities scene predicate)
+#+end_src
+
+Removes all entities that do not satisfy the predicate. Returns the modified scene.
+
+Example:
+
+#+begin_src scheme
+; Remove all entities with type 'enemy
+(scene-filter-entities scene
+ (lambda (e) (not (eq? (entity-type e) 'enemy))))
+#+end_src
+
+** ~scene-find-tagged~
+
+#+begin_src scheme
+(scene-find-tagged scene tag)
+#+end_src
+
+Returns the first entity whose ~#:tags~ list (a list of symbols) contains the given tag, or ~#f~ if not found.
+
+** ~scene-find-all-tagged~
+
+#+begin_src scheme
+(scene-find-all-tagged scene tag)
+#+end_src
+
+Returns a list of all entities whose ~#:tags~ list contains the given tag. Returns ~'()~ if none found.
+
+** Accessor functions (auto-generated by defstruct)
+
+- ~scene-entities~, ~scene-entities-set!~
+- ~scene-tilemap~, ~scene-tilemap-set!~
+- ~scene-camera~, ~scene-camera-set!~
+- ~scene-tileset-texture~, ~scene-tileset-texture-set!~
+
+* Entity (~downstroke/entity~)
+
+#+begin_src scheme
+(import downstroke/entity)
+#+end_src
+
+The entity module provides property list (plist) accessors for game objects. Entities are immutable plists, never modified in place.
+
+** ~entity-ref~
+
+#+begin_src scheme
+(entity-ref entity key #!optional default)
+#+end_src
+
+Retrieves the value of a key from an entity plist. Returns ~default~ (or ~#f~) if the key is not found. If ~default~ is a procedure, it is called with no arguments to produce the default.
+
+Example:
+
+#+begin_src scheme
+(entity-ref player #:x 0) ; Get x, defaulting to 0
+(entity-ref player #:tags '()) ; Get tags, defaulting to empty list
+#+end_src
+
+** ~entity-type~
+
+#+begin_src scheme
+(entity-type entity)
+#+end_src
+
+Shorthand for ~(entity-ref entity #:type #f)~. Returns the entity's ~#:type~ field or ~#f~.
+
+** ~entity-set~
+
+#+begin_src scheme
+(entity-set entity key value)
+#+end_src
+
+Returns a new plist with the key/value updated. **Does not modify the original entity.** This is a functional (immutable) operation.
+
+Example:
+
+#+begin_src scheme
+(define player (list #:x 100 #:y 200 #:type 'player))
+(define moved (entity-set player #:x 150))
+; player is still (list #:x 100 #:y 200 #:type 'player)
+; moved is (list #:x 150 #:y 200 #:type 'player)
+#+end_src
+
+** ~entity-update~
+
+#+begin_src scheme
+(entity-update entity key proc #!optional default)
+#+end_src
+
+Functional update: applies ~proc~ to the current value of ~key~ and updates the entity with the result. Returns a new plist.
+
+Example:
+
+#+begin_src scheme
+(entity-update player #:x (lambda (x) (+ x 10)))
+; Equivalent to (entity-set player #:x (+ 10 (entity-ref player #:x 0)))
+#+end_src
+
+** Shared Entity Keys
+
+All entities can have these keys. Not all are required:
+
+| Key | Type | Description |
+|-----|------|-------------|
+| ~#:type~ | symbol | Entity type (e.g., 'player, 'enemy) |
+| ~#:x~ | number | X position in pixels |
+| ~#:y~ | number | Y position in pixels |
+| ~#:width~ | number | Bounding box width in pixels |
+| ~#:height~ | number | Bounding box height in pixels |
+| ~#:vx~ | number | X velocity in pixels/frame |
+| ~#:vy~ | number | Y velocity in pixels/frame |
+| ~#:tile-id~ | integer | Sprite index in tileset (1-indexed) |
+| ~#:tags~ | list | List of symbols for lookup (e.g., '(player)) |
+| ~#:gravity?~ | boolean | Apply gravity to this entity? |
+| ~#:on-ground?~ | boolean | Is entity touching a solid tile below? |
+| ~#:facing~ | integer | 1 (right) or -1 (left) |
+| ~#:solid?~ | boolean | Participate in AABB entity collisions? |
+| ~#:anim-name~ | symbol | Current animation name |
+| ~#:anim-frame~ | integer | Current frame index |
+| ~#:anim-tick~ | integer | Ticks in current frame |
+
+* Physics (~downstroke/physics~)
+
+#+begin_src scheme
+(import downstroke/physics)
+#+end_src
+
+The physics module implements the main collision and movement pipeline. The physics pipeline runs automatically before the user's ~update:~ hook.
+
+** Physics Pipeline Order
+
+The built-in physics runs in this order each frame:
+
+1. ~apply-acceleration~ — consume ~#:ay~ into ~#:vy~
+2. ~apply-gravity~ — add gravity to ~#:vy~
+3. ~apply-velocity-x~ — move by ~#:vx~
+4. ~resolve-tile-collisions-x~ — snap against horizontal tile collisions
+5. ~apply-velocity-y~ — move by ~#:vy~
+6. ~resolve-tile-collisions-y~ — snap against vertical tile collisions
+7. ~detect-ground~ — set ~#:on-ground?~ if standing on a tile
+
+(This separation ensures smooth sliding along walls.)
+
+** ~apply-acceleration~
+
+#+begin_src scheme
+(apply-acceleration entity)
+#+end_src
+
+Consumes ~#:ay~ (one-shot acceleration) into ~#:vy~ and clears ~#:ay~ to 0. Only applies if ~#:gravity?~ is true.
+
+** ~apply-gravity~
+
+#+begin_src scheme
+(apply-gravity entity)
+#+end_src
+
+Adds the gravity constant (1 pixel/frame²) to ~#:vy~. Only applies if ~#:gravity?~ is true.
+
+** ~apply-velocity-x~
+
+#+begin_src scheme
+(apply-velocity-x entity)
+#+end_src
+
+Updates ~#:x~ by adding ~#:vx~. Returns a new entity.
+
+** ~apply-velocity-y~
+
+#+begin_src scheme
+(apply-velocity-y entity)
+#+end_src
+
+Updates ~#:y~ by adding ~#:vy~. Returns a new entity.
+
+** ~apply-velocity~
+
+#+begin_src scheme
+(apply-velocity entity)
+#+end_src
+
+Legacy function: updates both ~#:x~ and ~#:y~ by their respective velocities.
+
+** ~resolve-tile-collisions-x~
+
+#+begin_src scheme
+(resolve-tile-collisions-x entity tilemap)
+#+end_src
+
+Detects and resolves collisions between the entity's bounding box and solid tiles along the X axis. Snaps the entity's ~#:x~ to the near/far tile edge and sets ~#:vx~ to 0. Returns a new entity.
+
+** ~resolve-tile-collisions-y~
+
+#+begin_src scheme
+(resolve-tile-collisions-y entity tilemap)
+#+end_src
+
+Detects and resolves collisions between the entity's bounding box and solid tiles along the Y axis. Snaps the entity's ~#:y~ to the near/far tile edge and sets ~#:vy~ to 0. Returns a new entity.
+
+** ~detect-ground~
+
+#+begin_src scheme
+(detect-ground entity tilemap)
+#+end_src
+
+Probes one pixel below the entity's feet to detect if it is standing on a solid tile. Sets ~#:on-ground?~ to true or false. Only applies if ~#:gravity?~ is true. Returns a new entity.
+
+** ~apply-jump~
+
+#+begin_src scheme
+(apply-jump entity jump-pressed?)
+#+end_src
+
+If the jump button is pressed and the entity is on ground, sets ~#:ay~ to ~(- #:jump-force)~ (default 15 pixels/frame). On the next frame, ~apply-acceleration~ consumes this into ~#:vy~. Returns a new entity.
+
+** ~resolve-entity-collisions~
+
+#+begin_src scheme
+(resolve-entity-collisions entities)
+#+end_src
+
+Detects and resolves AABB collisions between all pairs of entities with ~#:solid?~ true. Pushes overlapping entities apart along the axis of minimum penetration and sets their velocities in the push direction. Returns a new entity list.
+
+** ~scene-resolve-collisions~
+
+#+begin_src scheme
+(scene-resolve-collisions scene)
+#+end_src
+
+Applies ~resolve-entity-collisions~ to the scene's entity list. Returns the modified scene.
+
+** Physics Constants
+
+- ~*gravity*~ = 1 (pixels per frame per frame)
+- ~*jump-force*~ = 15 (vertical acceleration on jump)
+
+* Input (~downstroke/input~)
+
+#+begin_src scheme
+(import downstroke/input)
+#+end_src
+
+The input module handles keyboard, joystick, and game controller events. It maintains the current and previous input state to support pressed/released detection.
+
+** ~*default-input-config*~
+
+The default input configuration record. Use as the ~input-config:~ parameter to ~make-game~, or create a custom one with ~make-input-config~.
+
+| Action | Keyboard | Joystick | Controller |
+|--------|----------|----------|------------|
+| ~up~ | W or Up | Y-axis negative | DPad Up, Left-Y negative |
+| ~down~ | S or Down | Y-axis positive | DPad Down, Left-Y positive |
+| ~left~ | A or Left | X-axis negative | DPad Left, Left-X negative |
+| ~right~ | D or Right | X-axis positive | DPad Right, Left-X positive |
+| ~a~ | J or Z | Button 0 | A button |
+| ~b~ | K or X | Button 1 | B button |
+| ~start~ | Return | Button 7 | Start button |
+| ~select~ | (unmapped) | Button 6 | Back button |
+| ~quit~ | Escape | (unmapped) | (unmapped) |
+
+** ~input-held?~
+
+#+begin_src scheme
+(input-held? state action)
+#+end_src
+
+Returns true if the action is currently held down. ~action~ is a symbol like ~'left~ or ~'a~.
+
+** ~input-pressed?~
+
+#+begin_src scheme
+(input-pressed? state action)
+#+end_src
+
+Returns true if the action was pressed in this frame (held now but not in the previous frame).
+
+** ~input-released?~
+
+#+begin_src scheme
+(input-released? state action)
+#+end_src
+
+Returns true if the action was released in this frame (held previously but not now).
+
+** ~create-input-state~
+
+#+begin_src scheme
+(create-input-state config)
+#+end_src
+
+Initializes an input state record from a configuration. All actions start as unpressed.
+
+** Example: Player Movement
+
+#+begin_src scheme
+(define (update-player game dt)
+ (let ((input (game-input game))
+ (player (car (scene-entities (game-scene game)))))
+ (if (input-pressed? input 'a)
+ (apply-jump player #t)
+ player)))
+#+end_src
+
+* Renderer (~downstroke/renderer~)
+
+#+begin_src scheme
+(import downstroke/renderer)
+#+end_src
+
+The renderer module provides SDL2 drawing abstractions.
+
+** ~render-scene!~
+
+#+begin_src scheme
+(render-scene! renderer scene)
+#+end_src
+
+Draws the entire scene: first the tilemap layers, then all entities. Called automatically by ~game-run!~ before the user's ~render:~ hook. Does nothing if the scene is ~#f~.
+
+** ~entity-screen-coords~
+
+#+begin_src scheme
+(entity-screen-coords entity camera)
+#+end_src
+
+Returns a list ~(x y width height)~ of the entity's bounding box in screen (viewport) coordinates. Pure function, testable without SDL2.
+
+Example:
+
+#+begin_src scheme
+(entity-screen-coords player (make-camera x: 100 y: 50))
+; If player is at world (200, 100) with size (16, 16):
+; Returns (100 50 16 16) -- offset by camera position
+#+end_src
+
+** ~entity-flip~
+
+#+begin_src scheme
+(entity-flip entity)
+#+end_src
+
+Returns an SDL2 flip list based on the entity's ~#:facing~ field. Returns ~'(horizontal)~ if facing is -1, ~'()~ otherwise.
+
+** ~draw-ui-text~
+
+#+begin_src scheme
+(draw-ui-text renderer font text color x y)
+#+end_src
+
+Renders a single line of text to the screen at the given pixel coordinates. ~color~ is an SDL2 color struct. Positions are in screen (viewport) space, not world space. Does not cache; call once per frame for each text element.
+
+* Assets (~downstroke/assets~)
+
+#+begin_src scheme
+(import downstroke/assets)
+#+end_src
+
+The assets module provides a simple registry for game resources.
+
+** ~make-asset-registry~
+
+#+begin_src scheme
+(make-asset-registry)
+#+end_src
+
+Creates an empty asset registry (hash-table). Returned by ~make-game~ automatically.
+
+** ~asset-set!~
+
+#+begin_src scheme
+(asset-set! registry key value)
+#+end_src
+
+Stores a value in the registry by key (any hashable value). Overwrites any existing value.
+
+** ~asset-ref~
+
+#+begin_src scheme
+(asset-ref registry key)
+#+end_src
+
+Retrieves a value from the registry by key. Returns ~#f~ if not found.
+
+** Example: Storing Fonts
+
+#+begin_src scheme
+(define (preload game)
+ (let ((font (ttf:open-font "assets/font.ttf" 16)))
+ (game-asset-set! game 'main-font font)))
+
+(define (render-overlay game)
+ (let ((font (game-asset game 'main-font)))
+ (draw-ui-text (game-renderer game) font "Score: 100"
+ (sdl2:make-color 255 255 255) 10 10)))
+#+end_src
+
+* Sound (~downstroke/sound~)
+
+#+begin_src scheme
+(import downstroke/sound)
+#+end_src
+
+The sound module provides music and sound effect playback via SDL_mixer.
+
+** ~init-audio!~
+
+#+begin_src scheme
+(init-audio!)
+#+end_src
+
+Initializes SDL_mixer with default settings (44100 Hz, stereo, 512 buffer). Call once in the ~preload:~ hook.
+
+** ~load-sounds!~
+
+#+begin_src scheme
+(load-sounds! sound-alist)
+#+end_src
+
+Loads sound effects from an alist of ~(name . path)~ pairs, where ~name~ is a symbol and ~path~ is a file path. Stores them globally for playback.
+
+Example:
+
+#+begin_src scheme
+(load-sounds! '((jump . "assets/jump.wav")
+ (hit . "assets/hit.wav")))
+#+end_src
+
+** ~play-sound~
+
+#+begin_src scheme
+(play-sound name)
+#+end_src
+
+Plays a loaded sound effect by name (symbol). Plays on the first available channel. No-op if the sound is not found.
+
+** ~load-music!~
+
+#+begin_src scheme
+(load-music! path)
+#+end_src
+
+Loads background music from a file. Replaces any previously loaded music.
+
+** ~play-music!~
+
+#+begin_src scheme
+(play-music! volume)
+#+end_src
+
+Plays the loaded music on an infinite loop. ~volume~ is a number from 0.0 to 1.0.
+
+** ~stop-music!~
+
+#+begin_src scheme
+(stop-music!)
+#+end_src
+
+Stops the currently playing music immediately.
+
+** ~set-music-volume!~
+
+#+begin_src scheme
+(set-music-volume! volume)
+#+end_src
+
+Changes the music volume while it is playing. ~volume~ is 0.0 to 1.0.
+
+** ~cleanup-audio!~
+
+#+begin_src scheme
+(cleanup-audio!)
+#+end_src
+
+Releases all audio resources. Call at shutdown or in a cleanup hook.
+
+* Animation (~downstroke/animation~)
+
+#+begin_src scheme
+(import downstroke/animation)
+#+end_src
+
+The animation module provides simple frame-based sprite animation.
+
+** ~set-animation~
+
+#+begin_src scheme
+(set-animation entity name)
+#+end_src
+
+Switches the entity to a named animation, resetting frame and tick counters to 0. No-op if the animation is already active (prevents restarting mid-loop). Returns a new entity.
+
+** ~animate-entity~
+
+#+begin_src scheme
+(animate-entity entity animations)
+#+end_src
+
+Advances the entity's animation frame based on elapsed ticks. ~animations~ is an alist of ~(name . animation-plist)~ pairs. Each animation plist has ~#:frames~ (list of tile IDs, 0-indexed) and ~#:duration~ (ticks per frame). Returns a new entity with updated ~#:anim-tick~, ~#:anim-frame~, and ~#:tile-id~.
+
+Example:
+
+#+begin_src scheme
+(define player-animations
+ '((idle #:frames (28) #:duration 10)
+ (walk #:frames (27 28) #:duration 10)
+ (jump #:frames (29) #:duration 1)))
+
+(define player (list #:anim-name 'walk #:anim-frame 0 #:anim-tick 0))
+(animate-entity player player-animations)
+; After 10 ticks, frame advances to 1 (wraps to 0 after reaching length)
+#+end_src
+
+** ~frame->tile-id~
+
+#+begin_src scheme
+(frame->tile-id frames frame-idx)
+#+end_src
+
+Converts a frame index to a tile ID (1-indexed). Used internally by ~animate-entity~.
+
+* Scene Loader (~downstroke/scene-loader~)
+
+#+begin_src scheme
+(import downstroke/scene-loader)
+#+end_src
+
+The scene-loader module provides utilities for loading Tiled maps and instantiating entities from prefabs.
+
+** ~game-load-scene!~
+
+#+begin_src scheme
+(game-load-scene! game filename)
+#+end_src
+
+Loads a TMX (Tiled) map from a file and creates a scene. Steps:
+
+1. Loads the tilemap from the file
+2. Creates an SDL2 texture from the tileset image
+3. Creates an empty scene with the tilemap and camera
+4. Stores the tilemap in game assets under key ~'tilemap~
+5. Sets the scene on the game
+6. Returns the scene
+
+Example:
+
+#+begin_src scheme
+(define (create-game game)
+ (game-load-scene! game "assets/level1.tmx")
+ ; Now add entities to the scene...
+ )
+#+end_src
+
+** ~create-tileset-texture~
+
+#+begin_src scheme
+(create-tileset-texture renderer tilemap)
+#+end_src
+
+Creates an SDL2 texture from the tileset image embedded in a tilemap struct. Useful when you need the texture independently of ~game-load-scene!~.
+
+** ~make-prefab-registry~
+
+#+begin_src scheme
+(make-prefab-registry name1 constructor1 name2 constructor2 ...)
+#+end_src
+
+Creates a hash-table mapping symbol names to entity constructor functions. Constructors have the signature ~(lambda (x y w h) entity)~.
+
+Example:
+
+#+begin_src scheme
+(define (make-player x y w h)
+ (list #:type 'player #:x x #:y y #:width w #:height h
+ #:vx 0 #:vy 0 #:gravity? #t #:tile-id 29))
+
+(define (make-enemy x y w h)
+ (list #:type 'enemy #:x x #:y y #:width w #:height h
+ #:vx -2 #:vy 0 #:gravity? #t #:tile-id 5))
+
+(define prefabs
+ (make-prefab-registry
+ 'player make-player
+ 'enemy make-enemy))
+#+end_src
+
+** ~instantiate-prefab~
+
+#+begin_src scheme
+(instantiate-prefab registry type x y w h)
+#+end_src
+
+Looks up a constructor by type in the registry and calls it with the given position and size. Returns the entity plist, or ~#f~ if the type is not registered.
+
+** ~tilemap-objects->entities~
+
+#+begin_src scheme
+(tilemap-objects->entities tilemap instantiate-fn)
+#+end_src
+
+Converts the TMX object list (from Tiled) into entity plists. Each object's type (string from XML) is converted to a symbol and passed to ~instantiate-fn~. Filters out ~#f~ results (unregistered types).
+
+~instantiate-fn~ should have the signature ~(lambda (type x y w h) entity-or-#f)~, typically the curried version of ~instantiate-prefab~:
+
+Example:
+
+#+begin_src scheme
+(let* ((prefabs (make-prefab-registry 'player make-player 'enemy make-enemy))
+ (inst (lambda (t x y w h) (instantiate-prefab prefabs t x y w h)))
+ (entities (tilemap-objects->entities tilemap inst)))
+ (scene-entities-set! scene entities))
+#+end_src