summer-engine 4.3.0 → 4.6.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 (231) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +3 -1
  3. package/.codex-plugin/plugin.json +5 -3
  4. package/.cursor-plugin/plugin.json +4 -2
  5. package/.factory-plugin/plugin.json +4 -2
  6. package/AGENTS.md +2 -2
  7. package/CHANGELOG.md +100 -0
  8. package/README.md +67 -27
  9. package/commands/start.md +19 -15
  10. package/commands/summer.md +1 -0
  11. package/dist/cli/command-usage.d.ts +3 -1
  12. package/dist/cli/command-usage.js +10 -4
  13. package/dist/cli/commands/create.d.ts +1 -25
  14. package/dist/cli/commands/create.js +10 -138
  15. package/dist/cli/commands/dev.d.ts +66 -0
  16. package/dist/cli/commands/dev.js +234 -0
  17. package/dist/cli/commands/install.d.ts +15 -58
  18. package/dist/cli/commands/install.js +31 -481
  19. package/dist/cli/commands/login.js +1 -1
  20. package/dist/cli/commands/ops.d.ts +17 -1
  21. package/dist/cli/commands/ops.js +97 -9
  22. package/dist/cli/commands/preview.d.ts +58 -0
  23. package/dist/cli/commands/preview.js +181 -0
  24. package/dist/cli/commands/run.d.ts +1 -17
  25. package/dist/cli/commands/run.js +17 -206
  26. package/dist/cli/commands/setup.js +175 -32
  27. package/dist/cli/index.js +5 -0
  28. package/dist/core/agent-host.d.ts +3 -0
  29. package/dist/core/agent-host.js +1 -1
  30. package/dist/core/api-client.d.ts +10 -2
  31. package/dist/core/api-client.js +13 -8
  32. package/dist/core/capabilities/agent-playbook.js +1 -0
  33. package/dist/core/capabilities/apps.d.ts +9 -9
  34. package/dist/core/capabilities/asset-import.d.ts +4 -1
  35. package/dist/core/capabilities/asset-import.js +15 -6
  36. package/dist/core/capabilities/capture-gameplay.js +1 -1
  37. package/dist/core/capabilities/connect-signal.d.ts +2 -2
  38. package/dist/core/capabilities/content-make/index.d.ts +81 -0
  39. package/dist/core/capabilities/content-make/index.js +706 -0
  40. package/dist/core/capabilities/content-make/media.d.ts +57 -0
  41. package/dist/core/capabilities/content-make/media.js +172 -0
  42. package/dist/core/capabilities/content-make/plan.d.ts +130 -0
  43. package/dist/core/capabilities/content-make/plan.js +433 -0
  44. package/dist/core/capabilities/content-make/render.d.ts +64 -0
  45. package/dist/core/capabilities/content-make/render.js +370 -0
  46. package/dist/core/capabilities/content-make/study.d.ts +13 -0
  47. package/dist/core/capabilities/content-make/study.js +165 -0
  48. package/dist/core/capabilities/content-make/types.d.ts +173 -0
  49. package/dist/core/capabilities/content-make/types.js +6 -0
  50. package/dist/core/capabilities/content-make/voice.d.ts +7 -0
  51. package/dist/core/capabilities/content-make/voice.js +27 -0
  52. package/dist/core/capabilities/events.d.ts +2 -2
  53. package/dist/core/capabilities/export-game.d.ts +4 -1
  54. package/dist/core/capabilities/export-game.js +4 -3
  55. package/dist/core/capabilities/export-presets.d.ts +2 -1
  56. package/dist/core/capabilities/export-presets.js +2 -1
  57. package/dist/core/capabilities/go-live.d.ts +14 -0
  58. package/dist/core/capabilities/go-live.js +408 -0
  59. package/dist/core/capabilities/image-generation.d.ts +6 -6
  60. package/dist/core/capabilities/item-manifest.d.ts +92 -0
  61. package/dist/core/capabilities/item-manifest.js +247 -0
  62. package/dist/core/capabilities/placement.d.ts +7 -7
  63. package/dist/core/capabilities/projects-local.d.ts +68 -0
  64. package/dist/core/capabilities/projects-local.js +172 -0
  65. package/dist/core/capabilities/publish-build.d.ts +13 -2
  66. package/dist/core/capabilities/publish-build.js +87 -21
  67. package/dist/core/capabilities/publish-download.js +1 -1
  68. package/dist/core/capabilities/runtime-control.d.ts +7 -2
  69. package/dist/core/capabilities/runtime-control.js +27 -1
  70. package/dist/core/capabilities/seeing/args.d.ts +2 -2
  71. package/dist/core/capabilities/store-checks.d.ts +45 -0
  72. package/dist/core/capabilities/store-checks.js +85 -0
  73. package/dist/core/capabilities/store-details.d.ts +37 -0
  74. package/dist/core/capabilities/store-details.js +94 -0
  75. package/dist/core/capabilities/summer-bundle.d.ts +21 -2
  76. package/dist/core/capabilities/summer-bundle.js +54 -7
  77. package/dist/core/capabilities/sync-items.d.ts +89 -0
  78. package/dist/core/capabilities/sync-items.js +352 -0
  79. package/dist/core/capabilities/tool-dispatch.js +66 -6
  80. package/dist/core/capabilities/video-ogv.d.ts +49 -0
  81. package/dist/core/capabilities/video-ogv.js +260 -0
  82. package/dist/core/capability-skew.js +2 -2
  83. package/dist/core/cli-usage-beacon.js +26 -2
  84. package/dist/core/cli-usage.d.ts +5 -2
  85. package/dist/core/cli-usage.js +8 -2
  86. package/dist/core/engine-install.d.ts +53 -0
  87. package/dist/core/engine-install.js +93 -0
  88. package/dist/core/engine-installer.d.ts +134 -0
  89. package/dist/core/engine-installer.js +760 -0
  90. package/dist/core/engine-launch.d.ts +126 -0
  91. package/dist/core/engine-launch.js +398 -0
  92. package/dist/core/engine-self-start.d.ts +63 -0
  93. package/dist/core/engine-self-start.js +282 -0
  94. package/dist/core/engine.d.ts +7 -1
  95. package/dist/core/engine.js +9 -2
  96. package/dist/core/error-codes.d.ts +4 -1
  97. package/dist/core/error-codes.js +7 -3
  98. package/dist/core/library-read.d.ts +2 -2
  99. package/dist/core/library-search.d.ts +2 -2
  100. package/dist/core/local-project.d.ts +50 -0
  101. package/dist/core/local-project.js +234 -0
  102. package/dist/core/ops/areas.js +9 -3
  103. package/dist/core/ops/catalog.d.ts +9 -0
  104. package/dist/core/ops/catalog.js +56 -4
  105. package/dist/core/ops/engine-answer.d.ts +4 -1
  106. package/dist/core/ops/engine-answer.js +16 -3
  107. package/dist/core/ops/engine.js +4 -4
  108. package/dist/core/ops/save-files.d.ts +6 -1
  109. package/dist/core/ops/save-files.js +67 -50
  110. package/dist/core/ops/search.js +9 -2
  111. package/dist/core/preview/input.d.ts +35 -0
  112. package/dist/core/preview/input.js +105 -0
  113. package/dist/core/preview/launch-config.d.ts +52 -0
  114. package/dist/core/preview/launch-config.js +137 -0
  115. package/dist/core/preview/page.d.ts +22 -0
  116. package/dist/core/preview/page.js +232 -0
  117. package/dist/core/preview/preview-source.d.ts +179 -0
  118. package/dist/core/preview/preview-source.js +350 -0
  119. package/dist/core/preview/server.d.ts +26 -0
  120. package/dist/core/preview/server.js +218 -0
  121. package/dist/core/telemetry.d.ts +20 -16
  122. package/dist/core/telemetry.js +92 -16
  123. package/dist/core/templates.d.ts +20 -0
  124. package/dist/core/templates.js +300 -3
  125. package/dist/core/tool-profiles.d.ts +2 -2
  126. package/dist/core/tool-profiles.js +1 -0
  127. package/dist/core/usage-fields.d.ts +41 -0
  128. package/dist/core/usage-fields.js +150 -0
  129. package/dist/core/util/sleep.d.ts +3 -0
  130. package/dist/core/util/sleep.js +12 -0
  131. package/dist/installer/agent-config.d.ts +29 -0
  132. package/dist/installer/agent-config.js +75 -11
  133. package/dist/installer/agent-detect.d.ts +44 -0
  134. package/dist/installer/agent-detect.js +166 -0
  135. package/dist/installer/agent-table.d.ts +19 -0
  136. package/dist/installer/agent-table.js +130 -0
  137. package/dist/installer/setup-error.d.ts +19 -0
  138. package/dist/installer/setup-error.js +34 -0
  139. package/dist/installer/version-check.d.ts +5 -0
  140. package/dist/installer/version-check.js +21 -0
  141. package/dist/mcp/ops-surface.d.ts +7 -0
  142. package/dist/mcp/ops-surface.js +40 -3
  143. package/dist/mcp/server.d.ts +14 -4
  144. package/dist/mcp/server.js +42 -8
  145. package/dist/mcp/tool-surface.d.ts +8 -0
  146. package/dist/mcp/tool-surface.js +19 -2
  147. package/dist/mcp/tools/creator-tools.js +92 -14
  148. package/dist/mcp/tools/debug-tools.js +2 -0
  149. package/dist/mcp/tools/local-project-tools.d.ts +6 -0
  150. package/dist/mcp/tools/local-project-tools.js +33 -0
  151. package/dist/mcp/tools/navigation-tools.js +2 -1
  152. package/dist/mcp/tools/project-tools.js +5 -8
  153. package/dist/mcp/tools/seeing-tools.js +1 -1
  154. package/dist/mcp/tools/visual-tools.js +1 -1
  155. package/dist/mcp/tools/with-engine.js +1 -1
  156. package/dist/project-memory/agent-instructions.js +11 -3
  157. package/gemini-extension.json +3 -1
  158. package/library/references/mcp-tools-reference/mcp-tools-reference.md +18 -5
  159. package/library/skills/README.md +12 -8
  160. package/library/skills/animated-loop/SKILL.md +10 -16
  161. package/library/skills/cinematic-cutscene/SKILL.md +7 -18
  162. package/library/skills/headless-scripting/SKILL.md +7 -4
  163. package/library/skills/in-game-purchases/SKILL.md +25 -8
  164. package/library/skills/in-game-purchases/resource.yaml +2 -1
  165. package/library/skills/launch-ready/SKILL.md +351 -0
  166. package/library/skills/launch-ready/resource.yaml +45 -0
  167. package/library/skills/make-content/SKILL.md +202 -0
  168. package/library/skills/make-content/resource.yaml +32 -0
  169. package/library/skills/make-game/SKILL.md +6 -4
  170. package/library/skills/make-game/resource.yaml +2 -1
  171. package/library/skills/navigate-summer/SKILL.md +4 -2
  172. package/library/skills/new-project/SKILL.md +15 -22
  173. package/library/skills/new-project/resource.yaml +2 -1
  174. package/library/skills/painted-25d-world/SKILL.md +366 -0
  175. package/library/skills/painted-25d-world/_building-blocks/blender-route.md +33 -0
  176. package/library/skills/painted-25d-world/_building-blocks/blockout_builder.gd +149 -0
  177. package/library/skills/painted-25d-world/_building-blocks/capture_paint_view.gd +32 -0
  178. package/library/skills/painted-25d-world/_building-blocks/paint_projection.gdshader +23 -0
  179. package/library/skills/painted-25d-world/_building-blocks/paintcam_check.gd +25 -0
  180. package/library/skills/painted-25d-world/_building-blocks/painted_door.gd +17 -0
  181. package/library/skills/painted-25d-world/_building-blocks/painted_location.gd +56 -0
  182. package/library/skills/painted-25d-world/_building-blocks/painted_player.gd +125 -0
  183. package/library/skills/painted-25d-world/_building-blocks/painted_world.gd +111 -0
  184. package/library/skills/painted-25d-world/_building-blocks/tools/cam.py +57 -0
  185. package/library/skills/painted-25d-world/_building-blocks/tools/detail4k.py +81 -0
  186. package/library/skills/painted-25d-world/_building-blocks/tools/overlay.py +17 -0
  187. package/library/skills/painted-25d-world/_building-blocks/tools/preview.py +60 -0
  188. package/library/skills/painted-25d-world/resource.yaml +39 -0
  189. package/library/skills/publish-your-game/SKILL.md +13 -14
  190. package/library/skills/publish-your-game/resource.yaml +4 -3
  191. package/library/skills/release-checklist/SKILL.md +188 -0
  192. package/library/skills/release-checklist/resource.yaml +40 -0
  193. package/library/skills/release-readiness/SKILL.md +1 -1
  194. package/library/skills/release-readiness/resource.yaml +1 -1
  195. package/library/skills/store-art/SKILL.md +1 -1
  196. package/library/skills/store-art/resource.yaml +1 -1
  197. package/library/skills/store-listing/SKILL.md +1 -1
  198. package/library/skills/store-listing/resource.yaml +1 -1
  199. package/library/skills/summer-cli/SKILL.md +2 -0
  200. package/library/skills/summer-store/SKILL.md +295 -39
  201. package/library/skills/summer-store/resource.yaml +9 -3
  202. package/library/skills/trailer-shot/SKILL.md +3 -3
  203. package/library/templates/README.md +5 -0
  204. package/library/tools/content-make/resource.yaml +132 -0
  205. package/library/tools/frame-shot/GUIDE.md +1 -1
  206. package/library/tools/go-live/resource.yaml +67 -0
  207. package/library/tools/import-from-url/GUIDE.md +1 -0
  208. package/library/tools/import-from-url-batch/GUIDE.md +1 -1
  209. package/library/tools/play/GUIDE.md +2 -0
  210. package/library/tools/projects-create-local/resource.yaml +55 -0
  211. package/library/tools/projects-open-local/resource.yaml +51 -0
  212. package/library/tools/publish-build/resource.yaml +3 -3
  213. package/library/tools/screenshot/GUIDE.md +1 -1
  214. package/library/tools/store-set-details/resource.yaml +114 -0
  215. package/library/tools/sync-items/resource.yaml +72 -0
  216. package/package.json +1 -1
  217. package/registry/generated/aliases.json +6 -0
  218. package/registry/generated/counts.json +3 -3
  219. package/registry/generated/gemini-extension.json +3 -1
  220. package/registry/generated/index.json +463 -37
  221. package/registry/generated/marketplace.claude.json +1 -1
  222. package/registry/generated/plugin.claude-directory.json +4 -2
  223. package/registry/generated/plugin.claude.json +3 -1
  224. package/registry/generated/plugin.codex.json +5 -3
  225. package/registry/generated/plugin.cursor.json +4 -2
  226. package/registry/generated/plugin.factory.json +4 -2
  227. package/registry/generated/server.json +2 -2
  228. package/registry/generated/skills-index.md +12 -8
  229. package/registry/generated/skills-registry.json +62 -5
  230. package/registry/operations/legacy-names.json +40 -0
  231. package/registry/operations/public-catalog.json +10144 -387
@@ -0,0 +1,232 @@
1
+ /**
2
+ * The one page `summer preview` serves. Self-contained: inline style and
3
+ * script, no fonts, no CDN, no request leaves 127.0.0.1 (the server's
4
+ * Content-Security-Policy enforces it), and no token anywhere (the server
5
+ * holds the engine connection).
6
+ *
7
+ * Loop: fetch /api/frame, show it, wait the interval the server derived from
8
+ * the measured capture time, repeat. Hidden tab = no requests. Each frame
9
+ * response carries the status (x-summer-status) so one request a frame is all
10
+ * the page makes. The first frame after the page loads or comes back into
11
+ * view, and the Refresh button, ask for a fresh one (?fresh=1): game frames
12
+ * are otherwise about 20 s apart, and the header says how old the frame is.
13
+ * While the game runs, keys and clicks on the frame go to it; a key release is
14
+ * always sent, so no key stays held in the game. A page framed inside another
15
+ * page sends no input.
16
+ *
17
+ * For agents checking the page: <body data-state> is connecting, no_editor,
18
+ * editor or game; #status holds the status line; #refresh fetches a fresh
19
+ * frame; the page logs nothing to the console in normal operation, so a
20
+ * console error is a real one.
21
+ */
22
+ export const PREVIEW_PAGE_HTML = `<!doctype html>
23
+ <html lang="en">
24
+ <head>
25
+ <meta charset="utf-8">
26
+ <meta name="viewport" content="width=device-width, initial-scale=1">
27
+ <title>Summer preview</title>
28
+ <style>
29
+ :root { --cobalt: #0047ab; --ink: #111; --muted: #6b6b6b; --line: #e6e6e6; }
30
+ * { box-sizing: border-box; }
31
+ html, body { height: 100%; margin: 0; }
32
+ body { display: flex; flex-direction: column; background: #fff; color: var(--ink);
33
+ font: 13px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; }
34
+ header, footer { display: flex; align-items: center; gap: 12px; padding: 8px 14px; min-height: 36px; }
35
+ header { border-bottom: 1px solid var(--line); }
36
+ footer { border-top: 1px solid var(--line); color: var(--muted); }
37
+ .brand { color: var(--cobalt); font-weight: 700; }
38
+ #project { font-weight: 600; }
39
+ #scene { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
40
+ .spacer { flex: 1; }
41
+ #source { color: #fff; background: var(--cobalt); border-radius: 4px; padding: 1px 8px; font-weight: 600; }
42
+ #source:empty { display: none; }
43
+ #rate { color: var(--muted); font-variant-numeric: tabular-nums; }
44
+ #refresh { font: inherit; color: var(--cobalt); background: #fff; border: 1px solid var(--line); border-radius: 4px; padding: 1px 8px; cursor: pointer; }
45
+ body[data-state="no_editor"] #refresh, body[data-state="connecting"] #refresh { display: none; }
46
+ main { flex: 1; min-height: 0; position: relative; display: flex; align-items: center; justify-content: center; outline: none; }
47
+ #frame { width: 100%; height: 100%; object-fit: contain; display: block; user-select: none; -webkit-user-drag: none; }
48
+ body[data-state="game"] #frame { cursor: crosshair; }
49
+ #empty { display: none; text-align: center; max-width: 520px; padding: 24px; }
50
+ #empty h1 { font-size: 20px; margin: 0 0 8px; }
51
+ #empty code { display: inline-block; margin-top: 8px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 4px; color: var(--cobalt); }
52
+ body[data-state="no_editor"] #frame, body[data-state="connecting"] #frame { display: none; }
53
+ body[data-state="no_editor"] #empty, body[data-state="connecting"] #empty { display: block; }
54
+ #status { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
55
+ </style>
56
+ </head>
57
+ <body data-state="connecting">
58
+ <header>
59
+ <span class="brand">Summer</span>
60
+ <span id="project"></span>
61
+ <span id="scene"></span>
62
+ <span class="spacer"></span>
63
+ <span id="source"></span>
64
+ <span id="rate"></span>
65
+ <button id="refresh" type="button" title="Capture a fresh frame now">Refresh</button>
66
+ </header>
67
+ <main id="stage" tabindex="0">
68
+ <img id="frame" alt="Live frame from Summer Engine">
69
+ <div id="empty">
70
+ <h1 id="empty-title">Looking for Summer Engine</h1>
71
+ <div id="empty-text"></div>
72
+ </div>
73
+ </main>
74
+ <footer><span id="status">Looking for Summer Engine</span></footer>
75
+ <script>
76
+ (() => {
77
+ const $ = (id) => document.getElementById(id);
78
+ const frame = $("frame");
79
+ const stage = $("stage");
80
+ const framed = window.top !== window.self;
81
+ let status = null;
82
+ let timer = null;
83
+ let loop = 0;
84
+ let lastUrl = null;
85
+ let note = null;
86
+ const shown = [];
87
+
88
+ function render() {
89
+ const s = status;
90
+ if (!s) return;
91
+ const message = typeof s.message === "string" ? s.message : "";
92
+ document.body.dataset.state = s.state;
93
+ document.body.dataset.source = s.frame ? s.frame.source : "";
94
+ $("project").textContent = s.project || "";
95
+ $("scene").textContent = s.scene || "";
96
+ $("source").textContent = s.state === "game" ? "Game" : s.state === "editor" ? "Editor" : "";
97
+ const now = Date.now();
98
+ while (shown.length > 2 && now - shown[0] > 12000) shown.shift();
99
+ const fps = shown.length > 1 ? (shown.length - 1) / ((shown[shown.length - 1] - shown[0]) / 1000) : 0;
100
+ const rate = [];
101
+ if (s.frame && s.frame.source === "game") rate.push("frame " + Math.max(0, Math.round((now - s.frame.capturedAt) / 1000)) + " s old");
102
+ else if (s.frame && fps) rate.push(fps.toFixed(1) + " fps");
103
+ if (s.frame && s.latencyMs != null) rate.push(s.latencyMs + " ms per frame");
104
+ $("rate").textContent = rate.join(" · ");
105
+ $("status").textContent = note && now < note.until ? note.text : message;
106
+ const parts = message.split(": run ");
107
+ $("empty-title").textContent = s.state === "connecting" ? "Looking for Summer Engine" : parts[0];
108
+ const empty = $("empty-text");
109
+ empty.textContent = "";
110
+ if (parts[1]) {
111
+ const code = document.createElement("code");
112
+ code.textContent = parts[1];
113
+ empty.append("Run this in the project folder:", document.createElement("br"), code);
114
+ }
115
+ document.title = (s.project ? s.project + " · " : "") + "Summer preview";
116
+ }
117
+
118
+ async function show(blob) {
119
+ const url = URL.createObjectURL(blob);
120
+ const next = new Image();
121
+ next.src = url;
122
+ try { await next.decode(); } catch (_) {}
123
+ frame.src = url;
124
+ if (lastUrl) URL.revokeObjectURL(lastUrl);
125
+ lastUrl = url;
126
+ shown.push(Date.now());
127
+ }
128
+
129
+ function readStatus(value) {
130
+ return value && typeof value === "object" && typeof value.state === "string" ? value : null;
131
+ }
132
+
133
+ async function tick(id, fresh) {
134
+ if (id !== loop || document.hidden) return;
135
+ const started = performance.now();
136
+ let wait = 2000;
137
+ try {
138
+ const res = await fetch(fresh ? "/api/frame?fresh=1" : "/api/frame", { cache: "no-store" });
139
+ const header = res.headers.get("x-summer-status");
140
+ let next = null;
141
+ if (res.status === 200) {
142
+ if (header) next = readStatus(JSON.parse(decodeURIComponent(header)));
143
+ await show(await res.blob());
144
+ } else {
145
+ next = readStatus(await res.json().catch(() => null));
146
+ }
147
+ if (next) {
148
+ status = next;
149
+ wait = status.intervalMs || 2000;
150
+ }
151
+ } catch (_) {
152
+ status = { state: "no_editor", message: "The preview server stopped: run npx -y summer-engine@latest preview", frame: null };
153
+ }
154
+ try { render(); } catch (_) {}
155
+ if (id !== loop || document.hidden) return;
156
+ timer = setTimeout(() => tick(id, false), Math.max(0, wait - (performance.now() - started)));
157
+ }
158
+
159
+ function start() {
160
+ clearTimeout(timer);
161
+ loop += 1;
162
+ if (!document.hidden) tick(loop, true);
163
+ }
164
+ document.addEventListener("visibilitychange", start);
165
+ $("refresh").addEventListener("click", start);
166
+ setInterval(() => { if (!document.hidden) { try { render(); } catch (_) {} } }, 1000);
167
+
168
+ function framePoint(event) {
169
+ const rect = frame.getBoundingClientRect();
170
+ const w = frame.naturalWidth, h = frame.naturalHeight;
171
+ if (!w || !h) return null;
172
+ const scale = Math.min(rect.width / w, rect.height / h);
173
+ const x = (event.clientX - rect.left - (rect.width - w * scale) / 2) / scale;
174
+ const y = (event.clientY - rect.top - (rect.height - h * scale) / 2) / scale;
175
+ return x < 0 || y < 0 || x > w || y > h ? null : { x: x, y: y };
176
+ }
177
+
178
+ function inputOn() {
179
+ return !framed && status && status.input;
180
+ }
181
+
182
+ // Releases go out even while input is off, so a key never stays held.
183
+ async function send(inputs, release) {
184
+ if (framed || (!release && !inputOn())) return;
185
+ try {
186
+ const res = await fetch("/api/input", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ inputs: inputs }) });
187
+ const body = await res.json();
188
+ note = body.ok || release ? null : { text: "Input not sent: " + body.error, until: Date.now() + 4000 };
189
+ } catch (_) {
190
+ note = { text: "Input not sent: the preview server did not answer", until: Date.now() + 4000 };
191
+ }
192
+ try { render(); } catch (_) {}
193
+ }
194
+
195
+ frame.addEventListener("click", (event) => {
196
+ const point = framePoint(event);
197
+ stage.focus();
198
+ if (point && status && status.state === "game") send([{ kind: "click", x: point.x, y: point.y, button: 1 }], false);
199
+ });
200
+ frame.addEventListener("contextmenu", (event) => {
201
+ if (!inputOn() || status.state !== "game") return;
202
+ event.preventDefault();
203
+ const point = framePoint(event);
204
+ if (point) send([{ kind: "click", x: point.x, y: point.y, button: 2 }], false);
205
+ });
206
+ const held = new Set();
207
+ function onKey(event, pressed) {
208
+ if (pressed) {
209
+ if (!inputOn() || event.metaKey) return;
210
+ event.preventDefault();
211
+ if (held.has(event.code)) return;
212
+ held.add(event.code);
213
+ send([{ kind: "key", code: event.code, pressed: true }], false);
214
+ } else if (held.delete(event.code)) {
215
+ event.preventDefault();
216
+ send([{ kind: "key", code: event.code, pressed: false }], true);
217
+ }
218
+ }
219
+ stage.addEventListener("keydown", (event) => onKey(event, true));
220
+ stage.addEventListener("keyup", (event) => onKey(event, false));
221
+ stage.addEventListener("blur", () => {
222
+ const codes = Array.from(held);
223
+ held.clear();
224
+ if (codes.length) send(codes.map((code) => ({ kind: "key", code: code, pressed: false })), true);
225
+ });
226
+
227
+ start();
228
+ })();
229
+ </script>
230
+ </body>
231
+ </html>
232
+ `;
@@ -0,0 +1,179 @@
1
+ /**
2
+ * What `summer preview` shows: the running game's frames, or the editor
3
+ * viewport when no game runs, read through the same local API client every
4
+ * engine tool uses (core/api-client.ts). The server (./server.ts) asks for a
5
+ * frame only while a page is visible and asking, so a closed or hidden tab
6
+ * costs the engine nothing.
7
+ *
8
+ * Capture path, measured on Summer Engine 0.7.2 (2026-10-19, M-series Mac):
9
+ * - editor viewport, GET /api/snapshot/viewport (1280x671 JPEG, ~47 KB):
10
+ * p50 45 ms with the editor in front, 100-150 ms with it in the background
11
+ * (an unfocused editor sleeps ~100 ms per main-loop pass);
12
+ * - running game, GET /api/snapshot/game (1280x720 JPEG): 1.25-1.35 s, the
13
+ * same for GameProbe at any max_dim, and requests queue one after another.
14
+ * The game renders, reads back and PNG-encodes a full-size frame on its
15
+ * main thread: captured back to back, a game at 117 fps fell to 13 fps;
16
+ * - IsGameRunning 13 ms; a game snapshot with no game 12 ms (not_running).
17
+ * So the pace comes from the measured capture time, never a fixed rate: one
18
+ * frame every max(200 ms, share x the recent capture time). The editor's
19
+ * share is 2 (4-5 fps while the view changes, 2 fps once two frames in a row
20
+ * come back identical). The game's share is 16: a frame about every 20 s, so
21
+ * the game is held for well under a tenth of the time and a person playing
22
+ * it does not feel the preview. A check that needs the game as it is now asks
23
+ * for a fresh frame (/api/frame?fresh=1, the page's first frame, its Refresh
24
+ * button), at most one every 2 s.
25
+ *
26
+ * Status is read on a slower clock: whether a game runs (1 s), the project
27
+ * and scene names (5 s), and a fresh connection attempt while no editor
28
+ * answers (2 s). An editor restart (new token or port) is followed.
29
+ */
30
+ import type { EngineSnapshot, SnapshotOptions } from "../api-client.js";
31
+ import { type EngineCapabilities } from "../capability-skew.js";
32
+ import { type PreviewInput } from "./input.js";
33
+ /** The engine calls the preview makes: a structural subset of EngineApiClient. */
34
+ export interface PreviewEngine {
35
+ viewportSnapshot(options?: SnapshotOptions): Promise<EngineSnapshot>;
36
+ gameSnapshot(options?: SnapshotOptions): Promise<EngineSnapshot>;
37
+ executeOps(ops: Record<string, unknown>[], options?: Record<string, unknown>, timeoutMs?: number): Promise<unknown>;
38
+ getProjectState(): Promise<unknown>;
39
+ getSceneState(): Promise<unknown>;
40
+ credentialsChanged(): Promise<boolean>;
41
+ getEngineCapabilities?(): EngineCapabilities | undefined;
42
+ getEngineVersion?(): string | undefined;
43
+ }
44
+ /** Fastest pace: 5 frames a second. */
45
+ export declare const MIN_FRAME_INTERVAL_MS = 200;
46
+ /** Slowest pace while a frame still comes back. */
47
+ export declare const MAX_FRAME_INTERVAL_MS = 30000;
48
+ /** Capture may take at most 1/share of the time: the editor's viewport
49
+ * read is light; a game frame stalls the game while it is read. */
50
+ export declare const LATENCY_SHARE: Readonly<Record<"game" | "editor", number>>;
51
+ /** Editor pace once the view has stopped changing (2 fps). */
52
+ export declare const EDITOR_IDLE_INTERVAL_MS = 500;
53
+ /** Identical frames in a row before the editor counts as idle. */
54
+ export declare const IDLE_AFTER_SAME_FRAMES = 2;
55
+ /** Fresh frames on demand: at most one capture start this often. */
56
+ export declare const FRESH_MIN_GAP_MS = 2000;
57
+ /** Pace of the page while no editor answers. */
58
+ export declare const NO_EDITOR_INTERVAL_MS = 2000;
59
+ export declare const PLAY_CHECK_MS = 1000;
60
+ export declare const META_CHECK_MS = 5000;
61
+ export declare const RECONNECT_MS = 2000;
62
+ export declare const LINK_CHECK_MS = 2000;
63
+ /** After a running game's frame failed for a reason other than "not running",
64
+ * show the editor and try the game again after this long. */
65
+ export declare const GAME_RETRY_MS = 10000;
66
+ /** A request that comes sooner than this share of the pace after the last
67
+ * capture started gets that capture's frame again: two tabs, or the page and
68
+ * an agent's own request, never double the engine's work. */
69
+ export declare const SHARE_FRAME_FRACTION = 0.75;
70
+ export declare const NO_EDITOR_MESSAGE = "Editor not running: run npx -y summer-engine@latest dev";
71
+ export declare const GAME_NOT_RUNNING_MESSAGE = "Game not running: showing the editor";
72
+ export type PreviewState = "connecting" | "no_editor" | "editor" | "game";
73
+ export interface PreviewFrame {
74
+ seq: number;
75
+ source: "game" | "editor";
76
+ bytes: Buffer;
77
+ mime: string;
78
+ width: number;
79
+ height: number;
80
+ /** The game window's size when the engine scaled the frame down. */
81
+ sourceWidth?: number;
82
+ sourceHeight?: number;
83
+ capturedAt: number;
84
+ latencyMs: number;
85
+ }
86
+ export interface PreviewStatus {
87
+ service: "summer-preview";
88
+ state: PreviewState;
89
+ /** The one status line the page shows. */
90
+ message: string;
91
+ project: string | null;
92
+ scene: string | null;
93
+ engineVersion: string | null;
94
+ frame: {
95
+ seq: number;
96
+ source: "game" | "editor";
97
+ width: number;
98
+ height: number;
99
+ capturedAt: number;
100
+ latencyMs: number;
101
+ } | null;
102
+ /** Average capture time of the source being shown, ms. */
103
+ latencyMs: number | null;
104
+ /** How long the page waits between frames (from the measured latency). */
105
+ intervalMs: number;
106
+ /** Keys (and clicks on a game frame) reach the running game. */
107
+ input: boolean;
108
+ }
109
+ export type InputResult = {
110
+ ok: true;
111
+ sent: number;
112
+ } | {
113
+ ok: false;
114
+ error: string;
115
+ };
116
+ export interface PreviewSourceOptions {
117
+ connect: () => Promise<PreviewEngine>;
118
+ now?: () => number;
119
+ /** Where failure details go; what the page sees stays short. */
120
+ log?: (line: string) => void;
121
+ }
122
+ /** The pace for a `source` capture that takes `latencyMs`; `idle` = the
123
+ * editor view has stopped changing. */
124
+ export declare function frameIntervalMs(latencyMs: number | null, source?: "game" | "editor", idle?: boolean): number;
125
+ /** Schedule a batch frame by frame: a key pressed and released (or released
126
+ * and pressed again) in the same batch gets the second event one frame later,
127
+ * so a fast tap still reads as a press in the game. */
128
+ export declare function scheduleInputBatch(events: Record<string, unknown>[]): Record<string, unknown>[];
129
+ export declare class PreviewSource {
130
+ private readonly connectEngine;
131
+ private readonly now;
132
+ private readonly log;
133
+ private client;
134
+ private attempted;
135
+ private lastConnectAt;
136
+ private lastLinkCheckAt;
137
+ private linkError;
138
+ private playing;
139
+ private playingScene;
140
+ private lastPlayCheckAt;
141
+ private project;
142
+ private scene;
143
+ private lastMetaAt;
144
+ private gameBlockedUntil;
145
+ private gameNote;
146
+ private inputSupported;
147
+ private latency;
148
+ private current;
149
+ private seq;
150
+ private inflight;
151
+ private lastCaptureAt;
152
+ private sameFrames;
153
+ private inputChain;
154
+ private inputQueue;
155
+ constructor(options: PreviewSourceOptions);
156
+ /** The current state, without asking the engine anything. */
157
+ status(): PreviewStatus;
158
+ /** The newest frame: captured now, or the last one when another viewer
159
+ * asked moments ago. `fresh` captures now unless a capture started in the
160
+ * last FRESH_MIN_GAP_MS. Null while no editor answers. One capture at a time. */
161
+ frame(options?: {
162
+ fresh?: boolean;
163
+ }): Promise<PreviewFrame | null>;
164
+ /** Send clicks and keys to the running game, also while its frames cannot
165
+ * be read (a key release must never be lost). Calls are sent one at a time,
166
+ * in order; input that arrives meanwhile goes out as one script. */
167
+ sendInput(inputs: PreviewInput[]): Promise<InputResult>;
168
+ private pace;
169
+ private state;
170
+ private message;
171
+ private drop;
172
+ /** The connected client, connecting or following a restart as needed. */
173
+ private engine;
174
+ /** Whether a game runs (and which scene). False when the editor is gone. */
175
+ private refreshPlaying;
176
+ private refreshMeta;
177
+ private capture;
178
+ private read;
179
+ }