summer-engine 2.6.6 → 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.
Files changed (168) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +4 -1
  3. package/.codex-plugin/plugin.json +6 -2
  4. package/.cursor-plugin/plugin.json +5 -1
  5. package/.opencode/INSTALL.md +1 -1
  6. package/.opencode/plugins/summer.js +2 -2
  7. package/AGENTS.md +11 -7
  8. package/CHANGELOG.md +40 -0
  9. package/GEMINI.md +9 -7
  10. package/README.md +63 -21
  11. package/_persona/summer/SKILL.md +1 -0
  12. package/assets/autopilot/README.md +51 -0
  13. package/assets/autopilot/autopilot.gd +222 -0
  14. package/assets/autopilot/probe_base.gd +262 -0
  15. package/assets/autopilot/run.sh +78 -0
  16. package/commands/gameskill.md +11 -2
  17. package/commands/summer.md +13 -0
  18. package/dist/bin/summer.js +12 -0
  19. package/dist/commands/config.d.ts +2 -0
  20. package/dist/commands/config.js +53 -0
  21. package/dist/commands/create.d.ts +1 -0
  22. package/dist/commands/create.js +38 -6
  23. package/dist/commands/debug.d.ts +2 -0
  24. package/dist/commands/debug.js +44 -0
  25. package/dist/commands/doctor.js +112 -8
  26. package/dist/commands/list.js +1 -1
  27. package/dist/commands/login.d.ts +19 -0
  28. package/dist/commands/login.js +123 -36
  29. package/dist/commands/logout.js +3 -16
  30. package/dist/commands/logs.d.ts +2 -0
  31. package/dist/commands/logs.js +19 -0
  32. package/dist/commands/mcp.js +7 -2
  33. package/dist/commands/open.js +1 -1
  34. package/dist/commands/publish.d.ts +2 -0
  35. package/dist/commands/publish.js +20 -0
  36. package/dist/commands/releases.d.ts +2 -0
  37. package/dist/commands/releases.js +20 -0
  38. package/dist/commands/run.js +1 -1
  39. package/dist/commands/skills.js +1 -1
  40. package/dist/lib/api-client.d.ts +15 -4
  41. package/dist/lib/api-client.js +138 -13
  42. package/dist/lib/async-op-lifecycle.d.ts +7 -2
  43. package/dist/lib/async-op-lifecycle.js +32 -9
  44. package/dist/lib/auth.d.ts +40 -12
  45. package/dist/lib/auth.js +173 -37
  46. package/dist/lib/cloud/engine-bridge.d.ts +17 -4
  47. package/dist/lib/cloud/engine-bridge.js +32 -6
  48. package/dist/lib/config.d.ts +20 -0
  49. package/dist/lib/config.js +142 -0
  50. package/dist/lib/creator.d.ts +93 -0
  51. package/dist/lib/creator.js +393 -0
  52. package/dist/lib/debug-report.d.ts +50 -0
  53. package/dist/lib/debug-report.js +308 -0
  54. package/dist/lib/engine.d.ts +35 -2
  55. package/dist/lib/engine.js +240 -6
  56. package/dist/lib/game-task-plan.js +4 -4
  57. package/dist/lib/mcp-boot-notice.d.ts +3 -0
  58. package/dist/lib/mcp-boot-notice.js +7 -0
  59. package/dist/lib/mcp-log.d.ts +3 -0
  60. package/dist/lib/mcp-log.js +72 -0
  61. package/dist/lib/public-surface-paths.d.ts +13 -0
  62. package/dist/lib/public-surface-paths.js +42 -0
  63. package/dist/lib/skills-registry.d.ts +33 -1
  64. package/dist/lib/skills-registry.js +55 -0
  65. package/dist/lib/store.d.ts +14 -0
  66. package/dist/lib/store.js +145 -0
  67. package/dist/lib/summer-compatibility.d.ts +6 -0
  68. package/dist/lib/summer-compatibility.js +22 -0
  69. package/dist/mcp/server.d.ts +10 -3
  70. package/dist/mcp/server.js +104 -11
  71. package/dist/mcp/tools/asset-tools.js +33 -12
  72. package/dist/mcp/tools/cloud-tools.js +15 -15
  73. package/dist/mcp/tools/creator-tools.d.ts +2 -0
  74. package/dist/mcp/tools/creator-tools.js +105 -0
  75. package/dist/mcp/tools/debug-tools.js +44 -1
  76. package/dist/mcp/tools/file-tools.d.ts +2 -0
  77. package/dist/mcp/tools/file-tools.js +154 -0
  78. package/dist/mcp/tools/generate-tools.js +159 -11
  79. package/dist/mcp/tools/project-tools.js +37 -27
  80. package/dist/mcp/tools/scene-tools.js +106 -29
  81. package/dist/mcp/tools/visual-tools.js +4 -3
  82. package/dist/mcp/tools/with-engine.js +4 -4
  83. package/hooks/session-start.sh +5 -5
  84. package/package.json +3 -1
  85. package/references/collaborative-protocol.md +50 -0
  86. package/references/gd-style.md +121 -0
  87. package/references/godot-version.md +51 -0
  88. package/references/mcp-tools-reference.md +199 -0
  89. package/references/public-surface-language.md +30 -0
  90. package/references/summer-folder.md +90 -0
  91. package/references/template-registry.md +73 -0
  92. package/skills/2d-assets/character-portrait/SKILL.md +17 -12
  93. package/skills/2d-assets/concept-art/SKILL.md +10 -9
  94. package/skills/2d-assets/create-asset-sheet/SKILL.md +17 -15
  95. package/skills/2d-assets/pixel-art/SKILL.md +36 -20
  96. package/skills/2d-assets/skybox-panorama/SKILL.md +32 -37
  97. package/skills/2d-assets/sprite-sheet/SKILL.md +40 -35
  98. package/skills/2d-assets/tileable-texture/SKILL.md +28 -22
  99. package/skills/2d-assets/ui-graphics/SKILL.md +46 -32
  100. package/skills/2d-assets/use-widget-asset/SKILL.md +8 -6
  101. package/skills/3d-assets/character-model/SKILL.md +34 -19
  102. package/skills/3d-assets/environment-kit/SKILL.md +11 -5
  103. package/skills/3d-assets/organic-model/SKILL.md +8 -5
  104. package/skills/3d-assets/prop-model/SKILL.md +9 -3
  105. package/skills/3d-assets/vehicle-model/SKILL.md +32 -19
  106. package/skills/README.md +1 -1
  107. package/skills/ai-and-npcs/design-npc/SKILL.md +31 -13
  108. package/skills/animation/animation-tree/SKILL.md +39 -15
  109. package/skills/animation/facial-and-lipsync/SKILL.md +25 -8
  110. package/skills/animation/generate-motion/SKILL.md +47 -15
  111. package/skills/animation/procedural-animation/SKILL.md +31 -13
  112. package/skills/animation/retarget/SKILL.md +127 -79
  113. package/skills/audio/adaptive-music/SKILL.md +24 -20
  114. package/skills/audio/ambient-bed/SKILL.md +22 -16
  115. package/skills/audio/audio-direction/SKILL.md +15 -10
  116. package/skills/audio/music-track/SKILL.md +18 -12
  117. package/skills/audio/sound-effect/SKILL.md +17 -12
  118. package/skills/audio/voice-line/SKILL.md +42 -42
  119. package/skills/character-controllers/fps-controller/SKILL.md +26 -16
  120. package/skills/debugging/debug/SKILL.md +74 -20
  121. package/skills/deployment/export-and-ship/SKILL.md +105 -38
  122. package/skills/deployment/remote-deploy/SKILL.md +140 -0
  123. package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +26 -1
  124. package/skills/gameplay-mechanics/design-mechanic/SKILL.md +23 -15
  125. package/skills/level-design/design-level/SKILL.md +22 -8
  126. package/skills/level-design/scene-to-level/SKILL.md +13 -7
  127. package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +8 -8
  128. package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +2 -7
  129. package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +26 -18
  130. package/skills/performance/tune-performance/SKILL.md +51 -32
  131. package/skills/rendering-and-lighting/3d-lighting/SKILL.md +34 -5
  132. package/skills/rendering-and-lighting/art-direction/SKILL.md +6 -8
  133. package/skills/scene-and-project/brainstorm-game/SKILL.md +7 -9
  134. package/skills/scene-and-project/make-game/SKILL.md +2 -1
  135. package/skills/scene-and-project/play/SKILL.md +10 -4
  136. package/skills/scene-and-project/scene-composition/SKILL.md +21 -8
  137. package/skills/scene-and-project/summer-cloud/SKILL.md +92 -0
  138. package/skills/scripting-patterns/gdscript-patterns/SKILL.md +1 -1
  139. package/skills/ui-and-ux/ui-basics/SKILL.md +16 -7
  140. package/skills/video/animated-loop/SKILL.md +63 -39
  141. package/skills/video/cinematic-cutscene/SKILL.md +66 -30
  142. package/skills/video/trailer-shot/SKILL.md +30 -21
  143. package/skills/visual-effects/_building-blocks/additive-billboard-particles.md +25 -16
  144. package/skills/visual-effects/_building-blocks/trauma-shake-snippet.md +5 -3
  145. package/skills/visual-effects/game-feel/SKILL.md +51 -33
  146. package/skills/visual-effects/recipes/dissolve/SKILL.md +51 -9
  147. package/skills/visual-effects/recipes/fire/SKILL.md +136 -49
  148. package/skills/visual-effects/recipes/hit-spark/SKILL.md +45 -8
  149. package/skills/visual-effects/recipes/lightning/SKILL.md +59 -27
  150. package/skills/visual-effects/recipes/magic-glow/SKILL.md +59 -26
  151. package/skills/visual-effects/recipes/muzzle-flash/SKILL.md +68 -25
  152. package/skills/visual-effects/recipes/smoke/SKILL.md +89 -28
  153. package/skills/visual-effects/recipes/water-ripple/SKILL.md +83 -23
  154. package/skills/workflow/brainstorming/SKILL.md +1 -1
  155. package/skills/workflow/debugging-game-feel/SKILL.md +29 -9
  156. package/skills/workflow/diagnosing-perf-regressions/SKILL.md +46 -21
  157. package/skills/workflow/gameskill/SKILL.md +11 -2
  158. package/skills/workflow/headless-scripting/SKILL.md +407 -0
  159. package/skills/workflow/investigating-bugs/SKILL.md +4 -2
  160. package/skills/workflow/mcpupdate/SKILL.md +9 -9
  161. package/skills/workflow/playtesting-a-feature/SKILL.md +94 -21
  162. package/skills/workflow/skill-create/SKILL.md +7 -7
  163. package/skills/workflow/skill-test/SKILL.md +3 -3
  164. package/skills/workflow/using-summer/SKILL.md +32 -3
  165. package/skills/workflow/verification-before-completion/SKILL.md +2 -0
  166. package/skills/workflow/writing-plans/SKILL.md +7 -7
  167. package/skills/workflow/writing-skills/SKILL.md +7 -5
  168. package/skills/workflow/writing-skills/testing-skills-with-subagents.md +1 -1
@@ -0,0 +1,222 @@
1
+ # Autopilot — drives the player through a list of waypoints, saves a frame at each,
2
+ # asserts what it saw, and exits.
3
+ #
4
+ # This is a starting point, not a finished test. Edit CONFIG below to match your game,
5
+ # then add your own assertions in _check_at_waypoint(). Everything here is ordinary
6
+ # GDScript against your real running game — there is no test framework to learn.
7
+ #
8
+ # Run it:
9
+ # bash tests/autopilot/run.sh
10
+ # or from an agent, as a RunVerification probe (see tests/autopilot/README.md).
11
+
12
+ extends "res://tests/autopilot/probe_base.gd"
13
+
14
+ # ── CONFIG — edit this ────────────────────────────────────────────────────────────
15
+
16
+ # Node path to the player, from the current scene root.
17
+ const PLAYER_PATH := "Player"
18
+
19
+ # Input action names from your project's Input Map. Leave an entry empty to skip it.
20
+ const ACT_LEFT := "move_left"
21
+ const ACT_RIGHT := "move_right"
22
+ const ACT_UP := "" # "move_forward" in 3D, "move_up" in top-down 2D
23
+ const ACT_DOWN := ""
24
+ const ACT_JUMP := "jump"
25
+
26
+ # Where to walk. 2D games use x/y in pixels; 3D games use x/z in metres (y ignored).
27
+ # Positions are WORLD positions, not offsets.
28
+ const WAYPOINTS := [
29
+ Vector3(300, 0, 0),
30
+ Vector3(600, 0, 0),
31
+ ]
32
+
33
+ # How close counts as arrived, and how long to allow per leg before giving up.
34
+ const ARRIVE_EPSILON := 40.0
35
+ const LEG_TIMEOUT_FRAMES := 240
36
+
37
+ # ── END CONFIG ────────────────────────────────────────────────────────────────────
38
+
39
+ var _player: Node = null
40
+ var _is_3d := false
41
+
42
+
43
+ func _ready() -> void:
44
+ await super._ready()
45
+
46
+ # Let the scene settle. Physics frames, not timers — timers are wall-clock and
47
+ # make results differ between runs and between machines.
48
+ for _i in 30:
49
+ await get_tree().physics_frame
50
+
51
+ if not _bind_player():
52
+ finish()
53
+ return
54
+
55
+ report("player_path", PLAYER_PATH)
56
+ report("player_class", _player.get_class())
57
+ report("mode", "3D" if _is_3d else "2D")
58
+ report("start_pos", str(_pos()))
59
+ dump_tree()
60
+ save_frame("00_start")
61
+
62
+ for i in WAYPOINTS.size():
63
+ var target: Vector3 = WAYPOINTS[i]
64
+ var arrived := await _walk_to(target)
65
+ report("waypoint_%d_reached" % i, arrived)
66
+ report("waypoint_%d_pos" % i, str(_pos()))
67
+ save_frame("%02d_waypoint_%d" % [i + 1, i])
68
+ _check_at_waypoint(i)
69
+ if not arrived:
70
+ report("failed_at_waypoint", i)
71
+ break
72
+
73
+ _check_at_end()
74
+ finish()
75
+
76
+
77
+ # ── Your assertions go here ───────────────────────────────────────────────────────
78
+
79
+ # Called after arriving at (or failing to reach) each waypoint.
80
+ func _check_at_waypoint(index: int) -> void:
81
+ # Assert things that are TRUE OR FALSE, not things that are approximately a number.
82
+ # Hold durations are wall-clock, so exact distances drift between runs.
83
+ #
84
+ # var hud := get_tree().current_scene.get_node_or_null("UI/HUD")
85
+ # report("hud_visible_at_%d" % index, hud != null and hud.visible)
86
+ # report("enemies_alive_at_%d" % index, get_tree().get_nodes_in_group("enemy").size())
87
+ pass
88
+
89
+
90
+ # Called once after the last waypoint.
91
+ func _check_at_end() -> void:
92
+ # report("player_alive", _player.has_method("is_alive") and _player.is_alive())
93
+ pass
94
+
95
+
96
+ # ── Machinery — you should not need to edit below this line ───────────────────────
97
+
98
+ func _bind_player() -> bool:
99
+ var scene := get_tree().current_scene
100
+ if scene == null:
101
+ report("error", "no current scene — is run/main_scene set in project.godot?")
102
+ return false
103
+ _player = scene.get_node_or_null(PLAYER_PATH)
104
+ if _player == null:
105
+ report("error", "player not found at '%s'" % PLAYER_PATH)
106
+ report("scene_children", _child_names(scene))
107
+ dump_tree()
108
+ return false
109
+ _is_3d = _player is Node3D
110
+ if not (_is_3d or _player is Node2D):
111
+ report("error", "player at '%s' is neither Node2D nor Node3D" % PLAYER_PATH)
112
+ return false
113
+ return true
114
+
115
+
116
+ func _child_names(n: Node) -> Array:
117
+ var names := []
118
+ for c in n.get_children():
119
+ names.append(c.name)
120
+ return names
121
+
122
+
123
+ # Player position normalised to a Vector3 so one code path handles 2D and 3D.
124
+ # In 2D, x/y are screen pixels and z is unused.
125
+ func _pos() -> Vector3:
126
+ if _is_3d:
127
+ var p: Vector3 = _player.global_position
128
+ return p
129
+ var p2: Vector2 = _player.global_position
130
+ return Vector3(p2.x, p2.y, 0.0)
131
+
132
+
133
+ # Distance that matters for "did we arrive", measured ONLY on axes the game can
134
+ # actually steer. A side-on platformer has no up/down actions, so its Y is gravity,
135
+ # not a destination — including it would mean the player never "arrives" while
136
+ # standing on the ground 368 pixels below Y=0. That is a real failure this scaffold
137
+ # hit on its first run.
138
+ func _planar_distance(a: Vector3, b: Vector3) -> float:
139
+ var d := 0.0
140
+ if _has_horizontal_actions():
141
+ d += pow(b.x - a.x, 2.0)
142
+ if _has_secondary_actions():
143
+ var d2 := (b.z - a.z) if _is_3d else (b.y - a.y)
144
+ d += pow(d2, 2.0)
145
+ return sqrt(d)
146
+
147
+
148
+ func _has_horizontal_actions() -> bool:
149
+ return _action_live(ACT_LEFT) or _action_live(ACT_RIGHT)
150
+
151
+
152
+ func _has_secondary_actions() -> bool:
153
+ return _action_live(ACT_UP) or _action_live(ACT_DOWN)
154
+
155
+
156
+ func _action_live(action: String) -> bool:
157
+ return action != "" and InputMap.has_action(action)
158
+
159
+
160
+ func _walk_to(target: Vector3) -> bool:
161
+ var frames := 0
162
+ var held: Array[String] = []
163
+
164
+ while frames < LEG_TIMEOUT_FRAMES:
165
+ var here := _pos()
166
+ if _planar_distance(here, target) <= ARRIVE_EPSILON:
167
+ _release(held)
168
+ return true
169
+
170
+ var want := _actions_toward(here, target)
171
+ # Only touch actions that changed, so a held key stays held across frames
172
+ # instead of being released and re-pressed every frame.
173
+ for a in held:
174
+ if not want.has(a):
175
+ _set_action(a, false)
176
+ for a in want:
177
+ if not held.has(a):
178
+ _set_action(a, true)
179
+ held = want
180
+
181
+ await get_tree().physics_frame
182
+ frames += 1
183
+
184
+ _release(held)
185
+ return false
186
+
187
+
188
+ func _actions_toward(here: Vector3, target: Vector3) -> Array[String]:
189
+ var out: Array[String] = []
190
+ var dx := target.x - here.x
191
+ if absf(dx) > ARRIVE_EPSILON * 0.5:
192
+ var a := ACT_RIGHT if dx > 0.0 else ACT_LEFT
193
+ if _action_live(a):
194
+ out.append(a)
195
+
196
+ # Second axis: z in 3D, y in 2D. Skipped when the game has no such actions
197
+ # (a side-on platformer, for example).
198
+ if _has_secondary_actions():
199
+ var d2 := (target.z - here.z) if _is_3d else (target.y - here.y)
200
+ if absf(d2) > ARRIVE_EPSILON * 0.5:
201
+ var b := ACT_DOWN if d2 > 0.0 else ACT_UP
202
+ if _action_live(b):
203
+ out.append(b)
204
+
205
+ return out
206
+
207
+
208
+ # Press and release directly rather than via press(), which awaits its own hold
209
+ # duration on the wall clock. Holding across physics frames is what makes an
210
+ # autopilot run reproducible.
211
+ func _set_action(action: String, pressed: bool) -> void:
212
+ if action == "" or not InputMap.has_action(action):
213
+ return
214
+ var ev := InputEventAction.new()
215
+ ev.action = action
216
+ ev.pressed = pressed
217
+ Input.parse_input_event(ev)
218
+
219
+
220
+ func _release(held: Array) -> void:
221
+ for a in held:
222
+ _set_action(a, false)
@@ -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
@@ -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 desktop engine (Godot fork in C++) + the canonical CLI. The engine source lives at `editor/`, `core/`, and the Summer-specific module at `modules/1summer_engine/`. The Summer CLI lives at `tools/summer-cli/`. It ships as `npm i -g summer-engine`. It does install/login/create/run for the engine, plus `summer skills install --as-claude-skill` which writes a curated subset of skills into `~/.claude/skills/` for any agent to auto-discover.
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 `~/development/<game-name>` (Godot 4.6) or whatever game we were just working on. Real working code in this repo is the gold standard for examples.
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
 
@@ -0,0 +1,13 @@
1
+ You are the Summer Engine command router. The user invoked `/summer` because they want game-development help inside Summer Engine, not generic software advice.
2
+
3
+ Arguments:
4
+
5
+ ```
6
+ $ARGUMENTS
7
+ ```
8
+
9
+ First, route the request to the closest Summer skill. Use `summer:using-summer` if you need base context before choosing.
10
+
11
+ If the arguments start with `debug`, or the user is reporting a crash, error, broken behavior, cloud/Codex failure, or anything they want to send to Summer support, use `summer:debugging/debug`. In that support-report mode, call `summer_create_debug_report` when available and tell the user where the Markdown report was written. Continue fixing the issue only if the user also asked for a fix.
12
+
13
+ For all other requests, follow the `/summer` persona routing table from the Summer skill family and start the appropriate game-dev workflow.
@@ -17,6 +17,11 @@ import { setupCommand } from "../commands/setup.js";
17
17
  import { doctorCommand } from "../commands/doctor.js";
18
18
  import { planCommand } from "../commands/plan.js";
19
19
  import { cloudCommand } from "../commands/cloud.js";
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";
20
25
  import { getBanner } from "../lib/banner.js";
21
26
  import { c, sym } from "../lib/format.js";
22
27
  const require = createRequire(import.meta.url);
@@ -42,9 +47,14 @@ program.addCommand(skillsCommand);
42
47
  program.addCommand(mcpCommand);
43
48
  program.addCommand(setupCommand);
44
49
  program.addCommand(doctorCommand);
50
+ program.addCommand(debugCommand);
45
51
  program.addCommand(planCommand);
46
52
  program.addCommand(orchestratorCommand);
47
53
  program.addCommand(cloudCommand);
54
+ program.addCommand(configCommand);
55
+ program.addCommand(publishCommand);
56
+ program.addCommand(releasesCommand);
57
+ program.addCommand(logsCommand);
48
58
  program.parseAsync().catch((err) => {
49
59
  console.error(err instanceof Error ? err.message : String(err));
50
60
  process.exit(1);
@@ -63,10 +73,12 @@ function printIntro(version) {
63
73
  console.log(` ${c.dim("$")} npx -y summer-engine@latest setup claude-code --yes`);
64
74
  console.log(` ${c.dim("$")} npx -y summer-engine@latest create 3d-basic my-game`);
65
75
  console.log(` ${c.dim("$")} npx -y summer-engine@latest plan make a small arena shooter`);
76
+ console.log(` ${c.dim("$")} npx -y summer-engine@latest debug "player crashes on pickup"`);
66
77
  console.log(` ${c.dim("$")} npx -y summer-engine@latest memory`);
67
78
  console.log(` ${c.dim("$")} npx -y summer-engine@latest doctor`);
68
79
  console.log("");
69
80
  console.log(` ${c.bold("Slash commands")} ${c.dim("(in your AI agent)")}`);
81
+ console.log(` ${c.brand("/summer debug")} ${c.dim("Create a support-ready debug report")}`);
70
82
  console.log(` ${c.brand("/debug")} ${c.dim("Triage and fix a bug end-to-end")}`);
71
83
  console.log(` ${c.brand("/play")} ${c.dim("Run the game and report state")}`);
72
84
  console.log("");
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const configCommand: Command;