# Package a browser game for CLI-Games (embed protocol V3)

You are helping a game team package a finished, keyboard-capable browser
game for CLI-Games. Preserve the game itself. Do not redesign its controls
around a second input vocabulary and do not ask for a workspace game
identity: protocol V3 uses the game’s ordinary `keydown`/`keyup` code and a
manifest inside the ZIP.

## What to produce

One ZIP with these files at its root:

- `index.html` and every local game file it uses.
- `cli-games.json`, matching schema `cli-games.embed-manifest.v1`.
- `cli-games-embed-v3.js`, downloaded unchanged from
  https://www.cli-games.com/creator/embed-sdk/cli-games-embed-v3.js.

Load `cli-games-embed-v3.js` before the game’s own script:

```html
<script src="./cli-games-embed-v3.js"></script>
<script src="./game.js"></script>
```

The ZIP is served file-for-file. There is no server-side install, build, or
transpile step. Run the game’s normal production build first, then package
that output. One enclosing folder is accepted, but a flat root is clearest.

## Add cli-games.json

Use this exact shape, replacing every sample fact with the game’s facts:

```json
{
  "schema": "cli-games.embed-manifest.v1",
  "protocol": 3,
  "title": "Keyboard Check",
  "command": "keyboard-check",
  "description": "A minimal keyboard-capable browser game prepared for review.",
  "category": "arcade",
  "keys": [
    { "key": "ArrowLeft", "label": "LEFT", "aria": "Move left", "hold": true },
    { "key": "ArrowRight", "label": "RIGHT", "aria": "Move right", "hold": true },
    { "key": " ", "label": "ACT", "aria": "Use the main action" },
    { "key": "Escape", "label": "MENU", "aria": "Return to the menu" }
  ],
  "scoring": {
    "maxScore": 30,
    "minDurationMs": 0,
    "maxDurationMs": 1800000,
    "scoreMayDecrease": false
  }
}
```

Manifest rules:

- `command` is lowercase letters/numbers with single hyphens, at most 63
  characters. It becomes the terminal command and hosted game identity.
- `description` is the 20–500 character public-catalog description.
- `category` is exactly one of `theClassics`, `arcade`, `terminalSkills`,
  or `dungeonCrawlers`.
- `keys` contains 1–16 exact `KeyboardEvent.key` strings. V3.0 requires a
  keyboard-capable game; pointer-only or gamepad-only games do not qualify.
- Give each key a short `label` and useful `aria` description. These create
  terminal commands and phone controls. Set `hold: true` only for keys
  players may hold down.
- Omit `scoring` for an unscored game. If present, all four scoring fields
  are required and the score/game-over calls below become required.
- Do not add fields. Unknown fields fail validation. Creator name, bio, and
  optional website belong in the account-bound workspace, not this file.
- There is no launch-link field. The game page launches the exact `command`
  inside CLI-Games; an optional creator website is separately labelled as
  an outbound credit.

The manifest is the only source for title, command, catalog description,
category, keys, and scoring. Those fields are read-only in the workspace.
Change the file and re-upload when any of them changes.

## Keep the game’s keyboard controls

The supplied `cli-games-embed-v3.js` accepts only host-approved V3
key down/up messages and redispatches synthetic `KeyboardEvent`s. Existing
`keydown` and `keyup` listeners therefore remain the game’s input path:

```js
const embed = window.CliGamesEmbed

embed.onStart(({ runId, seed }) => {
  resetGame({ runId, seed })
})

window.addEventListener('keydown', (event) => {
  if (event.key === 'ArrowLeft') moveLeft()
  if (event.key === 'ArrowRight') moveRight()
  if (event.key === ' ') useMainAction()
  if (event.key === 'Escape') finishRun()
})

window.addEventListener('keyup', (event) => {
  releaseHeldKey(event.key)
})

function showPlayableMenu() {
  renderMenu()
  // Required after every completed run or return to the playable menu.
  embed.ready()
}
```

Every manifest key must appear in shipped game code and do what its label
says. Do not add a custom postMessage input bridge.

The shim exposes `window.CliGamesEmbed`:

- `onStart(listener)` supplies a host-owned `runId` and unsigned 32-bit
  `seed`. Reset the run there. Use that seed for gameplay/scoring randomness
  instead of `Math.random()` or wall-clock time.
- The shim automatically sends the initial `ready` at DOM ready. After the
  host starts a run, call `ready()` every time the player finishes and the
  game returns to a playable menu. This is what makes the next run visible.
- Host traffic is namespaced as `cli-games-embed-host`; frame
  reporting is namespaced as `cli-games-embed`. The unchanged shim
  owns those envelopes and derives game identity from its served path.

## Optional progress and scoring

An unscored game may skip all reporting calls. If `scoring` is present in
`cli-games.json`, call `score` as the score changes and `gameOver` exactly
once when the run ends:

```js
// Optional progress snapshot. Send after start and meaningful changes.
embed.state({ status: 'playing', level: 2, levelCount: 6, moves: 19, score: 20 })

// Required when cli-games.json includes scoring.
embed.score(20)

// Send once when a scored run ends, then return to the menu and call ready().
embed.gameOver(30)
showPlayableMenu()
```

The optional `state` object has exactly five fields: `status`, `level`,
`levelCount`, `moves`, `score`. Status is `ready`, `playing`, `won`, or
`lost`. Numbers are non-negative integers; level starts at 1, never exceeds
levelCount, and score must remain within the manifest maximum. The shim
increments the required state-message sequence for each run.

Embedded scores are creator-reported and live in an isolated UGC trust class.
Packaging does not grant verified-replay status or access to competitive
economy features.

## Sandbox and upload limits

- The game runs in an opaque, cookie-free iframe on a separate origin.
- Network requests, analytics, shared storage, navigation, forms, popups,
  downloads, and access to the CLI-Games page are blocked. Bundle every
  script, image, font, audio file, worker, and WASM module locally.
- Local Canvas, WebGL, WebAssembly, Web Workers, blob URLs, and data assets
  are supported within the sandbox.
- Served file extensions: .html .js .mjs .css .json .md .txt .png .jpg .jpeg .gif .webp .svg .mp3 .ogg .wav .wasm .woff .woff2 .ico.
- ZIP at most 50 MB; unpacked at most 100 MB;
  at most 20 MB per file; at most 2,000 files.
- Symlinks, unsafe paths, external URLs, `<base>` tags, missing local assets,
  and undeclared archive tricks are rejected.
- All content must be suitable for children old enough to use a shell, and
  the creator must have permission to submit every included asset.

## What intake checks

Upload parses the strict manifest, resolves every local asset, checks the
sandbox limits, confirms the host bridge is present, and looks for the
manifest keys in
shipped game code. No input listener or mostly missing keys blocks the ZIP;
an isolated missing key is named as a warning for human review. When scoring
is declared, missing score/game-over reporting blocks the ZIP.

## Release meaning

The packaging guide is public, but upload workspaces are invitation-only and
require a verified CLI-Games account. The first upload remains private. The
creator plays the exact staged ZIP,
reviews the read-only manifest facts and creator credit, accepts the current
terms, and submits one fingerprint. A V3 submission authorizes CLI-Games to
publish that unchanged fingerprint if independent internal review approves
it. There is no later creator publish step. A changed ZIP or manifest creates
a new candidate that must be submitted and reviewed again. The creator may
withdraw the game from public serving afterward.

CLI-Games controls sandboxing, host controls, catalog registration, UGC
labels, public status, and review. Acceptance is not guaranteed, and this
pilot promises no traffic, revenue, permanent hosting, or replay-grade score
verification.

## Before returning the ZIP

1. Open `index.html` with the network disabled and play through a full run.
2. Resize it across desktop, portrait phone, and landscape phone shapes.
3. Confirm every manifest key works through ordinary `keydown`/`keyup` code.
4. Confirm the shim loads before the game and was not edited.
5. Confirm each completed run returns to a playable menu and calls `ready()`.
6. Validate every state and score against the manifest limits.
7. Zip the folder contents so `index.html`, `cli-games.json`, and the shim
   are visible immediately at the archive root.

Return the ZIP plus a short list of any placeholders or unresolved rights.
The human guide and downloads are at
https://www.cli-games.com/creator/embed-sdk.
