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