Records and APIs #
This reference covers public data contracts. Start with Architecture for ownership and data flow, or Deployment for OAuth and ingestion configuration. Core checkpoint contracts are in sessions and snapshots.
Published records #
Checked-in Lexicons define net.oscillatory.radcohesion.scenario and
net.oscillatory.radcohesion.run. Shared parsers and mappers live in
src/records/; network and storage input must pass validation before use.
Cell/color tuples in the core map to Lexicon-compatible objects on the wire.
Scenarios contain the name, creation time, identity, explicit polyhedron and
subdivision, topology fingerprint, occupied cells, and colors. Shape is
icosahedron or octahedron, subdivision is an integer from 1 through 10, and
every cell/piece ID must belong to that board. Scenario identity describes
canonical board content, independent of the name and publication location.
A public reference is a DID-based AT URI plus record CID. Handles are discovery and display metadata. Pinned reads fail if that exact version is unavailable; they do not silently substitute a newer layout. Local imports are separate editable copies. Saved challenges embed their full layout beside the reference.
Runs contain one setup: seed, starting level, rules version, RNG version, and
initialBoard. That board requires shape, subdivision, fingerprint, occupied
cells, and colors; empty arrays represent the default starting board. Optional
publishedScenario pins the source URI/CID. Publishing a run does not create a
scenario record. Challenge identity is derived from the setup, not stored in
the record, and excludes seed and publication URI. Ending level derives from
starting level and faces cleared. Continuous timing is part of the run's rules
version; RNG version remains explicit.
Results include start/finish wall-clock times, score, cleared faces, misses,
placedPieces, segmentPlayingTimeMs, totalPlayingTimeMs, startKind, and
endReason (strikes or jammed). Fresh attempts have equal segment and total
playing time. Checkpoint attempts retain cumulative totals while segment time
counts active play since resume. Pauses are excluded. An already-finished
checkpoint does not create another result; restart creates a fresh attempt.
Missing cumulative time or board compatibility metadata is rejected.
The public $type identifies the schema; runs have no second numeric record
version. Incompatible public schemas need a new Lexicon NSID. Local storage
documents and core snapshots have independent versions. There is no automatic
missing-field conversion. All results are unverified summaries: valid JSON,
CID, author, or timestamps do not prove earned scores or uninterrupted play.
Records contain no move history or replay reference. Checkpoint publication
and replay verification remain roadmap work.
Publication and retries #
OAuth requests atproto plus create/delete permissions for the two app
collections; it requests neither game-save nor Bluesky social writes.
An insufficient session grant produces an actionable sign-out/sign-in error.
The browser SDK handles PKCE, DPoP, refresh, and OAuth session storage.
Record creation allows unresolved Lexicons rather than requiring public schema
discovery; local parsing still validates the payload.
Each local run ID maps to a deterministic record key. Before creating it, publication checks the key; retries and concurrent publishers accept an existing record only when the normalized payload matches. A different payload at that key is a conflict. Deletion uses the loaded CID as a swap guard. Publishing scenarios creates independent copies. Deleting public records cannot recall copies others made. Signing in never automatically publishes local data.
Public reads #
Paths are relative to the deployment base. Reads require no login.
| Endpoint | Query and response |
|---|---|
GET /api/scenarios |
Optional trimmed name substring q (≤64 characters), limit (default 24, max 100), offset (max 100,000). Newest active-author scenarios, exact URI/CID, name, author/handle, publication date, occupied count, layout, and pagination total. |
GET /api/players/<DID> |
kind=scenarios|runs, limit (default 24, max 100), offset (max 1,000,000). Player metadata and paginated publications; unknown/inactive players return 404. Local and Guest items are excluded. |
GET /api/leaderboards |
challenge=<identity>, mode=best|all, limit (default 25, max 100), offset (max 100,000). Ranked current results for an exact challenge. |
GET /api/leaderboards/overview |
limit (default 24, max 100), offset (max 100,000), optional challenge=<identity>. Submitted layouts, populated starting levels/identities, run counts, latest activity, and representative pinned references. |
Reads use SQLite, without per-item PDS requests. Standings combine active indexed account runs and unpromoted Guest runs. Best mode selects each source identity's highest score before pagination; All keeps all current results. Equal scores share a rank; stable secondary ordering affects only row display. Account labels use a currently verified handle when available, otherwise DID; blank anonymous names display as Guest.
Overview groups equivalent board layouts independently of publication URI.
runCount counts eligible submissions; bestRunCount applies the same
per-player selection as standings. Names are taken from current indexed
scenarios only when URI, CID, and active author still match. Other custom
layouts are labeled Custom scenario. Looking up a challenge's submitted
snapshot keeps its standings and play setup accessible after source deletion.
Guest submissions and publication linking #
GET /api/leaderboards/identity issues a random one-year HttpOnly,
SameSite=Lax cookie scoped to the API path. SQLite stores its SHA-256 hash,
not the bearer value. POST /api/leaderboards/runs accepts an existing local
FinishedRun as run, plus optional displayName (≤40 characters). It uses
the same run validator and challenge derivation as ingestion and creates no
AT Protocol record.
The same run ID and payload retry idempotently after rate checks; the first display name is retained. Reusing an ID with a different payload conflicts. Names and results are public, and the cookie establishes browser ownership rather than verified player identity.
POST /api/leaderboards/promotions accepts at most 200 unique {runId, uri, cid} references owned by that cookie. It never treats a caller-supplied DID as
proof and creates neither a Guest identity nor a submission. Pending links
persist until indexing confirms an active account's exact URI, CID, and full
canonical run payload. Commits, PDS snapshots, and reactivation reconcile links.
Mismatched, absent, invalid, or inactive publications leave Guest rows visible.
Confirmed rows remain hidden receipts: deleting the publication does not
restore the Guest result, and retries cannot create another Guest entry.
The browser queues unaccepted link requests locally and retries across navigation/reload, with delays increasing from 5 to 60 seconds and on restored connectivity. It does not republish records. Once accepted, the server waits for indexing independently of the browser.
Submission/linking requests check same-origin, cookie ownership, a 128 KiB body limit, a 15-second body deadline, 30 requests/minute per peer IP, and 10/minute per cookie. Bounds include 200 Guest runs per browser, 100,000 total runs, and 200,000 identity rows. Empty identities expire after 30 days; cleanup runs at most hourly after the identity endpoint's peer-IP check. Configure proxy trust as described in Deployment so visitors do not all share the proxy's rate-limit identity.