Game records

Game reports use the APGameRecord JSON format, modelled after chess PGN but expressed as JSON so multi-player and complex games (such as Homeworlds) can be represented.

Schema

The authoritative schema is src/schemas/gamerecord.json. TypeScript types are generated from it via npm run json2ts.

See the schema reference for a generated field listing.

Key fields

Header

Field Purpose
header.game.name Game name (must be consistent across services)
header.site.name Site identifier (e.g. Abstract Play)
header.site.gameid Unique game ID on the site (required for rating)
header.date-end When the game ended (used for chronological rating)
header.players Player list with userid, name, and result
header.unrated When true, raters skip this record
header.outcome-type Solo: binary, graded, score, or timed
header.score-direction Solo: higher or lower (timed is always lower)
header.challenge-seed Persisted at archive when the run used seeded RNG (GameRng): the challenge id assigned before play (provided or platform-generated). Required for replay, rematch, and same-seed leaderboards.
players[].grade Solo graded: tier id (e.g. good)
players[].passed Solo binary: pass/fail

Player results

Each player gets a result number. Higher numbers indicate better performance. For two-player games:

Moves

moves is an array of rounds; each round is an array of moves in seating order. Raters use moves.length as the round count. By default, records with fewer than 3 rounds are skipped (minRounds option).

Validation

Use Rater.run() to validate records against the JSON Schema before rating:

import { ELOBasic } from "@abstractplay/recranks";

const rater = new ELOBasic();
const results = rater.run([jsonString1, jsonString2]);

runProcessed() accepts pre-parsed objects and skips schema validation — useful when records are already validated upstream.

CLI tools

Script Purpose
bin/validate.ts Validate game record JSON files (<game-uid> [file.json])
bin/validate.ts --solo-leaderboard <file.json> Dev CSV: best-per-user solo ranks by game + variant + seed
bin/convert.ts Convert legacy formats to APGameRecord
bin/convert-homeworlds.ts Convert Homeworlds-specific records

Edge cases

Raters skip (with warnings) records that have:

Solo records (players.length === 1) are skipped silently — competitive raters only process two-player games.

Solo seeded play — archive contract

When a solo title uses GameRng (initRng / challengeSeed on game state), the completed record must include header.challenge-seed — whether the seed was chosen at create or generated by the platform. gameslib genRecord() writes this field; node-backend archive must not strip it.

Field When present
header.challenge-seed Every archived solo run that consumed seeded RNG
header.outcome-type Solo titles that declare an outcome model (binary, graded, score, timed)

Solo records with challenge-seed can be replay-validated via validateSeededSoloReplay() (see bin/validate.ts). Same-seed leaderboards group on (game.name, variants, challenge-seed).

Set failHard: true to throw instead of warning for self-play and contradictory results.