> ## Documentation Index
> Fetch the complete documentation index at: https://summer-18f03259-codex-native-multiplayer-entry.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent Prompt

> Canonical instructions for an AI coding agent to build a multiplayer Summer game in GDScript with Summer Engine and publish it for review.

<Note>
  This page covers the public Summercraft SDK submission contract. For a supplied native
  multiplayer development build, first read [Native multiplayer development](/knowledge-base/source-status#native-multiplayer-development)
  and the API reference shipped with that build. Public production playback remains not
  live; this does not establish whether your separately configured staging environment
  supports an online test. Do not mix the two integration paths.
</Note>

You are an AI coding agent. Your job is to use **Summer Engine** and the **Summer SDK** to
build a multiplayer **Summer game** in **GDScript**, export a game-only `.pck`, and publish
it to Summercraft for review. Follow this document top to bottom. Where a platform
capability is not live yet, this document says so explicitly.

Any page of these docs is fetchable as raw markdown by appending `.md` to its URL. The full page index is at `https://docs.summerengine.com/llms.txt`.

## 1. What Summercraft is

Summercraft (`https://summercraft.ai`) is the platform where Summer games built with the
Summer SDK are submitted. Use Summer Engine, extend the SDK base class `SummerGame`, export
a `.pck` containing only game files, and submit it through the publish API. A human reviews
every submission before its catalog status can become `published`. There is no auto-publish,
and a published catalog release is not playable yet.

* The publish API lives at `https://summercraft.ai/api/*`.
* Docs live at `https://docs.summerengine.com`.
* The Summer SDK contract defines `Summer`, `SummerGame`, `SummerPlayer`, and
  `SummerCharacter3D`. Local stubs let the project parse, and the export excludes those
  stubs. A future production runtime is intended to provide the real classes, but uploaded
  games cannot launch there today.

**What is live today:** creating a game record, direct-to-storage artifact upload with server-side sha256 verification, the manual review queue, and authenticated release downloads.

**What is not live yet (do not promise these to your operator):** playing uploaded Summer
games in the browser or desktop shell, hosted dedicated game servers for uploaded games,
automatic matchmaking, and the automated runtime sandbox. The examples use a
host-authoritative model, but no production transport is live. `SummerMultiplayerPeer`
(stock `@rpc` / `MultiplayerSynchronizer` compatibility) is a scaffolded contract still
being built.

## 2. Prerequisites — check all three before writing any code

1. **Summer Engine.** If it is missing, install it with
   `npx -y summer-engine@latest install`. Resolve the installed executable and set
   `SUMMER_ENGINE` to its path. On macOS the default is
   `/Applications/Summer.app/Contents/MacOS/Summer`. Run
   `"$SUMMER_ENGINE" --version` before continuing.
2. **A Summercraft access token.** Check for a `SUMMERCRAFT_ACCESS_TOKEN` environment variable or ask your operator for one. Without it every publish call returns `401 sign_in_required`. **Stop and ask before step 6 if you do not have it** — you can build and export everything first. Show your operator this if they do not know how to get one:
   > Sign in at `https://summercraft.ai` (create the account yourself — never let an agent handle your password). Then open your browser dev tools on that tab: Application → Cookies → `summercraft.ai`, and find the cookie whose name ends in `-auth-token` (it may be split into `.0`/`.1` chunks — concatenate their values in order). URL-decode the value; if the result starts with `base64-`, base64-decode the rest. Inside the JSON is an `access_token` field: that string is the Bearer token. It expires after about an hour, which is enough for one publish. There is no dedicated token page yet; this manual step is the current path.
3. **`curl` and a sha256 tool** (`shasum -a 256` or `sha256sum`).

<Note>
  A bare upstream binary is not the default path. On locked CI infrastructure where Summer
  Engine cannot be installed, use the explicitly labeled compatibility escape hatch in
  [local testing](/api-reference/summer-sdk/testing-your-game-locally). It can validate
  file-format behavior only and does not prove Summer SDK runtime compatibility.
</Note>

Auth model, exactly: every publish endpoint accepts `Authorization: Bearer <access token>` (or a signed-in browser session cookie). A present-but-invalid Bearer token is a hard `401` — it never falls back to a cookie. If you get `401` with a token set, the token has expired: ask your operator for a fresh one.

## 3. Know the rules before you build

Your `.pck` must contain **only your game**. The platform enforces, at review and in the browser submit scanner:

* **GDScript only.** No `.gdextension`, `.so`, `.dll`, `.dylib`, `.framework`, `.res` files.
* **No banned APIs** anywhere in your scripts, even in dead or conditional branches. The blocklist includes `OS.execute`, `OS.shell_open`, `OS.create_process`, `FileAccess`, `DirAccess`, `HTTPRequest`, `HTTPClient`, `JavaScriptBridge`, `ClassDB.instantiate`, `Thread.new`, `Expression`, raw sockets (`StreamPeerTCP`, `PacketPeerUDP`, `TCPServer`, `UDPServer`, `WebSocketPeer`), reflection calls (`.call(`, `.callv(`, `.call_deferred(`, `Callable(`), `Engine.get_singleton`, `Marshalls.base64_to_variant`, `ResourceSaver`, `ProjectSettings.load_resource_pack`, and root-tree escapes (`get_node("/root`, `get_tree().root`). Full list with rationale: `https://docs.summerengine.com/api-reference/summer-sdk/banned-apis-reference.md`. Your code targets the scaffolded `Summer.data` and `Summer.economy` contracts instead of owning I/O or networking; production persistence and economy backends are not live yet.
* **No reserved paths** in the pack: `res://sdk/`, `res://core/`, `res://server/`, `res://client/`, `res://official_games/`, `res://bootstrap.*`, `res://project.godot`, `res://summer.cfg`, `res://test_runner.*`. The export preset below keeps them out automatically.
* **`manifest.json` at the pack root** with required keys `id`, `name`, `version`,
  `summer_sdk` (`"1.0"`), `entry_scene`, `min_players`, `max_players`. The entry scene's root
  script must `extends SummerGame` to satisfy submission review and the future runtime
  contract; no production runtime currently loads uploaded packs.
* **Host-authoritative multiplayer is the only mode.** All gameplay decisions run on server paths guarded by `Summer.is_server()`. Clients render synced state via `set_synced`/`get_synced`; they never decide outcomes.

## 4. Build the game — multiplayer-native template

Build multiplayer by default: `min_players: 1` so it works solo, `max_players: 8` so it
works with friends, all authority server-side. The template below is a complete
king-of-the-hill arena. Run every Summer Engine validation step before publishing; adapt
the gameplay, but keep the structure.

Create this exact layout in a fresh directory:

```text theme={null}
hold-the-hill/
├── project.godot        # local dev config — never shipped
├── export_presets.cfg   # export preset — never shipped
├── manifest.json        # shipped at pack root
├── main.gd              # your game, extends SummerGame
├── main.tscn            # entry scene
├── player.tscn          # player scene (SummerCharacter3D template)
└── sdk/                 # LOCAL STUBS, excluded from export
    ├── summer.gd
    ├── summer_game.gd
    ├── summer_player.gd
    └── summer_character_3d.gd
```

The `sdk/` folder holds **local stubs** that mirror the SDK contract so your code parses and
smoke-runs without a Summercraft runtime. The export preset excludes `sdk/*`, so the stubs
never ship. The future runtime is intended to provide real classes at the same paths, but
that production path is not live. Do not add behavior to the stubs — they are compile shims,
not the SDK.

`project.godot`:

```ini theme={null}
; Engine configuration file.
config_version=5

[application]

config/name="Hold the Hill"
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.7")

[autoload]

Summer="*res://sdk/summer.gd"

[rendering]

renderer/rendering_method="mobile"
```

`manifest.json`:

```json theme={null}
{
  "id": "hold-the-hill",
  "name": "Hold the Hill",
  "version": "1.0.0",
  "summer_sdk": "1.0",
  "entry_scene": "main.tscn",
  "player_scene": "player.tscn",
  "min_players": 1,
  "max_players": 8,
  "description": "King-of-the-hill arena. Stand in the gold circle to score. First to 100 points wins.",
  "genre": "action",
  "tags": ["multiplayer", "arena", "king-of-the-hill"]
}
```

Change `id`, `name`, and the descriptive fields for your game. `entry_scene` and `player_scene` are relative to the manifest (pack root).

`main.gd` — the game. Every decision happens on the server path:

```gdscript theme={null}
extends SummerGame

const ROUND_SECONDS := 180.0
const MOVE_SPEED := 7.0
const GRAVITY := 20.0
const WIN_SCORE := 100
const HILL_RADIUS := 4.0
const POINT_INTERVAL := 1.0

var _hill_center := Vector3.ZERO
var _tick := 0.0
var _round_over := false

func _game_init() -> void:
	var spawn_root := get_node_or_null("SpawnPoints")
	if spawn_root:
		for child in spawn_root.get_children():
			if child is Node3D:
				spawn_points.append(child.global_position)
	var hill := get_node_or_null("Hill")
	if hill is Node3D:
		_hill_center = hill.global_position

func _game_start() -> void:
	_round_over = false
	Summer.set_time_limit(ROUND_SECONDS)
	Summer.send_announcement("Hold the hill! First to %d points wins." % WIN_SCORE)

func _game_end() -> void:
	_round_over = true
	var best = null
	var best_score := -1
	for p in get_players():
		var s := _score_of(p)
		if s > best_score:
			best_score = s
			best = p
	if best != null:
		Summer.send_announcement("%s wins with %d points." % [str(best.display_name), best_score])

func _player_joined(player) -> void:
	player.set_synced("score", 0)
	if player.has_method("respawn"):
		player.respawn(get_random_spawn_point())

func _player_left(_player) -> void:
	pass

func _process(delta: float) -> void:
	super._process(delta)
	if not Summer.is_server() or _round_over:
		return
	for p in get_players():
		apply_default_movement(p, delta, MOVE_SPEED, GRAVITY)
	_tick += delta
	if _tick < POINT_INTERVAL:
		return
	_tick = 0.0
	for p in get_players():
		if p is Node3D and p.global_position.distance_to(_hill_center) <= HILL_RADIUS:
			var s := _score_of(p) + 1
			p.set_synced("score", s)
			if s >= WIN_SCORE:
				Summer.send_announcement("%s takes the crown!" % str(p.display_name))
				end_game()
				return

func _score_of(player) -> int:
	var raw = player.get_synced("score")
	return int(raw) if raw != null else 0
```

`main.tscn` — entry scene: floor, hill marker, four spawn points, light, camera:

```text theme={null}
[gd_scene load_steps=6 format=3]

[ext_resource type="Script" path="res://main.gd" id="1"]

[sub_resource type="BoxShape3D" id="floor_shape"]
size = Vector3(40, 1, 40)

[sub_resource type="BoxMesh" id="floor_mesh"]
size = Vector3(40, 1, 40)

[sub_resource type="CylinderMesh" id="hill_mesh"]
top_radius = 4.0
bottom_radius = 4.0
height = 0.2

[sub_resource type="StandardMaterial3D" id="hill_mat"]
albedo_color = Color(0.95, 0.7, 0.2, 1)

[node name="Main" type="Node3D"]
script = ExtResource("1")

[node name="Floor" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0)

[node name="CollisionShape3D" type="CollisionShape3D" parent="Floor"]
shape = SubResource("floor_shape")

[node name="MeshInstance3D" type="MeshInstance3D" parent="Floor"]
mesh = SubResource("floor_mesh")

[node name="Hill" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0)
mesh = SubResource("hill_mesh")
material_override = SubResource("hill_mat")

[node name="SpawnPoints" type="Node3D" parent="."]

[node name="Spawn1" type="Node3D" parent="SpawnPoints"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12, 1, 12)

[node name="Spawn2" type="Node3D" parent="SpawnPoints"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -12, 1, 12)

[node name="Spawn3" type="Node3D" parent="SpawnPoints"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12, 1, -12)

[node name="Spawn4" type="Node3D" parent="SpawnPoints"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -12, 1, -12)

[node name="Players" type="Node3D" parent="."]

[node name="Sun" type="DirectionalLight3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.642788, 0.766044, 0, -0.766044, 0.642788, 0, 10, 0)
shadow_enabled = true

[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.819152, 0.573576, 0, -0.573576, 0.819152, 0, 18, 26)
```

`player.tscn` — references the runtime's 3D character template script. The reference is expected; the file itself must never be in your pack:

```text theme={null}
[gd_scene load_steps=4 format=3]

[ext_resource type="Script" path="res://sdk/summer_character_3d.gd" id="1"]

[sub_resource type="CapsuleShape3D" id="player_shape"]

[sub_resource type="CapsuleMesh" id="player_mesh"]

[node name="Player" type="CharacterBody3D"]
script = ExtResource("1")

[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
shape = SubResource("player_shape")

[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
mesh = SubResource("player_mesh")
```

The four stub files. `sdk/summer.gd`:

```gdscript theme={null}
# LOCAL STUB - excluded from export, never uploaded.
extends Node

func end_game() -> void: pass
func set_time_limit(_seconds: float) -> void: pass
func get_time_remaining() -> float: return 0.0
func get_players() -> Array: return []
func get_player_count() -> int: return 0
func get_max_players() -> int: return 8
func send_announcement(text: String) -> void: print("[Summer stub] announce: ", text)
func is_server() -> bool: return true
func is_client() -> bool: return false
```

`sdk/summer_game.gd`:

```gdscript theme={null}
# LOCAL STUB - excluded from export, never uploaded.
class_name SummerGame
extends Node3D

var spawn_points: Array = []

func _ready() -> void:
	_game_init()
	_game_start()

func _process(_delta: float) -> void:
	pass

func _game_init() -> void: pass
func _game_start() -> void: pass
func _game_end() -> void: pass
func _player_joined(_player) -> void: pass
func _player_left(_player) -> void: pass

func end_game(_from_timer: bool = false) -> void:
	_game_end()

func set_time_limit(_seconds: float) -> void: pass
func get_time_remaining() -> float: return 0.0
func get_players() -> Array: return []
func get_player_count() -> int: return 0
func get_max_players() -> int: return 8

func get_random_spawn_point() -> Vector3:
	if spawn_points.is_empty():
		return Vector3.ZERO
	return spawn_points[randi() % spawn_points.size()]

func apply_default_movement(_player, _delta: float, _move_speed: float = 7.0, _gravity: float = 20.0) -> void:
	pass
```

`sdk/summer_player.gd`:

```gdscript theme={null}
# LOCAL STUB - excluded from export, never uploaded.
class_name SummerPlayer
extends Node

var peer_id: int = 0
var player_id: String = ""
var display_name: String = "Player"
var avatar: Dictionary = {}

var _synced: Dictionary = {}

func set_synced(key: String, value) -> void:
	_synced[key] = value

func get_synced(key: String):
	return _synced.get(key)
```

`sdk/summer_character_3d.gd`:

```gdscript theme={null}
# LOCAL STUB - excluded from export, never uploaded.
class_name SummerCharacter3D
extends CharacterBody3D

var peer_id: int = 0
var player_id: String = ""
var display_name: String = "Player"
var avatar: Dictionary = {}

var health: float = 100.0
var max_health: float = 100.0
var is_alive: bool = true

var _synced: Dictionary = {}

func set_synced(key: String, value) -> void:
	_synced[key] = value

func get_synced(key: String):
	return _synced.get(key)

func respawn(position_in: Vector3 = Vector3.ZERO) -> void:
	global_position = position_in
	health = max_health
	is_alive = true

func damage(amount: float) -> void:
	health = maxf(0.0, health - amount)
	is_alive = health > 0.0

func heal(amount: float) -> void:
	health = minf(max_health, health + amount)

func kill() -> void:
	health = 0.0
	is_alive = false

func teleport(position_in: Vector3) -> void:
	global_position = position_in
```

`export_presets.cfg` — this is what keeps stubs and project config out of the pack:

```ini theme={null}
[preset.0]

name="Summer Game PCK"
platform="Linux"
runnable=true
advanced_options=false
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter="manifest.json"
exclude_filter="sdk/*"
export_path="game.pck"
patches=PackedStringArray()
encryption_include_filters=""
encryption_exclude_filters=""
seed=0
encrypt_pck=false
encrypt_directory=false
script_export_mode=0

[preset.0.options]

custom_template/debug=""
custom_template/release=""
debug/export_console_wrapper=1
binary_format/embed_pck=false
texture_format/s3tc_bptc=true
texture_format/etc2_astc=false
binary_format/architecture="x86_64"
ssh_remote_deploy/enabled=false
```

`script_export_mode=0` ships your GDScript as readable text — required so review can read your source. If you add asset folders, they are included automatically (`all_resources`); add non-resource data files (e.g. extra `.json`) to `include_filter` as a comma-separated list.

## 5. Validate and export

Run all four checks. `$SUMMER_ENGINE` is the installed Summer Engine executable.

```bash theme={null}
# 5a. Import resources (first run only; expect exit 0)
"$SUMMER_ENGINE" --headless --path . --import

# 5b. Smoke run: scene loads, hooks fire, no script errors (expect exit 0,
#     and the announcement line printed by the stub)
"$SUMMER_ENGINE" --headless --path . --quit-after 120

# 5c. Banned-API self-check: must print nothing
grep -rnE "OS\.(execute|shell_open|create_process|create_instance|kill)|FileAccess|DirAccess|HTTPRequest|HTTPClient|JavaScriptBridge|ClassDB\.instantiate|Thread\.new|Mutex\.new|Semaphore\.new|StreamPeerTCP|PacketPeerUDP|TCPServer|UDPServer|WebSocketPeer|\.callv?\(|\.call_deferred\(|Callable\(|Engine\.get_singleton|Expression|Marshalls\.base64_to_variant|ResourceSaver|ProjectSettings\.load_resource_pack|get_node\(\"/root|get_tree\(\)\.root" \
  --include="*.gd" --include="*.tscn" --include="*.tres" . | grep -v "^\./sdk/"

# 5d. Export the pack (no export templates needed for --export-pack)
"$SUMMER_ENGINE" --headless --path . --export-pack "Summer Game PCK" game.pck
```

Notes on the checks: the pass condition for 5c is **empty output** — `grep` exits non-zero when it finds nothing, so do not treat its exit code as a failure. If the smoke run prints `SCRIPT ERROR`, fix it before exporting. Review requires parseable source, but no production platform runtime currently loads or refuses submitted packs.

Then measure the artifact and keep the values for step 6:

```bash theme={null}
ls -l game.pck   # must be >= 1024 bytes and <= 536870912 (512 MiB)
SHA256=$(shasum -a 256 game.pck | cut -d' ' -f1)   # Linux: sha256sum game.pck | cut -d' ' -f1
SIZE_BYTES=$(wc -c < game.pck | tr -d ' ')
echo "$SHA256 $SIZE_BYTES"
```

## 6. Publish through the live API

Base URL `https://summercraft.ai`. All requests JSON unless stated. Every step needs `Authorization: Bearer $SUMMERCRAFT_ACCESS_TOKEN`.

**Budget warning:** each of the three write steps (create game, upload-url, finalize) has its own limit of **1 request per hour per account**. Validation failures (4xx before the limit check) do not spend it, but a successful call does. You get one full publish attempt per hour — do not experiment against these endpoints; get steps 4–5 right first.

### 6a. Create the game record (once per game, not per version)

```bash theme={null}
curl -sS -X POST "https://summercraft.ai/api/games/engine" \
  -H "Authorization: Bearer $SUMMERCRAFT_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Hold the Hill"}'
```

* `201 { "gameId": "<uuid>", "slug": "hold-the-hill", "kind": "engine", "status": "draft", "uploadUrlEndpoint": "/api/games/<uuid>/releases/upload-url" }`. Save it: `GAME_ID=<gameId from the response>`.
* Optional body field `"slug"`: an explicit slug fails with `409 slug_taken` if taken; without it the server derives one and walks around collisions.
* `401 sign_in_required` → token missing/expired. (This endpoint's 401 text mentions only the session cookie — Bearer tokens are accepted here all the same.) `429 rate_limited` → body includes `retryAt`; wait. `409 slug_taken` → pick another slug.
* Skip this step for updates to an existing game — reuse its `gameId`.

### 6b. Request the presigned upload URL

Declare exactly what you measured in step 5. `version` must match `[A-Za-z0-9][A-Za-z0-9._-]{0,31}` with no `..`; `sha256` is the lowercase hex digest; `sizeBytes` the exact byte count.

```bash theme={null}
curl -sS -X POST "https://summercraft.ai/api/games/$GAME_ID/releases/upload-url" \
  -H "Authorization: Bearer $SUMMERCRAFT_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"version\": \"1.0.0\", \"sha256\": \"$SHA256\", \"sizeBytes\": $SIZE_BYTES}"
```

* `200 { "uploadUrl", "method": "PUT", "headers": { "content-type": "application/octet-stream", "if-none-match": "*" }, "objectKey", "maxBytes", "expiresAt", "finalizeUrl" }`.
* The URL expires in 1 hour. **Both returned headers are folded into the URL's signature** — the PUT must send them exactly, nothing extra that conflicts, or storage answers `403 SignatureDoesNotMatch`.
* Errors: `404 game_not_found_or_forbidden` (wrong `gameId` or not your game) · `409 not_a_godot_game` (that `gameId` is a web game) · `409 version_exists` (bump `version`; released versions are immutable) · `413 artifact_too_large` · `429 rate_limited`.

### 6c. PUT the artifact

```bash theme={null}
curl -sS -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/octet-stream" \
  -H "If-None-Match: *" \
  --data-binary @game.pck
```

* Success is an empty `200`. No `Authorization` header here — the URL itself is the credential.
* `412 Precondition Failed` → an object already exists at this key (the key is write-once). If your earlier PUT half-landed, finalize will detect and delete a corrupt one; a clean object under the same (version, sha256) means this exact artifact was already uploaded — just finalize.
* `403 SignatureDoesNotMatch` → your headers differ from the two returned ones.

### 6d. Finalize — the server verifies your bytes

```bash theme={null}
curl -sS -X POST "https://summercraft.ai/api/games/$GAME_ID/releases/finalize" \
  -H "Authorization: Bearer $SUMMERCRAFT_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"version\": \"1.0.0\", \"sha256\": \"$SHA256\", \"changelog\": \"Initial release.\"}"
```

The server HEADs the stored object, streams it through sha256, and compares against your declaration. Nothing you claim is trusted.

* `201 { "releaseId", "gameId", "version", "sha256", "sizeBytes", "pckUrl", "status": "pending_review", "adminNotified", "detail" }` — you are done; the release is queued for human review.
* `409 no_upload_intent` → you skipped 6b, or the declaration differs from what 6b recorded.
* `409 artifact_missing` → the PUT never landed; redo 6c.
* `400 checksum_mismatch` / `400 size_mismatch` → the stored object was corrupt; it has been deleted so the key is free — redo 6c with the same URL if unexpired, then finalize again.
* `429 rate_limited` → finalize has its own hourly budget; wait for `retryAt`.
* `changelog` is optional, max 2000 chars.

## 7. After submission — what to tell your operator

Report exactly this, no more:

1. The game record exists at status `draft`; release `<version>` is `pending_review` in the manual review queue. A human reviews every submission; there is no automatic publish and no guaranteed review time.
2. When approved, the game and active release receive `published` catalog status; if rejected, the reason appears for the creator on `https://summercraft.ai/creator`. Published catalog status does not make the uploaded game playable yet.
3. The owner (and admins) can verify the stored artifact any time — response includes a short-lived (5 min) download URL plus the server-verified `sha256`:
   ```bash theme={null}
   curl -sS "https://summercraft.ai/api/games/$GAME_ID/releases/1.0.0/download-url" \
     -H "Authorization: Bearer $SUMMERCRAFT_ACCESS_TOKEN"
   ```
   Other signed-in users can fetch it only after the game is published.
4. Playing uploaded Summer games in the browser or desktop shell, hosted game servers,
   automatic matchmaking, and the runtime sandbox are not live yet — publishing today
   means entering the catalog pipeline, not instant playability. Do not claim otherwise.
   The canonical status is
   `https://docs.summerengine.com/knowledge-base/source-status.md#platform-capability-status`.

## 8. Updating the game later

Releases are immutable. To ship a fix: bump `manifest.json` `version` **and** use the same new version string in 6b/6d, re-export, and repeat 6b → 6c → 6d with the same `gameId`. Every update goes through review again. Remember the 1/hour budgets.

## 9. If something here does not match reality

Error bodies from this API are self-describing: `{ "error": "<code>", "detail": "<what to do next>" }` — trust the `detail` text. For anything else, fetch the full reference: `https://docs.summerengine.com/api-reference/summer-sdk/exporting-and-uploading-your-game.md` and `https://docs.summerengine.com/api-reference/summer-sdk/submission-guide.md`. Report doc bugs to `founders@summerengine.com`.
