# Game Engine · API 1 · engine major 1

Read `/architecture.html` for ownership and implemented limits. The engine is a
separate service; it never reads an archive database or imports archive modules.
`GET /api/health` and `/api/version` return `{status,version,revision,engine}`.

## Identity and authorization
`GET /api/config` describes the configured identity adapter. Initially this is
Mage Archive's AuthReturn registration and membership endpoint. This explicitly
shares the existing archive account identity, not its data or runtime. Use
AuthReturn.init with the returned `identity.app`. Only archive members may enter.

`POST /api/access/exchange {token:<archive ID token>}` verifies RS256 signature,
issuer, exact audience, expiry, ID-token use and membership over HTTPS. Returns
`{token,expires_at,owner}`: a random engine-only capability valid for 24 hours.
Use `Authorization: Bearer <engine token>` on all other APIs. Keep tokens out of
URLs, reports and logs. The capability is not an archive credential. Owner status
comes from the archive's verified membership response. Existing engine access
continues during archive outages; renewal requires membership verification.
Use a 15-second exchange deadline. AuthReturn/JWKS/membership calls have five-second
timeouts. Ordinary gameplay has no archive or model-provider calls.
Dedicated live tests must use only `mage-archive-test@fairystack.com`; follow the
archive test-login script. Never use the operator's player for automated checks.

## Content and workshop
- `GET /api/releases` → `{releases:[{id,title,description,sha256,published_at,scenes}]}`.
- `GET /api/releases/<pack>@<version>` → `{id,sha256,published_at,pack}`.
- `POST /api/workshop/validate {pack}` → `{status:"completed",sha256,scenes,engine}`.
- Owner `PUT /api/workshop/assets/<sha256>` uploads raw PNG/WAV, at most 24 MB,
  requiring the exact SHA-256. Content scripts and other file types are rejected.
- Owner `POST /api/workshop/drafts {pack}` → `{id,status:"validated",preview}`.
  The ID is the pack digest. Drafts are durable, and validation rejects unknown
  fields, missing references, blocked spawns and unreachable entity interactions.
- Owner `GET /api/workshop/drafts` → `{drafts:[{id,pack,created_at,status}]}`.
- Owner `GET /api/workshop/drafts/<digest>` returns one validated draft.
- Owner `POST /api/workshop/drafts/<digest>/publish {}` →
  `{id,sha256,status:"completed"}`. All assets must already exist. Existing release
  IDs with different bytes return 409. Identical publication is idempotent.
- `GET /api/assets/<sha256>` delivers immutable public authored artwork or audio;
  no background sources or private provenance may be uploaded.

Reads/writes should use eight-second deadlines (asset upload 15 seconds). These
operations are synchronous and return completed/failed, never orphaned jobs.
JSON request limit is 2 MB. The body-reading owner cancels after five seconds.

Content format is schema 1: `{schema:1,engine:1,id,version,title,description,start,
player:{name,asset?},assets:{<id>:{sha256,mime}},scenes:[...]}`. Scene fields:
`{id,title,objective,palette:[#hex,#hex,#hex],spawn:{x,y},tiles:[{x,y,kind,blocked}],
entities:[...],music?}`. Tile kinds: stone/path/moss/rune/bridge. Coordinates are
integers within ±100. Entity fields: `id,name,x,y,kind` (actor/object/exit), optional
asset, dialogue/after arrays of `{speaker,text}`, completeFlag, effects, beats and
exit. `traveler` names the player speaker; others name scene entities.
Effects: `{type:"set-flag",scope:"player"|"room",key,value:boolean}`,
`{type:"grant-item",item}`, `{type:"move-actor",entity,x,y}`.
Beats: `[{line:<zero-based>,effects:[...]}]`. Exit:
`{scene,conditions:[{scope:"player"|"room",key,equals:boolean}]}`.
Effects run only from authored beats or completed dialogue, never client commands.
Completion needs a `completeFlag`; individual actor moves and dialogue checkpoints
are player-owned. Room flags are explicitly shared. No generated JavaScript.
Player/actor image assets use a four-direction, three-frame 192 × 192 PNG atlas.

### Archive adapter
Member-authenticated `GET https://mage-archive.jessald.fairystack.com/api/workshop/pack`
returns `{pack,downloads:[{sha256,url,mime}],source:{adapter,version}}`. Read with a
10-second timeout, validate, retrieve only the explicit same-origin public asset
URLs, and check every digest before uploading. Keep private source provenance in
the archive. `scripts/import-archive.mjs` performs this authoring-time import;
`--publish` uses the workshop upload/draft/publish APIs with an owner capability.
Bootstrap releases are committed under packs/ with content-addressed seed assets;
ordinary edits go through new drafts/releases. Do not alter a published version.

## Sessions and saves
- `GET /api/sessions` → `{sessions:[{id,release,updated_at}]}` for the caller only.
- `POST /api/sessions {release,request_id}` → `{id}`; stable 8–80 character request
  IDs deduplicate creation. A room pins its release permanently.
- `GET /api/sessions/<uuid>` returns the owned saved snapshot, without renewing presence.
- `POST /api/sessions/<uuid>/sync {client,sequence,takeover?,actions:[]}` returns
  `{id,release,now,updated_at,room:{flags},self,peers,members}`. `client` is a page
  ID; sequence is a positive strictly increasing integer. At most one action per
  request. Another page must explicitly send takeover:true. Retired clients cannot
  reclaim control; reload creates a new page identity. Peer presence expires in 12s.
- Action `{id,type:"move",x,y}` requests an authoritative collision-checked route.
- Action `{id,type:"interact",entity}` requires the player beside that entity.
- Actions `{id,type:"advance"}` and `{id,type:"leave-dialogue"}` own dialogue.
  Stable action IDs deduplicate effects across lost responses and process restarts.
  Reusing an action ID with different data fails. All accepted actions autosave.
- Session owner `POST /api/sessions/<uuid>/invite {}` → `{code,expires_at}`.
  Invitation is single-use, expires in one hour, and is not an identity grant.
- Authenticated `POST /api/sessions/join {id,code}` joins that room, up to eight members.
  Invite/join is agent-driven; credentials and invitation codes do not appear in URLs.

Five-second sync calls retry for at most 30 seconds, then terminal timeout and
Reconnect. Browser local storage retains the one unacknowledged action for reload.
A rejected action requires explicit discard; selection and dialogue DOM survive
ordinary polling. A new tab/refresh may require Take control. Shared room flags
and per-player inventory/flags/actor moves/dialogue never overwrite each other.
Saves live under `/home/app_game_engine/data/`, owned by app_game_engine. One writer
only; do not enable rolling generations against this file store.

Failures return `{status:"failed"|"timed_out",step,error}` with 400 malformed JSON,
401 missing/expired access, 403 authorization, 404 absent/unowned resource,
409 conflict/control ownership, 413 size, 422 content/action validation, 429 limit,
or 5xx dependency/storage failure. Invalid transactions leave prior state intact.

## Resource links
- `https://game-engine.jessald.fairystack.com/` — member library and saved sessions.
- `/?pack=<slug>&release=<semver>` — immutable release entry, e.g.
  `/?pack=unwritten-hour&release=1.0.0` or `/?pack=signal-station&release=1.0.0`.
- `/?session=<uuid>` — current session, members only; UUID is not an access grant.
- `/?view=workshop` — release evidence; private drafts require owner authority.
- `/?preview=<64-lowercase-hex-digest>` — owner-only read-only draft scene preview.
- `/architecture.html` — public explanatory ownership/recovery contract, not a
  claim of live health. `/api/version` is the current deployed revision evidence.
- `/agent-guide.md` — current API guide; no credentials or one-use login links.

## Verification
`npm test` covers validation, release pinning, ownership, shared/private state,
idempotency, restart, ordering, conversation recovery, full four-chapter execution,
HTTP boundaries and workshop roles. `node tests/browser.mjs` checks the real local
HTTP stack and browser. `ENGINE_LIVE=1 node tests/live.mjs` uses the dedicated test
identity over HTTPS. Fixture checks and live results are reported separately.

## Usage verification
The registered Grid Glance tracker URL is compatibility ingress to Observatory.
The browser sends POST /api/usage/track and /api/usage/track-session to
https://observatory.aisloppy.com; live verification requires successful responses
from both. See Observatory’s live agent guide for the merged usage contract.

## Identity refresh and rendering
`GET /api/me` returns `{identity,owner,expires_at}`. Identity is an opaque
server-verified account identifier, never an email or an access grant. Browser
startup and AuthReturn callbacks share one initialization owner. Refreshing the
same identity updates the connection credential without appending library cards,
workshop rows or restarting a player controller. Changing identity disposes the
previous view before displaying the new account’s sessions.
`node tests/login-browser.mjs` covers cached/uncached startup, duplicate callbacks,
selected text during refresh, workshop rows, player control and account changes.
