summer-engine 3.5.2 → 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.
Files changed (40) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/.codex-plugin/plugin.json +3 -3
  4. package/.cursor-plugin/plugin.json +2 -2
  5. package/.factory-plugin/plugin.json +2 -2
  6. package/CHANGELOG.md +8 -2
  7. package/dist/core/capabilities/sfx-choices.d.ts +91 -0
  8. package/dist/core/capabilities/sfx-choices.js +291 -0
  9. package/dist/core/capabilities/tool-dispatch.js +35 -0
  10. package/dist/mcp/tools/generate-tools.js +70 -3
  11. package/gemini-extension.json +1 -1
  12. package/library/references/mcp-tools-reference/mcp-tools-reference.md +5 -3
  13. package/library/references/mcp-tools-reference/resource.yaml +1 -1
  14. package/library/skills/README.md +2 -2
  15. package/library/skills/ambient-bed/SKILL.md +3 -1
  16. package/library/skills/ambient-bed/resource.yaml +1 -1
  17. package/library/skills/audio-direction/SKILL.md +5 -5
  18. package/library/skills/audio-direction/resource.yaml +1 -1
  19. package/library/skills/export-and-ship/SKILL.md +2 -0
  20. package/library/skills/multiplayer-publish/SKILL.md +8 -4
  21. package/library/skills/release-readiness/SKILL.md +141 -2
  22. package/library/skills/release-readiness/resource.yaml +4 -1
  23. package/library/skills/sound-effect/SKILL.md +83 -158
  24. package/library/skills/sound-effect/resource.yaml +8 -2
  25. package/library/skills/ui-basics/SKILL.md +1 -0
  26. package/library/tools/generate-audio/resource.yaml +6 -2
  27. package/library/tools/generate-sfx-choices/resource.yaml +69 -0
  28. package/library/tools/get-choices/resource.yaml +50 -0
  29. package/package.json +1 -1
  30. package/registry/generated/aliases.json +2 -0
  31. package/registry/generated/counts.json +2 -2
  32. package/registry/generated/gemini-extension.json +1 -1
  33. package/registry/generated/index.json +104 -17
  34. package/registry/generated/marketplace.claude.json +2 -2
  35. package/registry/generated/plugin.claude.json +2 -2
  36. package/registry/generated/plugin.codex.json +3 -3
  37. package/registry/generated/plugin.cursor.json +2 -2
  38. package/registry/generated/plugin.factory.json +2 -2
  39. package/registry/generated/skills-index.md +2 -2
  40. package/registry/generated/skills-registry.json +2 -2
@@ -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 ID (e.g. 'eleven_multilingual_v2')"),
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.
@@ -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.2",
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": {
@@ -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 (105 tools)
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 (5 — metered)
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.0
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
@@ -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) ★ | Generate short SFX one-shots — footsteps, weapon swings, UI clicks, hit impacts — wired as AudioStreamPlayer/2D/3D that auto-frees on finished. | assets |
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,7 +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
+ | [*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 |
151
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 |
152
152
 
153
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.2
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 UI click, 80ms, no reverb, neutral pitch", durationSeconds=0.5)
210
- summer_generate_audio(capability="sound_effects", text="warm major-third coin pickup, 300ms, light reverb", durationSeconds=0.5)
211
- summer_generate_audio(capability="sound_effects", text="punchy low-mid player damage hit, slight detune, 200ms, no tail", durationSeconds=0.5)
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.6
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
@@ -299,6 +299,8 @@ Per platform, point the user at the correct upload path:
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
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.
302
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`.
303
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.
304
306
  - **Steam:** `steamcmd` + Steam Pipe (`builder_app_xxx.vdf`). Set up depots first in Steamworks.
@@ -141,7 +141,9 @@ scene the pack will start; step 4d checks that. The `Exported by Summer`
141
141
  line must name a released Summer: export with the released editor from the
142
142
  download page, never a development build, and export again after you update
143
143
  Summer. Players' apps run the packs on the released Summer of that version;
144
- step 4e checks it. Every
144
+ step 4e checks it. The Summer Games apps for iPhone and Android start only
145
+ exports on their own MAJOR.MINOR, so export with the current Summer release
146
+ (`release-readiness` check 9). Every
145
147
  result message and every export error, with its fix, is in
146
148
  [export-errors.md](export-errors.md).
147
149
 
@@ -265,8 +267,10 @@ of `.zip`. That writes `client.pck` alone.
265
267
  Right after the Build is released, open the game in a Summer Games app,
266
268
  join online and play until you are in the game, before players hear about
267
269
  the update. A wrong Game ID only fails under a real hosted launch, so
268
- nothing before this step catches it. An agent can't do this; ask the user
269
- and record the result. The whole list of checks before a release is
270
+ nothing before this step catches it. Keep playing, signed in, for at least 3
271
+ minutes: the game must not close and the player must stay signed in
272
+ (`release-readiness` check 10). An agent can't do this; ask the user and
273
+ record the result. The whole list of checks before a release is
270
274
  `release-readiness`, and the creator docs have it at
271
275
  https://docs.summerengine.com/publishing/release-readiness.
272
276
 
@@ -293,7 +297,7 @@ Use step 4d to see which scene runs.
293
297
  - [ ] The bundle's `server.pck`, run with no scene, loads the authority scene first.
294
298
  - [ ] `Exported by Summer` names a released Summer, and the bundle's `client.pck` run on that release prints no `SCRIPT ERROR`, `Parse Error` or `Failed to load script`.
295
299
  - [ ] Every `Summer.initialize` call passes no Game ID.
296
- - [ ] After release: a real online join reaches the game (`release-readiness`).
300
+ - [ ] After release: a real online join reaches the game, and a signed-in player keeps playing for 3 minutes (`release-readiness` checks 2 and 10).
297
301
  - [ ] Local Play passed with the same project.
298
302
 
299
303
  ## Common mistakes
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: release-readiness
3
- description: "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."
3
+ description: "Before releasing a Summer game version, prove players can play it: server scene, online join, progress, Game ID, released Summer, packed files, app version."
4
4
  license: MIT
5
5
  compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
6
  category: deployment
@@ -78,7 +78,8 @@ failures:
78
78
 
79
79
  Checks 1 and 2 apply to every game with a server (`summer.build.json` names
80
80
  a `server.exportPreset`). Checks 3 and 4 apply to every game with online
81
- play. Checks 5, 6 and 7 apply to every game.
81
+ play. Checks 5 to 8 and 10 apply to every game. Check 9 applies to every
82
+ bundle for iPhone or Android.
82
83
 
83
84
  ### 1. The server pack starts the authority scene
84
85
 
@@ -242,6 +243,120 @@ https://summerengine.com/download (or, with their OK, run
242
243
  `summer install --yes`). Export again with it, and rerun this check on the
243
244
  new `.zip` before you upload it.
244
245
 
246
+ ### 8. Every file the game reads at run time is in the pack
247
+
248
+ **Why.** The export packs scenes, scripts and imported assets. A file the
249
+ game opens itself by path (`FileAccess`, `DirAccess`, `load_from_file`,
250
+ `FontFile.load_dynamic_font`, `ConfigFile.load`) that has no `.import`
251
+ beside it, such as a `.bin`, `.json` or `.txt`, is a non-resource file. It
252
+ goes into the pack only when the preset's `include_filter` (**Resources >
253
+ Filters to export non-resource files/folders**) matches it and
254
+ `exclude_filter` doesn't. Each preset has its own filters: the summer.games
255
+ preset doesn't inherit one set on another preset. A file left out is not an
256
+ export error; on players' devices `FileAccess` just returns nothing and the
257
+ game's fallback runs. Fonts show it first: in the Summer Games app on iPhone
258
+ a `SystemFont` has no system fonts to use, so every glyph draws as a box,
259
+ while the editor, Local Play and desktop find a system font and look right.
260
+ A real release shipped this way: its font file was missing from the
261
+ uploaded pack, and every letter on iPhone was a box.
262
+
263
+ **Check it.**
264
+
265
+ 1. Find every read by path:
266
+ `grep -rn -E 'FileAccess\.|DirAccess\.|load_from_file|load_dynamic_font|ConfigFile' --include=*.gd .`
267
+ Note each `res://` file or folder they read; skip `user://` (saves). A
268
+ file is raw when no `<file>.import` sits beside it.
269
+ 2. Check the filters:
270
+ `grep -n -E '^name=|^include_filter=|^exclude_filter=' export_presets.cfg`.
271
+ The summer.games preset's `include_filter` must match each raw file the
272
+ client reads, and its `exclude_filter` must not. Do the same on the
273
+ server preset for files the authority reads.
274
+ 3. List the pack. Save this script **outside** the project, for example as
275
+ `~/list_pack.gd`:
276
+
277
+ ```gdscript
278
+ extends SceneTree
279
+ # Prints one "PACK res://..." line per file in the pack mounted with --main-pack.
280
+ func _init() -> void:
281
+ _walk("res://")
282
+ quit()
283
+
284
+ func _walk(dir: String) -> void:
285
+ var d := DirAccess.open(dir)
286
+ if d == null:
287
+ return
288
+ d.include_hidden = true
289
+ for f in d.get_files():
290
+ print("PACK ", dir.path_join(f))
291
+ for s in d.get_directories():
292
+ _walk(dir.path_join(s))
293
+ ```
294
+
295
+ ```sh
296
+ perl -e 'alarm shift; exec @ARGV' 60 <summer> --headless --main-pack build/bundle/client.pck --script ~/list_pack.gd > build/client-pack.txt 2>&1
297
+ grep -Fx 'PACK res://fonts/MyFont.bin' build/client-pack.txt # once per raw file from step 1
298
+ ```
299
+
300
+ Every raw file must print its line. No line means the file is not in the
301
+ pack, whatever the preset says. List `server.pck` the same way for files
302
+ the authority reads.
303
+ 4. `grep -rn SystemFont --include=*.gd --include=*.tscn --include=*.tres .`
304
+ must find nothing on a path phone players reach, fallbacks included.
305
+ 5. After publishing, open the game on an iPhone in the Summer Games app and
306
+ look at the title and one menu: letters, not boxes. An agent can't do
307
+ this step; report it as **not run** and ask the user.
308
+
309
+ **Fix.** Add each missing file to the summer.games preset's non-resource
310
+ filter (for example `fonts/*.bin`), or import it as a resource instead.
311
+ Ship the font in the project (an imported `.ttf`/`.otf`, or raw bytes that
312
+ step 3 proves are packed) and fall back to `ThemeDB.fallback_font`, Summer's
313
+ built-in font, never to `SystemFont`. Export again and rerun steps 3 and 5.
314
+
315
+ ### 9. Export with the Summer version line the Summer Games apps run
316
+
317
+ **Why.** The Summer Games apps for iPhone and Android carry Summer inside
318
+ the app and download none. An app starts a game only when the export's
319
+ `summerVersion` has the app's MAJOR.MINOR (for example `0.7`) and a patch no
320
+ newer than the app's. A `0.6.x` export doesn't start in an app that carries
321
+ `0.7`: the player sees **The game could not start**. The desktop app fetches
322
+ the right Summer for each game, so the game can run on Mac and Windows and
323
+ still fail on every phone. The upload checks don't catch it.
324
+
325
+ **Check it.**
326
+
327
+ 1. Read the stamp and platforms:
328
+ `python3 -c 'import json; b=json.load(open("build/bundle/summer-bundle.json")); print(b["export"]["summerVersion"], b["client"]["targetPlatforms"])'`
329
+ 2. If `targetPlatforms` lists `ios` or `android`, the first two numbers of
330
+ `summerVersion` must match the current Summer release, shown on
331
+ https://summerengine.com/download and in Help > About after updating.
332
+ 3. After publishing, open the game on a phone in the Summer Games app, as in
333
+ check 2. "The game could not start" there, while desktop works, is this
334
+ check failing.
335
+
336
+ **Fix.** Ask the user to update Summer to the current release (or, with
337
+ their OK, run `summer install --yes`). Export again, upload it as a new
338
+ version and promote it. Do this again after every Summer minor update
339
+ (0.6 to 0.7), because the apps move to the new line.
340
+
341
+ ### 10. A signed-in player keeps playing for three minutes
342
+
343
+ **Why.** While the game runs, it calls Summer services for the signed-in
344
+ player: analytics, player data, the store. A short test can end before the
345
+ first of those calls. A real release closed for every player about a minute
346
+ in, with "That link can't be opened", and left them signed out: a Summer
347
+ service refused the player's sign-in, and the app ended play. The fault was
348
+ Summer's, not the game's, and no check on the game's files could see it.
349
+
350
+ **Check it.** After publishing, sign in to a Summer Games app on a phone,
351
+ open the released version and play it for at least 3 minutes, through at
352
+ least one analytics event. The game must not close, and the player must
353
+ still be signed in afterwards. Do it in each Summer Games app the game
354
+ targets. An agent can't do this step; report it as **not run** and ask the
355
+ user, then record what they saw.
356
+
357
+ **Fix.** Nothing in the game. Ask the user to report it to Summer with the
358
+ game's name, the app and the time it happened.
359
+
245
360
  ## Troubleshooting
246
361
 
247
362
  ### Every online join fails with `world_start_timeout`, but Local Play works
@@ -263,6 +378,27 @@ export, and the scripts use something the released version doesn't have.
263
378
  Re-export with the current Summer release, upload the new version and
264
379
  promote it.
265
380
 
381
+ ### Text shows as boxes on iPhone
382
+
383
+ Every letter draws as an empty box in the Summer Games app on iPhone, while
384
+ the game looks right on desktop. The game most likely reads a font file that
385
+ isn't in the pack and falls back to `SystemFont`. Run check 8.
386
+
387
+ ### "The game could not start" on iPhone or Android
388
+
389
+ The game works on desktop, but the phone app says "The game could not
390
+ start". The `.zip` most likely comes from a Summer version line the app
391
+ doesn't carry. Run check 9, then export again with the current Summer
392
+ release, upload and promote.
393
+
394
+ ### The game closes after about a minute and the player is signed out
395
+
396
+ The Summer Games app closes the game 20 to 60 seconds into play with "Play
397
+ paused" or "That link can't be opened", and the player finds they are signed
398
+ out. A Summer service refused the player's sign-in while the game played.
399
+ This is Summer's fault, not the game's: see check 10, and ask the user to
400
+ report it with the time it happened.
401
+
266
402
  ### The game server's log is empty
267
403
 
268
404
  An empty server log is not proof that the server hung or never started. A
@@ -295,4 +431,7 @@ here would have caught:
295
431
  - [ ] 5. Players can get back to the title from inside a session, online and offline.
296
432
  - [ ] 6. Every `Summer.initialize` passes no ID, or an ID the game got from Summer.
297
433
  - [ ] 7. The export stamp names a released Summer and its `engineSha`, and `client.pck` run on that released Summer prints no `SCRIPT ERROR`, `Parse Error` or `Failed to load script`.
434
+ - [ ] 8. Every file the game reads by path has a `PACK res://…` line in the listing of `client.pck` (or `server.pck`), no phone text uses `SystemFont`, and text shows as letters on an iPhone (or is reported as not run).
435
+ - [ ] 9. For an iPhone or Android bundle, `summerVersion` has the current Summer release's MAJOR.MINOR.
436
+ - [ ] 10. A signed-in player played the released version for 3 minutes in each Summer Games app, through an analytics event, without the game closing or being signed out (or it is reported as not run).
298
437
  - [ ] One line per check reported to the user before anything is changed.
@@ -1,13 +1,16 @@
1
1
  id: skill/release-readiness
2
2
  kind: skill
3
3
  version: 1.0.0
4
- summary: "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."
4
+ summary: "Before releasing a Summer game version, prove players can play it: server scene, online join, progress, Game ID, released Summer, packed files, app version."
5
5
  use_when:
6
6
  - "before uploading, promoting or releasing a new version to players: is it ready to release"
7
7
  - "every online join fails with world_start_timeout, or the hosted server's log is empty, while Local Play works"
8
8
  - "Summer.initialize fails with game_identity_mismatch: the initialized Game ID does not match hosted runtime context"
9
9
  - "checking that a hosted game's server.pck starts the authority scene and not the title screen"
10
10
  - "players get a blank or black screen when they open the released game, but it runs in the editor: client scripts fail with Parse Error or Failed to load script"
11
+ - "text shows as boxes on iPhone but looks fine on desktop: a font or other file read with FileAccess is missing from the pack, or SystemFont is used"
12
+ - "the Summer Games phone app says the game could not start, but it works on desktop: the game's Summer version line differs from the app's"
13
+ - "the game closes about a minute into play with Play paused or That link can't be opened, and the player is signed out"
11
14
  - "a game release failed for players and the check that would have caught it needs adding"
12
15
  do_not_use_when:
13
16
  - "only producing a local export for a jam or prototype nobody else plays — skill/export-and-ship"