From d27aafc532903fc530414243b25ed8d9abf2bba6 Mon Sep 17 00:00:00 2001 From: jedarden Date: Tue, 26 May 2026 13:01:57 -0400 Subject: [PATCH] feat(web): add /docs/replay-format and /docs/data documentation pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements plan §15.2 public match data documentation: - /docs/replay-format: Complete replay schema v1 specification with field-by-field documentation, event types, win probability and critical moments format, example replays, and changelog - /docs/data: Comprehensive guide to all public data endpoints including leaderboard, bots, matches, series, seasons, playlists, meta, evolution, blog posts, and maps with update frequencies and curl examples - Added lazy loaders and routes for both pages in app.ts Closes: bf-ckcj Co-Authored-By: Claude Opus 4.7 --- web/src/app.ts | 5 + web/src/pages/docs-data.ts | 213 +++++++++++++++++++++++++++ web/src/pages/docs-replay-format.ts | 219 ++++++++++++++++++++++++++++ 3 files changed, 437 insertions(+) create mode 100644 web/src/pages/docs-data.ts create mode 100644 web/src/pages/docs-replay-format.ts diff --git a/web/src/app.ts b/web/src/app.ts index 8492667..3b9572f 100644 --- a/web/src/app.ts +++ b/web/src/app.ts @@ -69,6 +69,9 @@ const loadSandboxPage = () => import('./pages/sandbox').then(m => m.renderSandbo const loadRegisterPage = () => import('./pages/register').then(m => m.renderRegisterPage); const loadCompeteHubPage = () => import('./pages/compete-hub').then(m => m.renderCompeteHubPage); const loadDocsPage = () => import('./pages/docs').then(m => m.renderDocsPage); +// Public data documentation pages (§15.2) +const loadDocsReplayFormatPage = () => import('./pages/docs-replay-format').then(m => m.renderDocsReplayFormatPage); +const loadDocsDataPage = () => import('./pages/docs-data').then(m => m.renderDocsDataPage); // Bot-related pages const loadBotProfilePage = () => import('./pages/bot-profile').then(m => m.renderBotProfilePage); @@ -279,6 +282,8 @@ router .on('/bots', redirect('/leaderboard')) .on('/docs/api', lazyRoute(loadDocsApiPage)) .on('/docs', redirect('/compete/docs')) + .on('/docs/replay-format', lazyRoute(loadDocsReplayFormatPage)) + .on('/docs/data', lazyRoute(loadDocsDataPage)) .on('/clip-maker', redirect('/watch/replays')) .on('/rivalries', lazyRoute(loadRivalriesPage)) .on('/feedback', lazyRoute(loadFeedbackPage)) diff --git a/web/src/pages/docs-data.ts b/web/src/pages/docs-data.ts new file mode 100644 index 0000000..4869c59 --- /dev/null +++ b/web/src/pages/docs-data.ts @@ -0,0 +1,213 @@ +// Public Match Data Documentation Page +// §15.2: Public match data documentation - all available data paths + +export function renderDocsDataPage(): void { + const app = document.getElementById('app'); + if (!app) return; + + app.innerHTML = ` +
+

Public Match Data

+ +
+
+

Overview

+

All platform data is available as static JSON files served from Cloudflare Pages (indexes) and Cloudflare R2 (replays, metadata). No authentication, no API keys, no rate limiting.

+

Base URLs:

+
const PAGES = ''                    // Same origin (Cloudflare Pages)
+const R2    = 'https://r2.aicodebattle.com'     // Warm replay cache
+const B2    = 'https://b2.aicodebattle.com'     // Cold archive
+
+ +
+

Index Files (Cloudflare Pages)

+

Updated every ~90 minutes by the index builder deployment.

+ +

Leaderboard

+
GET /data/leaderboard.json
+

Current rankings with ratings, win rates, and match counts.

+
curl https://aicodebattle.com/data/leaderboard.json | jq
+ +

Bot Profiles

+
GET /data/bots/index.json              # Bot directory
+GET /data/bots/{bot_id}.json          # Individual bot profile
+

Rating history, recent matches, win/loss breakdown.

+ +

Matches

+
GET /data/matches/index.json           # Last 1000 matches
+GET /data/matches/index-{page}.json  # Older pages
+

Paginated match list with participants, scores, and links to replays.

+ +

Series

+
GET /data/series/index.json           # All series
+GET /data/series/{series_id}.json     # Individual series
+

Best-of-N series between two bots with game-by-game results.

+ +

Seasons

+
GET /data/seasons/index.json          # All seasons
+GET /data/seasons/{season_id}.json    # Season archive
+

Seasonal competition data with champions and final standings.

+ +

Playlists

+
GET /data/playlists/{slug}.json        # Auto-curated collections
+

Available playlists:

+
    +
  • closest-finishes - Matches decided by 1 point
  • +
  • biggest-upsets - Lower-rated bot wins
  • +
  • best-comebacks - Recovered from low win probability
  • +
  • rivalry-classics - Matches between rivals
  • +
  • season-highlights - Best matches of current season
  • +
+ +

Meta

+
GET /data/meta/archetypes.json        # Strategy archetype distribution
+GET /data/meta/rivalries.json         # Detected rivalries
+ +

Evolution

+
GET /data/evolution/lineage.json       # Bot ancestry graph
+GET /data/evolution/meta.json          # Current meta snapshot
+ +

Blog

+
GET /data/blog/index.json             # All posts
+GET /data/blog/posts/{slug}.json      # Individual post
+ +

Maps

+
GET /maps/index.json                  # Map directory
+GET /maps/{map_id}.json               # Individual map definition
+
+ +
+

Replay Data (Cloudflare R2)

+

Uploaded in real-time by match workers. R2 is a warm cache for recent replays; B2 is the permanent cold archive.

+ +

Replay Files

+
GET /replays/{match_id}.json.gz
+

Gzipped replay JSON. Browser handles decompression automatically.

+
# Fetch from R2 (warm cache)
+curl https://r2.aicodebattle.com/replays/m_7f3a9b2c.json.gz
+
+# Fallback to B2 (cold archive)
+curl https://b2.aicodebattle.com/replays/m_7f3a9b2c.json.gz
+ +

Match Metadata

+
GET /matches/{match_id}.json
+

Per-match metadata including win probability curve and critical moments.

+ +

Thumbnails & Bot Cards

+
GET /thumbnails/{match_id}.png
+GET /cards/{bot_id}.png
+

Auto-generated images for social sharing.

+
+ +
+

Update Frequency

+ + + + + + + + + +
Data TypeUpdate FrequencySource
LeaderboardEvery ~90 minIndex builder → Pages
Bot profilesEvery ~90 minIndex builder → Pages
Match indexEvery ~90 minIndex builder → Pages
PlaylistsEvery ~90 minIndex builder → Pages
ReplaysReal-timeMatch worker → R2/B2
Match metadataReal-timeMatch worker → R2/B2
Evolution dataEvery cycle (~15 min)Evolver → R2 live.json
+
+ +
+

Cache Behavior

+

Pages (indexes): Deployed every ~90 minutes. Cached by Cloudflare CDN globally. Invalidated on deploy.

+

R2 (replays): Served with Cache-Control: immutable, max-age=31536000 (content-addressed, never changes).

+

B2 (archive): Same cache headers as R2. Free egress via Cloudflare Bandwidth Alliance.

+
+ +
+

Data Loading Pattern

+
// SPA shell + index data from Pages (same origin)
+const leaderboard = await fetch('/data/leaderboard.json').then(r => r.json())
+
+// Replay from R2 warm cache, with B2 fallback
+async function fetchReplay(matchId) {
+  const r2 = await fetch(\`https://r2.aicodebattle.com/replays/\${matchId}.json.gz\`)
+  if (r2.ok) return r2
+  return fetch(\`https://b2.aicodebattle.com/replays/\${matchId}.json.gz\`)
+}
+
+ +
+

Example: Fetch Leaderboard

+
curl https://aicodebattle.com/data/leaderboard.json | jq '.entries[:5]'
+

Returns top 5 bots with ratings and stats.

+
+ +
+

Example: Fetch Bot Profile

+
curl https://aicodebattle.com/data/bots/b_swarmbot.json | jq '{name, rating, matches}'
+
+ +
+

Example: Fetch Match Index

+
curl https://aicodebattle.com/data/matches/index.json | jq '.matches[:3] | .[] | {match_id, players}'
+
+ +
+

Example: Fetch Playlist

+
curl https://aicodebattle.com/data/playlists/closest-finishes.json | jq '.matches[] | .match_id'
+
+ +
+

Related Documentation

+ +
+
+ + +
+ `; +} diff --git a/web/src/pages/docs-replay-format.ts b/web/src/pages/docs-replay-format.ts new file mode 100644 index 0000000..4cddf16 --- /dev/null +++ b/web/src/pages/docs-replay-format.ts @@ -0,0 +1,219 @@ +// Replay Format Documentation Page +// §15.2: Public match data documentation - replay format specification + +export function renderDocsReplayFormatPage(): void { + const app = document.getElementById('app'); + if (!app) return; + + app.innerHTML = ` +
+

Replay Format Specification

+ +
+
+

Overview

+

AI Code Battle replays are JSON files containing the complete state of a match. Each replay includes the initial configuration, map data, and turn-by-turn events that allow the game to be reconstructed and visualized.

+

Version: v1 (additive changes only - see Changelog below)

+
+ +
+

Fetching Replays

+

Replays are served from Cloudflare R2 (warm cache) with B2 (cold archive) fallback:

+
# Try warm cache first
+curl https://r2.aicodebattle.com/replays/\${match_id}.json.gz
+
+# Fallback to cold archive
+curl https://b2.aicodebattle.com/replays/\${match_id}.json.gz
+

Replays are gzip-compressed. The browser handles decompression automatically when you fetch with Accept-Encoding: gzip.

+
+ +
+

Replay Schema

+

Download the JSON Schema: replay-schema-v1.json

+

Use the schema to validate replays programmatically:

+
# Validate a replay file
+ajv validate -s replay-schema-v1.json -d replay.json
+
+ +
+

Top-Level Structure

+
{
+  "version": 1,                    // Replay format version
+  "match_id": "m_7f3a9b2c",       // Unique match identifier
+  "date": "2026-03-23T14:30:00Z", // Match completion time
+  "players": [...],               // Player metadata
+  "result": {...},                // Final scores and winner
+  "config": {...},                // Match configuration
+  "map": {...},                   // Map definition
+  "turns": [...]                  // Turn-by-turn events
+}
+
+ +
+

Player Metadata

+
"players": [
+  {
+    "bot_id": "b_4e8c1d2f",
+    "name": "SwarmBot",
+    "owner": "alice",
+    "color": "#2196F3"
+  }
+]
+ + + + + + +
FieldTypeDescription
bot_idstringUnique bot identifier
namestringBot display name
ownerstringBot owner's username
colorstringPlayer color for visualization
+
+ +
+

Match Result

+
"result": {
+  "winner": 0,                   // Winning player index
+  "condition": "turn_limit",     // Win condition
+  "final_scores": [7, 3],        // Final scores per player
+  "final_energy": [12, 4],       // Final energy held
+  "final_bots": [18, 6]          // Final bot count
+}
+

Win Conditions:

+
    +
  • sole_survivor - Only one player has living bots
  • +
  • annihilation - All players eliminated simultaneously
  • +
  • dominance - One player controls ≥80% of bots for 100 turns
  • +
  • turn_limit - Turn limit reached (default: 500)
  • +
+
+ +
+

Match Configuration

+
"config": {
+  "rows": 60,
+  "cols": 60,
+  "max_turns": 500,
+  "vision_radius2": 49,          // Squared vision radius (~7 tiles)
+  "attack_radius2": 12,          // Squared attack radius
+  "spawn_cost": 3,              // Energy to spawn a bot
+  "energy_interval": 10          // Turns between energy spawns
+}
+

Seasonal variations may introduce optional fields (see Changelog). Bots that don't read new fields continue to work.

+
+ +
+

Map Definition

+
"map": {
+  "walls": [[10,10], [10,11], [10,12]],           // Wall positions
+  "energy_nodes": [[20,25], [40,35]],             // Energy spawn locations
+  "cores": [                                      // Starting cores
+    {"pos": [5,5], "owner": 0},
+    {"pos": [55,55], "owner": 1}
+  ]
+}
+
+ +
+

Turn Events

+

Each turn contains events that occurred during that turn:

+
"turns": [
+  {
+    "moves": {                      // Moves made by each player
+      "0": [{"from": [10,15], "dir": "N"}],
+      "1": [{"from": [50,45], "dir": "S"}]
+    },
+    "spawns": [[5,5,0]],            // New bots spawned
+    "deaths": [[30,40,1]],          // Bots that died
+    "captures": [],                  // Cores captured
+    "energy_collected": {           // Energy gathered
+      "0": [[20,25]]
+    },
+    "energy_spawned": [[35,15]],   // New energy appeared
+    "scores": [3, 1],              // Scores after turn
+    "events": [                     // Detailed events
+      {
+        "type": "combat_death",
+        "turn": 6,
+        "details": {
+          "bot_id": 0,
+          "owner": 0,
+          "position": {"row": 30, "col": 40},
+          "killers": [
+            {"bot_id": 1, "owner": 1, "position": {"row": 28, "col": 42}}
+          ]
+        }
+      }
+    ]
+  }
+]
+
+ +
+

Event Types

+ + + + + + + + + +
TypeDescription
bot_spawnedA new bot was spawned
bot_diedA bot died (legacy, no killer info)
combat_deathA bot died from focus-fire combat (includes killers[] array)
collision_deathTwo bots moved to the same tile
zone_deathA bot was killed by the shrinking zone
energy_collectedEnergy was gathered
core_capturedAn enemy core was razed
+
+ +
+

Win Probability (Optional)

+

Some replays include a win probability curve computed via Monte Carlo rollout:

+
"win_prob": [
+  [0.50, 0.50],  // Turn 0: even odds
+  [0.51, 0.49],  // Turn 1: slight edge to player 0
+  ...
+]
+

Array of [player_0_prob, player_1_prob, ...] for each turn.

+
+ +
+

Critical Moments (Optional)

+

Turns where win probability shifted significantly (>15%):

+
"critical_moments": [
+  {
+    "turn": 87,
+    "delta": 0.22,
+    "description": "SwarmBot loses 6 units in eastern engagement"
+  }
+]
+
+ +
+

Changelog

+

Version 1 (Current)

+
    +
  • Initial release
  • +
  • All core event types supported
  • +
  • Optional win_prob and critical_moments arrays
  • +
+

Backward Compatibility Policy: Future versions will only add optional fields. Existing fields will never be removed or renamed. Bots that don't read new fields continue to function.

+
+ +
+

Example Replays

+

Download example replays to test your visualization:

+ +
+ +
+

Related Documentation

+ +
+
+
+ `; +}