summer-engine 3.5.1 → 3.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +3 -2
- package/.codex-plugin/plugin.json +4 -3
- package/.cursor-plugin/plugin.json +3 -2
- package/.factory-plugin/plugin.json +3 -2
- package/CHANGELOG.md +22 -1
- package/commands/summer.md +1 -0
- package/dist/core/capabilities/sfx-choices.d.ts +91 -0
- package/dist/core/capabilities/sfx-choices.js +291 -0
- package/dist/core/capabilities/tool-dispatch.js +35 -0
- package/dist/mcp/hosted-mount.js +2 -1
- package/dist/mcp/provider-safe-schema.d.ts +10 -0
- package/dist/mcp/provider-safe-schema.js +96 -0
- package/dist/mcp/server.js +3 -0
- package/dist/mcp/tools/generate-tools.js +70 -3
- package/gemini-extension.json +2 -1
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +5 -3
- package/library/references/mcp-tools-reference/resource.yaml +1 -1
- package/library/skills/README.md +4 -3
- package/library/skills/ambient-bed/SKILL.md +3 -1
- package/library/skills/ambient-bed/resource.yaml +1 -1
- package/library/skills/audio-direction/SKILL.md +5 -5
- package/library/skills/audio-direction/resource.yaml +1 -1
- package/library/skills/export-and-ship/SKILL.md +4 -0
- package/library/skills/make-game/SKILL.md +21 -2
- package/library/skills/make-game/resource.yaml +1 -0
- package/library/skills/multiplayer/SKILL.md +3 -1
- package/library/skills/multiplayer/resource.yaml +1 -0
- package/library/skills/multiplayer-movement/SKILL.md +1 -1
- package/library/skills/multiplayer-movement/files.md +2 -3
- package/library/skills/multiplayer-project/SKILL.md +20 -4
- package/library/skills/multiplayer-project/files.md +2 -3
- package/library/skills/multiplayer-publish/SKILL.md +90 -5
- package/library/skills/multiplayer-publish/resource.yaml +1 -0
- package/library/skills/multiplayer-state/SKILL.md +1 -1
- package/library/skills/multiplayer-state/files.md +2 -3
- package/library/skills/multiplayer-testing/SKILL.md +13 -0
- package/library/skills/multiplayer-testing/resource.yaml +1 -0
- package/library/skills/publish-your-game/SKILL.md +2 -0
- package/library/skills/release-readiness/SKILL.md +437 -0
- package/library/skills/release-readiness/resource.yaml +44 -0
- package/library/skills/sound-effect/SKILL.md +83 -158
- package/library/skills/sound-effect/resource.yaml +8 -2
- package/library/skills/summer-parties/SKILL.md +1 -1
- package/library/skills/ui-basics/SKILL.md +1 -0
- package/library/tools/generate-audio/resource.yaml +6 -2
- package/library/tools/generate-sfx-choices/resource.yaml +69 -0
- package/library/tools/get-choices/resource.yaml +50 -0
- package/package.json +1 -1
- package/registry/generated/aliases.json +2 -0
- package/registry/generated/counts.json +3 -3
- package/registry/generated/gemini-extension.json +2 -1
- package/registry/generated/index.json +163 -24
- package/registry/generated/marketplace.claude.json +2 -2
- package/registry/generated/plugin.claude.json +3 -2
- package/registry/generated/plugin.codex.json +4 -3
- package/registry/generated/plugin.cursor.json +3 -2
- package/registry/generated/plugin.factory.json +3 -2
- package/registry/generated/skills-index.md +4 -3
- package/registry/generated/skills-registry.json +15 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
/** String formats OpenAI strict mode accepts; other providers accept no more. */
|
|
3
|
+
export const STRICT_PROVIDER_FORMATS = new Set(["date-time", "time", "date", "duration", "email", "hostname", "ipv4", "ipv6", "uuid"]);
|
|
4
|
+
// Where a schema holds subschemas: one, a list, or a name -> schema map.
|
|
5
|
+
const SCHEMA_KEYS = new Set(["items", "additionalItems", "additionalProperties", "contains", "not", "if", "then", "else", "propertyNames", "unevaluatedItems", "unevaluatedProperties"]);
|
|
6
|
+
const SCHEMA_LIST_KEYS = new Set(["anyOf", "oneOf", "allOf", "prefixItems"]);
|
|
7
|
+
const SCHEMA_MAP_KEYS = new Set(["properties", "patternProperties", "dependentSchemas", "$defs", "definitions"]);
|
|
8
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
+
function resolvePointer(root, ref) {
|
|
10
|
+
let node = root;
|
|
11
|
+
for (const raw of ref.slice(2).split("/")) {
|
|
12
|
+
if (raw === "")
|
|
13
|
+
continue;
|
|
14
|
+
const key = raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
15
|
+
if (Array.isArray(node))
|
|
16
|
+
node = node[Number(key)];
|
|
17
|
+
else if (isObject(node))
|
|
18
|
+
node = node[key];
|
|
19
|
+
else
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return node;
|
|
23
|
+
}
|
|
24
|
+
function uniqueSchemas(schemas) {
|
|
25
|
+
const seen = new Map();
|
|
26
|
+
for (const schema of schemas)
|
|
27
|
+
seen.set(JSON.stringify(schema), schema);
|
|
28
|
+
return [...seen.values()];
|
|
29
|
+
}
|
|
30
|
+
function rewrite(schema, root, expanding) {
|
|
31
|
+
if (!isObject(schema))
|
|
32
|
+
return schema;
|
|
33
|
+
const child = (value) => rewrite(value, root, expanding);
|
|
34
|
+
const { $ref, ...rest } = schema;
|
|
35
|
+
if (typeof $ref === "string" && ($ref === "#" || $ref.startsWith("#/"))) {
|
|
36
|
+
// A recursive shape cannot be inlined; accept any value there and let zod validate it.
|
|
37
|
+
const target = expanding.includes($ref) ? {} : rewrite(resolvePointer(root, $ref), root, [...expanding, $ref]);
|
|
38
|
+
return { ...(isObject(target) ? target : {}), ...child(rest) };
|
|
39
|
+
}
|
|
40
|
+
const out = {};
|
|
41
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
42
|
+
if (key === "additionalItems" || key === "prefixItems")
|
|
43
|
+
continue;
|
|
44
|
+
if (SCHEMA_KEYS.has(key))
|
|
45
|
+
out[key] = Array.isArray(value) ? value.map(child) : child(value);
|
|
46
|
+
else if (SCHEMA_LIST_KEYS.has(key) && Array.isArray(value))
|
|
47
|
+
out[key] = value.map(child);
|
|
48
|
+
else if (SCHEMA_MAP_KEYS.has(key) && isObject(value))
|
|
49
|
+
out[key] = Object.fromEntries(Object.entries(value).map(([name, sub]) => [name, child(sub)]));
|
|
50
|
+
else
|
|
51
|
+
out[key] = value;
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(schema.type) && !("anyOf" in out)) {
|
|
54
|
+
delete out.type;
|
|
55
|
+
out.anyOf = schema.type.map((type) => ({ type }));
|
|
56
|
+
}
|
|
57
|
+
if (typeof out.format === "string" && !STRICT_PROVIDER_FORMATS.has(out.format))
|
|
58
|
+
delete out.format;
|
|
59
|
+
if (isObject(out.propertyNames) && Object.keys(out.propertyNames).length === 1 && out.propertyNames.type === "string")
|
|
60
|
+
delete out.propertyNames;
|
|
61
|
+
const tuple = Array.isArray(schema.prefixItems) ? schema.prefixItems : Array.isArray(schema.items) ? schema.items : null;
|
|
62
|
+
if (tuple) {
|
|
63
|
+
const extra = Array.isArray(schema.prefixItems) ? schema.items : schema.additionalItems;
|
|
64
|
+
const open = extra !== undefined && extra !== false;
|
|
65
|
+
const members = uniqueSchemas([...tuple, ...(isObject(extra) ? [extra] : [])].map(child));
|
|
66
|
+
out.items = members.length === 1 ? members[0] : members.length === 0 ? {} : { anyOf: members };
|
|
67
|
+
out.minItems ??= tuple.length;
|
|
68
|
+
if (!open)
|
|
69
|
+
out.maxItems ??= tuple.length;
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
export function providerSafeSchema(schema) {
|
|
74
|
+
const out = rewrite(schema, schema, []);
|
|
75
|
+
if (isObject(out)) {
|
|
76
|
+
// Every local $ref is inlined, so the definition tables are dead weight.
|
|
77
|
+
delete out.definitions;
|
|
78
|
+
delete out.$defs;
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Wrap the SDK's tools/list so every tool's inputSchema leaves the server in
|
|
84
|
+
* the provider-safe form. Call after the first registerTool (the SDK installs
|
|
85
|
+
* its tools/list handler then).
|
|
86
|
+
*/
|
|
87
|
+
export function serveProviderSafeToolSchemas(server) {
|
|
88
|
+
const handlers = server.server._requestHandlers;
|
|
89
|
+
const listTools = handlers.get("tools/list");
|
|
90
|
+
if (!listTools)
|
|
91
|
+
return;
|
|
92
|
+
server.server.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
|
|
93
|
+
const result = await listTools(request, extra);
|
|
94
|
+
return { ...result, tools: result.tools.map((tool) => ({ ...tool, inputSchema: providerSafeSchema(tool.inputSchema) })) };
|
|
95
|
+
});
|
|
96
|
+
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -27,6 +27,7 @@ import { registerLibraryTools } from "./tools/library-tools.js";
|
|
|
27
27
|
import { getCachedBootDriftNotice as getCachedNotice, setCachedBootDriftNotice, } from "./boot-notice.js";
|
|
28
28
|
import { appendMcpLogEvent } from "../core/mcp-log.js";
|
|
29
29
|
import { prepareHostedMount } from "./hosted-mount.js";
|
|
30
|
+
import { serveProviderSafeToolSchemas } from "./provider-safe-schema.js";
|
|
30
31
|
import { buildBootDriftNotice, fetchLatestRegistryVersion, } from "../installer/version-check.js";
|
|
31
32
|
import { TOOLKIT_VERSION as version } from "../core/version.js";
|
|
32
33
|
let processDiagnosticsInstalled = false;
|
|
@@ -329,6 +330,8 @@ export function createMcpServer() {
|
|
|
329
330
|
// The playbook is also an MCP prompt so prompt-surfacing hosts get it
|
|
330
331
|
// natively (same content as the summer_get_agent_playbook tool).
|
|
331
332
|
registerPlaybookPrompt(server);
|
|
333
|
+
// Strict providers (Kimi, OpenAI strict) reject tuple and $ref schemas.
|
|
334
|
+
serveProviderSafeToolSchemas(server);
|
|
332
335
|
return { server, getRegisteredToolCount };
|
|
333
336
|
}
|
|
334
337
|
export async function startMcpServer(options = {}) {
|
|
@@ -2,6 +2,8 @@ import { z } from "zod";
|
|
|
2
2
|
import { BOARD_ENDPOINT, boardArgsSchema, boardContent, boardQuery } from "../../core/capabilities/board.js";
|
|
3
3
|
import { IMAGE_GENERATION_TIMEOUT_MS, imageGenerationArgsSchema, saveGeneratedImage } from "../../core/capabilities/image-generation.js";
|
|
4
4
|
import { buildMotionRequestBody, motionErrorHint, motionGenerationArgsSchema, motionJobFailureHint, } from "../../core/capabilities/motion-generation.js";
|
|
5
|
+
import { MAX_CHOICES_WAIT_SECONDS, defaultCanOpenBrowser, generateSfxChoices, getChoices, getChoicesArgsSchema, openInStudioTab, sfxChoicesArgsSchema, SFX_CHOICES_ENDPOINT, } from "../../core/capabilities/sfx-choices.js";
|
|
6
|
+
import open from "open";
|
|
5
7
|
import { getAuthToken } from "../../core/auth.js";
|
|
6
8
|
import { resolveGatewayUrl } from "../../core/config.js";
|
|
7
9
|
import { readJsonResponse } from "../../core/util/http.js";
|
|
@@ -10,6 +12,7 @@ import { TOOLKIT_VERSION as CLI_VERSION } from "../../core/version.js";
|
|
|
10
12
|
const TOOL_BY_ENDPOINT = {
|
|
11
13
|
"/api/mcp/generate/image": "summer_generate_image",
|
|
12
14
|
"/api/mcp/generate/audio": "summer_generate_audio",
|
|
15
|
+
[SFX_CHOICES_ENDPOINT]: "summer_generate_sfx_choices",
|
|
13
16
|
"/api/mcp/generate/3d": "summer_generate_3d",
|
|
14
17
|
"/api/mcp/generate/video": "summer_generate_video",
|
|
15
18
|
"/api/mcp/generate/motion": "summer_generate_motion",
|
|
@@ -356,10 +359,21 @@ Requires authentication: run 'npx -y summer-engine@latest login' first.`, {
|
|
|
356
359
|
|
|
357
360
|
Capabilities:
|
|
358
361
|
- "text_to_speech" — Convert text to spoken audio. Requires 'text'. Optional 'voiceId'.
|
|
359
|
-
- "sound_effects" — Generate sound effects from description. Requires 'text'. Optional 'durationSeconds'.
|
|
362
|
+
- "sound_effects" — Generate sound effects from description. Requires 'text'. Optional 'durationSeconds', 'modelId', 'loop'.
|
|
360
363
|
- "music" — Generate background music. Requires 'prompt'. Optional 'durationSeconds'.
|
|
361
364
|
- "text_to_dialogue" — Multi-voice dialogue. Requires 'inputs' array of {text, voiceId} objects.
|
|
362
365
|
|
|
366
|
+
Sound-effect models (modelId; omit it to use the person's approved default,
|
|
367
|
+
else ElevenLabs):
|
|
368
|
+
- "eleven_text_to_sound_v2" — ElevenLabs SFX v2. Strong all-rounder, 0.5-30 s,
|
|
369
|
+
the only one that makes seamless loops (loop: true).
|
|
370
|
+
- "sonilo-sfx-1.1" — Sonilo SFX 1.1. Strong all-rounder, >= 0.5 s.
|
|
371
|
+
- "stable-audio-3-sfx" — Stable Audio 3 SFX. Sometimes best on fast movement
|
|
372
|
+
(swings, whooshes), less consistent; >= 1 s.
|
|
373
|
+
The best model changes a lot from one sound to the next. For the first sound
|
|
374
|
+
effect in a chat, offer the person three versions to pick from by ear:
|
|
375
|
+
summer_generate_sfx_choices.
|
|
376
|
+
|
|
363
377
|
The 'options' object is spread into the ElevenLabs SDK call at the TOP level, so
|
|
364
378
|
only keys the SDK accepts there have any effect. Voice tuning is NOT top-level and
|
|
365
379
|
is NOT snake_case: pass it as options.voiceSettings with camelCase keys —
|
|
@@ -388,11 +402,16 @@ Requires authentication: run 'npx -y summer-engine@latest login' first.`, {
|
|
|
388
402
|
modelId: z
|
|
389
403
|
.string()
|
|
390
404
|
.optional()
|
|
391
|
-
.describe("ElevenLabs model
|
|
405
|
+
.describe("Model id. text_to_speech: an ElevenLabs model (e.g. 'eleven_multilingual_v2'). " +
|
|
406
|
+
"sound_effects: eleven_text_to_sound_v2, sonilo-sfx-1.1 or stable-audio-3-sfx; omit to use the person's approved default"),
|
|
392
407
|
durationSeconds: z
|
|
393
408
|
.number()
|
|
394
409
|
.optional()
|
|
395
410
|
.describe("Duration in seconds for SFX or music"),
|
|
411
|
+
loop: z
|
|
412
|
+
.boolean()
|
|
413
|
+
.optional()
|
|
414
|
+
.describe("sound_effects only: ask for a seamless loop (eleven_text_to_sound_v2 only)"),
|
|
396
415
|
inputs: z
|
|
397
416
|
.array(z.object({ text: z.string(), voiceId: z.string() }))
|
|
398
417
|
.optional()
|
|
@@ -403,7 +422,7 @@ Requires authentication: run 'npx -y summer-engine@latest login' first.`, {
|
|
|
403
422
|
.describe("Provider params, spread at the SDK's top level. Voice tuning goes in " +
|
|
404
423
|
"options.voiceSettings with camelCase keys (stability, similarityBoost, " +
|
|
405
424
|
"style, speed, useSpeakerBoost); flat snake_case is silently ignored."),
|
|
406
|
-
}, async ({ capability, text, prompt, voiceId, modelId, durationSeconds, inputs, options }) => {
|
|
425
|
+
}, async ({ capability, text, prompt, voiceId, modelId, durationSeconds, loop, inputs, options }) => {
|
|
407
426
|
const body = { capability };
|
|
408
427
|
if (text)
|
|
409
428
|
body.text = text;
|
|
@@ -415,6 +434,8 @@ Requires authentication: run 'npx -y summer-engine@latest login' first.`, {
|
|
|
415
434
|
body.modelId = modelId;
|
|
416
435
|
if (durationSeconds)
|
|
417
436
|
body.durationSeconds = durationSeconds;
|
|
437
|
+
if (loop !== undefined)
|
|
438
|
+
body.loop = loop;
|
|
418
439
|
if (inputs)
|
|
419
440
|
body.inputs = inputs;
|
|
420
441
|
if (options)
|
|
@@ -426,6 +447,52 @@ Requires authentication: run 'npx -y summer-engine@latest login' first.`, {
|
|
|
426
447
|
return successResult(result.data);
|
|
427
448
|
});
|
|
428
449
|
// ========================================================================
|
|
450
|
+
// summer_generate_sfx_choices + summer_get_choices
|
|
451
|
+
// Shared implementation: core/capabilities/sfx-choices.ts
|
|
452
|
+
// ========================================================================
|
|
453
|
+
const sfxChoicesDeps = {
|
|
454
|
+
post: (endpoint, body, timeoutMs) => mcpGenerate(endpoint, body, timeoutMs),
|
|
455
|
+
get: (endpoint, params, timeoutMs) => mcpGet(endpoint, params, timeoutMs),
|
|
456
|
+
studioOpen: (path, reviewUrl) => openInStudioTab(path, reviewUrl),
|
|
457
|
+
openUrl: (url) => open(url),
|
|
458
|
+
canOpenBrowser: () => defaultCanOpenBrowser(),
|
|
459
|
+
};
|
|
460
|
+
server.tool("summer_generate_sfx_choices", `Make one sound effect with several models (default three) and show the
|
|
461
|
+
versions to the person on a Summer Studio page, where they listen and approve
|
|
462
|
+
one or more. The approved model becomes their default for sound effects.
|
|
463
|
+
|
|
464
|
+
Use it for the first sound effect in a chat after the person said yes to
|
|
465
|
+
"three versions to pick from", or whenever they want to compare. The page
|
|
466
|
+
opens in their open Summer Studio tab when there is one (needs the store
|
|
467
|
+
sign-in), else in the browser; open:false only returns reviewUrl.
|
|
468
|
+
|
|
469
|
+
Then tell the person the returned "say" text and call summer_get_choices
|
|
470
|
+
with the choiceSetId. Do not pick for them.
|
|
471
|
+
|
|
472
|
+
Models: eleven_text_to_sound_v2 (0.5-30 s, loops), sonilo-sfx-1.1 (>= 0.5 s),
|
|
473
|
+
stable-audio-3-sfx (>= 1 s; give durationSeconds >= 1 when it is included).
|
|
474
|
+
Each version is a billed generation.
|
|
475
|
+
|
|
476
|
+
Cloud tool — runs on Summer's servers and works WITHOUT the Summer Engine app open.
|
|
477
|
+
Requires authentication: run 'npx -y summer-engine@latest login' first.`, sfxChoicesArgsSchema.shape, async (args) => {
|
|
478
|
+
const result = await generateSfxChoices(sfxChoicesArgsSchema.parse(args), sfxChoicesDeps);
|
|
479
|
+
return result.ok ? successResult(result) : errorResult(String(result.error), result);
|
|
480
|
+
});
|
|
481
|
+
server.tool("summer_get_choices", `Read a sound-effect choice set from summer_generate_sfx_choices: whether the
|
|
482
|
+
person decided ("pending" or "decided"), which versions they approved (with
|
|
483
|
+
audioUrl, assetId and their note) and their default sound-effect model.
|
|
484
|
+
|
|
485
|
+
waitSeconds (max ${MAX_CHOICES_WAIT_SECONDS}) waits for the decision before answering; call again
|
|
486
|
+
while status is "pending". Import each approved clip with
|
|
487
|
+
summer_import_from_url (url: audioUrl, path: res://audio/sfx/<name>.<fileExtension>)
|
|
488
|
+
or summer_import_asset_by_id (assetId), then wire it.
|
|
489
|
+
|
|
490
|
+
Cloud tool — runs on Summer's servers and works WITHOUT the Summer Engine app open.
|
|
491
|
+
Requires authentication: run 'npx -y summer-engine@latest login' first.`, getChoicesArgsSchema.shape, async (args) => {
|
|
492
|
+
const result = await getChoices(getChoicesArgsSchema.parse(args), sfxChoicesDeps);
|
|
493
|
+
return result.ok ? successResult(result) : errorResult(String(result.error), result);
|
|
494
|
+
});
|
|
495
|
+
// ========================================================================
|
|
429
496
|
// summer_generate_3d
|
|
430
497
|
// ========================================================================
|
|
431
498
|
server.tool("summer_generate_3d", `Generate a 3D model via Summer Engine Studio.
|
package/gemini-extension.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_generated": "GENERATED from integrations/gemini — do not edit; npm run generate:registry",
|
|
3
3
|
"name": "summer",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.6.0",
|
|
5
5
|
"description": "Agent tooling for Summer Engine: MCP bridge, context primer, and game-dev skills. Use with the npm CLI for skill files on disk.",
|
|
6
6
|
"contextFileName": "GEMINI.md",
|
|
7
7
|
"mcpServers": {
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"./library/skills/psx-retro-rendering/",
|
|
94
94
|
"./library/skills/publish-your-game/",
|
|
95
95
|
"./library/skills/realtime-wet-surfaces/",
|
|
96
|
+
"./library/skills/release-readiness/",
|
|
96
97
|
"./library/skills/remote-deploy/",
|
|
97
98
|
"./library/skills/retarget/",
|
|
98
99
|
"./library/skills/running-in-the-cloud/",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
**Rule of thumb:** project reads/writes go through Summer; live hierarchy/inspector changes use scene tools; process-level work remains with the host.
|
|
19
19
|
|
|
20
|
-
## Tool surface (
|
|
20
|
+
## Tool surface (107 tools)
|
|
21
21
|
|
|
22
22
|
### Project files (4)
|
|
23
23
|
|
|
@@ -211,13 +211,15 @@ Preview. One fast, read-only call that walks every node of a 3D scene and lists
|
|
|
211
211
|
| `summer_import_hdri` | Search Poly Haven's CC0 HDRIs (public API, no Summer login), import the `.hdr`/`.exr` into `res://sky/`, and get the exact `summer_run_script` snippet that wires it as the WorldEnvironment sky. |
|
|
212
212
|
| `summer_slice_asset_sheet` | Detect and crop every distinct asset from a generated sheet image into named individual assets (works without the engine; import the results afterwards). |
|
|
213
213
|
|
|
214
|
-
### Asset generation (
|
|
214
|
+
### Asset generation (7 — metered)
|
|
215
215
|
|
|
216
216
|
| Tool | Use |
|
|
217
217
|
|---|---|
|
|
218
218
|
| `summer_generate_image` | AI image gen. |
|
|
219
219
|
| `summer_generate_3d` | Image-to-3D. |
|
|
220
|
-
| `summer_generate_audio` | SFX / music gen. |
|
|
220
|
+
| `summer_generate_audio` | SFX / music gen. Sound effects: `modelId` picks ElevenLabs SFX v2, Sonilo SFX 1.1 or Stable Audio 3 SFX; omitted = the person's approved default. |
|
|
221
|
+
| `summer_generate_sfx_choices` | One sound effect from several models, shown on a Summer Studio page where the person approves one or more. |
|
|
222
|
+
| `summer_get_choices` | Read which sound-effect versions the person approved (audioUrl, assetId, note). |
|
|
221
223
|
| `summer_generate_video` | Video gen. |
|
|
222
224
|
| `summer_generate_motion` | Generate/apply 3D skeletal motion from a rigged asset. |
|
|
223
225
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
id: reference/mcp-tools-reference
|
|
2
2
|
kind: reference
|
|
3
|
-
version: 1.4.
|
|
3
|
+
version: 1.4.1
|
|
4
4
|
summary: Canonical reference for which Summer MCP tool to call — skills should reference tool names exactly as written here.
|
|
5
5
|
use_when:
|
|
6
6
|
- choosing between Summer MCP tools and host tools for an operation
|
package/library/skills/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Summer skills
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> 123 skills, grouped by their primary domain. **Generated** by `npm run generate:registry` from each skill's `resource.yaml`; `--check` fails when this file and the library disagree. Do not edit by hand.
|
|
4
4
|
|
|
5
5
|
The folder is flat on purpose (`docs/design/DECISIONS.md`, D3): a skill usually belongs to several domains, so categories live in each skill's `facets.domains` and this index is rendered from them. The first domain listed is the primary one. `summer skills list --by-domain` prints the same grouping; `summer skills info <slug>` shows one skill.
|
|
6
6
|
|
|
@@ -18,7 +18,7 @@ Status: **stable** unless marked; *preview* = not yet exercised in-engine by the
|
|
|
18
18
|
- [character-controller](#character-controller) (2)
|
|
19
19
|
- [creator](#creator) (4)
|
|
20
20
|
- [debug](#debug) (1)
|
|
21
|
-
- [deployment](#deployment) (
|
|
21
|
+
- [deployment](#deployment) (3)
|
|
22
22
|
- [editor](#editor) (1)
|
|
23
23
|
- [gameplay](#gameplay) (6)
|
|
24
24
|
- [level-design](#level-design) (2)
|
|
@@ -117,7 +117,7 @@ Status: **stable** unless marked; *preview* = not yet exercised in-engine by the
|
|
|
117
117
|
| [ambient-bed](./ambient-bed/SKILL.md) | Generate a long looping location ambience — forest, dungeon, city, spaceship, cave — a looping AudioStreamPlayer on the Ambient bus with a seamless loop. | assets |
|
|
118
118
|
| [audio-direction](./audio-direction/SKILL.md) ★ | Define the game's sonic identity — music style, instruments, SFX vocabulary, dynamic music plan — output as an audio bible at .summer/audio-bible.md. | game-design |
|
|
119
119
|
| [music-track](./music-track/SKILL.md) ★ | Generate a looped or cinematic music track — loops authored at >=30s with a marked loop point, cinematic tracks at >=60s linear. | assets |
|
|
120
|
-
| [sound-effect](./sound-effect/SKILL.md) ★ |
|
|
120
|
+
| [sound-effect](./sound-effect/SKILL.md) ★ | Make SFX one-shots and timed sequences (swings, hits, whooshes, reloads, UI) - offer three model versions to pick from, then wire them as AudioStreamPlayers. | assets |
|
|
121
121
|
| [voice-line](./voice-line/SKILL.md) ★ | Generate TTS voice lines — NPC barks, narrator, dialogue — with voice-id sourcing, a character-to-voice decision tree, and multi-line dialogue support. | assets |
|
|
122
122
|
|
|
123
123
|
## character-controller
|
|
@@ -147,6 +147,7 @@ Status: **stable** unless marked; *preview* = not yet exercised in-engine by the
|
|
|
147
147
|
| skill | when to use | also |
|
|
148
148
|
|---|---|---|
|
|
149
149
|
| [export-and-ship](./export-and-ship/SKILL.md) ★ | Assess and prepare a game export: inventory installed templates and targets, validate release assets and config, produce supported builds after approval. | project |
|
|
150
|
+
| [*release-readiness*](./release-readiness/SKILL.md) (preview) | Before releasing a Summer game version, prove players can play it: server scene, online join, progress, Game ID, released Summer, packed files, app version. | multiplayer, playtest |
|
|
150
151
|
| [remote-deploy](./remote-deploy/SKILL.md) ★ | Run or test the game on a real device — phone, tablet, another computer — via the Remote Deploy button, runnable export presets, and on-device debugging. | project |
|
|
151
152
|
|
|
152
153
|
## editor
|
|
@@ -103,13 +103,15 @@ If the user wants a busier bed (market, party), still prevent foreground events:
|
|
|
103
103
|
```
|
|
104
104
|
summer_generate_audio(
|
|
105
105
|
capability: "sound_effects",
|
|
106
|
+
modelId: "eleven_text_to_sound_v2",
|
|
107
|
+
loop: true,
|
|
106
108
|
text: "forest at dawn, gentle wind through leaves, distant bird calls every 6 seconds, soft creaking branches, no music, no human sound, 22s seamless loop",
|
|
107
109
|
durationSeconds: 22
|
|
108
110
|
)
|
|
109
111
|
// Then: summer_import_from_url(url: "<fileUrl>", path: "res://audio/ambient/forest_dawn.wav")
|
|
110
112
|
```
|
|
111
113
|
|
|
112
|
-
`sound_effects` reads from **`text`**, not `prompt` — passing `prompt` is a 400 `text_required`. (`prompt` is the `music` capability's field.) The clip comes back as WAV, so a `.wav` target path is correct.
|
|
114
|
+
Name `modelId: "eleven_text_to_sound_v2"` with `loop: true`: it is the only sound-effect model that makes seamless loops, and without a `modelId` the person's default sound-effect model runs, which may not loop. `sound_effects` reads from **`text`**, not `prompt` — passing `prompt` is a 400 `text_required`. (`prompt` is the `music` capability's field.) The clip comes back as WAV, so a `.wav` target path is correct.
|
|
113
115
|
|
|
114
116
|
22s is the SFX ceiling. Always generate at the ceiling for ambient — longer cycle means less recognized repetition.
|
|
115
117
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
id: skill/ambient-bed
|
|
2
2
|
kind: skill
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.3
|
|
4
4
|
summary: Generate a long looping location ambience — forest, dungeon, city, spaceship, cave — a looping AudioStreamPlayer on the Ambient bus with a seamless loop.
|
|
5
5
|
use_when:
|
|
6
6
|
- generating a long looping location ambience — forest at dawn, dungeon air, city street, spaceship hum, cave drip — non-melodic and low-energy
|
|
@@ -203,15 +203,15 @@ Ask:
|
|
|
203
203
|
|
|
204
204
|
### 10. Optional — generate first SFX seeds
|
|
205
205
|
|
|
206
|
-
If the user wants to start generating, propose 3-5 SFX seeds aligned with the vocabulary. These are starting points, not finals.
|
|
206
|
+
If the user wants to start generating, propose 3-5 SFX seeds aligned with the vocabulary. These are starting points, not finals. Generate them with the `sound-effect` skill: the first sound effect in a chat starts with its question (three versions from three models, picked by ear in Summer Studio), and its prompt shape is the one that tested well.
|
|
207
207
|
|
|
208
208
|
```
|
|
209
|
-
summer_generate_audio(capability="sound_effects", text="soft woody
|
|
210
|
-
summer_generate_audio(capability="sound_effects", text="
|
|
211
|
-
summer_generate_audio(capability="sound_effects", text="
|
|
209
|
+
summer_generate_audio(capability="sound_effects", text="Foley one-shot: a fingertip taps a small wooden button, soft woody click, no tail, close-mic, dry, no reverb, no music, no voices.", durationSeconds=0.5)
|
|
210
|
+
summer_generate_audio(capability="sound_effects", text="Foley one-shot: a small gold coin drops onto a wooden table, bright metallic ting with a short ring, close-mic, dry, no music, no voices.", durationSeconds=0.5)
|
|
211
|
+
summer_generate_audio(capability="sound_effects", text="Foley one-shot: a fist hits a padded leather vest, low-mid thump, no tail, close-mic, dry, no reverb, no music, no voices.", durationSeconds=0.5)
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
`capability` is required; `sound_effects` reads from `text` (not `prompt`), and the length argument is `durationSeconds` (not `duration`) with a floor of 0.5 s. Ask for the character you want inside the text and let the player only use the front of the clip — you cannot request 80 ms.
|
|
214
|
+
`capability` is required; `sound_effects` reads from `text` (not `prompt`), and the length argument is `durationSeconds` (not `duration`) with a floor of 0.5 s (1 s for Stable Audio 3 SFX). Leave out `modelId` so the person's approved default model runs. Ask for the character you want inside the text and let the player only use the front of the clip — you cannot request 80 ms.
|
|
215
215
|
|
|
216
216
|
Ask first:
|
|
217
217
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
id: skill/audio-direction
|
|
2
2
|
kind: skill
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.7
|
|
4
4
|
summary: Define the game's sonic identity — music style, instruments, SFX vocabulary, dynamic music plan — output as an audio bible at .summer/audio-bible.md.
|
|
5
5
|
use_when:
|
|
6
6
|
- defining the sonic identity — music style, instruments, SFX vocabulary, dynamic music plan
|
|
@@ -298,6 +298,10 @@ Per platform, point the user at the correct upload path:
|
|
|
298
298
|
- **summer.games:** `summer_export_game` writes the summer.games `.zip`; `summer_publish_build` uploads it to the game's store listing after the user confirms the exact game, file and version (needs `summer login --store`). If it answers `store_auth_refused`, the user uploads the `.zip` in Studio instead.
|
|
299
299
|
- Per platform, the store takes: iPhone, Android, macOS and Windows in the Summer apps = one summer.games bundle (`summer_export_game` `targets`, format `bundle`; no template, no signing; a game without a server targets `ios`/`android` only). Web = an HTML5 `.zip` on Summer's WebGPU Forward+ template (`targets:["web"]`; the project must render with Forward+ or Mobile). A macOS or Windows download for the store page = `targets:["macos"]` or `["windows"]` with format `download`. Download exports need the template: `summer_export_templates` `install` fetches it from Summer's CDN.
|
|
300
300
|
- Hosted games: if `summer_export_game` fails with "… is in the source graph's authority_engine domain, which the client pack leaves out", an authority root in `source-domains.json` holds a file the client loads (often the composition `.tres`). Narrow that root to the server-only files, or move the shared file under a `shared` path, then export again. Every exporter message is in the `multiplayer-publish` skill's export-errors.md; the folder rules are in `multiplayer-project`.
|
|
301
|
+
- Every summer.games bundle: export with the released Summer editor from the download page, never a development build, and export again after updating Summer. Otherwise its scripts can fail to compile on players' devices and the game opens to an empty window, though every upload check passes. `release-readiness` check 7 proves it.
|
|
302
|
+
- Every file the game reads by path at run time (`FileAccess`, `DirAccess`, `load_from_file`) that Summer doesn't import, such as a `.bin` or `.json`, must match the **summer.games** preset's own `include_filter` (Resources > Filters to export non-resource files/folders); a filter on another preset doesn't carry over. A left-out file is no export error, it is just missing on players' devices. Never use `SystemFont` for phone text: on iPhone it draws every glyph as a box. `release-readiness` check 8 lists the pack.
|
|
303
|
+
- iPhone and Android bundles: the Summer Games apps start only exports on their Summer version line (same MAJOR.MINOR, patch no newer). Export with the current Summer release and export again after every Summer minor update, or players see "The game could not start" on phones while desktop works. `release-readiness` check 9.
|
|
304
|
+
- Hosted games, before the upload: a clean export does not prove players can join. Run `release-readiness` on the `.zip`: the bundle's `server.pck`, started with no scene, must load the authority scene (the server preset needs the `summer_authority` custom feature), and `Summer.initialize` must pass no hard-coded Game ID. Otherwise every online join fails with `world_start_timeout`.
|
|
301
305
|
- Store page art: key art, covers and icons carry no text, logos or UI (the store page shows the title). Screenshots are real gameplay and may show the game's own HUD; add no captions, logos or overlays. Capture them with `summer_capture_gameplay` (offscreen, no editor needed; `resolution` `1920x1080`, or `1080x1920` for portrait; get past a title menu with the game's own flags in `args`, e.g. `["--autostart"]`, or `steps` such as `[{"press":"Play"},{"wait":3000},{"shot":true}]`), or `summer_screenshot` target `game` while the editor runs the game.
|
|
302
306
|
- **Steam:** `steamcmd` + Steam Pipe (`builder_app_xxx.vdf`). Set up depots first in Steamworks.
|
|
303
307
|
- **itch.io:** `butler push <path> <user>/<game>:<channel>` (e.g. `butler push build/win <user>/mygame:windows`).
|
|
@@ -47,6 +47,7 @@ These rules from the playbooks apply to every genre:
|
|
|
47
47
|
- **2D scales to the viewport.** Set `display/window/stretch/mode = canvas_items` and `stretch/aspect = expand`; UI uses Control anchors; code-positioned nodes compute from `get_viewport_rect().size`. Never hardcode 1920x1080.
|
|
48
48
|
- **Playable slice, not tech demo.** Name the core loop in one sentence; every input gets feedback (tween, sound, particle, flash, shake); progression is visible; a restart exists. The bar is "feels good for 30 seconds".
|
|
49
49
|
- **Fast first draft when someone is waiting.** The first playable version uses shapes, the palette and any template art, plus at most one or two generated assets the person specifically asked for; generation takes minutes, so it never blocks the first play. Say plainly that it is a first draft to get a feel for the game, that the art is not final, and ask if this is the direction they want. Generate the rest of the art in later passes.
|
|
50
|
+
- **A way back to the title.** From inside play, a pause menu or a **Leave** button returns to the title screen without quitting the app.
|
|
50
51
|
- **Text the default font can show.** No emoji or symbols (▲ ◀ ★ ♥ ✓) in UI text: they render as boxes with hex codes. Use words or icons; see `ui-basics`.
|
|
51
52
|
|
|
52
53
|
## The Pipeline
|
|
@@ -95,6 +96,12 @@ This phase doesn't have a dedicated skill yet — do it inline. Read `.summer/Ga
|
|
|
95
96
|
2. **Core mechanics** — ranked, max 3 (the brief enforces this; respect it)
|
|
96
97
|
3. **Cut list** — what's explicitly NOT in scope
|
|
97
98
|
|
|
99
|
+
If the game has online play, the plan also fixes three product defaults; each one was missed by a real release:
|
|
100
|
+
|
|
101
|
+
- **Online play is reachable by default.** A new player gets to it from the title without changing a setting. Hiding it behind a setting or a grown-ups gate is the owner's explicit decision, recorded in the plan, and it means someone joins online before every release.
|
|
102
|
+
- **Progress is saved by the server.** Online progress goes through authority player data (`summer-player-data`), not only the player's local files.
|
|
103
|
+
- **A way back to the title** from inside an online and an offline session.
|
|
104
|
+
|
|
98
105
|
Save the plan to `.summer/build-plan.md`. Format:
|
|
99
106
|
|
|
100
107
|
```markdown
|
|
@@ -236,8 +243,10 @@ Only if `summer_get_diagnostics` flags rendering or physics hotspots. Don't opti
|
|
|
236
243
|
1. `play` from main scene.
|
|
237
244
|
2. Walk every mechanic from the plan.
|
|
238
245
|
3. Hit the win condition (or lose condition).
|
|
239
|
-
4.
|
|
240
|
-
5. `
|
|
246
|
+
4. Return to the title from inside play, then start again.
|
|
247
|
+
5. Online games: run Local Play (`multiplayer-testing`) and reach online play from the title as a new player, with default settings.
|
|
248
|
+
6. `summer_get_diagnostics` clean.
|
|
249
|
+
7. `debug` until clean if not.
|
|
241
250
|
|
|
242
251
|
**Don't skip this even if every mechanic worked individually.** Integration bugs hide between mechanics.
|
|
243
252
|
|
|
@@ -249,6 +258,14 @@ Skill: export-and-ship
|
|
|
249
258
|
|
|
250
259
|
Pre-flight checklist (icon, store banners, build config) before producing release builds for the targets in the brief.
|
|
251
260
|
|
|
261
|
+
Before any version reaches players, run:
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
Skill: release-readiness
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
It checks the file you upload, not the editor: a hosted game's `server.pck` starts its authority scene, `Summer.initialize` passes no hard-coded Game ID, online play is reachable, progress is saved by the server, and players can get back to the title. Then, after publishing, the user joins online with the released version. Report each check as pass, fail, not applicable or not run. Hosted games publish through `multiplayer-publish`; store pages through `publish-your-game`.
|
|
268
|
+
|
|
252
269
|
If the brief said "jam" or "prototype", skip this phase. If the brief said "game I want to release", run it.
|
|
253
270
|
|
|
254
271
|
## Anti-Patterns
|
|
@@ -260,6 +277,8 @@ If the brief said "jam" or "prototype", skip this phase. If the brief said "game
|
|
|
260
277
|
| Stack art direction on top of unfinished mechanics | Art changes are reversible only if the underlying scene structure is stable. Get mechanics done first. |
|
|
261
278
|
| Treat "polish" as required | If the brief says "jam in 2 days", polish is a cut item. Respect the brief. |
|
|
262
279
|
| Run all phases without confirming | The user owns scope. Stop at every checkpoint. |
|
|
280
|
+
| Ship online play behind a setting nobody changes | Hosted join is never exercised, so it breaks without anyone noticing. Reachable by default, or the owner joins online before every release. |
|
|
281
|
+
| Hard-code a Game ID in `Summer.initialize` | The summer.games bundle has no `gameId`; a different ID fails on the hosted server and every online join times out. Pass no ID. |
|
|
263
282
|
| Build a fully featured menu before there's a playable mechanic | Menu work is endless polish; player-loop work is the actual game. Mechanics first. |
|
|
264
283
|
| Fall back to "let me try a few things" mid-pipeline | If you don't know what to do, name the phase and ask the user, don't improvise around the structure. |
|
|
265
284
|
|
|
@@ -84,7 +84,7 @@ the sender's verified Session. Events are fire-and-forget.
|
|
|
84
84
|
|
|
85
85
|
| Word | Meaning |
|
|
86
86
|
|---|---|
|
|
87
|
-
| Game | Your game on Summer; its id is `gameId` in `summer.build.json
|
|
87
|
+
| Game | Your game on Summer; its id is `gameId` in `summer.build.json`. `Summer.initialize()` takes no ID: Summer knows which game is running |
|
|
88
88
|
| World | One running match or shared place, simulated by one authority |
|
|
89
89
|
| WorldDefinition | `world.json`: which scenes run the client and the authority, lifetime, persistence |
|
|
90
90
|
| Queue | How players find a World; declared in `summer.build.json` |
|
|
@@ -109,6 +109,8 @@ Each skill continues the same small game, so their code fits together:
|
|
|
109
109
|
what a passing run must show.
|
|
110
110
|
5. **`multiplayer-publish`**: export the summer.games bundle and upload it as
|
|
111
111
|
a Build.
|
|
112
|
+
6. **`release-readiness`**: before players get a version, prove the server
|
|
113
|
+
starts its authority scene and a real online join works.
|
|
112
114
|
|
|
113
115
|
## Add Summer services
|
|
114
116
|
|
|
@@ -176,7 +176,7 @@ func _ready() -> void:
|
|
|
176
176
|
spawner.entity_spawned.connect(_on_player_spawned)
|
|
177
177
|
spawner.command_received.connect(_on_command)
|
|
178
178
|
world.session_left.connect(_on_session_left)
|
|
179
|
-
var initialized: SummerResult = await Summer.initialize(
|
|
179
|
+
var initialized: SummerResult = await Summer.initialize().get_result_or_completed_signal()
|
|
180
180
|
if not initialized.ok:
|
|
181
181
|
push_error(initialized.message)
|
|
182
182
|
return
|
|
@@ -26,7 +26,7 @@ func _ready() -> void:
|
|
|
26
26
|
spawner.entity_spawned.connect(_on_player_spawned)
|
|
27
27
|
spawner.command_received.connect(_on_command)
|
|
28
28
|
world.session_left.connect(_on_session_left)
|
|
29
|
-
var initialized: SummerResult = await Summer.initialize(
|
|
29
|
+
var initialized: SummerResult = await Summer.initialize().get_result_or_completed_signal()
|
|
30
30
|
if not initialized.ok:
|
|
31
31
|
push_error(initialized.message)
|
|
32
32
|
return
|
|
@@ -141,7 +141,7 @@ func _ready() -> void:
|
|
|
141
141
|
# Connect before joining, or the first values arrive unseen.
|
|
142
142
|
spawner.state_group_created.connect(_on_group_created)
|
|
143
143
|
spawner.entity_spawned.connect(_on_entity_spawned)
|
|
144
|
-
var initialized: SummerResult = await Summer.initialize(
|
|
144
|
+
var initialized: SummerResult = await Summer.initialize().get_result_or_completed_signal()
|
|
145
145
|
if not initialized.ok:
|
|
146
146
|
push_error(initialized.message)
|
|
147
147
|
return
|
|
@@ -440,7 +440,6 @@ occurrence_streams = Array[SummerNetworkOccurrenceStream]([SubResource("Commands
|
|
|
440
440
|
extends RefCounted
|
|
441
441
|
## Shared by the client and the authority: ids, streams and shared rules.
|
|
442
442
|
|
|
443
|
-
const GAME_ID := "game_courtyard"
|
|
444
443
|
const QUEUE := &"courtyard"
|
|
445
444
|
const MOTION := &"courtyard.motion"
|
|
446
445
|
const CMD := &"courtyard.cmd"
|
|
@@ -74,7 +74,14 @@ textures/vram_compression/import_etc2_astc=true
|
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
The `.summer_client` and `.summer_authority` overrides tell each exported pack
|
|
77
|
-
which scene to start.
|
|
77
|
+
which scene to start. Summer starts the hosted server with no scene
|
|
78
|
+
argument, so `run/main_scene.summer_authority` is the only thing that makes
|
|
79
|
+
`server.pck` start the authority. It applies only when the server export
|
|
80
|
+
preset carries the custom feature `summer_authority` (`multiplayer-publish`).
|
|
81
|
+
Without it the server starts the client scene, never reports ready, and every
|
|
82
|
+
online join fails with `world_start_timeout`. Local Play names the scene
|
|
83
|
+
itself, so it works either way. Leave the `[summer] version` line the editor
|
|
84
|
+
writes.
|
|
78
85
|
|
|
79
86
|
## 4. The declarations
|
|
80
87
|
|
|
@@ -116,6 +123,9 @@ starts. Summer rejects unknown fields, so copy the shape exactly.
|
|
|
116
123
|
}
|
|
117
124
|
```
|
|
118
125
|
|
|
126
|
+
- `gameId` is the game's id in Summer Studio, for your publishing tools. The
|
|
127
|
+
summer.games export leaves it out of the bundle, so game code never reads
|
|
128
|
+
it: both sides call `Summer.initialize()` with no ID (sections 7 and 8).
|
|
119
129
|
- `queues[].minPlayers` / `maxPlayers` bound a match. `worldDefinition` names
|
|
120
130
|
the `definition_id` in `world.json`.
|
|
121
131
|
- `server.exportPreset` names the server export preset (`multiplayer-publish`).
|
|
@@ -250,7 +260,6 @@ on both sides:
|
|
|
250
260
|
extends RefCounted
|
|
251
261
|
## Shared by the client and the authority: ids, streams and shared rules.
|
|
252
262
|
|
|
253
|
-
const GAME_ID := "game_courtyard"
|
|
254
263
|
const QUEUE := &"courtyard"
|
|
255
264
|
const CMD := &"courtyard.cmd"
|
|
256
265
|
const MAX_PLAYERS := 8
|
|
@@ -321,7 +330,7 @@ func _ready() -> void:
|
|
|
321
330
|
spawner.entity_spawned.connect(_on_player_spawned)
|
|
322
331
|
spawner.command_received.connect(_on_command)
|
|
323
332
|
world.session_left.connect(_on_session_left)
|
|
324
|
-
var initialized: SummerResult = await Summer.initialize(
|
|
333
|
+
var initialized: SummerResult = await Summer.initialize().get_result_or_completed_signal()
|
|
325
334
|
if not initialized.ok:
|
|
326
335
|
push_error(initialized.message)
|
|
327
336
|
return
|
|
@@ -379,6 +388,11 @@ func _on_session_left(session: SummerSession, reason: String) -> void:
|
|
|
379
388
|
|
|
380
389
|
`authority/main.tscn` is a plain `Node` with that script ([files.md](files.md)).
|
|
381
390
|
|
|
391
|
+
- `Summer.initialize()` takes no Game ID. Summer takes the game's identity
|
|
392
|
+
from where it runs: Local Play, the hosted server or the Summer Games app.
|
|
393
|
+
Never pass a literal, a placeholder or a fallback ID: any ID that differs
|
|
394
|
+
from the hosted one fails with `game_identity_mismatch`, and the authority
|
|
395
|
+
stops before it reports ready.
|
|
382
396
|
- **Never** read who sent a Command from its payload: `request.get_session()`
|
|
383
397
|
is verified. Don't name your own classes `SummerSession`, `SummerWorld` or
|
|
384
398
|
other engine class names.
|
|
@@ -426,7 +440,7 @@ func _ready() -> void:
|
|
|
426
440
|
spawner = Game.build_network(self, PlayerArchetype)
|
|
427
441
|
# Connect signals like state_group_created here, before joining, or values
|
|
428
442
|
# that arrive during the join go unseen (multiplayer-movement adds them).
|
|
429
|
-
var initialized: SummerResult = await Summer.initialize(
|
|
443
|
+
var initialized: SummerResult = await Summer.initialize().get_result_or_completed_signal()
|
|
430
444
|
if not initialized.ok:
|
|
431
445
|
push_error(initialized.message)
|
|
432
446
|
return
|
|
@@ -510,6 +524,8 @@ warning yourself.
|
|
|
510
524
|
| `@rpc`, `MultiplayerSynchronizer`, `ENetMultiplayerPeer` | The Spawner, State groups and Commands | Unverified peers, no late join, no Summer hosting |
|
|
511
525
|
| `if multiplayer.is_server()` in one shared scene | Separate `client/` and `authority/` scenes | The engine starts the right scene on each side |
|
|
512
526
|
| Load an `authority/` file from client or shared code | Move what both sides need into `network/` | The export leaves `authority/` out of the players' pack |
|
|
527
|
+
| `Summer.initialize("game_…")`, or a fallback ID when `gameId` is missing | `Summer.initialize()` | The bundle has no `gameId`; a different ID fails hosted with `game_identity_mismatch` |
|
|
528
|
+
| Set `run/main_scene.summer_authority` and stop there | Also give the server preset the `summer_authority` feature | Without the feature the hosted server starts the client scene |
|
|
513
529
|
| Build the composition differently on each side | One `network/composition.tres`, loaded by both | Different compositions refuse each other |
|
|
514
530
|
| Send a Command right after `join` succeeds | Await `spawner.network_ready` first | The authority does not know the client's network until then |
|
|
515
531
|
| Free a player's things on `session_disconnected` | Free them on `session_left` | A disconnected player keeps their seat and may reconnect |
|