summer-engine 3.5.1 → 3.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.codex-plugin/plugin.json +2 -1
  4. package/.cursor-plugin/plugin.json +2 -1
  5. package/.factory-plugin/plugin.json +2 -1
  6. package/CHANGELOG.md +15 -0
  7. package/commands/summer.md +1 -0
  8. package/dist/mcp/hosted-mount.js +2 -1
  9. package/dist/mcp/provider-safe-schema.d.ts +10 -0
  10. package/dist/mcp/provider-safe-schema.js +96 -0
  11. package/dist/mcp/server.js +3 -0
  12. package/gemini-extension.json +2 -1
  13. package/library/skills/README.md +3 -2
  14. package/library/skills/export-and-ship/SKILL.md +2 -0
  15. package/library/skills/make-game/SKILL.md +21 -2
  16. package/library/skills/make-game/resource.yaml +1 -0
  17. package/library/skills/multiplayer/SKILL.md +3 -1
  18. package/library/skills/multiplayer/resource.yaml +1 -0
  19. package/library/skills/multiplayer-movement/SKILL.md +1 -1
  20. package/library/skills/multiplayer-movement/files.md +2 -3
  21. package/library/skills/multiplayer-project/SKILL.md +20 -4
  22. package/library/skills/multiplayer-project/files.md +2 -3
  23. package/library/skills/multiplayer-publish/SKILL.md +86 -5
  24. package/library/skills/multiplayer-publish/resource.yaml +1 -0
  25. package/library/skills/multiplayer-state/SKILL.md +1 -1
  26. package/library/skills/multiplayer-state/files.md +2 -3
  27. package/library/skills/multiplayer-testing/SKILL.md +13 -0
  28. package/library/skills/multiplayer-testing/resource.yaml +1 -0
  29. package/library/skills/publish-your-game/SKILL.md +2 -0
  30. package/library/skills/release-readiness/SKILL.md +298 -0
  31. package/library/skills/release-readiness/resource.yaml +41 -0
  32. package/library/skills/summer-parties/SKILL.md +1 -1
  33. package/package.json +1 -1
  34. package/registry/generated/counts.json +2 -2
  35. package/registry/generated/gemini-extension.json +2 -1
  36. package/registry/generated/index.json +63 -11
  37. package/registry/generated/marketplace.claude.json +1 -1
  38. package/registry/generated/plugin.claude.json +2 -1
  39. package/registry/generated/plugin.codex.json +2 -1
  40. package/registry/generated/plugin.cursor.json +2 -1
  41. package/registry/generated/plugin.factory.json +2 -1
  42. package/registry/generated/skills-index.md +3 -2
  43. package/registry/generated/skills-registry.json +14 -0
@@ -11,7 +11,7 @@
11
11
  {
12
12
  "name": "summer",
13
13
  "source": "./",
14
- "version": "3.5.1",
14
+ "version": "3.5.2",
15
15
  "description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a 105-tool MCP bridge to the local desktop app.",
16
16
  "author": {
17
17
  "name": "Summer Engine",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "_generated": "GENERATED from integrations/claude — do not edit; npm run generate:registry",
4
4
  "name": "summer",
5
- "version": "3.5.1",
5
+ "version": "3.5.2",
6
6
  "description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a 105-tool MCP bridge to the local desktop app.",
7
7
  "author": {
8
8
  "name": "Summer Engine",
@@ -96,6 +96,7 @@
96
96
  "./library/skills/psx-retro-rendering/",
97
97
  "./library/skills/publish-your-game/",
98
98
  "./library/skills/realtime-wet-surfaces/",
99
+ "./library/skills/release-readiness/",
99
100
  "./library/skills/remote-deploy/",
100
101
  "./library/skills/retarget/",
101
102
  "./library/skills/running-in-the-cloud/",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_generated": "GENERATED from integrations/codex — do not edit; npm run generate:registry",
3
3
  "name": "summer",
4
- "version": "3.5.1",
4
+ "version": "3.5.2",
5
5
  "description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a 105-tool MCP bridge. Build games by talking.",
6
6
  "author": {
7
7
  "name": "Summer Engine",
@@ -91,6 +91,7 @@
91
91
  "./library/skills/psx-retro-rendering/",
92
92
  "./library/skills/publish-your-game/",
93
93
  "./library/skills/realtime-wet-surfaces/",
94
+ "./library/skills/release-readiness/",
94
95
  "./library/skills/remote-deploy/",
95
96
  "./library/skills/retarget/",
96
97
  "./library/skills/running-in-the-cloud/",
@@ -3,7 +3,7 @@
3
3
  "_generated": "GENERATED from integrations/cursor — do not edit; npm run generate:registry",
4
4
  "name": "summer",
5
5
  "displayName": "Summer",
6
- "version": "3.5.1",
6
+ "version": "3.5.2",
7
7
  "description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a 105-tool MCP bridge to the local desktop app.",
8
8
  "author": {
9
9
  "name": "Summer Engine",
@@ -103,6 +103,7 @@
103
103
  "./library/skills/psx-retro-rendering/",
104
104
  "./library/skills/publish-your-game/",
105
105
  "./library/skills/realtime-wet-surfaces/",
106
+ "./library/skills/release-readiness/",
106
107
  "./library/skills/remote-deploy/",
107
108
  "./library/skills/retarget/",
108
109
  "./library/skills/running-in-the-cloud/",
@@ -2,7 +2,7 @@
2
2
  "_generated": "GENERATED from integrations/factory — do not edit; npm run generate:registry",
3
3
  "name": "summer",
4
4
  "description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a 105-tool MCP bridge to the local desktop app.",
5
- "version": "3.5.1",
5
+ "version": "3.5.2",
6
6
  "author": {
7
7
  "name": "Summer Engine",
8
8
  "email": "founders@summerengine.com",
@@ -88,6 +88,7 @@
88
88
  "./library/skills/psx-retro-rendering/",
89
89
  "./library/skills/publish-your-game/",
90
90
  "./library/skills/realtime-wet-surfaces/",
91
+ "./library/skills/release-readiness/",
91
92
  "./library/skills/remote-deploy/",
92
93
  "./library/skills/retarget/",
93
94
  "./library/skills/running-in-the-cloud/",
package/CHANGELOG.md CHANGED
@@ -4,6 +4,21 @@ All notable changes to summer-engine will be documented here. Following [Keep a
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ Hosted games: agents check that players can actually join before a release.
8
+
9
+ - **`release-readiness` check 7: export with a released Summer, and prove the scripts compile on the runtime players get.** Players' apps run a bundle on the released Summer of the version its export stamp names. A development build that still stamps an older released version can use APIs that release lacks; its scripts then fail to compile on every player's device (`Parse Error: Static function "…" not found`, `Failed to load script`) and the game opens to an empty window, while every upload check passes. The check reads the stamp from `summer-bundle.json`, compares `engineSha` with the release's, and runs `client.pck` on that released Summer with `--headless --verbose --main-pack`, failing on `SCRIPT ERROR`, `Parse Error` or `Failed to load script`. `multiplayer-publish` adds the run as step 4e, and `export-and-ship` says to export summer.games bundles with the released editor only.
10
+
11
+ - **New `release-readiness` skill.** Before a version reaches players, it runs six checks on the summer.games `.zip` you upload: the bundle's `server.pck`, started with no scene, loads the authority scene; a real online join with the released version reaches the game; online play is reachable by default; the server saves online progress through player data; players can get back to the title; and `Summer.initialize` passes no hard-coded Game ID. It reports pass, fail, not applicable or not run for each, and grows by one check after every release that fails in a way none of them caught. The same checklist is in the docs at `/publishing/release-readiness`.
12
+ - **The multiplayer skills call `Summer.initialize()` with no Game ID.** The summer.games bundle leaves `gameId` out, so an ID in code (or a fallback when it is missing) fails on the hosted server with `game_identity_mismatch` and every online join ends in `world_start_timeout`.
13
+ - `multiplayer-publish` explains why the server preset needs the `summer_authority` custom feature, adds a pre-upload step that runs the bundle's `server.pck` with no scene, a real online join after release, and what an empty server log does and doesn't mean. `multiplayer-testing` lists what Local Play can't catch; `make-game` plans online play reachable by default, server-saved progress and a way back to the title, and runs `release-readiness` before shipping.
14
+
15
+ ## [3.5.2] - 2026-10-10
16
+
17
+ Summer works with Kimi Code and other agents whose model checks tool schemas strictly.
18
+
19
+ - Kimi Code no longer fails every prompt with `400 tools.function.parameters is not a valid moonshot flavored json schema ... items must be an object` when Summer is connected. Ten tools (`summer_align_distribute_3d`, `summer_raycast`, `summer_instantiate_scene`, `summer_capture_gameplay`, `summer_test_placement`, `summer_snap_to_surface`, `summer_navigation_probe`, `summer_attach_to_surface`, `summer_repeat_along`, `summer_game_input`) described their `[x, y, z]` and `[x, y]` vectors in a form Moonshot rejects. Every tool now lists its parameters in the JSON Schema subset strict providers accept: a vector is an array of numbers with an exact length, and no schema points into another with `$ref`. The hosted store tools get the same treatment. The tools still reject a vector of the wrong length.
20
+ - If you added Summer tools to Kimi's `disabledTools` to work around this, remove them after updating, then restart Kimi Code.
21
+
7
22
  ## [3.5.1] - 2026-10-10
8
23
 
9
24
  Summer Games now connects from almost every agent setup supports, and large store uploads finish.
@@ -47,6 +47,7 @@ Skills are invoked by their bare slug, such as `brainstorm-game`. There is no `s
47
47
  | add an animation to [character] | `generate-motion` |
48
48
  | wire up a state machine for [character] | `animation-tree` |
49
49
  | the game feels mushy / add juice | `game-feel` |
50
+ | is it ready to release / check before publishing / online joins fail with `world_start_timeout` | `release-readiness` |
50
51
  | ship a trailer | `trailer-shot` |
51
52
  | cutscene for [X] | `cinematic-cutscene` |
52
53
  | let me play it | `play` |
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { connectHostedMcp } from "../core/hosted-mcp.js";
4
4
  import { getStoreAccessToken, OAuthError, resolveMcpResourceUrl } from "../core/oauth.js";
5
5
  import { appendMcpLogEvent } from "../core/mcp-log.js";
6
+ import { providerSafeSchema } from "./provider-safe-schema.js";
6
7
  /**
7
8
  * One Summer Engine MCP. The cloud tools a creator's agent needs to publish
8
9
  * (projects, store page, build upload, submit for the owner's approval, the
@@ -73,7 +74,7 @@ export function prepareHostedMount(server, deps = defaultHostedMountDependencies
73
74
  ...result,
74
75
  tools: result.tools.map((tool) => {
75
76
  const hosted = schemas.get(tool.name);
76
- return hosted ? { ...tool, inputSchema: hosted.inputSchema, ...(hosted.annotations ? { annotations: hosted.annotations } : {}), ...(hosted.title ? { title: hosted.title } : {}) } : tool;
77
+ return hosted ? { ...tool, inputSchema: providerSafeSchema(hosted.inputSchema), ...(hosted.annotations ? { annotations: hosted.annotations } : {}), ...(hosted.title ? { title: hosted.title } : {}) } : tool;
77
78
  }),
78
79
  };
79
80
  });
@@ -0,0 +1,10 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /** String formats OpenAI strict mode accepts; other providers accept no more. */
3
+ export declare const STRICT_PROVIDER_FORMATS: Set<string>;
4
+ export declare function providerSafeSchema<T>(schema: T): T;
5
+ /**
6
+ * Wrap the SDK's tools/list so every tool's inputSchema leaves the server in
7
+ * the provider-safe form. Call after the first registerTool (the SDK installs
8
+ * its tools/list handler then).
9
+ */
10
+ export declare function serveProviderSafeToolSchemas(server: McpServer): void;
@@ -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
+ }
@@ -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 = {}) {
@@ -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.5.1",
4
+ "version": "3.5.2",
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/",
@@ -1,6 +1,6 @@
1
1
  # Summer skills
2
2
 
3
- > 122 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.
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) (2)
21
+ - [deployment](#deployment) (3)
22
22
  - [editor](#editor) (1)
23
23
  - [gameplay](#gameplay) (6)
24
24
  - [level-design](#level-design) (2)
@@ -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, saved progress, no fixed Game ID, a released Summer compiles it. | 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
@@ -298,6 +298,8 @@ 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
+ - 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
303
  - 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
304
  - **Steam:** `steamcmd` + Steam Pipe (`builder_app_xxx.vdf`). Set up depots first in Steamworks.
303
305
  - **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. `summer_get_diagnostics` clean.
240
- 5. `debug` until clean if not.
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
 
@@ -21,6 +21,7 @@ related:
21
21
  skills:
22
22
  - skill/audio-direction
23
23
  - skill/export-and-ship
24
+ - skill/release-readiness
24
25
  - skill/design-mechanic
25
26
  - skill/tune-performance
26
27
  - skill/art-direction
@@ -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` and the argument to `Summer.initialize` |
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
 
@@ -25,6 +25,7 @@ related:
25
25
  - skill/multiplayer-state
26
26
  - skill/multiplayer-testing
27
27
  - skill/multiplayer-publish
28
+ - skill/release-readiness
28
29
  - skill/summer-matchmaking
29
30
  - skill/summer-player-data
30
31
  source: official
@@ -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(Game.GAME_ID).get_result_or_completed_signal()
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(Game.GAME_ID).get_result_or_completed_signal()
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(Game.GAME_ID).get_result_or_completed_signal()
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. Leave the `[summer] version` line the editor writes.
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(Game.GAME_ID).get_result_or_completed_signal()
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(Game.GAME_ID).get_result_or_completed_signal()
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 |
@@ -22,7 +22,7 @@ func _ready() -> void:
22
22
  spawner.entity_spawned.connect(_on_player_spawned)
23
23
  spawner.command_received.connect(_on_command)
24
24
  world.session_left.connect(_on_session_left)
25
- var initialized: SummerResult = await Summer.initialize(Game.GAME_ID).get_result_or_completed_signal()
25
+ var initialized: SummerResult = await Summer.initialize().get_result_or_completed_signal()
26
26
  if not initialized.ok:
27
27
  push_error(initialized.message)
28
28
  return
@@ -127,7 +127,7 @@ func _ready() -> void:
127
127
  spawner = Game.build_network(self, PlayerArchetype)
128
128
  # Connect signals like state_group_created here, before joining, or values
129
129
  # that arrive during the join go unseen (multiplayer-movement adds them).
130
- var initialized: SummerResult = await Summer.initialize(Game.GAME_ID).get_result_or_completed_signal()
130
+ var initialized: SummerResult = await Summer.initialize().get_result_or_completed_signal()
131
131
  if not initialized.ok:
132
132
  push_error(initialized.message)
133
133
  return
@@ -283,7 +283,6 @@ occurrence_streams = Array[SummerNetworkOccurrenceStream]([SubResource("Commands
283
283
  extends RefCounted
284
284
  ## Shared by the client and the authority: ids, streams and shared rules.
285
285
 
286
- const GAME_ID := "game_courtyard"
287
286
  const QUEUE := &"courtyard"
288
287
  const CMD := &"courtyard.cmd"
289
288
  const MAX_PLAYERS := 8