summer-engine 2.7.0 → 2.8.0
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.
- package/.claude-plugin/plugin.json +2 -1
- package/.codex-plugin/plugin.json +3 -2
- package/.cursor-plugin/plugin.json +2 -1
- package/.opencode/INSTALL.md +1 -1
- package/.opencode/plugins/summer.js +1 -1
- package/AGENTS.md +4 -4
- package/CHANGELOG.md +10 -0
- package/GEMINI.md +5 -4
- package/README.md +58 -19
- package/assets/autopilot/README.md +51 -0
- package/assets/autopilot/autopilot.gd +222 -0
- package/assets/autopilot/probe_base.gd +262 -0
- package/assets/autopilot/run.sh +78 -0
- package/commands/gameskill.md +11 -2
- package/dist/bin/summer.js +8 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +53 -0
- package/dist/commands/create.d.ts +1 -0
- package/dist/commands/create.js +38 -6
- package/dist/commands/list.js +1 -1
- package/dist/commands/login.d.ts +19 -0
- package/dist/commands/login.js +123 -36
- package/dist/commands/logout.js +3 -16
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +19 -0
- package/dist/commands/mcp.js +7 -2
- package/dist/commands/open.js +1 -1
- package/dist/commands/publish.d.ts +2 -0
- package/dist/commands/publish.js +20 -0
- package/dist/commands/releases.d.ts +2 -0
- package/dist/commands/releases.js +20 -0
- package/dist/commands/run.js +1 -1
- package/dist/lib/api-client.d.ts +4 -2
- package/dist/lib/api-client.js +28 -4
- package/dist/lib/auth.d.ts +40 -12
- package/dist/lib/auth.js +173 -37
- package/dist/lib/cloud/engine-bridge.d.ts +17 -4
- package/dist/lib/cloud/engine-bridge.js +32 -6
- package/dist/lib/config.d.ts +20 -0
- package/dist/lib/config.js +142 -0
- package/dist/lib/creator.d.ts +93 -0
- package/dist/lib/creator.js +393 -0
- package/dist/lib/engine.d.ts +35 -2
- package/dist/lib/engine.js +240 -6
- package/dist/lib/public-surface-paths.d.ts +13 -0
- package/dist/lib/public-surface-paths.js +42 -0
- package/dist/lib/skills-registry.d.ts +8 -0
- package/dist/lib/skills-registry.js +9 -0
- package/dist/lib/store.d.ts +14 -0
- package/dist/lib/store.js +145 -0
- package/dist/lib/summer-compatibility.d.ts +6 -0
- package/dist/lib/summer-compatibility.js +22 -0
- package/dist/mcp/server.d.ts +8 -1
- package/dist/mcp/server.js +25 -4
- package/dist/mcp/tools/creator-tools.d.ts +2 -0
- package/dist/mcp/tools/creator-tools.js +105 -0
- package/dist/mcp/tools/generate-tools.js +16 -6
- package/dist/mcp/tools/project-tools.js +12 -7
- package/dist/mcp/tools/scene-tools.js +6 -6
- package/dist/mcp/tools/visual-tools.js +4 -3
- package/hooks/pre-commit-doctor.sh +0 -0
- package/hooks/run-hook.cmd +0 -0
- package/hooks/session-start.sh +5 -5
- package/package.json +3 -1
- package/references/collaborative-protocol.md +50 -0
- package/references/gd-style.md +121 -0
- package/references/godot-version.md +51 -0
- package/references/mcp-tools-reference.md +199 -0
- package/references/public-surface-language.md +30 -0
- package/references/summer-folder.md +90 -0
- package/references/template-registry.md +73 -0
- package/skills/2d-assets/character-portrait/SKILL.md +17 -12
- package/skills/2d-assets/concept-art/SKILL.md +10 -9
- package/skills/2d-assets/pixel-art/SKILL.md +36 -20
- package/skills/2d-assets/skybox-panorama/SKILL.md +32 -37
- package/skills/2d-assets/sprite-sheet/SKILL.md +40 -35
- package/skills/2d-assets/tileable-texture/SKILL.md +28 -22
- package/skills/2d-assets/ui-graphics/SKILL.md +46 -32
- package/skills/2d-assets/use-widget-asset/SKILL.md +8 -6
- package/skills/3d-assets/character-model/SKILL.md +34 -19
- package/skills/3d-assets/environment-kit/SKILL.md +11 -5
- package/skills/3d-assets/organic-model/SKILL.md +8 -5
- package/skills/3d-assets/prop-model/SKILL.md +9 -3
- package/skills/3d-assets/vehicle-model/SKILL.md +32 -19
- package/skills/README.md +1 -1
- package/skills/ai-and-npcs/design-npc/SKILL.md +31 -13
- package/skills/animation/animation-tree/SKILL.md +39 -15
- package/skills/animation/facial-and-lipsync/SKILL.md +25 -8
- package/skills/animation/generate-motion/SKILL.md +47 -15
- package/skills/animation/procedural-animation/SKILL.md +31 -13
- package/skills/animation/retarget/SKILL.md +127 -79
- package/skills/audio/adaptive-music/SKILL.md +24 -20
- package/skills/audio/ambient-bed/SKILL.md +22 -16
- package/skills/audio/audio-direction/SKILL.md +15 -10
- package/skills/audio/music-track/SKILL.md +18 -12
- package/skills/audio/sound-effect/SKILL.md +17 -12
- package/skills/audio/voice-line/SKILL.md +42 -42
- package/skills/character-controllers/fps-controller/SKILL.md +26 -16
- package/skills/debugging/debug/SKILL.md +52 -20
- package/skills/deployment/export-and-ship/SKILL.md +105 -38
- package/skills/deployment/remote-deploy/SKILL.md +56 -23
- package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +26 -1
- package/skills/gameplay-mechanics/design-mechanic/SKILL.md +23 -15
- package/skills/level-design/design-level/SKILL.md +22 -8
- package/skills/level-design/scene-to-level/SKILL.md +13 -7
- package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +8 -8
- package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +2 -7
- package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +26 -18
- package/skills/performance/tune-performance/SKILL.md +51 -32
- package/skills/rendering-and-lighting/3d-lighting/SKILL.md +34 -5
- package/skills/rendering-and-lighting/art-direction/SKILL.md +6 -8
- package/skills/scene-and-project/brainstorm-game/SKILL.md +3 -5
- package/skills/scene-and-project/make-game/SKILL.md +2 -1
- package/skills/scene-and-project/play/SKILL.md +10 -4
- package/skills/scene-and-project/scene-composition/SKILL.md +21 -8
- package/skills/scene-and-project/summer-cloud/SKILL.md +1 -1
- package/skills/ui-and-ux/ui-basics/SKILL.md +16 -7
- package/skills/video/animated-loop/SKILL.md +63 -39
- package/skills/video/cinematic-cutscene/SKILL.md +66 -30
- package/skills/video/trailer-shot/SKILL.md +30 -21
- package/skills/visual-effects/_building-blocks/additive-billboard-particles.md +25 -16
- package/skills/visual-effects/_building-blocks/trauma-shake-snippet.md +5 -3
- package/skills/visual-effects/game-feel/SKILL.md +51 -33
- package/skills/visual-effects/recipes/dissolve/SKILL.md +51 -9
- package/skills/visual-effects/recipes/fire/SKILL.md +136 -49
- package/skills/visual-effects/recipes/hit-spark/SKILL.md +45 -8
- package/skills/visual-effects/recipes/lightning/SKILL.md +59 -27
- package/skills/visual-effects/recipes/magic-glow/SKILL.md +59 -26
- package/skills/visual-effects/recipes/muzzle-flash/SKILL.md +68 -25
- package/skills/visual-effects/recipes/smoke/SKILL.md +89 -28
- package/skills/visual-effects/recipes/water-ripple/SKILL.md +83 -23
- package/skills/workflow/brainstorming/SKILL.md +1 -1
- package/skills/workflow/brainstorming/scripts/start-server.sh +0 -0
- package/skills/workflow/brainstorming/scripts/stop-server.sh +0 -0
- package/skills/workflow/debugging-game-feel/SKILL.md +29 -9
- package/skills/workflow/diagnosing-perf-regressions/SKILL.md +46 -21
- package/skills/workflow/gameskill/SKILL.md +11 -2
- package/skills/workflow/headless-scripting/SKILL.md +407 -0
- package/skills/workflow/investigating-bugs/SKILL.md +4 -2
- package/skills/workflow/investigating-bugs/find-polluter.sh +0 -0
- package/skills/workflow/mcpupdate/SKILL.md +3 -3
- package/skills/workflow/playtesting-a-feature/SKILL.md +94 -21
- package/skills/workflow/skill-create/SKILL.md +7 -7
- package/skills/workflow/skill-test/SKILL.md +3 -3
- package/skills/workflow/using-summer/SKILL.md +12 -2
- package/skills/workflow/verification-before-completion/SKILL.md +2 -0
- package/skills/workflow/writing-plans/SKILL.md +7 -7
- package/skills/workflow/writing-skills/SKILL.md +7 -5
- package/skills/workflow/writing-skills/render-graphs.js +0 -0
- package/skills/workflow/writing-skills/testing-skills-with-subagents.md +1 -1
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# SUMMER - Verification Runner v1: probe base class.
|
|
2
|
+
#
|
|
3
|
+
# An AI-authored probe extends this base. The engine injects the probe as a singleton
|
|
4
|
+
# named "SummerProbe" into a disposable, invisible --summer-verify game instance. The
|
|
5
|
+
# base provides the runtime API (report / save_frame / dump_tree / press / key / finish)
|
|
6
|
+
# and guarantees a results.json is written even if the probe hangs (at-exit backstop +
|
|
7
|
+
# the frames-based quit-after ceiling in main.cpp).
|
|
8
|
+
#
|
|
9
|
+
# The engine sets two members on the instance before _ready() via `set()`:
|
|
10
|
+
# summer_out_dir : String - absolute temp dir to write results/frames into
|
|
11
|
+
# summer_max_seconds : int - soft budget; base auto-finishes at this ceiling
|
|
12
|
+
#
|
|
13
|
+
# Contract of <out_dir>/results.json:
|
|
14
|
+
# { "reports": {..}, "frames": [names], "errors_seen": [strings],
|
|
15
|
+
# "frame_warnings": [strings], "duration_ms": int, "finished": bool }
|
|
16
|
+
#
|
|
17
|
+
# PERCEPTION NOTES (measured against the shipped 0.5.55 binary, reproducible 3/3):
|
|
18
|
+
# * This instance is NOT --headless. Headless has no renderer, so
|
|
19
|
+
# viewport.get_texture().get_image() returns null and NO frame can ever be
|
|
20
|
+
# captured. The verify child is a real windowed instance parked offscreen at
|
|
21
|
+
# (-32000,-32000) precisely so capture works. Never add --headless here.
|
|
22
|
+
# * The viewport does not hold your scene yet during _ready(). Measured, with a
|
|
23
|
+
# red box on screen, sampling distinct colours in the captured image:
|
|
24
|
+
# in _ready() frames_drawn=0 -> 1 colour (clear colour only)
|
|
25
|
+
# after 1 process_frame frames_drawn=0 -> 1 colour (still empty)
|
|
26
|
+
# after 2 process_frames frames_drawn=1 -> 2 colours (the box is there)
|
|
27
|
+
# So `await settle()` before your FIRST save_frame(). save_frame() records a
|
|
28
|
+
# frame_warnings entry rather than lying if you skip it.
|
|
29
|
+
# * RenderingServer.force_draw() is NOT a shortcut. Measured: it fills the image
|
|
30
|
+
# with the clear colour (1 colour, all non-black) but the scene geometry is
|
|
31
|
+
# still missing. A plausible-but-wrong frame is worse than a blank one.
|
|
32
|
+
# * Audio IS assertable even though this instance forces the Dummy driver
|
|
33
|
+
# (main.cpp, summer_verify block). AudioServer still mixes, so measured:
|
|
34
|
+
# silent scene -> AudioServer.get_bus_peak_volume_left_db(0,0) == -200.0
|
|
35
|
+
# playing sound -> ... == -1.938
|
|
36
|
+
# * Performance monitors work here and only here. Measured in this instance:
|
|
37
|
+
# draw_calls=1, fps=46, objects=1, primitives=12. The same probe under
|
|
38
|
+
# --headless reports draw_calls=0, fps=1, objects=0 -- all meaningless.
|
|
39
|
+
extends Node
|
|
40
|
+
|
|
41
|
+
var summer_out_dir: String = ""
|
|
42
|
+
var summer_max_seconds: int = 20
|
|
43
|
+
|
|
44
|
+
var _reports: Dictionary = {}
|
|
45
|
+
var _frames: Array = []
|
|
46
|
+
var _frame_warnings: Array = []
|
|
47
|
+
var _start_ms: int = 0
|
|
48
|
+
var _finished: bool = false
|
|
49
|
+
|
|
50
|
+
func _enter_tree() -> void:
|
|
51
|
+
_start_ms = Time.get_ticks_msec()
|
|
52
|
+
|
|
53
|
+
func _ready() -> void:
|
|
54
|
+
# Hard soft-ceiling: if the probe body never calls finish(), auto-finish just before
|
|
55
|
+
# the main.cpp frames ceiling would kill us, so we still flush a partial results.json.
|
|
56
|
+
var t := Timer.new()
|
|
57
|
+
t.one_shot = true
|
|
58
|
+
t.wait_time = float(summer_max_seconds)
|
|
59
|
+
t.timeout.connect(_on_deadline)
|
|
60
|
+
add_child(t)
|
|
61
|
+
t.start()
|
|
62
|
+
|
|
63
|
+
func _on_deadline() -> void:
|
|
64
|
+
if not _finished:
|
|
65
|
+
report("_timeout", true)
|
|
66
|
+
finish()
|
|
67
|
+
|
|
68
|
+
# --- Runtime API for the AI-authored probe --------------------------------------------
|
|
69
|
+
|
|
70
|
+
func report(key: String, value) -> void:
|
|
71
|
+
_reports[key] = value
|
|
72
|
+
|
|
73
|
+
func settle(frames: int = 1) -> void:
|
|
74
|
+
# Await until the renderer has actually PRESENTED `frames` more frames. Call this
|
|
75
|
+
# before your first save_frame(), and after any change you want to see on screen.
|
|
76
|
+
#
|
|
77
|
+
# await settle() # first capture is now trustworthy
|
|
78
|
+
# save_frame("start")
|
|
79
|
+
#
|
|
80
|
+
# Counting presented frames (Engine.get_frames_drawn()) rather than awaiting
|
|
81
|
+
# process_frame is deliberate: a process frame can complete without the renderer
|
|
82
|
+
# having presented anything, which is exactly the blank-capture bug. See the
|
|
83
|
+
# PERCEPTION NOTES at the top for the measurements behind this.
|
|
84
|
+
#
|
|
85
|
+
# But that counter only advances when there IS a renderer. Under --headless it is
|
|
86
|
+
# 0 forever -- measured: 438 process frames awaited over 3005ms, frames_drawn
|
|
87
|
+
# still 0 -- so waiting on it there would spin until the probe budget killed the
|
|
88
|
+
# run. This base is normally injected into the windowed verify child, but the
|
|
89
|
+
# headless-scripting path runs it under `--headless -s`, where that would hang.
|
|
90
|
+
# So: fall back to process frames when there is no renderer, and bound the wait
|
|
91
|
+
# either way so settle() can never be the reason a probe fails to finish.
|
|
92
|
+
if not has_renderer():
|
|
93
|
+
# +1 because content lands a frame later than the naive count suggests.
|
|
94
|
+
for _i in range(maxi(1, frames) + 1):
|
|
95
|
+
await get_tree().process_frame
|
|
96
|
+
return
|
|
97
|
+
var target := Engine.get_frames_drawn() + maxi(1, frames)
|
|
98
|
+
var guard := 0
|
|
99
|
+
while Engine.get_frames_drawn() < target and guard < 600:
|
|
100
|
+
guard += 1
|
|
101
|
+
await get_tree().process_frame
|
|
102
|
+
if guard >= 600:
|
|
103
|
+
report("_settle_timeout", true)
|
|
104
|
+
|
|
105
|
+
func has_renderer() -> bool:
|
|
106
|
+
# True when this instance can actually draw. Use this, NOT OS.has_feature("headless"),
|
|
107
|
+
# which is measurably FALSE under --headless and will mislead you. The rendering
|
|
108
|
+
# method setting lies too -- it still reads "forward_plus" with no renderer behind it.
|
|
109
|
+
# DisplayServer's own name is the honest signal.
|
|
110
|
+
return DisplayServer.get_name() != "headless"
|
|
111
|
+
|
|
112
|
+
func settle_physics(frames: int = 1) -> void:
|
|
113
|
+
# Await N physics frames. Call this after building a scene and BEFORE asserting
|
|
114
|
+
# anything physical. A body added this frame has no space yet, so the natural
|
|
115
|
+
# "build the scene, immediately assert physics" probe throws on frame 0:
|
|
116
|
+
# Parameter "body->get_space()" is null
|
|
117
|
+
# at: godot_physics_server_2d.cpp:997
|
|
118
|
+
# One physics frame after add_child() is the minimum; raycasts and contacts
|
|
119
|
+
# generally want two.
|
|
120
|
+
for _i in range(maxi(1, frames)):
|
|
121
|
+
await get_tree().physics_frame
|
|
122
|
+
|
|
123
|
+
func settle_navigation(max_frames: int = 60) -> bool:
|
|
124
|
+
# Await until the navigation map is actually queryable, and report whether it got
|
|
125
|
+
# there. Returns true on success, false if it timed out.
|
|
126
|
+
#
|
|
127
|
+
# This exists because the failure is SILENT. NavigationServer3D.map_get_path()
|
|
128
|
+
# returns an EMPTY array with no error until the map has synchronized TWICE --
|
|
129
|
+
# iteration_id == 1 is not enough. Measured here, awaiting physics_frame:
|
|
130
|
+
# frame 0-1 iter=0 path_pts=0
|
|
131
|
+
# frame 2-3 iter=1 path_pts=0 <- still empty, still no error
|
|
132
|
+
# frame 4+ iter=2 path_pts=2 <- queryable
|
|
133
|
+
# So the obvious probe returns [] and reads as "navigation is broken" when
|
|
134
|
+
# nothing is wrong. (map_get_closest_point() does error; map_get_path() does not.)
|
|
135
|
+
# process_frame does NOT advance the iteration id as quickly -- await physics.
|
|
136
|
+
var map := get_viewport().world_3d.navigation_map
|
|
137
|
+
for _i in range(maxi(1, max_frames)):
|
|
138
|
+
if NavigationServer3D.map_get_iteration_id(map) >= 2:
|
|
139
|
+
return true
|
|
140
|
+
await get_tree().physics_frame
|
|
141
|
+
report("_navigation_settle_timeout", true)
|
|
142
|
+
return false
|
|
143
|
+
|
|
144
|
+
func save_frame(name: String) -> String:
|
|
145
|
+
# Capture the current viewport framebuffer to <out_dir>/<name> (jpg). Returns the
|
|
146
|
+
# absolute path written, or "" on failure.
|
|
147
|
+
#
|
|
148
|
+
# If no frame has been presented yet, the file still lands (so the web side can look
|
|
149
|
+
# at it) but a frame_warnings entry is recorded, because the image will be the flat
|
|
150
|
+
# clear colour rather than your scene. Fix that by `await settle()` first -- do not
|
|
151
|
+
# ignore the warning, the capture is not evidence of anything.
|
|
152
|
+
if not has_renderer():
|
|
153
|
+
var hw := "save_frame(\"%s\") called under --headless: there is NO renderer, get_image() returns null and no frame can ever be captured. Use the windowed verify path (--summer-verify), not --headless." % name
|
|
154
|
+
if not _frame_warnings.has(hw):
|
|
155
|
+
_frame_warnings.append(hw)
|
|
156
|
+
elif Engine.get_frames_drawn() < 1:
|
|
157
|
+
var w := "save_frame(\"%s\") ran before any frame was presented - the image is the clear colour, not the scene. `await settle()` first." % name
|
|
158
|
+
if not _frame_warnings.has(w):
|
|
159
|
+
_frame_warnings.append(w)
|
|
160
|
+
var vp := get_viewport()
|
|
161
|
+
if vp == null:
|
|
162
|
+
return ""
|
|
163
|
+
var tex := vp.get_texture()
|
|
164
|
+
if tex == null:
|
|
165
|
+
return ""
|
|
166
|
+
var img := tex.get_image()
|
|
167
|
+
if img == null:
|
|
168
|
+
return ""
|
|
169
|
+
var fname := name if name.ends_with(".jpg") else name + ".jpg"
|
|
170
|
+
var path := summer_out_dir.path_join(fname)
|
|
171
|
+
var err := img.save_jpg(path, 0.75)
|
|
172
|
+
if err == OK:
|
|
173
|
+
if not _frames.has(fname):
|
|
174
|
+
_frames.append(fname)
|
|
175
|
+
return path
|
|
176
|
+
return ""
|
|
177
|
+
|
|
178
|
+
func dump_tree(max_depth: int = 6) -> void:
|
|
179
|
+
# Summarize the live scene tree (names + classes) into the report under "tree".
|
|
180
|
+
var root := get_tree().root
|
|
181
|
+
var lines: Array = []
|
|
182
|
+
_dump_node(root, 0, max_depth, lines)
|
|
183
|
+
report("tree", lines)
|
|
184
|
+
|
|
185
|
+
func _dump_node(n: Node, depth: int, max_depth: int, lines: Array) -> void:
|
|
186
|
+
if depth > max_depth:
|
|
187
|
+
return
|
|
188
|
+
var indent := " ".repeat(depth)
|
|
189
|
+
lines.append("%s%s (%s)" % [indent, n.name, n.get_class()])
|
|
190
|
+
for c in n.get_children():
|
|
191
|
+
_dump_node(c, depth + 1, max_depth, lines)
|
|
192
|
+
|
|
193
|
+
func press(action: String, hold_ms: int = 100) -> void:
|
|
194
|
+
# Inject an input action press+release from INSIDE the process (no debugger).
|
|
195
|
+
var ev := InputEventAction.new()
|
|
196
|
+
ev.action = action
|
|
197
|
+
ev.pressed = true
|
|
198
|
+
Input.parse_input_event(ev)
|
|
199
|
+
await get_tree().create_timer(hold_ms / 1000.0).timeout
|
|
200
|
+
var up := InputEventAction.new()
|
|
201
|
+
up.action = action
|
|
202
|
+
up.pressed = false
|
|
203
|
+
Input.parse_input_event(up)
|
|
204
|
+
|
|
205
|
+
func key(keycode: int, hold_ms: int = 100) -> void:
|
|
206
|
+
# Inject a physical key press+release by keycode (e.g. KEY_SPACE).
|
|
207
|
+
var ev := InputEventKey.new()
|
|
208
|
+
ev.keycode = keycode
|
|
209
|
+
ev.physical_keycode = keycode
|
|
210
|
+
ev.pressed = true
|
|
211
|
+
Input.parse_input_event(ev)
|
|
212
|
+
await get_tree().create_timer(hold_ms / 1000.0).timeout
|
|
213
|
+
var up := InputEventKey.new()
|
|
214
|
+
up.keycode = keycode
|
|
215
|
+
up.physical_keycode = keycode
|
|
216
|
+
up.pressed = false
|
|
217
|
+
Input.parse_input_event(up)
|
|
218
|
+
|
|
219
|
+
func finish() -> void:
|
|
220
|
+
# Flush results.json and terminate the disposable instance.
|
|
221
|
+
if _finished:
|
|
222
|
+
return
|
|
223
|
+
_finished = true
|
|
224
|
+
_write_results(true)
|
|
225
|
+
get_tree().quit()
|
|
226
|
+
|
|
227
|
+
# --- Results writing ------------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
func _collect_errors() -> Array:
|
|
230
|
+
# The C++ SummerVerifyLogger appends error-level engine/script errors to errors.log.
|
|
231
|
+
var errors: Array = []
|
|
232
|
+
var p := summer_out_dir.path_join("errors.log")
|
|
233
|
+
if FileAccess.file_exists(p):
|
|
234
|
+
var f := FileAccess.open(p, FileAccess.READ)
|
|
235
|
+
if f != null:
|
|
236
|
+
while not f.eof_reached():
|
|
237
|
+
var line := f.get_line()
|
|
238
|
+
if line.strip_edges() != "":
|
|
239
|
+
errors.append(line)
|
|
240
|
+
return errors
|
|
241
|
+
|
|
242
|
+
func _write_results(finished: bool) -> void:
|
|
243
|
+
var out := {
|
|
244
|
+
"reports": _reports,
|
|
245
|
+
"frames": _frames,
|
|
246
|
+
"frame_warnings": _frame_warnings,
|
|
247
|
+
"errors_seen": _collect_errors(),
|
|
248
|
+
"duration_ms": Time.get_ticks_msec() - _start_ms,
|
|
249
|
+
"finished": finished,
|
|
250
|
+
}
|
|
251
|
+
var path := summer_out_dir.path_join("results.json")
|
|
252
|
+
var f := FileAccess.open(path, FileAccess.WRITE)
|
|
253
|
+
if f != null:
|
|
254
|
+
f.store_string(JSON.stringify(out, " "))
|
|
255
|
+
f.flush()
|
|
256
|
+
f.close()
|
|
257
|
+
|
|
258
|
+
func _exit_tree() -> void:
|
|
259
|
+
# At-exit backstop: if finish() was never called (quit-after ceiling in main.cpp
|
|
260
|
+
# killed us, or the probe crashed), still leave a partial results.json behind.
|
|
261
|
+
if not _finished:
|
|
262
|
+
_write_results(false)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Run the autopilot against this project and print the results.
|
|
3
|
+
#
|
|
4
|
+
# bash tests/autopilot/run.sh # runs autopilot.gd
|
|
5
|
+
# bash tests/autopilot/run.sh my_probe.gd # runs a different probe
|
|
6
|
+
#
|
|
7
|
+
# No editor required. This spawns the engine's offscreen verify instance, which
|
|
8
|
+
# has a REAL renderer — unlike --headless, which produces no pixels at all.
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
12
|
+
PROBE="${1:-$PROJECT_DIR/tests/autopilot/autopilot.gd}"
|
|
13
|
+
OUT="$PROJECT_DIR/tests/autopilot/out"
|
|
14
|
+
MAX_SECONDS="${MAX_SECONDS:-40}"
|
|
15
|
+
|
|
16
|
+
# Find the engine. Override with SUMMER_BIN=/path/to/engine if it lives elsewhere.
|
|
17
|
+
# There is no `godot` binary on a Summer install — do not substitute one.
|
|
18
|
+
if [[ -n "${SUMMER_BIN:-}" ]]; then
|
|
19
|
+
ENGINE="$SUMMER_BIN"
|
|
20
|
+
elif [[ -x "/Applications/Summer.app/Contents/MacOS/Summer" ]]; then
|
|
21
|
+
ENGINE="/Applications/Summer.app/Contents/MacOS/Summer"
|
|
22
|
+
elif [[ -x "${LOCALAPPDATA:-}/Summer/current/Summer.exe" ]]; then
|
|
23
|
+
ENGINE="${LOCALAPPDATA}/Summer/current/Summer.exe"
|
|
24
|
+
else
|
|
25
|
+
echo "Could not find the Summer engine binary." >&2
|
|
26
|
+
echo "Set SUMMER_BIN, or read engineBinaryPath from summer_get_project_context." >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
rm -rf "$OUT"
|
|
31
|
+
mkdir -p "$OUT"
|
|
32
|
+
|
|
33
|
+
# --disable-crash-handler: Summer's handler popen()s atos from inside a signal
|
|
34
|
+
# handler, which turns a clean failure into a hang. Never arm it on an
|
|
35
|
+
# agent-driven run. Note there is deliberately no --headless here: headless has
|
|
36
|
+
# no renderer, so save_frame() would produce nothing.
|
|
37
|
+
"$ENGINE" \
|
|
38
|
+
--disable-crash-handler \
|
|
39
|
+
--path "$PROJECT_DIR" \
|
|
40
|
+
--summer-verify "$PROBE" \
|
|
41
|
+
--summer-verify-out "$OUT" \
|
|
42
|
+
--summer-verify-max "$MAX_SECONDS" \
|
|
43
|
+
>"$OUT/engine.log" 2>&1 || true
|
|
44
|
+
|
|
45
|
+
if [[ ! -f "$OUT/results.json" ]]; then
|
|
46
|
+
echo "No results.json — the probe never started. Engine output:" >&2
|
|
47
|
+
tail -30 "$OUT/engine.log" >&2
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
cat "$OUT/results.json"
|
|
52
|
+
echo
|
|
53
|
+
echo "Frames and full log: $OUT"
|
|
54
|
+
|
|
55
|
+
# finished:false means the probe hit its time ceiling before calling finish().
|
|
56
|
+
# That is a failure, not a pass — exit non-zero so CI notices.
|
|
57
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
58
|
+
python3 - "$OUT/results.json" <<'PY'
|
|
59
|
+
import json, sys
|
|
60
|
+
d = json.load(open(sys.argv[1]))
|
|
61
|
+
reports = d.get("reports", {})
|
|
62
|
+
if not d.get("finished", False):
|
|
63
|
+
print("FAILED: probe did not finish (hit --summer-verify-max)", file=sys.stderr)
|
|
64
|
+
sys.exit(1)
|
|
65
|
+
if d.get("errors_seen"):
|
|
66
|
+
print(f"FAILED: {len(d['errors_seen'])} engine error(s) during the run", file=sys.stderr)
|
|
67
|
+
sys.exit(1)
|
|
68
|
+
if reports.get("error"):
|
|
69
|
+
print(f"FAILED: {reports['error']}", file=sys.stderr)
|
|
70
|
+
print("Edit the CONFIG block at the top of tests/autopilot/autopilot.gd.", file=sys.stderr)
|
|
71
|
+
sys.exit(1)
|
|
72
|
+
# Any waypoint the autopilot could not reach is a failure, whatever else passed.
|
|
73
|
+
missed = [k for k, v in reports.items() if k.endswith("_reached") and v is False]
|
|
74
|
+
if missed:
|
|
75
|
+
print(f"FAILED: waypoints not reached: {', '.join(sorted(missed))}", file=sys.stderr)
|
|
76
|
+
sys.exit(1)
|
|
77
|
+
PY
|
|
78
|
+
fi
|
package/commands/gameskill.md
CHANGED
|
@@ -6,9 +6,18 @@ This is the meta-skill: the loop that turns ad-hoc fixes into durable, reusable
|
|
|
6
6
|
|
|
7
7
|
Three repos, one ecosystem:
|
|
8
8
|
|
|
9
|
-
1. **`~/development/SummerEngine`** — the
|
|
9
|
+
1. **`~/development/SummerEngine`** — the Summer Engine desktop source and the
|
|
10
|
+
canonical CLI. The C++ engine is maintained against the upstream Godot
|
|
11
|
+
Engine codebase; the Summer-owned module lives at `modules/1summer_engine/`.
|
|
12
|
+
The Summer CLI lives at `tools/summer-cli/` and is normally invoked with
|
|
13
|
+
`npx -y summer-engine@latest`. It installs, signs in, creates, and runs Summer
|
|
14
|
+
projects and can install the current Summer skill bundle for supported
|
|
15
|
+
agents.
|
|
10
16
|
2. **`~/development/PublicSummerEngine`** — the web app at summerengine.com. Has its own AI skill stores at `src/lib/ai/skills/bundled/` and `public/knowledge/summer/skills/` for the in-browser chat agent. ALSO has its own MCP-style consumers of the engine API (`src/lib/bridge/direct-executor.ts`, `src/lib/ai/tools/run-and-verify.ts`) which call the same engine endpoints the CLI does.
|
|
11
|
-
3. **The active game project** — usually
|
|
17
|
+
3. **The active Summer game project** — usually
|
|
18
|
+
`~/development/<game-name>` or whatever game we were just working on. The
|
|
19
|
+
live compatibility line comes from Summer Engine, not this prompt. Real
|
|
20
|
+
working code in this repo is the gold standard for examples.
|
|
12
21
|
|
|
13
22
|
The skill system is the value flywheel: every game we ship teaches lessons → the lessons become skills → the next user (or AI) starts smarter. Your job here is to close that loop for what we just did.
|
|
14
23
|
|
package/dist/bin/summer.js
CHANGED
|
@@ -18,6 +18,10 @@ import { doctorCommand } from "../commands/doctor.js";
|
|
|
18
18
|
import { planCommand } from "../commands/plan.js";
|
|
19
19
|
import { cloudCommand } from "../commands/cloud.js";
|
|
20
20
|
import { debugCommand } from "../commands/debug.js";
|
|
21
|
+
import { configCommand } from "../commands/config.js";
|
|
22
|
+
import { publishCommand } from "../commands/publish.js";
|
|
23
|
+
import { releasesCommand } from "../commands/releases.js";
|
|
24
|
+
import { logsCommand } from "../commands/logs.js";
|
|
21
25
|
import { getBanner } from "../lib/banner.js";
|
|
22
26
|
import { c, sym } from "../lib/format.js";
|
|
23
27
|
const require = createRequire(import.meta.url);
|
|
@@ -47,6 +51,10 @@ program.addCommand(debugCommand);
|
|
|
47
51
|
program.addCommand(planCommand);
|
|
48
52
|
program.addCommand(orchestratorCommand);
|
|
49
53
|
program.addCommand(cloudCommand);
|
|
54
|
+
program.addCommand(configCommand);
|
|
55
|
+
program.addCommand(publishCommand);
|
|
56
|
+
program.addCommand(releasesCommand);
|
|
57
|
+
program.addCommand(logsCommand);
|
|
50
58
|
program.parseAsync().catch((err) => {
|
|
51
59
|
console.error(err instanceof Error ? err.message : String(err));
|
|
52
60
|
process.exit(1);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { CONFIG_KEYS, getConfigValue, isConfigKey, readSummerConfig, setConfigValue, unsetConfigValue, } from "../lib/config.js";
|
|
3
|
+
function requireKey(value) {
|
|
4
|
+
if (isConfigKey(value))
|
|
5
|
+
return value;
|
|
6
|
+
throw new Error(`Unknown config key "${value}". Recovery: use one of ${CONFIG_KEYS.join(", ")}.`);
|
|
7
|
+
}
|
|
8
|
+
async function printConfig(json) {
|
|
9
|
+
const config = await readSummerConfig();
|
|
10
|
+
const values = Object.fromEntries(CONFIG_KEYS.map((key) => [key, getConfigValue(config, key) ?? null]));
|
|
11
|
+
if (json) {
|
|
12
|
+
console.log(JSON.stringify(values, null, 2));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
for (const [key, value] of Object.entries(values)) {
|
|
16
|
+
console.log(`${key}=${value ?? "(not set)"}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export const configCommand = new Command("config")
|
|
20
|
+
.description("Read or update the shared ~/.summer configuration")
|
|
21
|
+
.option("--json", "Print all configuration as JSON")
|
|
22
|
+
.action(async (opts) => printConfig(Boolean(opts.json)));
|
|
23
|
+
configCommand
|
|
24
|
+
.command("get")
|
|
25
|
+
.description("Read one configuration value")
|
|
26
|
+
.argument("<key>", `One of: ${CONFIG_KEYS.join(", ")}`)
|
|
27
|
+
.action(async (rawKey) => {
|
|
28
|
+
const key = requireKey(rawKey);
|
|
29
|
+
const value = getConfigValue(await readSummerConfig(), key);
|
|
30
|
+
if (value === undefined) {
|
|
31
|
+
throw new Error(`${key} is not set. Recovery: run "summer config set ${key} <value>", or rely on the documented default.`);
|
|
32
|
+
}
|
|
33
|
+
console.log(value);
|
|
34
|
+
});
|
|
35
|
+
configCommand
|
|
36
|
+
.command("set")
|
|
37
|
+
.description("Set one non-secret configuration value")
|
|
38
|
+
.argument("<key>", `One of: ${CONFIG_KEYS.join(", ")}`)
|
|
39
|
+
.argument("<value>")
|
|
40
|
+
.action(async (rawKey, value) => {
|
|
41
|
+
const key = requireKey(rawKey);
|
|
42
|
+
await setConfigValue(key, value);
|
|
43
|
+
console.log(`Set ${key}.`);
|
|
44
|
+
});
|
|
45
|
+
configCommand
|
|
46
|
+
.command("unset")
|
|
47
|
+
.description("Remove one configuration override")
|
|
48
|
+
.argument("<key>", `One of: ${CONFIG_KEYS.join(", ")}`)
|
|
49
|
+
.action(async (rawKey) => {
|
|
50
|
+
const key = requireKey(rawKey);
|
|
51
|
+
await unsetConfigValue(key);
|
|
52
|
+
console.log(`Unset ${key}.`);
|
|
53
|
+
});
|
package/dist/commands/create.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
3
|
-
import { join, resolve } from "path";
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, writeFileSync } from "fs";
|
|
3
|
+
import { dirname, join, resolve } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
4
5
|
import { fetchRemoteTemplates, cloneTemplate, matchTemplate } from "../lib/remote-templates.js";
|
|
6
|
+
import { SUMMER_ENGINE_COMPATIBILITY } from "../lib/summer-compatibility.js";
|
|
5
7
|
const BUILTIN_TEMPLATES = [
|
|
6
8
|
{
|
|
7
9
|
name: "empty",
|
|
8
10
|
description: "Empty 3D project with just a root node",
|
|
9
11
|
builtin: true,
|
|
10
12
|
generate: (dir, name) => {
|
|
11
|
-
writeFileSync(join(dir, "project.godot"),
|
|
13
|
+
writeFileSync(join(dir, "project.godot"), renderProjectSettings(name, "res://main.tscn"));
|
|
12
14
|
writeFileSync(join(dir, "main.tscn"), emptyScene());
|
|
13
15
|
},
|
|
14
16
|
},
|
|
@@ -17,11 +19,34 @@ const BUILTIN_TEMPLATES = [
|
|
|
17
19
|
description: "3D scene with camera, light, and floor",
|
|
18
20
|
builtin: true,
|
|
19
21
|
generate: (dir, name) => {
|
|
20
|
-
writeFileSync(join(dir, "project.godot"),
|
|
22
|
+
writeFileSync(join(dir, "project.godot"), renderProjectSettings(name, "res://main.tscn"));
|
|
21
23
|
writeFileSync(join(dir, "main.tscn"), basicScene3D());
|
|
22
24
|
},
|
|
23
25
|
},
|
|
24
26
|
];
|
|
27
|
+
/**
|
|
28
|
+
* Copy the autopilot verification scaffold into a fresh project.
|
|
29
|
+
*
|
|
30
|
+
* Every project gets a working example of how to prove its own gameplay: a probe
|
|
31
|
+
* that drives the player with real input, saves rendered frames, asserts, and
|
|
32
|
+
* exits. Without it every agent reinvents verification from scratch, and most
|
|
33
|
+
* settle for asking the user to play the game instead.
|
|
34
|
+
*
|
|
35
|
+
* Never overwrites — an existing tests/autopilot/ is the user's, not ours.
|
|
36
|
+
*/
|
|
37
|
+
function scaffoldAutopilot(projectDir) {
|
|
38
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
// dist/commands/create.js -> package root
|
|
40
|
+
const source = resolve(here, "..", "..", "assets", "autopilot");
|
|
41
|
+
const target = join(projectDir, "tests", "autopilot");
|
|
42
|
+
if (!existsSync(source) || existsSync(target))
|
|
43
|
+
return false;
|
|
44
|
+
mkdirSync(target, { recursive: true });
|
|
45
|
+
for (const entry of readdirSync(source)) {
|
|
46
|
+
copyFileSync(join(source, entry), join(target, entry));
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
25
50
|
export const createCommand = new Command("create")
|
|
26
51
|
.description("Create a new Summer Engine project. Built-in templates work offline; community templates clone from github.com/SummerEngine/template-*.")
|
|
27
52
|
.argument("<template>", `Template slug. Built-in: ${BUILTIN_TEMPLATES.map((t) => t.name).join(", ")}. Or any community slug — see "summer list templates".`)
|
|
@@ -39,9 +64,12 @@ export const createCommand = new Command("create")
|
|
|
39
64
|
console.log(`Creating project from built-in '${templateName}' template...`);
|
|
40
65
|
mkdirSync(fullPath, { recursive: true });
|
|
41
66
|
builtin.generate(fullPath, dirName);
|
|
67
|
+
const scaffolded = scaffoldAutopilot(fullPath);
|
|
42
68
|
console.log(`\nProject created at ${fullPath}`);
|
|
43
69
|
console.log("\nNext steps:");
|
|
44
70
|
console.log(` summer run ${dirName}`);
|
|
71
|
+
if (scaffolded)
|
|
72
|
+
console.log(` bash ${dirName}/tests/autopilot/run.sh (verify the game without opening it)`);
|
|
45
73
|
console.log(" Ask your agent to run summer:brainstorm-game to create .summer/GameSoul.md");
|
|
46
74
|
return;
|
|
47
75
|
}
|
|
@@ -83,21 +111,25 @@ export const createCommand = new Command("create")
|
|
|
83
111
|
console.error(err.message);
|
|
84
112
|
process.exit(1);
|
|
85
113
|
}
|
|
114
|
+
const scaffolded = scaffoldAutopilot(fullPath);
|
|
86
115
|
console.log(`\nProject created at ${fullPath}`);
|
|
87
116
|
console.log("Source: " + remote.url);
|
|
88
117
|
console.log("\nNext steps:");
|
|
89
118
|
console.log(` summer run ${dirName}`);
|
|
119
|
+
if (scaffolded)
|
|
120
|
+
console.log(` bash ${dirName}/tests/autopilot/run.sh (verify the game without opening it)`);
|
|
90
121
|
console.log(" Ask your agent to run summer:brainstorm-game to create .summer/GameSoul.md");
|
|
91
122
|
});
|
|
92
|
-
function
|
|
123
|
+
export function renderProjectSettings(name, mainScene) {
|
|
93
124
|
return `; Summer Engine Project
|
|
94
125
|
; Generated by summer-engine CLI
|
|
126
|
+
; Technical base ${SUMMER_ENGINE_COMPATIBILITY.currentTechnicalBaseVersion}; Summer follows upstream continuously
|
|
95
127
|
|
|
96
128
|
[application]
|
|
97
129
|
|
|
98
130
|
config/name="${name}"
|
|
99
131
|
run/main_scene="${mainScene}"
|
|
100
|
-
config/features=PackedStringArray("
|
|
132
|
+
config/features=PackedStringArray("${SUMMER_ENGINE_COMPATIBILITY.projectFeatureTag}")
|
|
101
133
|
|
|
102
134
|
[rendering]
|
|
103
135
|
|
package/dist/commands/list.js
CHANGED
|
@@ -55,7 +55,7 @@ function listProjects() {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
if (projects.length === 0) {
|
|
58
|
-
console.log("No Summer Engine
|
|
58
|
+
console.log("No Summer Engine projects found in current directory.");
|
|
59
59
|
console.log("\nCreate one: summer create 3d-basic my-game");
|
|
60
60
|
return;
|
|
61
61
|
}
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -1,2 +1,21 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
export declare const loginCommand: Command;
|
|
3
|
+
export interface CreatorLoginDependencies {
|
|
4
|
+
openUrl: (url: string) => Promise<unknown>;
|
|
5
|
+
readSecret: (prompt: string) => Promise<string>;
|
|
6
|
+
log: (message: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The browser session mints the token; the token is pasted once into a hidden
|
|
10
|
+
* prompt. This deliberately does not replace the core Summer login JWT.
|
|
11
|
+
*/
|
|
12
|
+
export declare function runCreatorLogin(overrides?: Partial<CreatorLoginDependencies>): Promise<void>;
|
|
13
|
+
export interface LoginDependencies {
|
|
14
|
+
fetch: typeof fetch;
|
|
15
|
+
openUrl: (url: string) => Promise<unknown>;
|
|
16
|
+
randomId: () => string;
|
|
17
|
+
sleep: (ms: number) => Promise<void>;
|
|
18
|
+
now: () => number;
|
|
19
|
+
log: (message: string) => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function runLogin(overrides?: Partial<LoginDependencies>): Promise<void>;
|