summer-engine 3.3.0 → 3.4.1
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/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +4 -2
- package/.codex-plugin/plugin.json +5 -3
- package/.cursor-plugin/plugin.json +4 -2
- package/.factory-plugin/plugin.json +4 -2
- package/.opencode/INSTALL.md +8 -8
- package/.opencode/plugins/summer.js +2 -2
- package/AGENTS.md +7 -7
- package/CHANGELOG.md +50 -0
- package/GEMINI.md +1 -1
- package/NOTICE +1 -1
- package/README.md +32 -35
- package/assets/api-docs.json.gz +0 -0
- package/assets/autopilot/run.sh +2 -2
- package/assets/navigation/web-routes.json +1 -1
- package/commands/gameskill.md +2 -2
- package/commands/summer.md +25 -25
- package/dist/cli/commands/install.d.ts +10 -0
- package/dist/cli/commands/install.js +55 -10
- package/dist/cli/commands/open.d.ts +1 -1
- package/dist/cli/commands/run.d.ts +1 -1
- package/dist/cli/commands/run.js +1 -1
- package/dist/cli/commands/skills.js +2 -2
- package/dist/cli/commands/tool.d.ts +7 -0
- package/dist/cli/commands/tool.js +36 -1
- package/dist/core/api-client.d.ts +5 -6
- package/dist/core/api-client.js +12 -15
- package/dist/core/auth.js +2 -2
- package/dist/core/capabilities/asset-import.d.ts +0 -1
- package/dist/core/capabilities/camera-view.d.ts +1 -1
- package/dist/core/capabilities/camera-view.js +2 -2
- package/dist/core/capabilities/capture-gameplay.d.ts +69 -0
- package/dist/core/capabilities/capture-gameplay.js +283 -0
- package/dist/core/capabilities/connect-signal.d.ts +1 -2
- package/dist/core/capabilities/connect-signal.js +1 -2
- package/dist/core/capabilities/console-read.d.ts +2 -2
- package/dist/core/capabilities/console-read.js +2 -2
- package/dist/core/capabilities/creator.js +1 -1
- package/dist/core/capabilities/doctor.d.ts +9 -1
- package/dist/core/capabilities/doctor.js +47 -4
- package/dist/core/capabilities/engine-receipt.d.ts +5 -7
- package/dist/core/capabilities/engine-receipt.js +7 -9
- package/dist/core/capabilities/engine-run.d.ts +4 -0
- package/dist/core/capabilities/engine-run.js +33 -5
- package/dist/core/capabilities/export-engine-version.d.ts +15 -0
- package/dist/core/capabilities/export-engine-version.js +24 -0
- package/dist/core/capabilities/export-game.d.ts +9 -1
- package/dist/core/capabilities/export-game.js +115 -39
- package/dist/core/capabilities/export-presets.d.ts +1 -1
- package/dist/core/capabilities/export-presets.js +1 -1
- package/dist/core/capabilities/fabricate-mesh.d.ts +2 -3
- package/dist/core/capabilities/fabricate-mesh.js +2 -3
- package/dist/core/capabilities/frame-quality.d.ts +4 -4
- package/dist/core/capabilities/frame-quality.js +4 -4
- package/dist/core/capabilities/grep.d.ts +1 -1
- package/dist/core/capabilities/grep.js +1 -1
- package/dist/core/capabilities/image-generation.d.ts +9 -0
- package/dist/core/capabilities/image-generation.js +19 -1
- package/dist/core/capabilities/navigation/open.d.ts +1 -1
- package/dist/core/capabilities/navigation/open.js +1 -1
- package/dist/core/capabilities/navigation/render-product-map.js +1 -1
- package/dist/core/capabilities/navigation/targets.js +2 -2
- package/dist/core/capabilities/placement-batch.d.ts +1 -1
- package/dist/core/capabilities/placement-batch.js +2 -2
- package/dist/core/capabilities/play-determinism.d.ts +1 -1
- package/dist/core/capabilities/play-determinism.js +1 -1
- package/dist/core/capabilities/publish-build.d.ts +15 -2
- package/dist/core/capabilities/publish-build.js +78 -28
- package/dist/core/capabilities/publish-download.d.ts +1 -2
- package/dist/core/capabilities/publish-download.js +2 -3
- package/dist/core/capabilities/replace-node.d.ts +1 -3
- package/dist/core/capabilities/replace-node.js +1 -3
- package/dist/core/capabilities/runtime-control.d.ts +5 -7
- package/dist/core/capabilities/runtime-control.js +7 -9
- package/dist/core/capabilities/scene-batch.d.ts +1 -1
- package/dist/core/capabilities/scene-batch.js +1 -1
- package/dist/core/capabilities/scene-kind.js +1 -1
- package/dist/core/capabilities/scene-readback.d.ts +3 -4
- package/dist/core/capabilities/scene-readback.js +3 -4
- package/dist/core/capabilities/scene-script.d.ts +2 -2
- package/dist/core/capabilities/scene-script.js +2 -2
- package/dist/core/capabilities/seeing/shot-store.d.ts +1 -1
- package/dist/core/capabilities/seeing/shot-store.js +1 -1
- package/dist/core/capabilities/summer-bundle.d.ts +2 -3
- package/dist/core/capabilities/summer-bundle.js +2 -3
- package/dist/core/capabilities/surface-snap.d.ts +1 -1
- package/dist/core/capabilities/surface-snap.js +1 -1
- package/dist/core/capabilities/tool-dispatch.js +11 -1
- package/dist/core/capabilities/ui-control.d.ts +1 -2
- package/dist/core/capabilities/ui-control.js +1 -2
- package/dist/core/capabilities/world-snapshot.d.ts +1 -1
- package/dist/core/capabilities/world-snapshot.js +1 -1
- package/dist/core/capability-skew.d.ts +2 -3
- package/dist/core/capability-skew.js +4 -5
- package/dist/core/config.js +2 -2
- package/dist/core/engine-install.d.ts +16 -0
- package/dist/core/engine-install.js +32 -2
- package/dist/core/feedback/client.js +1 -1
- package/dist/core/hosted-mcp.d.ts +115 -0
- package/dist/core/hosted-mcp.js +164 -0
- package/dist/core/launch-posture.d.ts +6 -3
- package/dist/core/launch-posture.js +18 -5
- package/dist/core/library-read.js +1 -2
- package/dist/core/templates.d.ts +1 -2
- package/dist/core/templates.js +1 -2
- package/dist/mcp/hosted-mount.d.ts +13 -59
- package/dist/mcp/hosted-mount.js +24 -30
- package/dist/mcp/server.d.ts +2 -2
- package/dist/mcp/server.js +5 -4
- package/dist/mcp/tools/creator-tools.js +14 -4
- package/dist/mcp/tools/debug-tools.js +1 -1
- package/dist/mcp/tools/generate-tools.js +19 -9
- package/dist/mcp/tools/navigation-tools.d.ts +1 -1
- package/dist/mcp/tools/navigation-tools.js +1 -1
- package/dist/mcp/tools/spatial-tools.js +1 -1
- package/dist/mcp/tools/visual-tools.js +2 -2
- package/dist/mcp/tools/with-engine.d.ts +1 -2
- package/dist/mcp/tools/with-engine.js +1 -2
- package/gemini-extension.json +3 -1
- package/hooks/session-start.sh +2 -2
- package/integrations/README.md +70 -76
- package/integrations/claude/README.md +0 -3
- package/integrations/codex/README.md +1 -2
- package/integrations/copilot-jetbrains/README.md +1 -1
- package/integrations/cursor/README.md +1 -2
- package/integrations/factory/README.md +15 -11
- package/integrations/mistral-vibe/README.md +1 -1
- package/integrations/opencode/README.md +1 -1
- package/integrations/roo-code/README.md +1 -1
- package/integrations/rovo-dev/README.md +1 -1
- package/integrations/trae/README.md +1 -1
- package/integrations/windsurf/README.md +3 -3
- package/library/references/collaborative-protocol/collaborative-protocol.md +5 -5
- package/library/references/gd-style/gd-style.md +4 -6
- package/library/references/godot-version/godot-version.md +3 -3
- package/library/references/headless-routing/headless-routing.md +2 -2
- package/library/references/mcp-fresh-chat-playbook/mcp-fresh-chat-playbook.md +2 -2
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +18 -16
- package/library/references/product-map/product-map.md +1 -1
- package/library/references/public-surface-language/public-surface-language.md +1 -1
- package/library/references/summer-folder/summer-folder.md +1 -1
- package/library/skills/3d-lighting/SKILL.md +4 -4
- package/library/skills/README.md +11 -9
- package/library/skills/adaptive-music/SKILL.md +7 -7
- package/library/skills/ai-characters-in-games/SKILL.md +1 -1
- package/library/skills/ambient-bed/SKILL.md +12 -14
- package/library/skills/animated-loop/SKILL.md +42 -42
- package/library/skills/animation-tree/SKILL.md +6 -6
- package/library/skills/art-direction/SKILL.md +8 -11
- package/library/skills/asset-strategy/SKILL.md +3 -3
- package/library/skills/audio-direction/SKILL.md +8 -10
- package/library/skills/auto-fire-targeting/SKILL.md +4 -3
- package/library/skills/bounded-engine-runs/SKILL.md +95 -0
- package/library/skills/bounded-engine-runs/resource.yaml +29 -0
- package/library/skills/brainstorm-game/SKILL.md +5 -5
- package/library/skills/brainstorming/SKILL.md +9 -15
- package/library/skills/celeste-momentum-platforming/SKILL.md +8 -13
- package/library/skills/celeste-momentum-platforming/resource.yaml +1 -1
- package/library/skills/character-animation-wiring/SKILL.md +2 -2
- package/library/skills/character-model/SKILL.md +15 -26
- package/library/skills/character-movement/SKILL.md +1 -1
- package/library/skills/character-movement/references/movement-3d.md +1 -1
- package/library/skills/character-portrait/SKILL.md +4 -4
- package/library/skills/cinematic-cutscene/SKILL.md +38 -38
- package/library/skills/combat-basics/SKILL.md +1 -1
- package/library/skills/compatibility-renderer-traps/SKILL.md +78 -0
- package/library/skills/compatibility-renderer-traps/resource.yaml +28 -0
- package/library/skills/concept-art/SKILL.md +4 -4
- package/library/skills/create-asset-sheet/SKILL.md +12 -31
- package/library/skills/debug/SKILL.md +5 -4
- package/library/skills/debugging-game-feel/SKILL.md +1 -4
- package/library/skills/debugging-game-feel/resource.yaml +1 -1
- package/library/skills/design-level/SKILL.md +5 -5
- package/library/skills/design-mechanic/SKILL.md +6 -6
- package/library/skills/design-npc/SKILL.md +8 -15
- package/library/skills/diagnosing-perf-regressions/SKILL.md +5 -10
- package/library/skills/dispatching-parallel-agents/SKILL.md +1 -19
- package/library/skills/environment-kit/SKILL.md +5 -5
- package/library/skills/export-and-ship/SKILL.md +25 -24
- package/library/skills/fabricating-assets/SKILL.md +1 -1
- package/library/skills/facial-and-lipsync/SKILL.md +5 -5
- package/library/skills/fps-controller/SKILL.md +17 -16
- package/library/skills/fps-controller/resource.yaml +0 -2
- package/library/skills/game-feel/SKILL.md +6 -7
- package/library/skills/gameskill/SKILL.md +3 -3
- package/library/skills/gdscript-patterns/SKILL.md +3 -3
- package/library/skills/generate-motion/SKILL.md +5 -22
- package/library/skills/godogen-visual-proof-game-generation/SKILL.md +72 -90
- package/library/skills/godogen-visual-proof-game-generation/resource.yaml +0 -1
- package/library/skills/headless-scripting/SKILL.md +8 -8
- package/library/skills/headless-scripting/resource.yaml +1 -0
- package/library/skills/in-game-purchases/SKILL.md +123 -118
- package/library/skills/in-game-purchases/resource.yaml +2 -2
- package/library/skills/instantiate-asset-pack/SKILL.md +7 -3
- package/library/skills/investigating-bugs/SKILL.md +3 -11
- package/library/skills/investigating-bugs/condition-based-waiting.md +0 -10
- package/library/skills/investigating-bugs/root-cause-tracing.md +0 -8
- package/library/skills/lumera-single-image-scene-reconstruction/SKILL.md +15 -15
- package/library/skills/lumera-single-image-scene-reconstruction/resource.yaml +0 -1
- package/library/skills/make-game/SKILL.md +3 -5
- package/library/skills/make-game/references/2d-platformer.md +1 -1
- package/library/skills/multiplayer-project/SKILL.md +1 -1
- package/library/skills/multiplayer-testing/SKILL.md +4 -4
- package/library/skills/music-track/SKILL.md +40 -38
- package/library/skills/new-project/SKILL.md +4 -6
- package/library/skills/organic-model/SKILL.md +9 -9
- package/library/skills/pixel-art/SKILL.md +6 -6
- package/library/skills/play/SKILL.md +3 -4
- package/library/skills/playtesting-a-feature/SKILL.md +4 -6
- package/library/skills/procedural-animation/SKILL.md +10 -11
- package/library/skills/prop-model/SKILL.md +4 -4
- package/library/skills/psx-retro-rendering/SKILL.md +20 -20
- package/library/skills/psx-retro-rendering/resource.yaml +2 -3
- package/library/skills/realtime-wet-surfaces/SKILL.md +15 -15
- package/library/skills/realtime-wet-surfaces/resource.yaml +1 -2
- package/library/skills/remote-deploy/SKILL.md +20 -21
- package/library/skills/retarget/SKILL.md +19 -23
- package/library/skills/running-in-the-cloud/SKILL.md +5 -5
- package/library/skills/scene-composition/SKILL.md +5 -5
- package/library/skills/scene-hierarchy-design/SKILL.md +3 -3
- package/library/skills/scene-hierarchy-design/resource.yaml +0 -1
- package/library/skills/scene-scripting/SKILL.md +3 -3
- package/library/skills/scene-to-level/SKILL.md +28 -31
- package/library/skills/skill-create/SKILL.md +1 -1
- package/library/skills/skill-improve/SKILL.md +9 -9
- package/library/skills/skill-test/SKILL.md +10 -10
- package/library/skills/skintokens-auto-rigging/SKILL.md +18 -18
- package/library/skills/skintokens-auto-rigging/resource.yaml +3 -3
- package/library/skills/skybox-panorama/SKILL.md +8 -9
- package/library/skills/sound-effect/SKILL.md +19 -19
- package/library/skills/spatial-placement/SKILL.md +2 -2
- package/library/skills/spatial-placement/references/kit-placement-tools.md +12 -22
- package/library/skills/sprite-sheet/SKILL.md +10 -10
- package/library/skills/summer-analytics/SKILL.md +3 -3
- package/library/skills/summer-friends/SKILL.md +10 -10
- package/library/skills/summer-leaderboards/SKILL.md +6 -6
- package/library/skills/summer-match-results/SKILL.md +13 -13
- package/library/skills/summer-matchmaking/SKILL.md +11 -11
- package/library/skills/summer-parties/SKILL.md +9 -9
- package/library/skills/summer-player-data/SKILL.md +12 -12
- package/library/skills/summer-store/SKILL.md +6 -6
- package/library/skills/summer-world-chat/SKILL.md +9 -9
- package/library/skills/summer-world-saves/SKILL.md +7 -7
- package/library/skills/text-to-motion/rigging-for-text-motion.md +1 -1
- package/library/skills/tileable-texture/SKILL.md +1 -1
- package/library/skills/trailer-shot/SKILL.md +36 -36
- package/library/skills/tune-performance/SKILL.md +13 -13
- package/library/skills/tune-performance/resource.yaml +1 -1
- package/library/skills/ui-graphics/SKILL.md +7 -7
- package/library/skills/use-widget-asset/SKILL.md +7 -7
- package/library/skills/using-summer/SKILL.md +5 -7
- package/library/skills/vehicle-model/SKILL.md +6 -7
- package/library/skills/vehicle-model/resource.yaml +2 -2
- package/library/skills/verification-before-completion/SKILL.md +5 -6
- package/library/skills/vfx-dissolve/SKILL.md +4 -4
- package/library/skills/vfx-fire/SKILL.md +6 -6
- package/library/skills/vfx-fire/_building-blocks/additive-billboard-particles.md +3 -3
- package/library/skills/vfx-hit-spark/SKILL.md +4 -4
- package/library/skills/vfx-hit-spark/_building-blocks/additive-billboard-particles.md +3 -3
- package/library/skills/vfx-lightning/SKILL.md +5 -5
- package/library/skills/vfx-lightning/_building-blocks/additive-billboard-particles.md +3 -3
- package/library/skills/vfx-lightning/_building-blocks/trauma-shake-snippet.md +9 -3
- package/library/skills/vfx-magic-glow/SKILL.md +4 -4
- package/library/skills/vfx-magic-glow/_building-blocks/additive-billboard-particles.md +3 -3
- package/library/skills/vfx-muzzle-flash/SKILL.md +5 -5
- package/library/skills/vfx-muzzle-flash/_building-blocks/additive-billboard-particles.md +3 -3
- package/library/skills/vfx-muzzle-flash/_building-blocks/trauma-shake-snippet.md +9 -3
- package/library/skills/vfx-smoke/SKILL.md +5 -5
- package/library/skills/vfx-water-ripple/SKILL.md +2 -2
- package/library/skills/vfx-water-ripple/_building-blocks/additive-billboard-particles.md +3 -3
- package/library/skills/voice-line/SKILL.md +9 -9
- package/library/skills/web-compatibility-look/resource.yaml +1 -0
- package/library/skills/world-building-3d/references/spatial-tools.md +2 -2
- package/library/skills/writing-plans/SKILL.md +4 -4
- package/library/skills/writing-skills/SKILL.md +8 -8
- package/library/tools/capture-gameplay/resource.yaml +73 -0
- package/library/tools/creator-publish/resource.yaml +4 -4
- package/library/tools/creator-releases/resource.yaml +5 -5
- package/library/tools/generate-image/resource.yaml +15 -1
- package/library/tools/publish-build/resource.yaml +6 -2
- package/package.json +1 -1
- package/registry/generated/aliases.json +1 -0
- package/registry/generated/counts.json +3 -3
- package/registry/generated/gemini-extension.json +3 -1
- package/registry/generated/index.json +248 -135
- package/registry/generated/marketplace.claude.json +2 -2
- package/registry/generated/plugin.claude.json +4 -2
- package/registry/generated/plugin.codex.json +5 -3
- package/registry/generated/plugin.cursor.json +4 -2
- package/registry/generated/plugin.factory.json +4 -2
- package/registry/generated/skills-index.md +11 -9
- package/registry/generated/skills-registry.json +33 -6
- package/registry/schemas/domains.json +1 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { platform } from "node:os";
|
|
4
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { findEngineBinary } from "../engine-install.js";
|
|
6
|
+
import { runEngine } from "./engine-run.js";
|
|
7
|
+
import { resolveExportProject } from "./export-game.js";
|
|
8
|
+
import { BuildToolError } from "./summer-bundle.js";
|
|
9
|
+
const defaultDependencies = {
|
|
10
|
+
findBinary: () => findEngineBinary(platform()),
|
|
11
|
+
run: runEngine,
|
|
12
|
+
now: () => new Date(),
|
|
13
|
+
};
|
|
14
|
+
const DEFAULT_RESOLUTION = "1920x1080";
|
|
15
|
+
const MAX_FRAMES = 10;
|
|
16
|
+
/** The engine clamps --summer-verify-max to 240 (main.cpp SUMMER_VERIFY_MAX_SECONDS). */
|
|
17
|
+
const ENGINE_MAX_SECONDS = 240;
|
|
18
|
+
const IMPORT_TIMEOUT_MS = 5 * 60_000;
|
|
19
|
+
const PROBE_FILE = "capture_probe.gd";
|
|
20
|
+
function invalid(message, recovery) {
|
|
21
|
+
return new BuildToolError("capture_args_invalid", message, recovery);
|
|
22
|
+
}
|
|
23
|
+
function parseResolution(value) {
|
|
24
|
+
const text = value?.trim() || DEFAULT_RESOLUTION;
|
|
25
|
+
const match = /^(\d{2,5})x(\d{2,5})$/i.exec(text);
|
|
26
|
+
if (!match)
|
|
27
|
+
throw invalid(`resolution "${text}" is not WIDTHxHEIGHT.`, 'Recovery: pass e.g. "1920x1080" (landscape) or "1080x1920" (portrait).');
|
|
28
|
+
const width = Number(match[1]);
|
|
29
|
+
const height = Number(match[2]);
|
|
30
|
+
if (width < 64 || height < 64 || width > 7680 || height > 7680) {
|
|
31
|
+
throw invalid(`resolution ${text} is out of range.`, "Recovery: use 64 to 7680 pixels per side.");
|
|
32
|
+
}
|
|
33
|
+
return { width, height, text: `${width}x${height}` };
|
|
34
|
+
}
|
|
35
|
+
function boundedNumber(value, fallback, min, max, name) {
|
|
36
|
+
if (value === undefined)
|
|
37
|
+
return fallback;
|
|
38
|
+
if (!Number.isFinite(value) || value < min || value > max) {
|
|
39
|
+
throw invalid(`${name} must be between ${min} and ${max}.`, `Recovery: pass ${name} in that range, or omit it.`);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function checkScene(project, scene) {
|
|
44
|
+
const value = scene?.trim();
|
|
45
|
+
if (!value)
|
|
46
|
+
return undefined;
|
|
47
|
+
if (value.startsWith("uid://"))
|
|
48
|
+
return value;
|
|
49
|
+
if (!value.startsWith("res://") || !/\.(tscn|scn)$/i.test(value)) {
|
|
50
|
+
throw invalid(`scene "${value}" is not a res:// scene path.`, 'Recovery: pass a scene like "res://main.tscn", or omit scene to start the main scene.');
|
|
51
|
+
}
|
|
52
|
+
if (!existsSync(join(project, value.slice("res://".length)))) {
|
|
53
|
+
throw invalid(`${value} does not exist in ${project}.`, "Recovery: pass a scene file that exists, or omit scene to start the main scene.");
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function stamp(date) {
|
|
58
|
+
return date.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z").replace("T", "-");
|
|
59
|
+
}
|
|
60
|
+
/** <project>/.summer/captures/, ignored by the editor and by git (like .summer/exports). */
|
|
61
|
+
async function capturesDir(project) {
|
|
62
|
+
const dir = join(project, ".summer", "captures");
|
|
63
|
+
await mkdir(dir, { recursive: true });
|
|
64
|
+
for (const [name, body] of [
|
|
65
|
+
[".gdignore", ""],
|
|
66
|
+
[".gitignore", "*\n"],
|
|
67
|
+
]) {
|
|
68
|
+
const file = join(dir, name);
|
|
69
|
+
if (!existsSync(file))
|
|
70
|
+
await writeFile(file, body);
|
|
71
|
+
}
|
|
72
|
+
return dir;
|
|
73
|
+
}
|
|
74
|
+
/** Width and height from a PNG's IHDR chunk, or null when the file is not a PNG. */
|
|
75
|
+
export function pngSize(bytes) {
|
|
76
|
+
if (bytes.length < 24 || bytes.readUInt32BE(0) !== 0x89504e47 || bytes.toString("ascii", 12, 16) !== "IHDR")
|
|
77
|
+
return null;
|
|
78
|
+
return { width: bytes.readUInt32BE(16), height: bytes.readUInt32BE(20) };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The stock probe. Self-contained (extends Node, no base class to copy into the
|
|
82
|
+
* project); follows the verify-instance contract of assets/autopilot/probe_base.gd:
|
|
83
|
+
* the engine sets summer_out_dir and summer_max_seconds before _ready(), the
|
|
84
|
+
* first frames are empty until the scene has drawn twice, results.json is
|
|
85
|
+
* always written.
|
|
86
|
+
*/
|
|
87
|
+
export function captureProbeSource(frames, waitSeconds, intervalSeconds) {
|
|
88
|
+
return `# Written by summer_capture_gameplay. Safe to delete.
|
|
89
|
+
# Fully typed: projects that treat GDScript warnings as errors must still load it.
|
|
90
|
+
extends Node
|
|
91
|
+
|
|
92
|
+
var summer_out_dir: String = ""
|
|
93
|
+
var summer_max_seconds: int = 20
|
|
94
|
+
|
|
95
|
+
const FRAMES: int = ${frames}
|
|
96
|
+
const WAIT_SECONDS: float = ${waitSeconds.toFixed(3)}
|
|
97
|
+
const INTERVAL_SECONDS: float = ${intervalSeconds.toFixed(3)}
|
|
98
|
+
|
|
99
|
+
var _shots: Array = []
|
|
100
|
+
var _warnings: Array = []
|
|
101
|
+
var _start_ms: int = 0
|
|
102
|
+
var _done: bool = false
|
|
103
|
+
|
|
104
|
+
func _ready() -> void:
|
|
105
|
+
\tprocess_mode = Node.PROCESS_MODE_ALWAYS
|
|
106
|
+
\t_start_ms = Time.get_ticks_msec()
|
|
107
|
+
\tget_tree().create_timer(float(summer_max_seconds), true).timeout.connect(_on_deadline)
|
|
108
|
+
\t_run.call_deferred()
|
|
109
|
+
|
|
110
|
+
func _on_deadline() -> void:
|
|
111
|
+
\t_finish(false)
|
|
112
|
+
|
|
113
|
+
func _run() -> void:
|
|
114
|
+
\t# The viewport holds no scene until two frames have been drawn.
|
|
115
|
+
\tvar guard: int = 0
|
|
116
|
+
\twhile Engine.get_frames_drawn() < 2 and guard < 600:
|
|
117
|
+
\t\tguard += 1
|
|
118
|
+
\t\tawait get_tree().process_frame
|
|
119
|
+
\tif DisplayServer.get_name() == "headless":
|
|
120
|
+
\t\t_warnings.append("No renderer (headless): no frame can be captured.")
|
|
121
|
+
\t\t_finish(true)
|
|
122
|
+
\t\treturn
|
|
123
|
+
\tawait get_tree().create_timer(WAIT_SECONDS, true).timeout
|
|
124
|
+
\tfor i: int in range(FRAMES):
|
|
125
|
+
\t\tif i > 0:
|
|
126
|
+
\t\t\tawait get_tree().create_timer(INTERVAL_SECONDS, true).timeout
|
|
127
|
+
\t\tawait RenderingServer.frame_post_draw
|
|
128
|
+
\t\tvar img: Image = get_viewport().get_texture().get_image()
|
|
129
|
+
\t\tvar file_name: String = "shot-%02d.png" % (i + 1)
|
|
130
|
+
\t\tif img == null:
|
|
131
|
+
\t\t\t_warnings.append("%s: the viewport returned no image." % file_name)
|
|
132
|
+
\t\t\tcontinue
|
|
133
|
+
\t\tif img.save_png(summer_out_dir.path_join(file_name)) == OK:
|
|
134
|
+
\t\t\t_shots.append({"file": file_name, "width": img.get_width(), "height": img.get_height()})
|
|
135
|
+
\t\telse:
|
|
136
|
+
\t\t\t_warnings.append("%s: could not write the PNG." % file_name)
|
|
137
|
+
\t_finish(true)
|
|
138
|
+
|
|
139
|
+
func _errors() -> Array:
|
|
140
|
+
\tvar errors: Array = []
|
|
141
|
+
\tvar path: String = summer_out_dir.path_join("errors.log")
|
|
142
|
+
\tif FileAccess.file_exists(path):
|
|
143
|
+
\t\tvar f: FileAccess = FileAccess.open(path, FileAccess.READ)
|
|
144
|
+
\t\tif f != null:
|
|
145
|
+
\t\t\twhile not f.eof_reached():
|
|
146
|
+
\t\t\t\tvar line: String = f.get_line()
|
|
147
|
+
\t\t\t\tif line.strip_edges() != "":
|
|
148
|
+
\t\t\t\t\terrors.append(line)
|
|
149
|
+
\treturn errors
|
|
150
|
+
|
|
151
|
+
func _finish(finished: bool) -> void:
|
|
152
|
+
\tif _done:
|
|
153
|
+
\t\treturn
|
|
154
|
+
\t_done = true
|
|
155
|
+
\tvar out: Dictionary = {"shots": _shots, "frame_warnings": _warnings, "errors_seen": _errors(), "duration_ms": Time.get_ticks_msec() - _start_ms, "finished": finished}
|
|
156
|
+
\tvar f: FileAccess = FileAccess.open(summer_out_dir.path_join("results.json"), FileAccess.WRITE)
|
|
157
|
+
\tif f != null:
|
|
158
|
+
\t\tf.store_string(JSON.stringify(out, " "))
|
|
159
|
+
\t\tf.close()
|
|
160
|
+
\tget_tree().quit()
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
function outputTail(output) {
|
|
164
|
+
return output.trim().split("\n").slice(-30).join("\n");
|
|
165
|
+
}
|
|
166
|
+
export async function captureGameplay(input, overrides = {}) {
|
|
167
|
+
const deps = { ...defaultDependencies, ...overrides };
|
|
168
|
+
const project = resolveExportProject(input.project);
|
|
169
|
+
const resolution = parseResolution(input.resolution);
|
|
170
|
+
const frames = Math.round(boundedNumber(input.frames, 1, 1, MAX_FRAMES, "frames"));
|
|
171
|
+
const waitSeconds = boundedNumber(input.waitSeconds, 3, 0, 120, "waitSeconds");
|
|
172
|
+
const intervalSeconds = boundedNumber(input.intervalSeconds, 1, 0.1, 60, "intervalSeconds");
|
|
173
|
+
const scene = checkScene(project, input.scene);
|
|
174
|
+
const started = deps.now();
|
|
175
|
+
const binary = deps.findBinary();
|
|
176
|
+
if (!binary) {
|
|
177
|
+
throw new BuildToolError("engine_not_installed", "Summer Engine is not installed on this machine.", 'Recovery: run "summer install", or set SUMMER_BIN to the engine executable, then capture again.');
|
|
178
|
+
}
|
|
179
|
+
let out;
|
|
180
|
+
if (input.out?.trim()) {
|
|
181
|
+
out = isAbsolute(input.out) ? input.out : resolve(project, input.out);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
out = join(await capturesDir(project), stamp(started));
|
|
185
|
+
}
|
|
186
|
+
await mkdir(out, { recursive: true });
|
|
187
|
+
await rm(join(out, "results.json"), { force: true });
|
|
188
|
+
// A never-opened checkout has no .godot/ caches; the game would boot into
|
|
189
|
+
// missing-import errors. Build them once, headless (same as tests/autopilot).
|
|
190
|
+
let imported = false;
|
|
191
|
+
if (!existsSync(join(project, ".godot"))) {
|
|
192
|
+
const run = await deps.run(binary, ["--headless", "--import", "--disable-crash-handler", "--path", project], IMPORT_TIMEOUT_MS);
|
|
193
|
+
if (run.timedOut || !existsSync(join(project, ".godot"))) {
|
|
194
|
+
throw new BuildToolError("import_failed", "Summer Engine could not import the project's assets before the capture.", "Recovery: open the project once in Summer Engine (or run the engine with --headless --import), then capture again.", undefined, { output: outputTail(run.output) });
|
|
195
|
+
}
|
|
196
|
+
imported = true;
|
|
197
|
+
}
|
|
198
|
+
const probe = join(out, PROBE_FILE);
|
|
199
|
+
await writeFile(probe, captureProbeSource(frames, waitSeconds, intervalSeconds));
|
|
200
|
+
const maxSeconds = Math.min(ENGINE_MAX_SECONDS, Math.ceil(waitSeconds + (frames - 1) * intervalSeconds + 30));
|
|
201
|
+
const args = [
|
|
202
|
+
"--disable-crash-handler",
|
|
203
|
+
// No local HTTP API or ~/.summer discovery files for this throwaway run.
|
|
204
|
+
"--summer-no-api",
|
|
205
|
+
"--path",
|
|
206
|
+
project,
|
|
207
|
+
"--resolution",
|
|
208
|
+
resolution.text,
|
|
209
|
+
...(scene ? ["--scene", scene] : []),
|
|
210
|
+
"--summer-verify",
|
|
211
|
+
probe,
|
|
212
|
+
"--summer-verify-out",
|
|
213
|
+
out,
|
|
214
|
+
"--summer-verify-max",
|
|
215
|
+
String(maxSeconds),
|
|
216
|
+
];
|
|
217
|
+
let run;
|
|
218
|
+
try {
|
|
219
|
+
run = await deps.run(binary, args, (maxSeconds + 60) * 1000);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
throw new BuildToolError("engine_launch_failed", `Summer Engine could not start: ${error instanceof Error ? error.message : String(error)}.`, 'Recovery: run "summer doctor" and check the engine install.');
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
await rm(probe, { force: true });
|
|
226
|
+
}
|
|
227
|
+
const resultsPath = join(out, "results.json");
|
|
228
|
+
const results = existsSync(resultsPath)
|
|
229
|
+
? (() => {
|
|
230
|
+
try {
|
|
231
|
+
return JSON.parse(readFileSync(resultsPath, "utf8"));
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
})()
|
|
237
|
+
: null;
|
|
238
|
+
const engineErrors = (results?.errors_seen ?? []).filter((line) => !line.startsWith("WARNING|"));
|
|
239
|
+
if (!results || results.failure_reason || !results.shots?.length) {
|
|
240
|
+
const reason = results?.failure_reason
|
|
241
|
+
? `${results.failure_reason}: ${results.error ?? ""}`.trim()
|
|
242
|
+
: results
|
|
243
|
+
? "the game ran but no frame was saved"
|
|
244
|
+
: "the verify instance wrote no results";
|
|
245
|
+
throw new BuildToolError("capture_failed", `No gameplay frame was captured (${reason}).`, results?.failure_reason === "no_main_scene"
|
|
246
|
+
? "Recovery: set a main scene in Project Settings, or pass scene."
|
|
247
|
+
: "Recovery: read the engine errors below, fix what they name, and capture again. An engine older than the verify instance (before 0.5.55) cannot capture: update Summer Engine.", undefined, {
|
|
248
|
+
...(engineErrors.length ? { engineErrors: engineErrors.slice(0, 10) } : {}),
|
|
249
|
+
...(results?.frame_warnings?.length ? { frameWarnings: results.frame_warnings } : {}),
|
|
250
|
+
output: outputTail(run.output),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const captured = [];
|
|
254
|
+
for (const shot of results.shots) {
|
|
255
|
+
const path = join(out, shot.file);
|
|
256
|
+
if (!existsSync(path))
|
|
257
|
+
continue;
|
|
258
|
+
const size = pngSize(await readFile(path)) ?? { width: shot.width, height: shot.height };
|
|
259
|
+
captured.push({ path, ...size });
|
|
260
|
+
}
|
|
261
|
+
const warnings = [...(results.frame_warnings ?? [])];
|
|
262
|
+
if (results.finished === false)
|
|
263
|
+
warnings.push("The game hit its time limit before every frame was saved.");
|
|
264
|
+
const off = captured.filter((frame) => frame.width !== resolution.width || frame.height !== resolution.height);
|
|
265
|
+
if (off.length) {
|
|
266
|
+
warnings.push(`Frames are ${off[0].width}x${off[0].height}, not ${resolution.text}: the project's stretch settings (Project Settings > Display > Window) set the render size. Store screenshots need 1920x1080 or 1080x1920; crop or change the stretch mode.`);
|
|
267
|
+
}
|
|
268
|
+
if (engineErrors.length)
|
|
269
|
+
warnings.push(`The engine logged ${engineErrors.length} error(s) during the run; check the frames show the game as expected: ${engineErrors.slice(0, 3).join(" / ")}`);
|
|
270
|
+
return {
|
|
271
|
+
ok: true,
|
|
272
|
+
project,
|
|
273
|
+
scene: scene ?? "main scene",
|
|
274
|
+
resolution: resolution.text,
|
|
275
|
+
frames: captured,
|
|
276
|
+
out,
|
|
277
|
+
engine: binary,
|
|
278
|
+
imported,
|
|
279
|
+
durationMs: deps.now().getTime() - started.getTime(),
|
|
280
|
+
...(warnings.length ? { warnings } : {}),
|
|
281
|
+
next: "Look at each frame before using it. For the store: upload with summer_upload_image_begin/complete and set them as desktop.screenshots or mobile.screenshots (summer_store_set_art). The game's own HUD may show; add no captions, logos or overlays.",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* (src/mcp/tools/scene-tools.ts and tool-dispatch.ts) that leaves the
|
|
4
4
|
* connection in the saved scene, verified from the file.
|
|
5
5
|
*
|
|
6
|
-
* Why not the engine's ConnectSignal op:
|
|
7
|
-
* modules/1summer_engine/editor/ops/scene_ops.cpp) builds
|
|
6
|
+
* Why not the engine's ConnectSignal op: it builds
|
|
8
7
|
* `Callable(receiver, method)` and registers
|
|
9
8
|
* `add_do_method(emitter, "connect", signal, cb)` — no CONNECT_PERSIST, and the
|
|
10
9
|
* op reads no flags argument. PackedScene::pack writes only persistent
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* (src/mcp/tools/scene-tools.ts and tool-dispatch.ts) that leaves the
|
|
4
4
|
* connection in the saved scene, verified from the file.
|
|
5
5
|
*
|
|
6
|
-
* Why not the engine's ConnectSignal op:
|
|
7
|
-
* modules/1summer_engine/editor/ops/scene_ops.cpp) builds
|
|
6
|
+
* Why not the engine's ConnectSignal op: it builds
|
|
8
7
|
* `Callable(receiver, method)` and registers
|
|
9
8
|
* `add_do_method(emitter, "connect", signal, cb)` — no CONNECT_PERSIST, and the
|
|
10
9
|
* op reads no flags argument. PackedScene::pack writes only persistent
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* Output panel (EditorLog) never received them: a running game's errors are
|
|
7
7
|
* collected by the debugger (summer_get_debugger_errors / the `debugger`
|
|
8
8
|
* section of summer_get_diagnostics). The engine types console lines straight
|
|
9
|
-
* from EditorLog::MSG_TYPE (error / warning / std / editor
|
|
10
|
-
* GetConsoleOutput
|
|
9
|
+
* from EditorLog::MSG_TYPE (error / warning / std / editor) in
|
|
10
|
+
* GetConsoleOutput, so dropping `std` lines by default hides nothing that
|
|
11
11
|
* was an error; the trap is treating this tool's count as the post-play
|
|
12
12
|
* verdict at all. The note travels with the result because tool descriptions
|
|
13
13
|
* are read once and results are read every time.
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* Output panel (EditorLog) never received them: a running game's errors are
|
|
7
7
|
* collected by the debugger (summer_get_debugger_errors / the `debugger`
|
|
8
8
|
* section of summer_get_diagnostics). The engine types console lines straight
|
|
9
|
-
* from EditorLog::MSG_TYPE (error / warning / std / editor
|
|
10
|
-
* GetConsoleOutput
|
|
9
|
+
* from EditorLog::MSG_TYPE (error / warning / std / editor) in
|
|
10
|
+
* GetConsoleOutput, so dropping `std` lines by default hides nothing that
|
|
11
11
|
* was an error; the trap is treating this tool's count as the post-play
|
|
12
12
|
* verdict at all. The note travels with the result because tool descriptions
|
|
13
13
|
* are read once and results are read every time.
|
|
@@ -148,7 +148,7 @@ function assertUploadUrl(value) {
|
|
|
148
148
|
}
|
|
149
149
|
const local = url.hostname === "localhost" ||
|
|
150
150
|
url.hostname === "127.0.0.1" ||
|
|
151
|
-
url.hostname === "::1";
|
|
151
|
+
url.hostname === "[::1]";
|
|
152
152
|
if (url.username ||
|
|
153
153
|
url.password ||
|
|
154
154
|
(url.protocol !== "https:" && !(local && url.protocol === "http:"))) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type StoreAccessResult } from "../hosted-mcp.js";
|
|
1
2
|
import { type ProjectMemorySummary } from "../../project-memory/project-memory.js";
|
|
2
3
|
export type DoctorStatus = "ok" | "warning" | "fail";
|
|
3
4
|
export interface DoctorCheck {
|
|
@@ -22,7 +23,14 @@ interface DoctorOptions {
|
|
|
22
23
|
}
|
|
23
24
|
export declare function runDoctor(options?: DoctorOptions): Promise<DoctorResult>;
|
|
24
25
|
export declare function checkLogin(): Promise<DoctorCheck>;
|
|
25
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Store access: with a store sign-in ("summer login --store"), ask the store
|
|
28
|
+
* for the creator's games through the hosted Summer Engine MCP, so a refusal
|
|
29
|
+
* shows here with its reason instead of on the first publishing call.
|
|
30
|
+
* A warning at worst: only publishing needs the store.
|
|
31
|
+
*/
|
|
32
|
+
export declare function checkStoreSignIn(probe?: () => Promise<StoreAccessResult>, signedIn?: () => Promise<boolean>): Promise<DoctorCheck>;
|
|
33
|
+
export declare function checkEngineInstall(readVersion?: (binary: string) => string | null): DoctorCheck;
|
|
26
34
|
/** The one-line Project Memory verdict: Markdown counts plus the template pin
|
|
27
35
|
* (`.summer/project.json` is memory too — the first fact a fresh agent
|
|
28
36
|
* needs). Exported for tests. */
|
|
@@ -4,8 +4,11 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
|
4
4
|
import { getAuthToken, getUserInfo } from "../auth.js";
|
|
5
5
|
import { ENGINE_BIN_ENV, ENGINE_BINARY_ENV, findEngineBinary } from "../engine-install.js";
|
|
6
6
|
import { checkEngineHealth, getApiPort, getApiToken } from "../engine.js";
|
|
7
|
+
import { isVersionAtLeast } from "../launch-posture.js";
|
|
8
|
+
import { EXPORT_MIN_ENGINE_VERSION, installedEngineVersion } from "./export-engine-version.js";
|
|
7
9
|
import { c, pad, sym, tildeify } from "../format.js";
|
|
8
10
|
import { getMcpLogPath } from "../mcp-log.js";
|
|
11
|
+
import { checkStoreAccess, hasStoreSignIn } from "../hosted-mcp.js";
|
|
9
12
|
import { formatProjectPin, getProjectMemorySummary, } from "../../project-memory/project-memory.js";
|
|
10
13
|
import { buildCliVersionCheck, buildSkillsVersionCheck, defaultSkillMarkerCandidates, detectRecordedInstall, fetchLatestRegistryVersion, } from "../../installer/version-check.js";
|
|
11
14
|
import { TOOLKIT_VERSION as version } from "../version.js";
|
|
@@ -27,6 +30,7 @@ export async function runDoctor(options = {}) {
|
|
|
27
30
|
checks.push(await checkCliVersionCurrent());
|
|
28
31
|
checks.push(await checkSkillsVersion());
|
|
29
32
|
checks.push(await checkLogin());
|
|
33
|
+
checks.push(await checkStoreSignIn());
|
|
30
34
|
checks.push(checkEngineInstall());
|
|
31
35
|
checks.push(await checkLocalApi());
|
|
32
36
|
checks.push(await checkProjectMemory());
|
|
@@ -120,17 +124,56 @@ export async function checkLogin() {
|
|
|
120
124
|
message: user ? user.email : "signed in",
|
|
121
125
|
};
|
|
122
126
|
}
|
|
123
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Store access: with a store sign-in ("summer login --store"), ask the store
|
|
129
|
+
* for the creator's games through the hosted Summer Engine MCP, so a refusal
|
|
130
|
+
* shows here with its reason instead of on the first publishing call.
|
|
131
|
+
* A warning at worst: only publishing needs the store.
|
|
132
|
+
*/
|
|
133
|
+
export async function checkStoreSignIn(probe = () => checkStoreAccess(), signedIn = hasStoreSignIn) {
|
|
134
|
+
const base = { id: "store-access", label: "Store" };
|
|
135
|
+
if (!(await signedIn())) {
|
|
136
|
+
return { ...base, status: "ok", message: "no store sign-in (only publishing needs it: summer login --store)" };
|
|
137
|
+
}
|
|
138
|
+
const result = await probe();
|
|
139
|
+
if (result.status === "ok") {
|
|
140
|
+
return { ...base, status: "ok", message: `the store accepts this sign-in${result.games === null ? "" : ` (${result.games} ${result.games === 1 ? "game" : "games"})`}` };
|
|
141
|
+
}
|
|
142
|
+
if (result.status === "not_signed_in") {
|
|
143
|
+
return { ...base, status: "warning", message: `${result.message} (run: summer login --store)` };
|
|
144
|
+
}
|
|
145
|
+
if (result.status === "unreachable") {
|
|
146
|
+
return { ...base, status: "warning", message: `could not reach the store: ${result.message}`, details: { ...result } };
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
...base,
|
|
150
|
+
status: "warning",
|
|
151
|
+
message: `the store refused this sign-in${result.code ? ` (${result.code})` : ""}: ${result.message}${result.requestId ? ` [requestId ${result.requestId}]` : ""}`,
|
|
152
|
+
details: { ...result },
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
export function checkEngineInstall(readVersion = installedEngineVersion) {
|
|
124
156
|
const binary = findEngineBinary();
|
|
125
157
|
if (binary) {
|
|
126
158
|
// Shorten /Applications/Summer.app/Contents/MacOS/Summer -> /Applications/Summer.app
|
|
127
|
-
const display = binary.replace(/\/Contents\/MacOS\/Summer$/, "");
|
|
159
|
+
const display = tildeify(binary.replace(/\/Contents\/MacOS\/Summer$/, ""));
|
|
160
|
+
const version = readVersion(binary);
|
|
161
|
+
const details = { path: binary, version, minimumExportVersion: EXPORT_MIN_ENGINE_VERSION };
|
|
162
|
+
if (isVersionAtLeast(version, EXPORT_MIN_ENGINE_VERSION) === false) {
|
|
163
|
+
return {
|
|
164
|
+
id: "engine-install",
|
|
165
|
+
label: "Engine",
|
|
166
|
+
status: "warning",
|
|
167
|
+
message: `${display} is ${version}; summer.games exports need ${EXPORT_MIN_ENGINE_VERSION}+ (update: summer install --yes)`,
|
|
168
|
+
details,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
128
171
|
return {
|
|
129
172
|
id: "engine-install",
|
|
130
173
|
label: "Engine",
|
|
131
174
|
status: "ok",
|
|
132
|
-
message:
|
|
133
|
-
details
|
|
175
|
+
message: version ? `${display} (${version})` : display,
|
|
176
|
+
details,
|
|
134
177
|
};
|
|
135
178
|
}
|
|
136
179
|
return {
|
|
@@ -26,10 +26,9 @@ export declare function getFailureReason(value: {
|
|
|
26
26
|
* Decide whether an engine result envelope represents a FAILURE, and if so
|
|
27
27
|
* return a model-visible message. Returns null only for genuine success.
|
|
28
28
|
*
|
|
29
|
-
* Guards the two
|
|
30
|
-
* `isFailureSignal`):
|
|
29
|
+
* Guards the two bug classes the web platform's tool layer once had:
|
|
31
30
|
* - a failure `terminalState` (anything other than applied/no_op) is a failure
|
|
32
|
-
* even when results[] is absent — the
|
|
31
|
+
* even when results[] is absent — the "no-results envelope looked
|
|
33
32
|
* applied" masking. The poll loop surfaces timed_out/etc. here.
|
|
34
33
|
* - an explicit ok:false / status:"error" / failed op inside results[].
|
|
35
34
|
*
|
|
@@ -37,9 +36,8 @@ export declare function getFailureReason(value: {
|
|
|
37
36
|
*/
|
|
38
37
|
export declare function extractOpError(result: unknown): string | null;
|
|
39
38
|
/**
|
|
40
|
-
* The engine stamps `scenePersistence.persisted` on every scene-targeted batch
|
|
41
|
-
*
|
|
42
|
-
* explicit_scene_save_succeeded). It only means SaveScene returned OK, not
|
|
39
|
+
* The engine stamps `scenePersistence.persisted` on every scene-targeted batch.
|
|
40
|
+
* It only means SaveScene returned OK, not
|
|
43
41
|
* that the file holds the change: a ReparentNode subtree whose owners the
|
|
44
42
|
* engine cleared, or a ConnectSignal made without CONNECT_PERSIST, is missing
|
|
45
43
|
* from a file whose save "persisted". Rename it to
|
|
@@ -50,7 +48,7 @@ export declare function extractOpError(result: unknown): string | null;
|
|
|
50
48
|
export declare function honestSceneReceipt<T>(result: T): T;
|
|
51
49
|
/**
|
|
52
50
|
* An older engine answers an unknown op with a per-op "unknown op: <Kind>"
|
|
53
|
-
*
|
|
51
|
+
* Amend the envelope's error so the model gets
|
|
54
52
|
* the upgrade path instead of retrying, and stamp `failure_reason:
|
|
55
53
|
* "engine_lacks_op"` (+ `op`) so the result is detectable the same way as the
|
|
56
54
|
* capability pre-flight's MissingOpResult: programmatic callers read the
|
|
@@ -9,7 +9,7 @@ export function getFailureReason(value) {
|
|
|
9
9
|
? value.failure_reason
|
|
10
10
|
: undefined;
|
|
11
11
|
}
|
|
12
|
-
// 0.5.34
|
|
12
|
+
// Engine 0.5.34 receipt contract, shared with the web platform's tool layer.
|
|
13
13
|
// The async lifecycle (async-op-lifecycle.ts pollOpToTerminal) merges
|
|
14
14
|
// terminalState/errorClass onto the apply dict it returns. ONLY these two are
|
|
15
15
|
// "applied something / applied nothing-on-purpose" — every other terminal state
|
|
@@ -33,10 +33,9 @@ const TERMINAL_STATE_MESSAGES = {
|
|
|
33
33
|
* Decide whether an engine result envelope represents a FAILURE, and if so
|
|
34
34
|
* return a model-visible message. Returns null only for genuine success.
|
|
35
35
|
*
|
|
36
|
-
* Guards the two
|
|
37
|
-
* `isFailureSignal`):
|
|
36
|
+
* Guards the two bug classes the web platform's tool layer once had:
|
|
38
37
|
* - a failure `terminalState` (anything other than applied/no_op) is a failure
|
|
39
|
-
* even when results[] is absent — the
|
|
38
|
+
* even when results[] is absent — the "no-results envelope looked
|
|
40
39
|
* applied" masking. The poll loop surfaces timed_out/etc. here.
|
|
41
40
|
* - an explicit ok:false / status:"error" / failed op inside results[].
|
|
42
41
|
*
|
|
@@ -97,9 +96,8 @@ export function extractOpError(result) {
|
|
|
97
96
|
return null;
|
|
98
97
|
}
|
|
99
98
|
/**
|
|
100
|
-
* The engine stamps `scenePersistence.persisted` on every scene-targeted batch
|
|
101
|
-
*
|
|
102
|
-
* explicit_scene_save_succeeded). It only means SaveScene returned OK, not
|
|
99
|
+
* The engine stamps `scenePersistence.persisted` on every scene-targeted batch.
|
|
100
|
+
* It only means SaveScene returned OK, not
|
|
103
101
|
* that the file holds the change: a ReparentNode subtree whose owners the
|
|
104
102
|
* engine cleared, or a ConnectSignal made without CONNECT_PERSIST, is missing
|
|
105
103
|
* from a file whose save "persisted". Rename it to
|
|
@@ -123,11 +121,11 @@ export function honestSceneReceipt(result) {
|
|
|
123
121
|
return (out ?? result);
|
|
124
122
|
}
|
|
125
123
|
/** The per-op text an older engine answers for a Kind its dispatch ladder does
|
|
126
|
-
* not know (
|
|
124
|
+
* not know (`unknown op: <Kind>`). */
|
|
127
125
|
const UNKNOWN_OP_PATTERN = /unknown op/i;
|
|
128
126
|
/**
|
|
129
127
|
* An older engine answers an unknown op with a per-op "unknown op: <Kind>"
|
|
130
|
-
*
|
|
128
|
+
* Amend the envelope's error so the model gets
|
|
131
129
|
* the upgrade path instead of retrying, and stamp `failure_reason:
|
|
132
130
|
* "engine_lacks_op"` (+ `op`) so the result is detectable the same way as the
|
|
133
131
|
* capability pre-flight's MissingOpResult: programmatic callers read the
|
|
@@ -3,6 +3,10 @@ export interface EngineRun {
|
|
|
3
3
|
signal: NodeJS.Signals | null;
|
|
4
4
|
timedOut: boolean;
|
|
5
5
|
output: string;
|
|
6
|
+
/** The engine's "ERROR:" / "SCRIPT ERROR:" lines from the whole run (the first 20), even when the tail lost them. */
|
|
7
|
+
errors?: string[];
|
|
6
8
|
}
|
|
9
|
+
/** "ERROR: ..." and "SCRIPT ERROR: ..." lines of engine output, without colour codes or the "at:" lines. */
|
|
10
|
+
export declare function engineErrorLines(output: string, limit?: number): string[];
|
|
7
11
|
/** Spawn the engine (argv only, no shell), keep the last output, kill on timeout. */
|
|
8
12
|
export declare function runEngine(binary: string, args: string[], timeoutMs: number): Promise<EngineRun>;
|
|
@@ -2,17 +2,44 @@ import { spawn as nodeSpawn } from "node:child_process";
|
|
|
2
2
|
/** One headless engine process: argv only (no shell), the last output kept, killed on timeout. */
|
|
3
3
|
const OUTPUT_TAIL_BYTES = 8 * 1024;
|
|
4
4
|
const KILL_GRACE_MS = 5_000;
|
|
5
|
+
const MAX_ERROR_LINES = 20;
|
|
6
|
+
const MAX_ERROR_LINE_CHARS = 600;
|
|
7
|
+
const ANSI = /\u001b\[[0-9;]*m/g;
|
|
8
|
+
/** "ERROR: ..." and "SCRIPT ERROR: ..." lines of engine output, without colour codes or the "at:" lines. */
|
|
9
|
+
export function engineErrorLines(output, limit = MAX_ERROR_LINES) {
|
|
10
|
+
const lines = [];
|
|
11
|
+
for (const raw of output.split(/\r?\n/)) {
|
|
12
|
+
const line = raw.replace(ANSI, "").trim();
|
|
13
|
+
if (!/^(SCRIPT )?ERROR:/.test(line))
|
|
14
|
+
continue;
|
|
15
|
+
if (lines.length >= limit)
|
|
16
|
+
break;
|
|
17
|
+
lines.push(line.slice(0, MAX_ERROR_LINE_CHARS));
|
|
18
|
+
}
|
|
19
|
+
return lines;
|
|
20
|
+
}
|
|
5
21
|
/** Spawn the engine (argv only, no shell), keep the last output, kill on timeout. */
|
|
6
22
|
export function runEngine(binary, args, timeoutMs) {
|
|
7
23
|
return new Promise((resolvePromise, reject) => {
|
|
8
24
|
const child = nodeSpawn(binary, args, { stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
9
25
|
let output = "";
|
|
10
26
|
let timedOut = false;
|
|
11
|
-
|
|
12
|
-
|
|
27
|
+
// Error lines are collected per stream as they arrive, so a long run cannot push them out of the tail.
|
|
28
|
+
const errors = [];
|
|
29
|
+
const partial = { stdout: "", stderr: "" };
|
|
30
|
+
const collect = (text) => {
|
|
31
|
+
if (errors.length < MAX_ERROR_LINES)
|
|
32
|
+
errors.push(...engineErrorLines(text, MAX_ERROR_LINES - errors.length));
|
|
33
|
+
};
|
|
34
|
+
const keep = (stream) => (chunk) => {
|
|
35
|
+
const text = chunk.toString("utf8");
|
|
36
|
+
output = (output + text).slice(-OUTPUT_TAIL_BYTES);
|
|
37
|
+
const lines = (partial[stream] + text).split("\n");
|
|
38
|
+
partial[stream] = (lines.pop() ?? "").slice(-OUTPUT_TAIL_BYTES);
|
|
39
|
+
collect(lines.join("\n"));
|
|
13
40
|
};
|
|
14
|
-
child.stdout?.on("data", keep);
|
|
15
|
-
child.stderr?.on("data", keep);
|
|
41
|
+
child.stdout?.on("data", keep("stdout"));
|
|
42
|
+
child.stderr?.on("data", keep("stderr"));
|
|
16
43
|
let killTimer;
|
|
17
44
|
const timer = setTimeout(() => {
|
|
18
45
|
timedOut = true;
|
|
@@ -27,7 +54,8 @@ export function runEngine(binary, args, timeoutMs) {
|
|
|
27
54
|
child.on("close", (code, signal) => {
|
|
28
55
|
clearTimeout(timer);
|
|
29
56
|
clearTimeout(killTimer);
|
|
30
|
-
|
|
57
|
+
collect(`${partial.stdout}\n${partial.stderr}`);
|
|
58
|
+
resolvePromise({ code, signal, timedOut, output, errors });
|
|
31
59
|
});
|
|
32
60
|
});
|
|
33
61
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The oldest Summer Engine that exports what the Summer Games store takes.
|
|
3
|
+
* 0.6.0 has no "summer.games" export platform: it cannot read the presets
|
|
4
|
+
* summer_export_game writes and fails after a full engine start.
|
|
5
|
+
*/
|
|
6
|
+
export declare const EXPORT_MIN_ENGINE_VERSION = "0.7.0";
|
|
7
|
+
export declare const ENGINE_UPDATE_HOW = "run \"summer install --yes\" (or download it from summerengine.com/download)";
|
|
8
|
+
/** Installed Summer version of an engine binary, or null when this system cannot read it. */
|
|
9
|
+
export declare function installedEngineVersion(binary: string): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Refuse an engine older than EXPORT_MIN_ENGINE_VERSION, before anything is
|
|
12
|
+
* written to the project. An unreadable version passes: the engine's own
|
|
13
|
+
* export output then names what is wrong.
|
|
14
|
+
*/
|
|
15
|
+
export declare function assertExportEngine(binary: string, version: string | null): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { platform } from "node:os";
|
|
2
|
+
import { isVersionAtLeast, readInstalledEngineVersion } from "../launch-posture.js";
|
|
3
|
+
import { BuildToolError } from "./summer-bundle.js";
|
|
4
|
+
/**
|
|
5
|
+
* The oldest Summer Engine that exports what the Summer Games store takes.
|
|
6
|
+
* 0.6.0 has no "summer.games" export platform: it cannot read the presets
|
|
7
|
+
* summer_export_game writes and fails after a full engine start.
|
|
8
|
+
*/
|
|
9
|
+
export const EXPORT_MIN_ENGINE_VERSION = "0.7.0";
|
|
10
|
+
export const ENGINE_UPDATE_HOW = 'run "summer install --yes" (or download it from summerengine.com/download)';
|
|
11
|
+
/** Installed Summer version of an engine binary, or null when this system cannot read it. */
|
|
12
|
+
export function installedEngineVersion(binary) {
|
|
13
|
+
return readInstalledEngineVersion(binary, platform());
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Refuse an engine older than EXPORT_MIN_ENGINE_VERSION, before anything is
|
|
17
|
+
* written to the project. An unreadable version passes: the engine's own
|
|
18
|
+
* export output then names what is wrong.
|
|
19
|
+
*/
|
|
20
|
+
export function assertExportEngine(binary, version) {
|
|
21
|
+
if (isVersionAtLeast(version, EXPORT_MIN_ENGINE_VERSION) !== false)
|
|
22
|
+
return;
|
|
23
|
+
throw new BuildToolError("engine_too_old", `Summer Engine ${version} is installed; summer.games exports need Summer Engine ${EXPORT_MIN_ENGINE_VERSION} or newer.`, `Recovery: update Summer Engine to ${EXPORT_MIN_ENGINE_VERSION}+: ${ENGINE_UPDATE_HOW}, then export again. Nothing was written to the project.`, undefined, { engine: binary, engineVersion: version, minimumEngineVersion: EXPORT_MIN_ENGINE_VERSION });
|
|
24
|
+
}
|