spawnpoint-studio 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. {spawnpoint_studio-0.1.0/compiler/spawnpoint_studio.egg-info → spawnpoint_studio-0.2.0}/PKG-INFO +56 -16
  2. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/README.md +55 -15
  3. spawnpoint_studio-0.2.0/compiler/spawnpoint/__init__.py +8 -0
  4. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/build.py +3 -1
  5. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/cli.py +24 -4
  6. spawnpoint_studio-0.2.0/compiler/spawnpoint/demo.py +247 -0
  7. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/llm_server.py +48 -5
  8. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/playtest/report.py +4 -1
  9. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/playtest/runner.py +16 -1
  10. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/playtest/tuner.py +43 -14
  11. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/generate.py +12 -0
  12. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/assemble.py +47 -5
  13. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/brief.py +16 -1
  14. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/llm.py +32 -7
  15. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/web.py +2 -2
  16. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/webcheck.py +25 -7
  17. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0/compiler/spawnpoint_studio.egg-info}/PKG-INFO +56 -16
  18. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint_studio.egg-info/SOURCES.txt +9 -0
  19. spawnpoint_studio-0.2.0/compiler/tests/test_demo.py +79 -0
  20. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_playtest.py +24 -7
  21. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_studio.py +63 -0
  22. spawnpoint_studio-0.2.0/examples/dino_eggs.json +99 -0
  23. spawnpoint_studio-0.2.0/examples/night_survivor.json +428 -0
  24. spawnpoint_studio-0.2.0/examples/ninja_rescue.json +351 -0
  25. spawnpoint_studio-0.2.0/godot/addons/spawn_point/components/bubble_board_component.gd +413 -0
  26. spawnpoint_studio-0.2.0/godot/addons/spawn_point/components/experience_component.gd +53 -0
  27. spawnpoint_studio-0.2.0/godot/addons/spawn_point/components/level_up_menu_component.gd +225 -0
  28. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/spawner_component.gd +23 -1
  29. spawnpoint_studio-0.2.0/godot/addons/spawn_point/components/xp_orb_component.gd +67 -0
  30. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/manifest.json +395 -0
  31. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/hud.gd +48 -1
  32. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/world.gd +43 -0
  33. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/playtest.gd +57 -12
  34. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/record.gd +1 -0
  35. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/pyproject.toml +1 -1
  36. spawnpoint_studio-0.1.0/compiler/spawnpoint/__init__.py +0 -3
  37. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/LICENSE +0 -0
  38. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/MANIFEST.in +0 -0
  39. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/analyze.py +0 -0
  40. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/cockpit/__init__.py +0 -0
  41. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/cockpit/server.py +0 -0
  42. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/cockpit/static/app.css +0 -0
  43. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/cockpit/static/app.js +0 -0
  44. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/cockpit/static/index.html +0 -0
  45. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/diagnostics.py +0 -0
  46. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/expr.py +0 -0
  47. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/gif.py +0 -0
  48. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/godot.py +0 -0
  49. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/graph.py +0 -0
  50. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/level.py +0 -0
  51. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/manifest.py +0 -0
  52. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/playtest/__init__.py +0 -0
  53. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/project.py +0 -0
  54. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/py.typed +0 -0
  55. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/rules.py +0 -0
  56. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sfx.py +0 -0
  57. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/__init__.py +0 -0
  58. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/ai.py +0 -0
  59. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/contour.py +0 -0
  60. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/importer.py +0 -0
  61. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/resolve.py +0 -0
  62. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/spriteset.py +0 -0
  63. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/tiles.py +0 -0
  64. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/__init__.py +0 -0
  65. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/creative.py +0 -0
  66. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/dataset.py +0 -0
  67. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/edits.py +0 -0
  68. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/levelgen.py +0 -0
  69. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/orchestrator.py +0 -0
  70. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/prompts.py +0 -0
  71. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio_cli.py +0 -0
  72. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint_studio.egg-info/dependency_links.txt +0 -0
  73. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint_studio.egg-info/entry_points.txt +0 -0
  74. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint_studio.egg-info/requires.txt +0 -0
  75. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint_studio.egg-info/top_level.txt +0 -0
  76. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/conftest.py +0 -0
  77. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_ai_sprites.py +0 -0
  78. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_analyze.py +0 -0
  79. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_cockpit.py +0 -0
  80. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_e2e.py +0 -0
  81. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_expr.py +0 -0
  82. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_gif.py +0 -0
  83. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_godot_library.py +0 -0
  84. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_graph.py +0 -0
  85. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_level.py +0 -0
  86. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_live_llm.py +0 -0
  87. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_manifest.py +0 -0
  88. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_rules.py +0 -0
  89. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_sfx.py +0 -0
  90. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_sprites.py +0 -0
  91. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_web.py +0 -0
  92. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/examples/coin_hopper.json +0 -0
  93. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/examples/neon_arena.json +0 -0
  94. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/examples/pixel_arena.json +0 -0
  95. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/examples/pixel_hopper.json +0 -0
  96. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/aim_component.gd +0 -0
  97. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/animator_component.gd +0 -0
  98. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/chase_ai_component.gd +0 -0
  99. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/collectible_component.gd +0 -0
  100. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/gate_component.gd +0 -0
  101. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/health_component.gd +0 -0
  102. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/hitbox_component.gd +0 -0
  103. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/hurtbox_component.gd +0 -0
  104. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/juice_component.gd +0 -0
  105. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/patrol_ai_component.gd +0 -0
  106. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/platformer_movement_component.gd +0 -0
  107. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/player_input_component.gd +0 -0
  108. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/projectile_component.gd +0 -0
  109. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/stomp_component.gd +0 -0
  110. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/top_down_movement_component.gd +0 -0
  111. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/trigger_zone_component.gd +0 -0
  112. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/weapon_component.gd +0 -0
  113. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/camera_rig.gd +0 -0
  114. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/cockpit.gd +0 -0
  115. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/entity_core.gd +0 -0
  116. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/fx.gd +0 -0
  117. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/game_state.gd +0 -0
  118. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/level.gd +0 -0
  119. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/sfx.gd +0 -0
  120. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/sp.gd +0 -0
  121. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/shaders/flash.gdshader +0 -0
  122. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/shaders/floor_grid.gdshader +0 -0
  123. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/build.gd +0 -0
  124. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/build.tscn +0 -0
  125. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/capture.gd +0 -0
  126. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/capture.tscn +0 -0
  127. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/dump_manifest.gd +0 -0
  128. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/dump_manifest.tscn +0 -0
  129. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/playtest.tscn +0 -0
  130. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/playtest_nav.gd +0 -0
  131. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/record.tscn +0 -0
  132. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/smoke.gd +0 -0
  133. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/smoke.tscn +0 -0
  134. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/setup.cfg +0 -0
  135. {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spawnpoint-studio
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Turn a sentence into a juicy, playable Godot 4 game — locally, on a consumer GPU.
5
5
  Author: smoky1496
6
6
  License: MIT
@@ -24,7 +24,13 @@ Dynamic: license-file
24
24
 
25
25
  # Spawn Point
26
26
 
27
- **Compile a small JSON "game graph" into a juicy, runnable Godot 4 game — in seconds.**
27
+ **Describe a 2D game in one sentence — get a playable, juicy Godot 4 game in seconds.**
28
+
29
+ [![CI](https://github.com/leluong141996-dev/Spawn-Point/actions/workflows/ci.yml/badge.svg)](https://github.com/leluong141996-dev/Spawn-Point/actions/workflows/ci.yml)
30
+ [![PyPI](https://img.shields.io/pypi/v/spawnpoint-studio)](https://pypi.org/project/spawnpoint-studio/)
31
+ [![Model](https://img.shields.io/badge/%F0%9F%A4%97%20model-SpawnPoint--Coder--7B-yellow)](https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ)
32
+ [![Play the demos](https://img.shields.io/badge/%E2%96%B6%20play-demos%20in%20your%20browser-ff004d)](https://leluong141996-dev.github.io/Spawn-Point/)
33
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
28
34
 
29
35
  <p align="center">
30
36
  <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/pixel-hopper.gif" width="48%" alt="Pixel Hopper — generated platformer">
@@ -34,7 +40,10 @@ Dynamic: license-file
34
40
  <code>examples/pixel_arena.json</code>: generated sprites, collision traced from pixels, juice on by
35
41
  default. Recorded with <code>spawnpoint gif</code> (autopilot).</sub></p>
36
42
 
37
- Spawn Point is the foundation of an autonomous game-studio agent. Instead of asking an LLM to type
43
+ <p align="center"><b><a href="https://leluong141996-dev.github.io/Spawn-Point/">▶ Play the demos in your browser</a></b>: a Mario-style platformer, a ninja
44
+ rescue, a dinosaur-egg bubble shooter, a survivor with level-ups and an arena shooter. No install needed.</p>
45
+
46
+ Spawn Point is an open-source, autonomous game studio that runs on your own machine. Instead of asking an LLM to type
38
47
  hundreds of lines of GDScript (slow, expensive, and easy to break with Godot 3/4 mix-ups), the LLM
39
48
  assembles **pre-tested components** like LEGO, places them on an **ASCII map**, and wires gameplay
40
49
  with **declarative rules**. A deterministic compiler validates everything and lets Godot itself write
@@ -54,6 +63,24 @@ graph.json ──► validate (Python) ──► build scenes (Godot headless)
54
63
  | Hitboxes | drift from visuals | derived from the same shape as the visual |
55
64
  | Failure mode | crashes, syntax errors | structured diagnostics *before* Godot runs |
56
65
 
66
+ ## Install
67
+
68
+ Requirements: Python ≥ 3.10, [Godot 4.6](https://godotengine.org/download) on your `PATH` (`godot4` or
69
+ `godot`, or set `GODOT_BIN`), and for the LLM features one of: a 12 GB NVIDIA GPU with Docker (vLLM, fastest),
70
+ [Ollama](https://ollama.com/download) on any machine (Mac, AMD, or CPU), or any OpenAI-compatible API.
71
+
72
+ ```bash
73
+ pip install spawnpoint-studio
74
+ spawnpoint llm up # SpawnPoint-Coder-7B: vLLM on NVIDIA, otherwise Ollama (or set SPAWNPOINT_LLM_*)
75
+ spawnpoint create "a ninja platformer where you collect stars"
76
+ ```
77
+
78
+ No GPU? [Play the demos in your browser](https://leluong141996-dev.github.io/Spawn-Point/), or build the hand-written examples locally, no LLM needed:
79
+
80
+ ```bash
81
+ spawnpoint examples copy pixel_hopper && spawnpoint build pixel_hopper.json --run
82
+ ```
83
+
57
84
  ## Spawn Point Studio (web cockpit)
58
85
 
59
86
  ```bash
@@ -79,7 +106,7 @@ Standard library only on the server, one HTML/JS page on the client, Server-Sent
79
106
  ## Make a game from a sentence
80
107
 
81
108
  ```bash
82
- scripts/llm-up.sh # SpawnPoint-Coder-7B on vLLM (~11 GB VRAM) — or any OpenAI-compatible API
109
+ spawnpoint llm up # SpawnPoint-Coder-7B: vLLM (~11 GB VRAM) or Ollama — or any OpenAI-compatible API
83
110
  spawnpoint create "a ninja platformer where you collect stars"
84
111
  ```
85
112
 
@@ -125,10 +152,13 @@ edits as strict JSON, and the compiler turns that into Godot scenes and GDScript
125
152
  GPU (AWQ 4-bit). v2 on 30 human-written game ideas (English and Vietnamese): 87% fully right (v1: 73%),
126
153
  97% of checks passed; on held-out tasks: briefs 88% exact (v1: 36%), Vietnamese → English 97% (v1: 52%),
127
154
  tweaks 98%, entity edits 91%. It is tuned for 2D platformers and 2D top-down games only; other genres
128
- are no better than a general 7B coder. `scripts/llm-up.sh` downloads and serves it.
155
+ are no better than a general 7B coder. `spawnpoint llm up` downloads and serves it: the AWQ build on vLLM when
156
+ Docker and an NVIDIA GPU are present, otherwise the [GGUF build](https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-GGUF)
157
+ (Q4_K_M, ~4.7 GB) in Ollama. Force one with `--backend vllm|ollama`.
129
158
 
130
- LLM settings: `SPAWNPOINT_LLM_BASE_URL` (default `http://localhost:8000/v1`),
131
- `SPAWNPOINT_LLM_MODEL` (default: the server's first model), `SPAWNPOINT_LLM_API_KEY`.
159
+ LLM settings: `SPAWNPOINT_LLM_BASE_URL` (default: vLLM at `http://localhost:8000/v1`, or Ollama at
160
+ `http://localhost:11434/v1` when only Ollama answers), `SPAWNPOINT_LLM_MODEL` (default: SpawnPoint-Coder if the
161
+ server has it, else its first model), `SPAWNPOINT_LLM_API_KEY`.
132
162
  Web builds need the Godot **web export templates** (Editor → Manage Export Templates, or unpack
133
163
  `web_*.zip` from the official `.tpz` into `~/.local/share/godot/export_templates/<version>/`);
134
164
  use `--no-web` to skip and play natively with `spawnpoint run`.
@@ -223,13 +253,10 @@ stop making the mistakes the normalizer and compiler had to fix (roadmap item 7)
223
253
 
224
254
  ## Quick start (hand-written graphs)
225
255
 
226
- Requirements: Python ≥ 3.10 and [Godot 4.6](https://godotengine.org/download) on your `PATH`
227
- (`godot4` or `godot`, or set `GODOT_BIN`).
228
-
229
256
  ```bash
230
- pip install -e ".[dev]"
231
- spawnpoint build examples/coin_hopper.json --run # platformer
232
- spawnpoint build examples/neon_arena.json --run # top-down shooter
257
+ spawnpoint examples list
258
+ spawnpoint examples copy coin_hopper && spawnpoint build coin_hopper.json --run # platformer
259
+ spawnpoint examples copy neon_arena && spawnpoint build neon_arena.json --run # top-down shooter
233
260
  ```
234
261
 
235
262
  Controls: move **WASD / arrows**, jump **Space / Z / W**, dash **Shift / C**, shoot **X / J / left mouse**,
@@ -306,7 +333,9 @@ headless smoke test (spawn, scripted input, crash detection) and report `E_RUNTI
306
333
  | Movement | `PlatformerMovement` (coyote time, jump buffer, variable jump, apex hang, dash, double jump), `TopDownMovement` (accel/friction, dash) |
307
334
  | Controllers | `PlayerInput`, `PatrolAI`, `ChaseAI`, `Aim` |
308
335
  | Combat | `Health`, `Hitbox`, `Hurtbox`, `Stomp`, `Weapon`, `Projectile` |
309
- | World | `Collectible`, `TriggerZone`, `Spawner`, `Gate` |
336
+ | World | `Collectible`, `TriggerZone`, `Spawner` (ramping waves, spawns around the player), `Gate` |
337
+ | Progression | `Experience` (XP and levels), `XpOrb` (gems that fly to the player), `LevelUpMenu` (pause and pick one of three upgrades) |
338
+ | Game modes | `BubbleBoard` (a complete bubble shooter: bank shots, colour pops, falling clusters, a descending ceiling) |
310
339
  | Feel | `Juice` — attached to every entity unless `"juice": "none"` |
311
340
 
312
341
  Runtime autoloads: `GameState` (score, lives, variables, win/lose), `World` (spawning, rule
@@ -321,25 +350,36 @@ godot/ Godot project hosting the component library and its t
321
350
  tests/ headless GDScript tests
322
351
  compiler/spawnpoint/ Python compiler: graph schema, analysis, rules codegen, sfx, CLI
323
352
  compiler/spawnpoint/studio LLM orchestrator: brief, assembler, level generators, web export, browser check
324
- compiler/spawnpoint/sprites SpriteSet format, pixel-art generator, importer, contour → collision
353
+ compiler/spawnpoint/sprites SpriteSet format, pixel-art generator and tiles, importer, contour → collision, AI-art providers
354
+ compiler/spawnpoint/playtest bots, jump-aware planner, telemetry, heatmaps, tuner
355
+ compiler/spawnpoint/cockpit web studio: HTTP + SSE server and the single-page UI
325
356
  compiler/tests/ pytest (includes end-to-end builds and the GDScript suite)
326
357
  examples/ example graphs
327
- docs/ design specs
358
+ scripts/ helper scripts (local LLM)
359
+ docs/media/ README GIFs
328
360
  ```
329
361
 
330
362
  ## Development
331
363
 
332
364
  ```bash
365
+ git clone https://github.com/leluong141996-dev/Spawn-Point && cd Spawn-Point
366
+ pip install -e ".[dev]"
333
367
  pytest # everything: compiler, studio, end-to-end builds, web + browser, GDScript suite
334
368
  pytest -m "not llm" # without the live-LLM tests
335
369
  spawnpoint manifest # regenerate manifest.json after changing a component
336
370
  godot4 --headless --path godot --fixed-fps 60 res://tests/run_tests.tscn # GDScript suite only
337
371
  ```
338
372
 
373
+ `spawnpoint demo -o site` builds the browser demo site: the showcase graphs in `examples/`, one shared Godot
374
+ engine, each game checked in headless Chrome. The Pages workflow publishes it on every push.
375
+
339
376
  Adding a component: create `godot/addons/spawn_point/components/<name>_component.gd` following the
340
377
  contract (typed `@export`s with ranges, `SP_NAME` / `SP_HOSTS` / `SP_REQUIRES` / `SP_CATEGORY`
341
378
  constants, `##` docs on everything public), add tests in `godot/tests/`, then run `spawnpoint manifest`.
342
379
 
380
+ Contributions are welcome: open an issue to discuss a bigger change first, keep `pytest -m "not llm"`
381
+ green, and add a test with every fix. CI runs the full suite (with Godot) on every push and pull request.
382
+
343
383
  ## Roadmap
344
384
 
345
385
  1. ✅ Component library with juice
@@ -1,6 +1,12 @@
1
1
  # Spawn Point
2
2
 
3
- **Compile a small JSON "game graph" into a juicy, runnable Godot 4 game — in seconds.**
3
+ **Describe a 2D game in one sentence — get a playable, juicy Godot 4 game in seconds.**
4
+
5
+ [![CI](https://github.com/leluong141996-dev/Spawn-Point/actions/workflows/ci.yml/badge.svg)](https://github.com/leluong141996-dev/Spawn-Point/actions/workflows/ci.yml)
6
+ [![PyPI](https://img.shields.io/pypi/v/spawnpoint-studio)](https://pypi.org/project/spawnpoint-studio/)
7
+ [![Model](https://img.shields.io/badge/%F0%9F%A4%97%20model-SpawnPoint--Coder--7B-yellow)](https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ)
8
+ [![Play the demos](https://img.shields.io/badge/%E2%96%B6%20play-demos%20in%20your%20browser-ff004d)](https://leluong141996-dev.github.io/Spawn-Point/)
9
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
4
10
 
5
11
  <p align="center">
6
12
  <img src="https://raw.githubusercontent.com/leluong141996-dev/Spawn-Point/main/docs/media/pixel-hopper.gif" width="48%" alt="Pixel Hopper — generated platformer">
@@ -10,7 +16,10 @@
10
16
  <code>examples/pixel_arena.json</code>: generated sprites, collision traced from pixels, juice on by
11
17
  default. Recorded with <code>spawnpoint gif</code> (autopilot).</sub></p>
12
18
 
13
- Spawn Point is the foundation of an autonomous game-studio agent. Instead of asking an LLM to type
19
+ <p align="center"><b><a href="https://leluong141996-dev.github.io/Spawn-Point/">▶ Play the demos in your browser</a></b>: a Mario-style platformer, a ninja
20
+ rescue, a dinosaur-egg bubble shooter, a survivor with level-ups and an arena shooter. No install needed.</p>
21
+
22
+ Spawn Point is an open-source, autonomous game studio that runs on your own machine. Instead of asking an LLM to type
14
23
  hundreds of lines of GDScript (slow, expensive, and easy to break with Godot 3/4 mix-ups), the LLM
15
24
  assembles **pre-tested components** like LEGO, places them on an **ASCII map**, and wires gameplay
16
25
  with **declarative rules**. A deterministic compiler validates everything and lets Godot itself write
@@ -30,6 +39,24 @@ graph.json ──► validate (Python) ──► build scenes (Godot headless)
30
39
  | Hitboxes | drift from visuals | derived from the same shape as the visual |
31
40
  | Failure mode | crashes, syntax errors | structured diagnostics *before* Godot runs |
32
41
 
42
+ ## Install
43
+
44
+ Requirements: Python ≥ 3.10, [Godot 4.6](https://godotengine.org/download) on your `PATH` (`godot4` or
45
+ `godot`, or set `GODOT_BIN`), and for the LLM features one of: a 12 GB NVIDIA GPU with Docker (vLLM, fastest),
46
+ [Ollama](https://ollama.com/download) on any machine (Mac, AMD, or CPU), or any OpenAI-compatible API.
47
+
48
+ ```bash
49
+ pip install spawnpoint-studio
50
+ spawnpoint llm up # SpawnPoint-Coder-7B: vLLM on NVIDIA, otherwise Ollama (or set SPAWNPOINT_LLM_*)
51
+ spawnpoint create "a ninja platformer where you collect stars"
52
+ ```
53
+
54
+ No GPU? [Play the demos in your browser](https://leluong141996-dev.github.io/Spawn-Point/), or build the hand-written examples locally, no LLM needed:
55
+
56
+ ```bash
57
+ spawnpoint examples copy pixel_hopper && spawnpoint build pixel_hopper.json --run
58
+ ```
59
+
33
60
  ## Spawn Point Studio (web cockpit)
34
61
 
35
62
  ```bash
@@ -55,7 +82,7 @@ Standard library only on the server, one HTML/JS page on the client, Server-Sent
55
82
  ## Make a game from a sentence
56
83
 
57
84
  ```bash
58
- scripts/llm-up.sh # SpawnPoint-Coder-7B on vLLM (~11 GB VRAM) — or any OpenAI-compatible API
85
+ spawnpoint llm up # SpawnPoint-Coder-7B: vLLM (~11 GB VRAM) or Ollama — or any OpenAI-compatible API
59
86
  spawnpoint create "a ninja platformer where you collect stars"
60
87
  ```
61
88
 
@@ -101,10 +128,13 @@ edits as strict JSON, and the compiler turns that into Godot scenes and GDScript
101
128
  GPU (AWQ 4-bit). v2 on 30 human-written game ideas (English and Vietnamese): 87% fully right (v1: 73%),
102
129
  97% of checks passed; on held-out tasks: briefs 88% exact (v1: 36%), Vietnamese → English 97% (v1: 52%),
103
130
  tweaks 98%, entity edits 91%. It is tuned for 2D platformers and 2D top-down games only; other genres
104
- are no better than a general 7B coder. `scripts/llm-up.sh` downloads and serves it.
131
+ are no better than a general 7B coder. `spawnpoint llm up` downloads and serves it: the AWQ build on vLLM when
132
+ Docker and an NVIDIA GPU are present, otherwise the [GGUF build](https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-GGUF)
133
+ (Q4_K_M, ~4.7 GB) in Ollama. Force one with `--backend vllm|ollama`.
105
134
 
106
- LLM settings: `SPAWNPOINT_LLM_BASE_URL` (default `http://localhost:8000/v1`),
107
- `SPAWNPOINT_LLM_MODEL` (default: the server's first model), `SPAWNPOINT_LLM_API_KEY`.
135
+ LLM settings: `SPAWNPOINT_LLM_BASE_URL` (default: vLLM at `http://localhost:8000/v1`, or Ollama at
136
+ `http://localhost:11434/v1` when only Ollama answers), `SPAWNPOINT_LLM_MODEL` (default: SpawnPoint-Coder if the
137
+ server has it, else its first model), `SPAWNPOINT_LLM_API_KEY`.
108
138
  Web builds need the Godot **web export templates** (Editor → Manage Export Templates, or unpack
109
139
  `web_*.zip` from the official `.tpz` into `~/.local/share/godot/export_templates/<version>/`);
110
140
  use `--no-web` to skip and play natively with `spawnpoint run`.
@@ -199,13 +229,10 @@ stop making the mistakes the normalizer and compiler had to fix (roadmap item 7)
199
229
 
200
230
  ## Quick start (hand-written graphs)
201
231
 
202
- Requirements: Python ≥ 3.10 and [Godot 4.6](https://godotengine.org/download) on your `PATH`
203
- (`godot4` or `godot`, or set `GODOT_BIN`).
204
-
205
232
  ```bash
206
- pip install -e ".[dev]"
207
- spawnpoint build examples/coin_hopper.json --run # platformer
208
- spawnpoint build examples/neon_arena.json --run # top-down shooter
233
+ spawnpoint examples list
234
+ spawnpoint examples copy coin_hopper && spawnpoint build coin_hopper.json --run # platformer
235
+ spawnpoint examples copy neon_arena && spawnpoint build neon_arena.json --run # top-down shooter
209
236
  ```
210
237
 
211
238
  Controls: move **WASD / arrows**, jump **Space / Z / W**, dash **Shift / C**, shoot **X / J / left mouse**,
@@ -282,7 +309,9 @@ headless smoke test (spawn, scripted input, crash detection) and report `E_RUNTI
282
309
  | Movement | `PlatformerMovement` (coyote time, jump buffer, variable jump, apex hang, dash, double jump), `TopDownMovement` (accel/friction, dash) |
283
310
  | Controllers | `PlayerInput`, `PatrolAI`, `ChaseAI`, `Aim` |
284
311
  | Combat | `Health`, `Hitbox`, `Hurtbox`, `Stomp`, `Weapon`, `Projectile` |
285
- | World | `Collectible`, `TriggerZone`, `Spawner`, `Gate` |
312
+ | World | `Collectible`, `TriggerZone`, `Spawner` (ramping waves, spawns around the player), `Gate` |
313
+ | Progression | `Experience` (XP and levels), `XpOrb` (gems that fly to the player), `LevelUpMenu` (pause and pick one of three upgrades) |
314
+ | Game modes | `BubbleBoard` (a complete bubble shooter: bank shots, colour pops, falling clusters, a descending ceiling) |
286
315
  | Feel | `Juice` — attached to every entity unless `"juice": "none"` |
287
316
 
288
317
  Runtime autoloads: `GameState` (score, lives, variables, win/lose), `World` (spawning, rule
@@ -297,25 +326,36 @@ godot/ Godot project hosting the component library and its t
297
326
  tests/ headless GDScript tests
298
327
  compiler/spawnpoint/ Python compiler: graph schema, analysis, rules codegen, sfx, CLI
299
328
  compiler/spawnpoint/studio LLM orchestrator: brief, assembler, level generators, web export, browser check
300
- compiler/spawnpoint/sprites SpriteSet format, pixel-art generator, importer, contour → collision
329
+ compiler/spawnpoint/sprites SpriteSet format, pixel-art generator and tiles, importer, contour → collision, AI-art providers
330
+ compiler/spawnpoint/playtest bots, jump-aware planner, telemetry, heatmaps, tuner
331
+ compiler/spawnpoint/cockpit web studio: HTTP + SSE server and the single-page UI
301
332
  compiler/tests/ pytest (includes end-to-end builds and the GDScript suite)
302
333
  examples/ example graphs
303
- docs/ design specs
334
+ scripts/ helper scripts (local LLM)
335
+ docs/media/ README GIFs
304
336
  ```
305
337
 
306
338
  ## Development
307
339
 
308
340
  ```bash
341
+ git clone https://github.com/leluong141996-dev/Spawn-Point && cd Spawn-Point
342
+ pip install -e ".[dev]"
309
343
  pytest # everything: compiler, studio, end-to-end builds, web + browser, GDScript suite
310
344
  pytest -m "not llm" # without the live-LLM tests
311
345
  spawnpoint manifest # regenerate manifest.json after changing a component
312
346
  godot4 --headless --path godot --fixed-fps 60 res://tests/run_tests.tscn # GDScript suite only
313
347
  ```
314
348
 
349
+ `spawnpoint demo -o site` builds the browser demo site: the showcase graphs in `examples/`, one shared Godot
350
+ engine, each game checked in headless Chrome. The Pages workflow publishes it on every push.
351
+
315
352
  Adding a component: create `godot/addons/spawn_point/components/<name>_component.gd` following the
316
353
  contract (typed `@export`s with ranges, `SP_NAME` / `SP_HOSTS` / `SP_REQUIRES` / `SP_CATEGORY`
317
354
  constants, `##` docs on everything public), add tests in `godot/tests/`, then run `spawnpoint manifest`.
318
355
 
356
+ Contributions are welcome: open an issue to discuss a bigger change first, keep `pytest -m "not llm"`
357
+ green, and add a test with every fix. CI runs the full suite (with Godot) on every push and pull request.
358
+
319
359
  ## Roadmap
320
360
 
321
361
  1. ✅ Component library with juice
@@ -0,0 +1,8 @@
1
+ """Spawn Point: compile JSON game graphs into runnable Godot 4 projects."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("spawnpoint-studio") # single source of truth: pyproject.toml
7
+ except PackageNotFoundError: # running from a source tree that was never installed
8
+ __version__ = "0.0.0+source"
@@ -142,5 +142,7 @@ def _judge_smoke(report: dict, plan: dict, diags: Diagnostics) -> None:
142
142
  return
143
143
  if plan["genre"] == "platformer" and not report.get("on_floor_after_1s"):
144
144
  diags.warning("W_SMOKE_FLOOR", "(smoke)", "the player is not standing on a floor after 1 second")
145
- if report.get("moved_px", 0) < 4:
145
+ player = next((e for e in plan["entities"] if any(c["name"] == "PlayerInput" for c in e["components"])), None)
146
+ moves = player is None or any(c["name"] in ("PlatformerMovement", "TopDownMovement") for c in player["components"])
147
+ if moves and report.get("moved_px", 0) < 4: # a bubble-shooter launcher does not walk
146
148
  diags.warning("W_SMOKE_STUCK", "(smoke)", "the player did not move under scripted input (stuck?)")
@@ -73,14 +73,20 @@ def main(argv: list[str] | None = None) -> int:
73
73
  tw.add_argument("--no-web", action="store_true")
74
74
  tw.add_argument("--record", action="store_true", help="save LLM calls as local fine-tuning data")
75
75
 
76
- lm = sub.add_parser("llm", help="run SpawnPoint-Coder-7B locally (Docker + NVIDIA GPU)")
76
+ lm = sub.add_parser("llm", help="run SpawnPoint-Coder-7B locally (vLLM on NVIDIA, or Ollama anywhere)")
77
77
  lm.add_argument("action", choices=["up", "down", "status"])
78
78
  lm.add_argument("--port", type=int, default=8000)
79
+ lm.add_argument("--backend", choices=["auto", "vllm", "ollama"], default="auto",
80
+ help="auto: vLLM with Docker + an NVIDIA GPU, otherwise Ollama")
79
81
 
80
82
  ex = sub.add_parser("examples", help="list the example graphs, or copy one here")
81
83
  ex.add_argument("action", nargs="?", choices=["list", "copy"], default="list")
82
84
  ex.add_argument("name", nargs="?")
83
85
 
86
+ dm = sub.add_parser("demo", help="build every example as a web game plus a gallery page (static site)")
87
+ dm.add_argument("-o", "--out", type=Path, default=Path("site"))
88
+ dm.add_argument("--no-screenshots", action="store_true", help="skip the headless-Chrome check and thumbnails")
89
+
84
90
  gf = sub.add_parser("gif", help="record a gameplay GIF of a built project (autopilot; needs a display)")
85
91
  gf.add_argument("project", type=Path)
86
92
  gf.add_argument("-o", "--out", type=Path, help="output file (default: <project>/demo.gif)")
@@ -130,7 +136,8 @@ def main(argv: list[str] | None = None) -> int:
130
136
  return {"validate": _validate, "build": _build, "run": _run, "components": _components,
131
137
  "manifest": _manifest, "create": studio_cli.create, "tweak": studio_cli.tweak,
132
138
  "serve": studio_cli.serve, "dataset": studio_cli.dataset, "playtest": studio_cli.playtest, "studio": studio_cli.studio,
133
- "sprites": _sprites, "gif": _gif, "llm": _llm, "examples": _examples}[args.cmd](args)
139
+ "sprites": _sprites, "gif": _gif, "llm": _llm, "examples": _examples,
140
+ "demo": _demo}[args.cmd](args)
134
141
  except godot.GodotError as e:
135
142
  print(f"error: {e}", file=sys.stderr)
136
143
  if e.output:
@@ -207,8 +214,9 @@ def _llm(args) -> int:
207
214
  from . import llm_server
208
215
 
209
216
  try:
210
- return {"up": lambda: llm_server.up(args.port), "down": llm_server.down,
211
- "status": lambda: llm_server.status(args.port)}[args.action]()
217
+ return {"up": lambda: llm_server.up(args.port, backend=args.backend),
218
+ "down": lambda: llm_server.down(backend=args.backend),
219
+ "status": lambda: llm_server.status(args.port, backend=args.backend)}[args.action]()
212
220
  except (RuntimeError, subprocess.CalledProcessError) as e:
213
221
  print(f"error: {e}", file=sys.stderr)
214
222
  return 2
@@ -242,6 +250,18 @@ def _gif(args) -> int:
242
250
  return 0
243
251
 
244
252
 
253
+ def _demo(args) -> int:
254
+ from .demo import build_site
255
+
256
+ print(f"Building the demo site in {args.out}/")
257
+ games = build_site(args.out, screenshots=not args.no_screenshots)
258
+ failed = [g for g in games if g.errors]
259
+ for g in failed:
260
+ print(f"error: {g.slug}: " + "; ".join(g.errors[:3]), file=sys.stderr)
261
+ print(f"{len(games) - len(failed)}/{len(games)} games ok. Preview: python -m http.server -d {args.out}")
262
+ return 1 if failed else 0
263
+
264
+
245
265
  def _sprites(args) -> int:
246
266
  from .sprites import generate as gen, importer
247
267
  from .sprites.spriteset import SpriteError, load
@@ -0,0 +1,247 @@
1
+ """Static demo site: every example game built for the web, playable from one gallery page.
2
+
3
+ spawnpoint demo --out site
4
+
5
+ The games are the SHOWCASE examples below. Each one is built, exported as a single-threaded web build
6
+ (works on GitHub Pages, no special headers), checked in headless Chrome (no console errors, a real
7
+ screenshot for the card) and linked from index.html.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import html
13
+ import io
14
+ import json
15
+ import shutil
16
+ import tempfile
17
+ from dataclasses import dataclass, field
18
+ from pathlib import Path
19
+
20
+ from . import manifest as manifest_mod
21
+ from .build import build, load_graph
22
+ from .studio import web, webcheck
23
+
24
+ REPO = "https://github.com/leluong141996-dev/Spawn-Point"
25
+ MODEL = "https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ"
26
+ # (example graph, genre tag, one-line pitch, controls). Controls are ASCII: they travel through Godot's
27
+ # export_presets.cfg into the game page.
28
+ SHOWCASE = [
29
+ ("pixel_hopper.json", "platformer",
30
+ "A Mario-style romp: stomp slimes, double-jump, dash, and collect every coin to open the door.",
31
+ "Move: arrows / WASD | Jump: Space (twice to double-jump) | Dash: Shift | Restart: R"),
32
+ ("ninja_rescue.json", "platformer",
33
+ "Mom has been caged! Throw shurikens, dodge the masked throwers, grab the key up high and set her free.",
34
+ "Move: arrows / WASD | Jump: Space | Shuriken: X / J | Dash: Shift | Restart: R"),
35
+ ("dino_eggs.json", "bubble shooter",
36
+ "The classic egg shooter: bank shots off the walls, pop three dinosaur eggs of a colour, drop whole clusters.",
37
+ "Aim: mouse or Left / Right | Shoot: click / Space / X | Restart: R"),
38
+ ("night_survivor.json", "survivor",
39
+ "Survive three minutes of the night: auto-fire, collect gems, level up and pick a new power each time.",
40
+ "Move: arrows / WASD | Dash: Shift | Pick an upgrade: 1 / 2 / 3 or click | Restart: R"),
41
+ ("pixel_arena.json", "arena shooter",
42
+ "A top-down arena: blast through robots and turrets, then reach the portal.",
43
+ "Move: WASD | Aim: mouse | Shoot: click / X | Dash: Shift | Restart: R"),
44
+ ]
45
+
46
+
47
+ def game_overlay(controls: str) -> str:
48
+ """Injected into every game page: a way back to the gallery and the controls (bottom-left, clear of
49
+ the HUD). Hidden for screenshots (?shot)."""
50
+ return (
51
+ "<style>#sp-bar{position:fixed;bottom:30px;left:8px;z-index:10;display:flex;gap:10px;align-items:center;"
52
+ "font:12px/1.4 ui-monospace,Menlo,monospace;color:#fff;background:rgba(0,0,0,.55);padding:5px 10px;"
53
+ "border-radius:6px;pointer-events:none}#sp-bar a{color:#ffec27;text-decoration:none;pointer-events:auto}"
54
+ "@media (max-width:700px){#sp-bar span{display:none}}</style>"
55
+ "<script>addEventListener('DOMContentLoaded',function(){if(location.search.indexOf('shot')>=0)return;"
56
+ "var b=document.createElement('div');b.id='sp-bar';"
57
+ "b.innerHTML='<a href=\\\"../\\\">&lt;- all games</a><span>" + controls + "</span>';"
58
+ "document.body.appendChild(b);});</script>"
59
+ )
60
+
61
+
62
+ @dataclass
63
+ class Game:
64
+ slug: str
65
+ graph: Path
66
+ title: str = ""
67
+ tag: str = ""
68
+ blurb: str = ""
69
+ controls: str = ""
70
+ errors: list[str] = field(default_factory=list)
71
+
72
+
73
+ def discover(examples: Path | None = None) -> list[Game]:
74
+ examples = examples or manifest_mod.EXAMPLES_DIR
75
+ games: list[Game] = []
76
+ for name, tag, blurb, controls in SHOWCASE:
77
+ path = examples / name
78
+ if path.exists():
79
+ data = load_graph(path)
80
+ games.append(Game(path.stem.replace("_", "-"), path, data.get("name", path.stem), tag, blurb, controls))
81
+ return games
82
+
83
+
84
+ def build_site(out: Path, games: list[Game] | None = None, screenshots: bool = True, log=print) -> list[Game]:
85
+ """Builds every game into out/<slug>/ and writes out/index.html. Returns the games (with any errors)."""
86
+ games = discover() if games is None else games
87
+ out.mkdir(parents=True, exist_ok=True)
88
+ with tempfile.TemporaryDirectory() as tmp:
89
+ for g in games:
90
+ project = Path(tmp) / g.slug
91
+ result = build(load_graph(g.graph), project, base_dir=g.graph.parent)
92
+ if not result.ok:
93
+ g.errors = [str(d) for d in result.diagnostics.errors] or ["build failed"]
94
+ log(f" {g.slug}: build failed: {g.errors[0]}")
95
+ continue
96
+ seconds = web.export(project, head_include=game_overlay(g.controls))
97
+ target = out / g.slug
98
+ shutil.rmtree(target, ignore_errors=True)
99
+ shutil.copytree(project / web.WEB_DIR, target,
100
+ ignore=shutil.ignore_patterns(".gdignore", web.VERSION_FILE))
101
+ share_engine(out, target)
102
+ log(f" {g.slug}: built (web export {seconds:.1f}s)")
103
+ built = [g for g in games if (out / g.slug / "index.html").exists()]
104
+ if screenshots:
105
+ if webcheck.find_chrome() is None:
106
+ log("Chrome not found: skipping the browser check and screenshots")
107
+ else:
108
+ check_in_browser(out, built, log)
109
+ (out / "index.html").write_text(index_html(built))
110
+ (out / ".nojekyll").write_text("") # serve files as they are
111
+ return games
112
+
113
+
114
+ ENGINE_DIR = "engine"
115
+ ENGINE_FILES = ("index.wasm", "index.js", "index.audio.worklet.js", "index.audio.position.worklet.js")
116
+
117
+
118
+ def share_engine(out: Path, game: Path) -> None:
119
+ """Every game ships the same Godot engine (~37 MB of wasm). Keep one copy in out/engine/ and point each
120
+ game at it, so the site is small and the engine is cached after the first game."""
121
+ engine = out / ENGINE_DIR
122
+ engine.mkdir(exist_ok=True)
123
+ for name in ENGINE_FILES:
124
+ src = game / name
125
+ if not src.exists():
126
+ continue
127
+ dst = engine / name
128
+ if not dst.exists():
129
+ shutil.copy2(src, dst)
130
+ elif dst.read_bytes() != src.read_bytes():
131
+ raise RuntimeError(f"{game.name}/{name} differs from the shared engine (mixed Godot versions?)")
132
+ src.unlink()
133
+ page = game / "index.html"
134
+ text = page.read_text()
135
+ patched = (text.replace('src="index.js"', f'src="../{ENGINE_DIR}/index.js"')
136
+ .replace('"executable":"index"', f'"executable":"../{ENGINE_DIR}/index","mainPack":"index.pck"')
137
+ .replace('"index.wasm":', f'"../{ENGINE_DIR}/index.wasm":'))
138
+ if patched.count(f"../{ENGINE_DIR}/") != 3:
139
+ raise RuntimeError(f"{page}: unexpected Godot web shell, cannot point it at the shared engine")
140
+ page.write_text(patched)
141
+
142
+
143
+ def check_in_browser(out: Path, games: list[Game], log=print) -> None:
144
+ """Loads every game from the finished site in headless Chrome: no console errors, plus a thumbnail."""
145
+ import functools
146
+ import threading
147
+ from http.server import ThreadingHTTPServer
148
+
149
+ port = web.free_port(8150)
150
+ httpd = ThreadingHTTPServer(("127.0.0.1", port), functools.partial(web._Handler, directory=str(out)))
151
+ threading.Thread(target=httpd.serve_forever, daemon=True).start()
152
+ try:
153
+ for g in games:
154
+ check = webcheck.check(f"http://localhost:{port}/{g.slug}/?shot", seconds=8)
155
+ g.errors = list(check.errors)
156
+ if not any("Godot Engine" in line for line in check.console):
157
+ g.errors.append("the engine did not start")
158
+ if check.screenshot:
159
+ _thumbnail(check.screenshot, out / g.slug / "thumb.png")
160
+ log(f" {g.slug}: " + ("plays in Chrome" if not g.errors else "; ".join(g.errors[:2])))
161
+ finally:
162
+ httpd.shutdown()
163
+
164
+
165
+ def _thumbnail(png: bytes, path: Path, width: int = 480) -> None:
166
+ from PIL import Image
167
+
168
+ img = Image.open(io.BytesIO(png)).convert("RGB")
169
+ img.thumbnail((width, width))
170
+ img.save(path, optimize=True)
171
+
172
+
173
+ def index_html(games: list[Game]) -> str:
174
+ cards = "\n".join(_card(g) for g in games)
175
+ return f"""<!doctype html>
176
+ <html lang="en">
177
+ <head>
178
+ <meta charset="utf-8">
179
+ <meta name="viewport" content="width=device-width, initial-scale=1">
180
+ <title>Spawn Point demos</title>
181
+ <meta name="description" content="Five Godot 4 games built with Spawn Point: platformers, a bubble shooter, a survivor. Play in your browser.">
182
+ <meta property="og:title" content="Spawn Point demos: play Godot games in your browser">
183
+ <meta property="og:description" content="A Mario-style platformer, a ninja rescue, a dinosaur-egg shooter, a survivor and an arena, all built from Spawn Point components.">
184
+ <style>
185
+ :root {{ --bg:#14121c; --panel:#1f1c2b; --ink:#f4f1ff; --muted:#a59fbf; --accent:#ffec27; --ai:#29adff; --line:#332f45; }}
186
+ @media (prefers-color-scheme: light) {{
187
+ :root:not([data-theme="dark"]) {{ --bg:#f6f4fb; --panel:#ffffff; --ink:#1b1828; --muted:#5d5875; --accent:#b8860b; --ai:#0b6fb8; --line:#e3dff0; }}
188
+ }}
189
+ * {{ box-sizing:border-box }}
190
+ body {{ margin:0; background:var(--bg); color:var(--ink); font:16px/1.55 system-ui,-apple-system,Segoe UI,sans-serif }}
191
+ a {{ color:inherit }}
192
+ header, main, footer {{ max-width:1100px; margin:0 auto; padding:0 16px }}
193
+ header {{ padding-top:40px; padding-bottom:8px }}
194
+ h1 {{ font:700 clamp(28px,5vw,44px)/1.1 ui-monospace,Menlo,monospace; margin:0 0 10px; letter-spacing:-.02em }}
195
+ h1 span {{ color:var(--accent) }}
196
+ .lede {{ color:var(--muted); max-width:720px; margin:0 0 18px }}
197
+ .links {{ display:flex; flex-wrap:wrap; gap:10px; margin-bottom:12px }}
198
+ .links a {{ text-decoration:none; border:1px solid var(--line); background:var(--panel); padding:7px 12px; border-radius:8px; font-size:14px }}
199
+ .links a:hover {{ border-color:var(--accent) }}
200
+ code {{ font:13px ui-monospace,Menlo,monospace; background:var(--panel); border:1px solid var(--line); padding:2px 6px; border-radius:5px }}
201
+ .note {{ color:var(--muted); font-size:13px; margin:4px 0 26px }}
202
+ .grid {{ display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:18px; padding-bottom:40px }}
203
+ .card {{ background:var(--panel); border:1px solid var(--line); border-radius:12px; overflow:hidden; display:flex; flex-direction:column; text-decoration:none; transition:transform .12s, border-color .12s }}
204
+ .card:hover {{ transform:translateY(-2px); border-color:var(--accent) }}
205
+ .thumb {{ aspect-ratio:16/9; background:#000 center/cover no-repeat; image-rendering:pixelated; position:relative }}
206
+ .play {{ position:absolute; right:10px; bottom:10px; background:var(--accent); color:#000; font:700 13px ui-monospace,monospace; padding:5px 10px; border-radius:6px }}
207
+ .body {{ padding:12px 14px 16px; display:flex; flex-direction:column; gap:6px }}
208
+ .title {{ font-weight:700; font-size:17px }}
209
+ .tags {{ display:flex; gap:6px; flex-wrap:wrap }}
210
+ .tag {{ font:600 11px ui-monospace,monospace; text-transform:uppercase; letter-spacing:.04em; padding:2px 7px; border-radius:4px; border:1px solid var(--line); color:var(--muted) }}
211
+ .tag.ai {{ color:var(--ai); border-color:var(--ai) }}
212
+ .blurb {{ margin:2px 0 0; color:var(--ink) }}
213
+ .controls {{ margin:0; color:var(--muted); font:12px/1.5 ui-monospace,Menlo,monospace }}
214
+ footer {{ color:var(--muted); font-size:13px; padding-bottom:40px }}
215
+ </style>
216
+ </head>
217
+ <body>
218
+ <header>
219
+ <h1>Spawn<span>Point</span> demos</h1>
220
+ <p class="lede">Five games, five ways to play, all built by Spawn Point from small JSON game graphs and a
221
+ library of tested Godot 4 components: game feel, pixel art and collisions come for free. Click a card to play in
222
+ your browser, no install needed.</p>
223
+ <div class="links">
224
+ <a href="{REPO}">⭐ GitHub repo</a>
225
+ <a href="{MODEL}">🤗 SpawnPoint-Coder-7B</a>
226
+ <a href="https://pypi.org/project/spawnpoint-studio/">📦 pip install spawnpoint-studio</a>
227
+ </div>
228
+ <p class="note">Desktop + keyboard. Make your own from a sentence:
229
+ <code>spawnpoint create "a ninja platformer where you collect stars"</code></p>
230
+ </header>
231
+ <main class="grid">
232
+ {cards}
233
+ </main>
234
+ <footer>Built by the <a href="{REPO}/blob/main/.github/workflows/pages.yml">pages workflow</a> from the graphs in
235
+ <a href="{REPO}/tree/main/examples">examples/</a>. MIT licensed.</footer>
236
+ </body>
237
+ </html>
238
+ """
239
+
240
+
241
+ def _card(g: Game) -> str:
242
+ esc = html.escape
243
+ thumb = f' style="background-image:url({g.slug}/thumb.png)"'
244
+ tags = f'<span class="tag ai">{esc(g.tag)}</span>' if g.tag else ""
245
+ return (f'<a class="card" href="{g.slug}/"><div class="thumb"{thumb}><span class="play">▶ PLAY</span></div>'
246
+ f'<div class="body"><div class="title">{esc(g.title)}</div><div class="tags">{tags}</div>'
247
+ f'<p class="blurb">{esc(g.blurb)}</p><p class="controls">{esc(g.controls)}</p></div></a>')