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.
- {spawnpoint_studio-0.1.0/compiler/spawnpoint_studio.egg-info → spawnpoint_studio-0.2.0}/PKG-INFO +56 -16
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/README.md +55 -15
- spawnpoint_studio-0.2.0/compiler/spawnpoint/__init__.py +8 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/build.py +3 -1
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/cli.py +24 -4
- spawnpoint_studio-0.2.0/compiler/spawnpoint/demo.py +247 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/llm_server.py +48 -5
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/playtest/report.py +4 -1
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/playtest/runner.py +16 -1
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/playtest/tuner.py +43 -14
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/generate.py +12 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/assemble.py +47 -5
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/brief.py +16 -1
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/llm.py +32 -7
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/web.py +2 -2
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/webcheck.py +25 -7
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0/compiler/spawnpoint_studio.egg-info}/PKG-INFO +56 -16
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint_studio.egg-info/SOURCES.txt +9 -0
- spawnpoint_studio-0.2.0/compiler/tests/test_demo.py +79 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_playtest.py +24 -7
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_studio.py +63 -0
- spawnpoint_studio-0.2.0/examples/dino_eggs.json +99 -0
- spawnpoint_studio-0.2.0/examples/night_survivor.json +428 -0
- spawnpoint_studio-0.2.0/examples/ninja_rescue.json +351 -0
- spawnpoint_studio-0.2.0/godot/addons/spawn_point/components/bubble_board_component.gd +413 -0
- spawnpoint_studio-0.2.0/godot/addons/spawn_point/components/experience_component.gd +53 -0
- spawnpoint_studio-0.2.0/godot/addons/spawn_point/components/level_up_menu_component.gd +225 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/spawner_component.gd +23 -1
- spawnpoint_studio-0.2.0/godot/addons/spawn_point/components/xp_orb_component.gd +67 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/manifest.json +395 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/hud.gd +48 -1
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/world.gd +43 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/playtest.gd +57 -12
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/record.gd +1 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/pyproject.toml +1 -1
- spawnpoint_studio-0.1.0/compiler/spawnpoint/__init__.py +0 -3
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/LICENSE +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/MANIFEST.in +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/analyze.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/cockpit/__init__.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/cockpit/server.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/cockpit/static/app.css +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/cockpit/static/app.js +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/cockpit/static/index.html +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/diagnostics.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/expr.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/gif.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/godot.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/graph.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/level.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/manifest.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/playtest/__init__.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/project.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/py.typed +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/rules.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sfx.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/__init__.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/ai.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/contour.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/importer.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/resolve.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/spriteset.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/sprites/tiles.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/__init__.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/creative.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/dataset.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/edits.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/levelgen.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/orchestrator.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio/prompts.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint/studio_cli.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint_studio.egg-info/dependency_links.txt +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint_studio.egg-info/entry_points.txt +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint_studio.egg-info/requires.txt +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/spawnpoint_studio.egg-info/top_level.txt +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/conftest.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_ai_sprites.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_analyze.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_cockpit.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_e2e.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_expr.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_gif.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_godot_library.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_graph.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_level.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_live_llm.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_manifest.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_rules.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_sfx.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_sprites.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/compiler/tests/test_web.py +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/examples/coin_hopper.json +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/examples/neon_arena.json +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/examples/pixel_arena.json +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/examples/pixel_hopper.json +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/aim_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/animator_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/chase_ai_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/collectible_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/gate_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/health_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/hitbox_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/hurtbox_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/juice_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/patrol_ai_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/platformer_movement_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/player_input_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/projectile_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/stomp_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/top_down_movement_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/trigger_zone_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/components/weapon_component.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/camera_rig.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/cockpit.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/entity_core.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/fx.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/game_state.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/level.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/sfx.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/runtime/sp.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/shaders/flash.gdshader +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/shaders/floor_grid.gdshader +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/build.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/build.tscn +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/capture.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/capture.tscn +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/dump_manifest.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/dump_manifest.tscn +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/playtest.tscn +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/playtest_nav.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/record.tscn +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/smoke.gd +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/godot/addons/spawn_point/tools/smoke.tscn +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/setup.cfg +0 -0
- {spawnpoint_studio-0.1.0 → spawnpoint_studio-0.2.0}/setup.py +0 -0
{spawnpoint_studio-0.1.0/compiler/spawnpoint_studio.egg-info → spawnpoint_studio-0.2.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spawnpoint-studio
|
|
3
|
-
Version: 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
|
-
**
|
|
27
|
+
**Describe a 2D game in one sentence — get a playable, juicy Godot 4 game in seconds.**
|
|
28
|
+
|
|
29
|
+
[](https://github.com/leluong141996-dev/Spawn-Point/actions/workflows/ci.yml)
|
|
30
|
+
[](https://pypi.org/project/spawnpoint-studio/)
|
|
31
|
+
[](https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ)
|
|
32
|
+
[](https://leluong141996-dev.github.io/Spawn-Point/)
|
|
33
|
+
[](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
|
|
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
|
-
|
|
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. `
|
|
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:
|
|
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
|
-
|
|
231
|
-
spawnpoint build
|
|
232
|
-
spawnpoint build
|
|
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
|
|
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
|
-
|
|
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
|
-
**
|
|
3
|
+
**Describe a 2D game in one sentence — get a playable, juicy Godot 4 game in seconds.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/leluong141996-dev/Spawn-Point/actions/workflows/ci.yml)
|
|
6
|
+
[](https://pypi.org/project/spawnpoint-studio/)
|
|
7
|
+
[](https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ)
|
|
8
|
+
[](https://leluong141996-dev.github.io/Spawn-Point/)
|
|
9
|
+
[](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
|
|
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
|
-
|
|
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. `
|
|
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:
|
|
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
|
-
|
|
207
|
-
spawnpoint build
|
|
208
|
-
spawnpoint build
|
|
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
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
211
|
-
"
|
|
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=\\\"../\\\"><- 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>')
|