1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
|
#+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)
(scale 1)
(frame-delay 16)
(input-config *default-input-config*)
(preload #f)
(create #f)
(update #f)
(render #f)
(debug? #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 | Logical game width in pixels |
| ~height~ | integer | 480 | Logical game height in pixels |
| ~scale~ | positive integer | 1 | Whole-game pixel scaling factor (window = width×scale by height×scale) |
| ~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! |
| ~debug?~ | boolean | #f | Enable debug overlay drawing (collision boxes) |
The game object is the central hub. Use it to store/retrieve assets, manage scenes, and access the current input state.
*** Scaling
The ~scale:~ parameter controls integer pixel scaling for the entire game. When ~scale:~ is greater than 1, the OS window is created at ~width×scale~ by ~height×scale~ pixels, but SDL2's logical renderer size is set to ~width~ by ~height~. This means all game code (rendering, physics, coordinates) works in the logical resolution — SDL2 handles the upscaling automatically.
This affects everything uniformly: tiles, sprites, text, colored rectangles, and debug overlays. Mouse/touch input coordinates are also automatically mapped back to the logical resolution.
#+begin_src scheme
;; 320×240 game rendered in a 640×480 window (2× pixel scaling)
(make-game title: "Pixel Art Game" width: 320 height: 240 scale: 2)
;; 256×224 NES-style with 3× scaling → 768×672 window
(make-game title: "Retro Game" width: 256 height: 224 scale: 3)
#+end_src
Only positive integers are accepted; fractional or zero values signal an error.
*** Fullscreen
Downstroke does not provide a built-in ~fullscreen:~ keyword, but you can make any game fullscreen by setting the SDL2 window flag after the game starts. Use the ~preload:~ hook (the window exists by then):
#+begin_src scheme
(make-game
title: "My Game" width: 320 height: 240 scale: 2
preload: (lambda (game)
;; 'fullscreen-desktop scales to fill the screen without changing resolution
(sdl2:window-fullscreen-set! (game-window game) 'fullscreen-desktop)))
#+end_src
The two fullscreen modes are:
| Mode | SDL2 symbol | Behavior |
|------+-------------+-----------|
| Desktop fullscreen | ~'fullscreen-desktop~ | Fills the screen at the desktop resolution; SDL2 handles scaling and letterboxing. Best for most games. |
| Exclusive fullscreen | ~'fullscreen~ | Changes the display resolution to match ~width×scale~ by ~height×scale~. Use only when you need exclusive display control. |
Combine with ~scale:~ to get pixel-perfect fullscreen: set your logical resolution small (e.g. 320×240), use ~scale:~ for the default windowed size, and let ~'fullscreen-desktop~ handle the rest.
** ~game-run!~
#+begin_src scheme
(game-run! game)
#+end_src
Starts the main event loop. Initializes SDL2, opens the window (at ~width×scale~ by ~height×scale~ pixels), sets the logical render size when ~scale~ > 1, 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. Set the renderer clear color from the current scene's ~background:~ (see ~make-scene~), then clear the framebuffer (~#f~ or invalid value uses opaque black)
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)
(tileset #f)
(camera #f)
(tileset-texture #f)
(camera-target #f)
(background #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 |
| ~tileset~ | tileset/false | #f | Tileset metadata (from ~load-tileset~) when there is no tilemap; required with ~tileset-texture~ to draw ~#:tile-id~ sprites without a TMX map |
| ~camera~ | camera/false | #f | Viewport position |
| ~tileset-texture~ | SDL2 texture/false | #f | Rendered tileset image |
| ~camera-target~ | symbol/false | #f | Tag symbol of the entity to follow (set via ~update-scene~) |
| ~background~ | list/false | #f | Framebuffer clear color: ~(r g b)~ or ~(r g b a)~ (0–255). ~#f~ means opaque black. Set each frame in ~game-run!~ before ~SDL_RenderClear~. |
** ~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
Returns a new camera centered on an entity, clamping to stay within world bounds (never negative). ~viewport-w~ and ~viewport-h~ are the game window dimensions. The original camera is not modified.
** ~scene-add-entity~
#+begin_src scheme
(scene-add-entity scene entity)
#+end_src
Appends an entity to the scene's entity list. Returns a new scene; the original is not modified.
** ~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. Returns a new scene with the updated entity list; the original is not modified.
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-sync-groups~
#+begin_src scheme
(scene-sync-groups scene)
#+end_src
For every entity with ~#:group-id~ that is not an origin (~#:group-origin?~ is false), sets ~#:x~ and ~#:y~ to the corresponding origin’s position plus that entity’s ~#:group-local-x~ and ~#:group-local-y~. Origins are read from ~scene-entities~, so after a tween or other motion that returns a *new* origin plist, replace that origin in the scene’s list (match on ~#:group-id~ / ~#:group-origin?~) before calling ~scene-sync-groups~. Call after updating origin positions and before per-entity physics so platforms and collisions see a consistent pose. Returns a new scene; the original is not modified.
** ~scene-filter-entities~
#+begin_src scheme
(scene-filter-entities scene predicate)
#+end_src
Keeps only entities that satisfy the predicate. Returns a new scene; the original is not modified.
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)
Read accessors:
- ~scene-entities~, ~scene-tilemap~, ~scene-camera~, ~scene-tileset-texture~, ~scene-camera-target~, ~scene-background~
Functional updater (returns a new scene with the specified fields changed):
#+begin_src scheme
(update-scene scene entities: new-entities camera-target: 'player)
#+end_src
Mutating setters (~scene-entities-set!~, etc.) are also generated but should be avoided in favour of ~update-scene~ and the pure pipeline functions above. Use ~game-scene-set!~ at the boundary to store the final scene back on the game struct.
** ~tilemap-tile-at~
#+begin_src scheme
(tilemap-tile-at tilemap col row)
#+end_src
Returns the tile ID at grid position ~(col, row)~ across all layers. Returns ~0~ if the position is out of bounds or all layers have a 0 tile at that cell. Used internally by the physics module; also useful for manual tile queries.
* 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. It also defines ~entity-skips-pipeline?~ and the ~define-pipeline~ macro for frame pipeline steps that respect ~#:skip-pipelines~ (see ~docs/physics.org~ for the built-in physics step names).
** ~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
** ~entity-skips-pipeline?~
#+begin_src scheme
(entity-skips-pipeline? entity step-symbol)
#+end_src
Returns true if ~step-symbol~ appears in the entity’s ~#:skip-pipelines~ list (and that list is non-empty). The built-in physics step names are documented in ~docs/physics.org~; other engine modules may reserve additional symbols for their own frame phases (rendering, animation, etc.) using the same plist key.
** ~define-pipeline~
#+begin_src scheme
(define-pipeline (procedure-name skip-symbol) (entity-formal extra-formal ...) body ...)
#+end_src
Syntax for authors of per-entity pipeline steps: expands to a ~define~ that returns the **first** formal (the entity) unchanged when ~skip-symbol~ is listed in ~#:skip-pipelines~; otherwise runs ~body ...~ inside ~(let () ...)~. Used throughout ~downstroke-physics~; other modules can use it for consistent skip behavior. The procedure name and skip symbol differ when needed (e.g. ~detect-on-solid~ vs ~on-solid~).
** 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? |
| ~#:skip-pipelines~ | list | Symbols naming pipeline steps to skip; physics defines the built-in set (~docs/physics.org~); ~'tweens~ skips ~step-tweens~ |
| ~#:tween~ | tween/false | Active tween struct, auto-advanced by ~step-tweens~ |
| ~#: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 provides functions for movement, collision detection, and ground sensing. Call them manually in your ~update:~ hook in the order that suits your game type (see ~docs/physics.org~ for examples).
** Physics Pipeline Order
The built-in physics functions are normally run in this order each frame (after reading input, before rendering):
1. ~apply-jump~ — if jump pressed and on ground, set ~#:ay~
2. ~apply-acceleration~ — consume ~#:ay~ into ~#:vy~
3. ~apply-gravity~ — add gravity to ~#:vy~
4. ~apply-velocity-x~ — move by ~#:vx~
5. ~resolve-tile-collisions-x~ — snap against horizontal tile collisions
6. ~apply-velocity-y~ — move by ~#:vy~
7. ~resolve-tile-collisions-y~ — snap against vertical tile collisions
8. ~detect-on-solid~ — set ~#:on-ground?~ if standing on a tile and/or another solid entity (optional third argument)
9. ~resolve-entity-collisions~ — push apart solid entities (whole list)
Entities may list ~#:skip-pipelines~ to omit specific steps; see ~entity-skips-pipeline?~ under ~downstroke-entity~ and ~docs/physics.org~.
(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-on-solid~
#+begin_src scheme
(detect-on-solid entity tilemap #!optional other-entities)
#+end_src
Sets ~#:on-ground?~ to true if the entity is supported by a solid tile (probe below the feet) and/or, when ~other-entities~ is a list, by another solid's top surface (e.g. moving platforms). Omit the third argument for tile-only detection. Only applies if ~#:gravity?~ is true. Returns a new entity (the ~?~-suffix denotes call-site readability, not a boolean return value). Prefer calling after all collision resolution when using the entity list.
** ~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.
** ~input-any-pressed?~
#+begin_src scheme
(input-any-pressed? state config)
#+end_src
Returns true if any action in the configuration was pressed this frame. Useful for "press any key to continue" prompts.
** ~input-state->string~
#+begin_src scheme
(input-state->string state config)
#+end_src
Returns a human-readable string of all currently held actions, e.g., ~"[Input: left a]"~. Useful for debug displays.
** ~set-facing-from-vx~
#+begin_src scheme
(set-facing-from-vx entity vx)
#+end_src
Sets ~#:facing~ to ~1~ if ~vx > 0~, ~-1~ if ~vx < 0~, and returns the entity unchanged if ~vx = 0~. Convenience helper for keeping the facing direction in sync with horizontal velocity.
** ~apply-input-to-entity~
#+begin_src scheme
(apply-input-to-entity entity held?)
#+end_src
Applies an input map stored on the entity to compute a new ~#:vx~. The entity must have an ~#:input-map~ key: an alist of ~(action . (dvx . dvy))~ pairs. ~held?~ is a predicate ~(lambda (action) ...)~ (typically ~(lambda (a) (input-held? input a))~). Also updates ~#:facing~ from the resulting velocity.
Example:
#+begin_src scheme
;; Entity with embedded input map:
(list #:type 'player
#:input-map '((left . (-3 . 0)) (right . (3 . 0)))
#:move-speed 1
...)
;; In update:
(apply-input-to-entity player (lambda (a) (input-held? input a)))
#+end_src
** 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: tilemap layers (if any), then every entity. Sprites use ~#:tile-id~ and the scene's tileset texture when both are available; otherwise an entity with ~#:color~ ~(r g b)~ or ~(r g b a)~ is drawn as a filled rectangle. 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.
** Debug Drawing
Debug drawing displays collision boxes and tile grid overlays to visualize physics during development. Enable with the ~debug?:~ keyword on ~make-game~.
*** ~render-debug-scene!~
#+begin_src scheme
(render-debug-scene! renderer scene)
#+end_src
Renders debug overlays for the entire scene: tile grid boundaries and entity collision boxes. Call this in your render hook to see what the physics engine sees.
*** ~draw-debug-tiles~
#+begin_src scheme
(draw-debug-tiles renderer camera tilemap)
#+end_src
Draws a grid outline around all non-zero tiles in the tilemap. Useful for understanding tilemap layout and collision geometry. Color: purple (~+debug-tile-color+~).
*** ~draw-debug-entities~
#+begin_src scheme
(draw-debug-entities renderer camera scene)
#+end_src
Draws colored bounding boxes around all entities in the scene. Also draws attack hitboxes when the entity's ~#:attack-timer~ is active. Entity colors are:
| Type | Color | Value |
|------|-------|-------|
| Player | Blue (~+debug-player-color+~) | rgb(64, 128, 255) |
| Enemy | Red (~+debug-enemy-color+~) | rgb(220, 40, 40) |
| Attack Hitbox | Green (~+debug-attack-color+~) | rgb(0, 200, 80) |
| Tile | Purple (~+debug-tile-color+~) | rgb(140, 0, 220) |
*** Example: Enable Debug Mode
#+begin_src scheme
(define my-game
(make-game
title: "My Game" width: 600 height: 400
debug?: #t)) ;; Enable debug overlay
(game-run! my-game)
#+end_src
With debug mode enabled, the game renders the normal scene first, then overlays collision boxes and tile boundaries on top. This is useful for finding collision bugs and understanding the physics layout.
** Menus
The ~draw-menu-items~ helper renders a list of menu options with a cursor indicator.
*** ~draw-menu-items~
#+begin_src scheme
(draw-menu-items renderer font items cursor x y-start y-step
#!key (label-fn identity) (color #f) (prefix "> ") (no-prefix " "))
#+end_src
Renders a vertical menu with a cursor indicator on the currently selected item.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| ~renderer~ | renderer | (required) | SDL2 renderer |
| ~font~ | TTF font | (required) | Font for rendering text |
| ~items~ | list | (required) | List of menu items (any values) |
| ~cursor~ | integer | (required) | Index of highlighted item (0-based) |
| ~x~ | integer | (required) | X position in screen space |
| ~y-start~ | integer | (required) | Y position of first item |
| ~y-step~ | integer | (required) | Pixel spacing between items |
| ~label-fn~ | procedure | ~identity~ | Function to convert item to string (called for each item) |
| ~color~ | SDL2 color | white (255, 255, 255) | Text color |
| ~prefix~ | string | "~> ~" | Prefix for highlighted item |
| ~no-prefix~ | string | "~ ~" | Prefix for non-highlighted items |
*** Example: Menu State
#+begin_src scheme
(import downstroke-engine
downstroke-world
(prefix sdl2 "sdl2:")
(prefix sdl2-ttf "ttf:"))
(define menu-items '(("Start Game") ("Options") ("Quit")))
(define cursor (make-parameter 0))
(define my-game
(make-game
title: "Menu Game" width: 600 height: 400
preload: (lambda (game)
(let ((font (ttf:open-font "assets/font.ttf" 24)))
(game-asset-set! game 'menu-font font)))
create: (lambda (game)
(game-add-state! game 'menu
(make-game-state
render: (lambda (game)
(let ((font (game-asset game 'menu-font)))
(draw-menu-items (game-renderer game) font menu-items
(cursor)
100 100 40))))))))
(game-run! my-game)
#+end_src
** Sprite Fonts
Sprite fonts render text using a bitmap tileset instead of system fonts. Characters are stored as tiles in your tileset image, making them pixel-perfect and zero-dependency on TTF files.
*** ~make-sprite-font*~
#+begin_src scheme
(make-sprite-font* #!key tile-size spacing ranges)
#+end_src
Creates a sprite font from character ranges.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| ~tile-size~ | integer | (required) | Pixel width/height of each character tile |
| ~spacing~ | integer | 1 | Pixels between characters when rendered |
| ~ranges~ | list | (required) | List of ~(start-char end-char first-tile-id)~ triples |
The ~ranges~ parameter defines which tile IDs correspond to which characters. For example, ~(list (#\A #\M 917) (#\N #\Z 966) (#\0 #\9 868))~ maps characters A–M to tile IDs 917–929, N–Z to 966–978, and 0–9 to 868–877.
Characters are automatically uppercased when rendered (all lookups use uppercase).
*** ~sprite-font-char->tile-id~
#+begin_src scheme
(sprite-font-char->tile-id font ch)
#+end_src
Returns the tile ID for a character, or ~#f~ if not found. The character is automatically uppercased before lookup.
*** ~sprite-text-width~
#+begin_src scheme
(sprite-text-width font text)
#+end_src
Computes the pixel width of a string when rendered with the given font. Useful for centering text. Formula: ~(* n tile-size) + (* (- n 1) spacing)~ where ~n~ is the string length.
*** ~draw-sprite-text~
#+begin_src scheme
(draw-sprite-text renderer tileset-texture tileset font text x y)
#+end_src
Renders sprite-font text to the screen. ~tileset-texture~ is the SDL2 texture of your tileset image, and ~tileset~ is the tilemap structure containing tile layout information. Characters not in the font are silently skipped.
| Parameter | Type | Description |
|-----------|------|-------------|
| ~renderer~ | renderer | SDL2 renderer |
| ~tileset-texture~ | SDL2 texture | Rendered tileset image |
| ~tileset~ | tileset struct | Tileset from loaded tilemap |
| ~font~ | sprite-font struct | Font created with ~make-sprite-font*~ |
| ~text~ | string | Text to render |
| ~x, y~ | integers | Screen position in pixels |
*** Example: Sprite Font for Score Display
#+begin_src scheme
(import downstroke-engine
downstroke-scene-loader
downstroke-renderer)
;; Create a sprite font with A-Z at tiles 917–942, 0-9 at tiles 868–877
(define score-font
(make-sprite-font*
tile-size: 16
spacing: 1
ranges: (list
(list #\A #\Z 917)
(list #\0 #\9 868))))
(define my-game
(make-game
title: "Score Game" width: 600 height: 400
create: (lambda (game)
(game-load-scene! game "assets/level.tmx"))
render: (lambda (game)
(let ((scene (game-scene game)))
;; Display "SCORE: 1500" using sprite font
(draw-sprite-text (game-renderer game)
(scene-tileset-texture scene)
(tilemap-tileset (scene-tilemap scene))
score-font
"SCORE: 1500"
10 10)))))
(game-run! my-game)
#+end_src
* 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.
* Tweens (~downstroke-tween~)
#+begin_src scheme
(import downstroke-tween)
#+end_src
Time-based interpolation of numeric entity properties. Library-only — call from ~update:~; see ~docs/tweens.org~ for patterns with ~#:skip-pipelines~.
** ~make-tween~
#+begin_src scheme
(make-tween entity #!key props duration (delay 0) ease
(on-complete #f) (repeat 0) (yoyo? #f))
#+end_src
| Keyword | Description |
|---------+-------------|
| ~props~ | Alist ~((#:key . target-number) ...)~ |
| ~duration~ | Milliseconds of interpolation after ~delay~ |
| ~delay~ | Initial wait in ms (default 0, first cycle only) |
| ~ease~ | Symbol (e.g. ~quad-in-out~) or ~(lambda (t) ...)~ with ~t~ in [0,1] |
| ~on-complete~ | Optional ~(lambda (entity) ...)~ once at final completion (not called with ~repeat: -1~) |
| ~repeat~ | ~0~ = play once (default), ~N~ = replay N extra times, ~-1~ = loop forever |
| ~yoyo?~ | ~#f~ (default) = same direction, ~#t~ = reverse each cycle |
** ~tween-step~
#+begin_src scheme
(tween-step tween entity dt)
#+end_src
Returns two values: updated tween struct and updated entity. ~dt~ is elapsed milliseconds for this frame.
** ~tween-finished?~ / ~tween-active?~
** ~step-tweens~
#+begin_src scheme
(step-tweens entity dt)
#+end_src
Pipeline step: auto-advances ~#:tween~ on an entity. No-op if ~#:tween~ is absent. Removes ~#:tween~ when the tween finishes. Skipped when ~'tweens~ is in ~#:skip-pipelines~. See ~docs/tweens.org~ for patterns.
** Easing exports
~ease-linear~, ~ease-quad-in~, ~ease-quad-out~, ~ease-quad-in-out~, ~ease-cubic-in~, ~ease-cubic-out~, ~ease-cubic-in-out~, ~ease-sine-in-out~, ~ease-expo-in~, ~ease-expo-out~, ~ease-expo-in-out~, ~ease-back-out~, ~ease-named~, ~ease-resolve~.
* Animation (~downstroke-animation~)
#+begin_src scheme
(import downstroke-animation)
#+end_src
The animation module provides simple frame-based sprite animation.
** ~animation-frames~
#+begin_src scheme
(animation-frames anim)
#+end_src
Returns the ~#:frames~ list from an animation plist. Each element is a 0-indexed tile ID.
** ~animation-duration~
#+begin_src scheme
(animation-duration anim)
#+end_src
Returns the ~#:duration~ value from an animation plist (ticks per frame).
** ~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~.
* Tilemap (~downstroke-tilemap~)
#+begin_src scheme
(import downstroke-tilemap)
#+end_src
The tilemap module parses Tiled TMX and TSX files using the expat XML library, and provides struct accessors for tile and map data.
** ~load-tilemap~
#+begin_src scheme
(load-tilemap filename)
#+end_src
Loads and parses a TMX file. Automatically resolves and loads the referenced TSX tileset and its image. Returns a ~tilemap~ struct.
** ~load-tileset~
#+begin_src scheme
(load-tileset filename)
#+end_src
Loads and parses a TSX tileset file. Loads the image referenced in the tileset. Returns a ~tileset~ struct.
** ~tileset-tile~
#+begin_src scheme
(tileset-tile tileset tile-id)
#+end_src
Returns a ~tile~ struct for the given 1-indexed tile ID. The tile struct has ~tile-id~ and ~tile-rect~ (an SDL2 rect) fields with the source rectangle in the tileset image.
** ~tileset-rows~
#+begin_src scheme
(tileset-rows tileset)
#+end_src
Returns the number of rows in the tileset image (computed from ~tilecount / columns~).
** Tileset Accessors (auto-generated by defstruct)
- ~tileset-tilewidth~, ~tileset-tileheight~ — tile dimensions in pixels
- ~tileset-spacing~ — pixel gap between tiles in the source image
- ~tileset-tilecount~ — total number of tiles
- ~tileset-columns~ — number of tile columns
- ~tileset-image-source~ — path to the tileset image file
- ~tileset-image~ — loaded SDL2 surface
** Tilemap Accessors (auto-generated by defstruct)
- ~tilemap-width~, ~tilemap-height~ — map dimensions in tiles
- ~tilemap-tilewidth~, ~tilemap-tileheight~ — tile dimensions in pixels
- ~tilemap-tileset~ — the embedded ~tileset~ struct
- ~tilemap-layers~ — list of ~layer~ structs
- ~tilemap-objects~ — list of ~object~ structs (from the Tiled object layer)
** Layer Accessors (auto-generated by defstruct)
- ~layer-name~ — layer name string
- ~layer-width~, ~layer-height~ — layer dimensions in tiles
- ~layer-map~ — 2D list of tile GIDs (rows × columns)
** Object Accessors (auto-generated by defstruct)
- ~object-name~, ~object-type~ — object name and type (strings from XML)
- ~object-x~, ~object-y~ — position in pixels
- ~object-width~, ~object-height~ — size in pixels
- ~object-properties~ — alist of custom properties from Tiled
* 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
** ~game-load-tilemap!~
#+begin_src scheme
(game-load-tilemap! game key filename)
#+end_src
Loads a TMX tilemap file, stores it in the game asset registry under ~key~, and returns the tilemap struct.
** ~game-load-tileset!~
#+begin_src scheme
(game-load-tileset! game key filename)
#+end_src
Loads a TSX tileset file, stores it in the game asset registry under ~key~, and returns the tileset struct.
** ~game-load-font!~
#+begin_src scheme
(game-load-font! game key filename size)
#+end_src
Opens a TTF font at ~size~ points, stores it in the game asset registry under ~key~, and returns the font. Wraps ~ttf:open-font~.
** ~create-texture-from-tileset~
#+begin_src scheme
(create-texture-from-tileset renderer tileset)
#+end_src
Creates an SDL2 texture from a tileset struct's image surface (after ~load-tileset~ / ~game-load-tileset!~). Use with ~make-scene~ when ~tilemap~ is ~#f~ but entities use ~#:tile-id~.
** ~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!~.
** ~load-prefabs~
#+begin_src scheme
(load-prefabs filename engine-mixin-table user-hooks)
#+end_src
Loads a prefab definition file and returns a ~prefab-registry~ struct. The file must contain a Scheme expression with ~mixins~ and ~prefabs~ sections; an optional ~group-prefabs~ section defines multi-entity assemblies (see ~docs/entities.org~).
| Parameter | Type | Description |
|-----------|------|-------------|
| ~filename~ | string | Path to the prefab data file (e.g., ~"assets/prefabs.scm"~) |
| ~engine-mixin-table~ | alist | Extra engine-level mixins to merge; pass ~'()~ if none |
| ~user-hooks~ | alist | Extra instantiation hooks; pass ~'()~ if none |
Example:
#+begin_src scheme
(define *prefabs* (load-prefabs "assets/prefabs.scm" '() '()))
#+end_src
** ~reload-prefabs!~
#+begin_src scheme
(reload-prefabs! registry)
#+end_src
Reloads the prefab file that the registry was originally loaded from. Returns a new registry. Useful for hot-reloading prefab data during development without restarting the game.
** ~instantiate-prefab~
#+begin_src scheme
(instantiate-prefab registry type x y w h)
#+end_src
Looks up a prefab by type symbol in the registry and returns a fresh entity plist at the given position and size. Returns ~#f~ if the type is not registered. If the resulting entity has an ~#:on-instantiate~ hook, it is called with the entity before returning.
** ~instantiate-group-prefab~
#+begin_src scheme
(instantiate-group-prefab registry type origin-x origin-y)
#+end_src
Looks up a *group prefab* by type symbol and returns a list ~(origin member ...)~: one origin entity plus one entity per part. Optional group-level flags ~#:pose-only-origin?~ and ~#:static-parts?~ select origin/part profiles (see ~docs/entities.org~); defaults are ~#f~ (physics-driving origin, non-static parts). Each instance receives a fresh gensym ~#:group-id~ shared by the origin and all members. Returns ~#f~ if the type is not in ~group-prefabs~. After moving origins (tween and/or physics), ensure updated origins are stored in the scene’s entity list, then call ~scene-sync-groups~ so member ~#:x~ / ~#:y~ match ~origin + #:group-local-x/y~.
** ~tilemap-objects->entities~
#+begin_src scheme
(tilemap-objects->entities tilemap registry)
#+end_src
Converts the TMX object list (from Tiled) into entity plists using the prefab registry. Each object's type string is converted to a symbol and passed to ~instantiate-prefab~. Objects whose type has no registered prefab are silently filtered out.
Example:
#+begin_src scheme
(let* ((registry (load-prefabs "assets/prefabs.scm" '() '()))
(entities (tilemap-objects->entities tilemap registry)))
(update-scene scene entities: entities))
#+end_src
|