Add platform depth features: debug telemetry, territory heatmaps,
embeds, playlists, predictions, map evolution, series, event timeline, seasons, bot cards New section 13 (10 features) + Phase 9: - Bot debug telemetry: optional structured debug field in move response, rendered as targets/heatmaps/reasoning in the replay viewer - Three replay view modes: dots, Voronoi territory, influence gradient - Embeddable replay widget (~50KB iframe, OG tags, query params) - Auto-curated playlists: Closest Finishes, Upsets, Comebacks, etc. - Prediction system for non-coders (within CF free tier: ~864 writes/day) - Map evolution with breeding, engagement scoring, positional fairness monitoring, and user voting (upvote/downvote maps) - Multi-game series (bo3/bo5/bo7) across different maps with spoiler toggle - Match event timeline with clickable icon ribbon - Seasonal rotations with backward-compatible rule versioning (additive changes only: new optional fields, param tuning, new tile types that old bots can ignore), championship brackets, season archives - Bot profile cards as shareable PNGs with OG tags Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
46f5e2ac4a
commit
282a3aed94
1 changed files with 851 additions and 0 deletions
|
|
@ -1946,6 +1946,35 @@ replays with commentary and win probability, export clips for social
|
|||
sharing, view rivalries, and submit tactical feedback that influences the
|
||||
evolution pipeline.
|
||||
|
||||
### Phase 9: Platform Depth
|
||||
|
||||
**Deliverables:**
|
||||
- Bot debug telemetry: optional `debug` field in move response schema,
|
||||
stored in replay, rendered in viewer side panel + grid overlays
|
||||
- Replay view modes: dots (default), Voronoi territory, influence gradient
|
||||
— all computed client-side, toggled via viewer toolbar
|
||||
- Embeddable replay widget: `/embed/{match_id}` route on Pages, minimal
|
||||
Chrome, auto-play, ~50KB, Open Graph tags
|
||||
- Replay playlists: auto-curated collections rebuilt by index cron, stored
|
||||
in R2, browsable on the static site
|
||||
- Prediction system: D1 `predictions` table, Worker endpoints for submit
|
||||
+ resolve, prediction leaderboard JSON in R2
|
||||
- Map evolution pipeline: engagement scoring, breeding/mutation, symmetry
|
||||
validation, positional fairness monitoring, user map voting
|
||||
- Multi-game series: D1 `series` table, series scheduler, unified replay
|
||||
presentation, spoiler toggle
|
||||
- Match event timeline: client-side event extraction, icon ribbon in
|
||||
replay viewer, click-to-jump
|
||||
- Seasonal system: D1 `seasons` table, ladder reset logic, season archive
|
||||
pages, versioned game rules with backward compatibility
|
||||
- Bot profile cards: Canvas-rendered PNG, shareable URL with OG tags
|
||||
|
||||
**Exit criteria:** the platform supports seasonal competition with map
|
||||
evolution, multi-game series, predictions for non-coders, embeddable
|
||||
replays, curated playlists, three replay view modes, bot debug telemetry,
|
||||
event timelines, and shareable bot profile cards. All within Cloudflare
|
||||
free tier.
|
||||
|
||||
---
|
||||
|
||||
## 12. Enhanced Features
|
||||
|
|
@ -2388,3 +2417,825 @@ system guarantees you lose the 3v1."
|
|||
contribute strategic insight, the AI translates it into code, the platform
|
||||
evaluates it, and successful feedback is credited. This gives non-coders a
|
||||
way to participate meaningfully in the competition.
|
||||
|
||||
---
|
||||
|
||||
## 13. Platform Depth Features
|
||||
|
||||
### 13.1 Bot Debug Telemetry + Reasoning Visualization
|
||||
|
||||
Bots can optionally include a `debug` field in their move response. The
|
||||
engine stores it in the replay without interpreting it. The replay viewer
|
||||
renders it.
|
||||
|
||||
**Extended move response schema:**
|
||||
|
||||
```json
|
||||
{
|
||||
"moves": [
|
||||
{ "row": 10, "col": 15, "direction": "N" }
|
||||
],
|
||||
"debug": {
|
||||
"reasoning": "3 energy within 5 tiles east; enemy cluster north — avoiding",
|
||||
"targets": [
|
||||
{ "row": 20, "col": 25, "label": "energy", "priority": 0.9 },
|
||||
{ "row": 8, "col": 30, "label": "threat", "priority": 0.7 }
|
||||
],
|
||||
"values": {
|
||||
"energy_reserves": 7,
|
||||
"threat_level": "medium",
|
||||
"mode": "gathering"
|
||||
},
|
||||
"heatmap": {
|
||||
"name": "threat",
|
||||
"data": [[0, 0, 0.2, 0.8], [0, 0.1, 0.5, 0.9]]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Schema rules for `debug`:**
|
||||
- Entirely optional — bots that omit it behave identically
|
||||
- Max size: 10 KB per turn (prevents replay bloat; excess is truncated)
|
||||
- The engine never reads or acts on debug data — it's pass-through to replay
|
||||
- No fields inside `debug` are validated beyond size — bots can put anything
|
||||
- Only the bot's owner sees debug data by default; owners can toggle public
|
||||
visibility per-bot in their bot profile
|
||||
|
||||
**Replay viewer rendering:**
|
||||
|
||||
| Debug field | Rendering |
|
||||
|-------------|-----------|
|
||||
| `reasoning` | Text in a collapsible side panel, one entry per turn |
|
||||
| `targets` | Colored markers on the grid (green = high priority, red = low) with labels |
|
||||
| `values` | Key-value table in the side panel, updates each turn |
|
||||
| `heatmap` | Semi-transparent color overlay on the grid (blue→red gradient) |
|
||||
|
||||
All debug rendering is toggled via a "Debug" button in the viewer toolbar.
|
||||
When off, no debug data is shown (default for spectators). When on, the
|
||||
viewer shows the selected player's debug output.
|
||||
|
||||
**Replay size impact:**
|
||||
|
||||
A bot sending 5 KB of debug data per turn across 500 turns adds 2.5 MB
|
||||
to the replay. With gzip compression (~90% on structured JSON), that's
|
||||
~250 KB. Acceptable alongside the ~50 KB base replay.
|
||||
|
||||
**Why it matters:** This is a visual debugger for distributed bot code.
|
||||
Instead of reading logs, developers watch their bot's thought process
|
||||
alongside its actions. For spectators who opt in, seeing "the bot is
|
||||
scared of the northern cluster" while watching it move south creates
|
||||
narrative that no commentary system can match.
|
||||
|
||||
### 13.2 Territory Control Heatmap Overlay
|
||||
|
||||
The replay viewer supports three visualization modes, toggled via a toolbar
|
||||
dropdown. All computed client-side from bot positions — no server cost.
|
||||
|
||||
**Mode 1: Dots (default)**
|
||||
|
||||
The current view — bots as colored circles on the grid. Minimal, clean,
|
||||
fast.
|
||||
|
||||
**Mode 2: Voronoi Territory**
|
||||
|
||||
Each tile on the grid is colored by which player's nearest bot is closest.
|
||||
Creates clean territorial borders that shift each turn.
|
||||
|
||||
```
|
||||
Computation per turn:
|
||||
for each visible tile (row, col):
|
||||
min_dist = infinity
|
||||
owner = none
|
||||
for each bot on the grid:
|
||||
d = toroidal_distance_squared(tile, bot)
|
||||
if d < min_dist:
|
||||
min_dist = d
|
||||
owner = bot.owner
|
||||
tile_color = player_colors[owner] at 30% opacity
|
||||
```
|
||||
|
||||
For a 60×60 grid with 50 bots, that's 3,600 × 50 = 180,000 distance
|
||||
calculations per turn — trivial for modern JS (~1ms). The result is a
|
||||
per-tile color array rendered as a single full-grid Canvas `fillRect` pass
|
||||
underneath the bot sprites.
|
||||
|
||||
**Mode 3: Influence Gradient**
|
||||
|
||||
Force projection based on bot count and distance. Each player's influence
|
||||
at a tile is the sum of `1 / (1 + distance)` across all their bots.
|
||||
Rendered as a smooth gradient:
|
||||
|
||||
```
|
||||
for each visible tile:
|
||||
influence = [0, 0, ..., 0] // per player
|
||||
for each bot:
|
||||
d = toroidal_distance(tile, bot)
|
||||
influence[bot.owner] += 1.0 / (1.0 + d)
|
||||
dominant = argmax(influence)
|
||||
strength = influence[dominant] / sum(influence)
|
||||
tile_color = player_colors[dominant] at (strength × 50%) opacity
|
||||
```
|
||||
|
||||
The gradient creates a softer, more organic visualization than Voronoi —
|
||||
you can see where influence is strong (dense, saturated) vs weak (faint,
|
||||
contested). Frontlines appear as narrow bands where no player dominates.
|
||||
|
||||
**Performance:** both modes compute in <5ms per turn on a 60×60 grid.
|
||||
The replay viewer caches the overlay bitmap per turn and only recomputes
|
||||
on turn change. At 32 turns/second (16× speed), this stays within frame
|
||||
budget.
|
||||
|
||||
**Toolbar UI:**
|
||||
|
||||
```
|
||||
View: [Dots ▼] [Dots | Territory | Influence]
|
||||
```
|
||||
|
||||
Switching modes is instant — the underlying replay data doesn't change,
|
||||
only the rendering pipeline.
|
||||
|
||||
### 13.3 Embeddable Replay Widget
|
||||
|
||||
A lightweight, standalone replay player that works in an iframe anywhere.
|
||||
|
||||
**URL format:**
|
||||
```
|
||||
https://aicodebattle.com/embed/{match_id}
|
||||
https://aicodebattle.com/embed/{match_id}?start=87&speed=4&mode=territory
|
||||
```
|
||||
|
||||
**Query parameters:**
|
||||
|
||||
| Param | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `start` | 0 | Starting turn |
|
||||
| `speed` | 2 | Playback speed (1, 2, 4, 8, 16) |
|
||||
| `mode` | dots | Visualization mode (dots, territory, influence) |
|
||||
| `autoplay` | true | Start playing on load |
|
||||
| `controls` | true | Show play/pause and speed controls |
|
||||
|
||||
**Widget design:**
|
||||
|
||||
Stripped-down replay viewer: canvas + minimal controls bar. No scrubber,
|
||||
no side panel, no fog-of-war toggle. Just the match playing.
|
||||
|
||||
```
|
||||
┌──────────────────────────────┐
|
||||
│ │
|
||||
│ [Canvas] │
|
||||
│ │
|
||||
├──────────────────────────────┤
|
||||
│ ▶ 2x SwarmBot 3 — 1 Hunter │
|
||||
│ Watch full ↗ │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
"Watch full" links to the main replay page on aicodebattle.com.
|
||||
|
||||
**Implementation:**
|
||||
|
||||
- Separate route on Cloudflare Pages: `/embed/{match_id}`
|
||||
- Loads the same replay JSON from R2
|
||||
- Renders with the same Canvas engine, minus chrome
|
||||
- Total bundle: ~50 KB (JS + CSS)
|
||||
- Open Graph tags for rich previews when pasting the URL:
|
||||
```html
|
||||
<meta property="og:title" content="SwarmBot vs HunterBot — AI Code Battle" />
|
||||
<meta property="og:description" content="SwarmBot wins 3-1 in 342 turns" />
|
||||
<meta property="og:image" content="https://data.aicodebattle.com/thumbnails/m_7f3a9b2c.png" />
|
||||
```
|
||||
- Thumbnail: auto-generated PNG of the final turn state, created by the
|
||||
index rebuilder using OffscreenCanvas in a Worker (or pre-rendered by
|
||||
the match worker)
|
||||
|
||||
**Cloudflare free tier impact:** embed loads are Pages requests (unlimited).
|
||||
The replay JSON fetch is an R2 Class B read — already accounted for in the
|
||||
existing budget.
|
||||
|
||||
### 13.4 Replay Playlists + Auto-Curation
|
||||
|
||||
Automatically curated collections of replays, browsable from the static
|
||||
site's landing page.
|
||||
|
||||
**Playlist definitions:**
|
||||
|
||||
| Playlist | Query Criteria | Rebuild Frequency |
|
||||
|----------|---------------|-------------------|
|
||||
| "Closest Finishes" | `final_score_diff <= 1` sorted by `win_prob_crossings DESC` | Every 2 min (index cron) |
|
||||
| "Biggest Upsets" | `winner_rating - loser_rating <= -150` | Every 2 min |
|
||||
| "Best Comebacks" | `min(win_prob) < 0.2 AND winner = underdog` | Every 2 min |
|
||||
| "Evolution Breakthroughs" | Evolved bot's first win against a top-10 bot | Every 2 min |
|
||||
| "Rivalry Classics" | Matches between detected rivals, sorted by closeness | Every 2 min |
|
||||
| "This Week's Highlights" | Top 10 by community upvote count (from §12.6) | Every 2 min |
|
||||
| "New Bot Debuts" | First match of each newly registered bot | Every 2 min |
|
||||
| "Season Highlights" | Top 20 matches of the current season by engagement | Every 2 min |
|
||||
|
||||
**R2 storage:** `data/playlists/{slug}.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Closest Finishes",
|
||||
"description": "Matches decided by a single point or less",
|
||||
"updated_at": "2026-03-23T14:35:00Z",
|
||||
"matches": [
|
||||
{
|
||||
"match_id": "m_7f3a9b2c",
|
||||
"players": ["SwarmBot", "HunterBot"],
|
||||
"scores": [3, 2],
|
||||
"date": "2026-03-23T14:30:00Z",
|
||||
"thumbnail_url": "https://data.aicodebattle.com/thumbnails/m_7f3a9b2c.png",
|
||||
"enriched": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Static site UI:** landing page shows playlists as horizontal scrollable
|
||||
rows (Netflix-style). Each card shows a thumbnail, player names, and score.
|
||||
Click opens the replay.
|
||||
|
||||
**Cloudflare free tier impact:** playlist JSONs are tiny (<50 KB each).
|
||||
They're rebuilt by the existing index rebuilder cron — just additional D1
|
||||
queries and R2 writes within existing budget.
|
||||
|
||||
### 13.5 Prediction System
|
||||
|
||||
Visitors predict outcomes of upcoming notable matches. Correct predictions
|
||||
earn reputation. A prediction leaderboard tracks the best analysts.
|
||||
|
||||
**Which matches get predictions:**
|
||||
|
||||
The matchmaker flags a match as "predictable" when:
|
||||
- Both bots are in the top 20
|
||||
- It's a rivalry match
|
||||
- It's a series match (§13.7)
|
||||
- An evolved bot faces a top-10 human-written bot
|
||||
|
||||
At ~60 matches/hour, roughly 5–10% are flagged — about 3–6 per hour.
|
||||
|
||||
**Flow:**
|
||||
|
||||
1. Scheduler creates a match job with `predictable: true`
|
||||
2. Worker API writes the match to a `predictions_open` state in D1
|
||||
3. Static site shows "Upcoming Matches" with a predict button
|
||||
4. Visitor clicks a player to predict (stored via `POST /api/predict`)
|
||||
5. Prediction window: open from job creation until the match starts
|
||||
executing (typically 1–5 minutes)
|
||||
6. Match executes normally
|
||||
7. On result submission, Worker resolves predictions in D1
|
||||
8. Index rebuilder updates the prediction leaderboard JSON in R2
|
||||
|
||||
**D1 schema:**
|
||||
|
||||
```sql
|
||||
CREATE TABLE predictions (
|
||||
prediction_id TEXT PRIMARY KEY,
|
||||
match_id TEXT NOT NULL,
|
||||
predictor_id TEXT NOT NULL, -- localStorage-generated UUID
|
||||
predictor_name TEXT, -- optional display name
|
||||
predicted_winner INTEGER NOT NULL,
|
||||
correct INTEGER, -- null until resolved
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE predictor_stats (
|
||||
predictor_id TEXT PRIMARY KEY,
|
||||
predictor_name TEXT,
|
||||
correct INTEGER NOT NULL DEFAULT 0,
|
||||
incorrect INTEGER NOT NULL DEFAULT 0,
|
||||
streak INTEGER NOT NULL DEFAULT 0,
|
||||
best_streak INTEGER NOT NULL DEFAULT 0,
|
||||
rating REAL NOT NULL DEFAULT 1000.0
|
||||
);
|
||||
```
|
||||
|
||||
Predictor rating uses a simplified Elo: correct prediction on a balanced
|
||||
match (close ratings) = small gain; correct prediction on a heavy underdog
|
||||
= large gain.
|
||||
|
||||
**Cloudflare free tier check:**
|
||||
|
||||
| Metric | Usage | Limit |
|
||||
|--------|-------|-------|
|
||||
| D1 writes | ~6 predictions/match × 6 matches/hour × 24h = ~864/day | 100K/day |
|
||||
| D1 reads | ~50 leaderboard reads/day | 5M/day |
|
||||
| Worker requests | `POST /api/predict` ~864/day | 100K/day |
|
||||
|
||||
Comfortably within limits. Even at 10× the assumed prediction volume
|
||||
(8,640/day), still under 9% of the write limit.
|
||||
|
||||
**Static site UI:**
|
||||
|
||||
- "Predictions" page showing upcoming predictable matches with bot profiles
|
||||
and head-to-head records
|
||||
- One-click predict button (no login required — UUID from localStorage)
|
||||
- After match: result shown with "You were right/wrong" + points earned
|
||||
- Prediction leaderboard: top 50 analysts ranked by prediction rating
|
||||
|
||||
### 13.6 Map Evolution
|
||||
|
||||
Maps evolve alongside bots. High-engagement maps breed to produce new maps.
|
||||
Low-engagement maps retire. User feedback and positional fairness monitoring
|
||||
ensure quality.
|
||||
|
||||
**Engagement scoring:**
|
||||
|
||||
After each match, the map receives an engagement score:
|
||||
|
||||
```
|
||||
engagement = (
|
||||
win_prob_crossings × 3.0 +
|
||||
critical_moments × 2.0 +
|
||||
map_coverage_pct × 1.0 +
|
||||
closeness × 2.0 +
|
||||
avg_turn_count / max_turns × 1.0
|
||||
)
|
||||
|
||||
where:
|
||||
closeness = 1.0 - (abs(score_diff) / max(total_score, 1))
|
||||
map_coverage_pct = tiles_visited_by_any_bot / total_open_tiles
|
||||
```
|
||||
|
||||
The map's engagement score is the rolling average across its last 20 matches.
|
||||
|
||||
**Positional fairness monitoring:**
|
||||
|
||||
A map is **positionally fair** if no starting position has a systematic
|
||||
advantage. Monitored by tracking win rate per player slot:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
map_id,
|
||||
player_slot,
|
||||
COUNT(*) AS games,
|
||||
AVG(CASE WHEN winner = player_slot THEN 1.0 ELSE 0.0 END) AS win_rate
|
||||
FROM match_participants mp
|
||||
JOIN matches m ON m.match_id = mp.match_id
|
||||
GROUP BY map_id, player_slot
|
||||
HAVING COUNT(*) >= 20
|
||||
```
|
||||
|
||||
If any player slot's win rate deviates from the expected rate (1/N for
|
||||
N-player maps) by more than **10 percentage points** across 20+ matches,
|
||||
the map is flagged as **unfair** and removed from the competitive pool.
|
||||
|
||||
Example: on a 2-player map, if player slot 0 wins 62% of the time after
|
||||
20 matches, the map is flagged (62% - 50% = 12% > 10% threshold).
|
||||
|
||||
**User map voting:**
|
||||
|
||||
After watching a replay, visitors can upvote or downvote the map (not the
|
||||
match — the map). Stored in D1:
|
||||
|
||||
```sql
|
||||
CREATE TABLE map_votes (
|
||||
vote_id TEXT PRIMARY KEY,
|
||||
map_id TEXT NOT NULL,
|
||||
voter_id TEXT NOT NULL, -- localStorage UUID
|
||||
vote INTEGER NOT NULL, -- +1 or -1
|
||||
created_at TEXT NOT NULL,
|
||||
UNIQUE(map_id, voter_id)
|
||||
);
|
||||
```
|
||||
|
||||
Map voting influences the evolution system:
|
||||
- Maps with net negative votes get a 0.5× engagement multiplier (less likely
|
||||
to breed)
|
||||
- Maps with >10 net positive votes get a 1.5× multiplier
|
||||
- Maps with >20 net negative votes are force-retired regardless of engagement
|
||||
|
||||
The replay viewer shows a simple 👍/👎 widget for the map (not the bots)
|
||||
alongside map metadata (name, dimensions, wall density, energy count).
|
||||
|
||||
**Breeding algorithm:**
|
||||
|
||||
Runs weekly on the evolver (Rackspace Spot). Produces ~5 new maps per
|
||||
player-count tier.
|
||||
|
||||
```
|
||||
1. Select parents:
|
||||
- Top 5 maps by engagement × vote_multiplier for this player count
|
||||
- Weighted random: higher engagement = more likely to be selected
|
||||
|
||||
2. Crossover:
|
||||
- Divide parent maps into quadrants (or thirds for 3/6-player)
|
||||
- Randomly select quadrants from each parent
|
||||
- Compose into a new map
|
||||
|
||||
3. Apply symmetry:
|
||||
- Generate one sector from the composed quadrants
|
||||
- Mirror/rotate to fill the full map for the target player count
|
||||
- This guarantees positional fairness by construction
|
||||
|
||||
4. Mutate:
|
||||
- Randomly flip 5-10% of tiles (wall ↔ open)
|
||||
- Shift 1-3 energy node positions by 1-3 tiles
|
||||
- Apply cellular automata smoothing (2 iterations) to avoid
|
||||
jagged walls
|
||||
|
||||
5. Validate:
|
||||
- BFS from every core must reach every other core
|
||||
- BFS from every core must reach ≥3 energy nodes
|
||||
- Open area per player must be between 900 and 5000 tiles
|
||||
- Wall density must be between 5% and 30%
|
||||
|
||||
6. Smoke-test:
|
||||
- Run 3 matches with built-in bots on the candidate map
|
||||
- Engagement score must exceed 50th percentile of current pool
|
||||
- If failed: discard and retry (max 3 attempts per candidate)
|
||||
|
||||
7. Add to pool:
|
||||
- Store map JSON in R2
|
||||
- Insert into D1 maps table with `status: 'active'`
|
||||
- Available for matchmaking in the next scheduler cycle
|
||||
```
|
||||
|
||||
**Lifecycle:**
|
||||
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| `active` | In the matchmaking pool, eligible for competitive play |
|
||||
| `probation` | Fairness flag triggered — under review, still playable |
|
||||
| `retired` | Removed from pool (low engagement, unfair, or force-retired) |
|
||||
| `classic` | Top 5 all-time maps, immune from retirement |
|
||||
|
||||
- Active pool: 50 maps per player count (2, 3, 4, 6)
|
||||
- New maps: ~5 per week per player count
|
||||
- Retirement: bottom 10% by engagement score pruned monthly
|
||||
- Classic promotion: maps that sustain top-5 engagement for 3+ months
|
||||
|
||||
### 13.7 Multi-Game Series
|
||||
|
||||
Best-of-N matches between two bots across different maps. Series produce
|
||||
more meaningful ratings than single matches and create narrative arcs.
|
||||
|
||||
**Series types:**
|
||||
|
||||
| Type | Games | Trigger |
|
||||
|------|-------|---------|
|
||||
| Best-of-3 | 3 | Auto-scheduled for top-20 bots, 1 per day per bot |
|
||||
| Best-of-5 | 5 | Weekly featured series between top rivalries |
|
||||
| Best-of-7 | 7 | Season championship bracket (§13.9) |
|
||||
|
||||
**Map selection for series:**
|
||||
|
||||
Each game in a series uses a different map, selected to test different
|
||||
strategic dimensions:
|
||||
|
||||
```
|
||||
Game 1: Map with highest engagement score (the "classic")
|
||||
Game 2: Map with highest wall density in pool (corridors/chokepoints)
|
||||
Game 3: Map with lowest wall density in pool (open field)
|
||||
Game 4: Most recently evolved map (untested terrain)
|
||||
Game 5+: Random from remaining pool
|
||||
```
|
||||
|
||||
This ensures series test bot adaptability, not just performance on one
|
||||
map type.
|
||||
|
||||
**D1 schema:**
|
||||
|
||||
```sql
|
||||
CREATE TABLE series (
|
||||
series_id TEXT PRIMARY KEY,
|
||||
bot_a_id TEXT NOT NULL,
|
||||
bot_b_id TEXT NOT NULL,
|
||||
format INTEGER NOT NULL, -- 3, 5, or 7
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
a_wins INTEGER NOT NULL DEFAULT 0,
|
||||
b_wins INTEGER NOT NULL DEFAULT 0,
|
||||
season_id TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
completed_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE series_games (
|
||||
series_id TEXT NOT NULL,
|
||||
game_number INTEGER NOT NULL,
|
||||
match_id TEXT, -- null until played
|
||||
map_id TEXT NOT NULL,
|
||||
winner INTEGER,
|
||||
PRIMARY KEY (series_id, game_number)
|
||||
);
|
||||
```
|
||||
|
||||
**Execution:**
|
||||
|
||||
The scheduler creates all games in a series as pending jobs with sequential
|
||||
ordering. Workers execute them in order (game 2 doesn't start until game 1
|
||||
completes). If either bot reaches the winning threshold (2 for bo3, 3 for
|
||||
bo5, 4 for bo7), remaining games are skipped.
|
||||
|
||||
**Rating impact:**
|
||||
|
||||
Series results contribute to Glicko-2 ratings as follows:
|
||||
- Each individual game in the series contributes to the pairwise rating
|
||||
update (same as a single match)
|
||||
- The series winner gets a bonus rating adjustment of +10 mu (small but
|
||||
meaningful — rewards series consistency)
|
||||
|
||||
**Replay presentation:**
|
||||
|
||||
The series page (`/series/{series_id}`) shows all games as a unified
|
||||
experience:
|
||||
|
||||
```
|
||||
SwarmBot vs HunterBot — Best of 5 (Season 4 Semifinals)
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Game 1 ✓ SwarmBot 3-1 Map: The Labyrinth [Watch]
|
||||
Game 2 ✓ HunterBot 2-4 Map: Open Expanse [Watch]
|
||||
Game 3 ✓ HunterBot 1-3 Map: Coral Reef [Watch]
|
||||
Game 4 ??? [Reveal]
|
||||
Game 5 ??? [Reveal]
|
||||
|
||||
Series: HunterBot leads 2-1
|
||||
```
|
||||
|
||||
**Spoiler toggle:** by default, future games are hidden ("???"). Viewers
|
||||
click "Reveal" to show the result — or "Watch All" to experience the
|
||||
series sequentially with auto-advancing between games.
|
||||
|
||||
### 13.8 Match Event Timeline
|
||||
|
||||
A horizontal event ribbon below the replay canvas showing significant
|
||||
events as colored, clickable icons.
|
||||
|
||||
**Event types:**
|
||||
|
||||
| Icon | Event | Trigger |
|
||||
|------|-------|---------|
|
||||
| ⚔️ | Combat | 2+ bots died this turn |
|
||||
| 🏰 | Core captured | A core was razed |
|
||||
| 💎 | Energy milestone | Player collected 3+ energy in one turn |
|
||||
| 💀 | Mass death | 5+ bots died this turn |
|
||||
| 📈 | Momentum shift | Win probability crossed 50% |
|
||||
| 🌟 | Critical moment | Win probability shifted >15% |
|
||||
| 🐣 | Spawn wave | 3+ bots spawned this turn |
|
||||
|
||||
**Implementation:**
|
||||
|
||||
Events are extracted client-side from the replay data on load. For each
|
||||
turn, check the events array (deaths, captures, spawns, energy_collected)
|
||||
against the trigger thresholds. Win probability events come from the
|
||||
`win_prob` and `critical_moments` arrays already in the replay.
|
||||
|
||||
**Rendering:**
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ [Canvas] │
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ Win Prob: ~~~~~~~~~/\~~~~~/\~~~~/\~~~~~~ │ ← sparkline
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ Events: ·💎·····⚔️··💎···🏰⚔️···💎···⚔️💀··🏰🌟│ ← timeline
|
||||
├──────────────────────────────────────────────────┤
|
||||
│ ◄ ▶ ⏸ Turn 203/500 Speed: 4x View: [Dots]│ ← controls
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- Icons are positioned proportionally along the timeline by turn number
|
||||
- Hovering an icon shows a tooltip: "Turn 87: 3 bots killed in eastern
|
||||
corridor"
|
||||
- Clicking an icon scrubs the replay to that turn
|
||||
- Dense clusters of icons indicate "hot zones" of activity — visually
|
||||
obvious even at a glance
|
||||
- The timeline is rendered as an HTML element overlaid on the viewer
|
||||
(not Canvas) for accessibility and hover interactions
|
||||
|
||||
The event timeline and win probability graph work together: the graph
|
||||
shows the *trend*, the timeline shows the *moments*. A viewer can scan
|
||||
the timeline for icon clusters, then check the win probability graph to
|
||||
see if those moments mattered.
|
||||
|
||||
### 13.9 Seasonal Rotations
|
||||
|
||||
The platform runs in **seasons** — 4-week competitive periods with a fresh
|
||||
map pool, a new ladder, and a theme. Seasons provide urgency, freshness,
|
||||
and a reason to come back.
|
||||
|
||||
**Season structure:**
|
||||
|
||||
| Week | Phase | Description |
|
||||
|------|-------|-------------|
|
||||
| 1 | Discovery | New map pool + theme released. All bots start at default rating. Exploration matches. |
|
||||
| 2–3 | Competition | Main ladder. Matchmaking intensifies. Mid-season stats published. |
|
||||
| 4 | Championship | Top 8 bots by rating enter a best-of-7 bracket. Season champion crowned. |
|
||||
| Between | Break (3 days) | New maps bred via map evolution. Season archive published. |
|
||||
|
||||
**What resets each season:**
|
||||
- Glicko-2 ratings (mu/phi/sigma reset to defaults)
|
||||
- Map pool (evolved maps from previous season + new generated maps)
|
||||
- Prediction standings
|
||||
- Playlist contents
|
||||
|
||||
**What persists:**
|
||||
- Bot registrations and endpoints (bots don't re-register)
|
||||
- All-time records and historical season archives (browsable)
|
||||
- Evolution population (continues across seasons, adapts to new maps)
|
||||
- Community feedback and replay annotations
|
||||
|
||||
**D1 schema:**
|
||||
|
||||
```sql
|
||||
CREATE TABLE seasons (
|
||||
season_id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
theme TEXT NOT NULL,
|
||||
rules_version INTEGER NOT NULL,
|
||||
started_at TEXT NOT NULL,
|
||||
ended_at TEXT,
|
||||
champion_id TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active'
|
||||
);
|
||||
```
|
||||
|
||||
**Season themes and game rule versioning:**
|
||||
|
||||
Each season can introduce **minor rule variations** that keep the meta
|
||||
fresh. The critical constraint: **existing bots must continue to work
|
||||
without modification.** This is achieved through additive, optional
|
||||
changes only.
|
||||
|
||||
**Backward compatibility rules:**
|
||||
|
||||
```
|
||||
ALLOWED per-season changes (additive, non-breaking):
|
||||
✓ New tile types that bots can ignore (treated as open by old bots)
|
||||
✓ New optional fields in the game state JSON (old bots ignore them)
|
||||
✓ Adjusted numeric parameters within the existing schema:
|
||||
- vision_radius2, attack_radius2, spawn_cost, energy_interval
|
||||
- These are sent in the config object each match — bots that read
|
||||
config adapt automatically; bots that hardcode values still work
|
||||
but may be suboptimal
|
||||
✓ New scoring bonuses (additive to existing scoring)
|
||||
✓ Map pool changes (different maps, not different map format)
|
||||
|
||||
FORBIDDEN (would break existing bots):
|
||||
✗ Removing or renaming existing fields in game state / move schema
|
||||
✗ Changing the meaning of existing fields
|
||||
✗ New required fields in the move response
|
||||
✗ Changing the coordinate system or grid topology
|
||||
✗ Removing movement directions (N/E/S/W)
|
||||
✗ Changing the turn structure (phases must remain in the same order)
|
||||
```
|
||||
|
||||
**Example seasonal themes:**
|
||||
|
||||
| Season | Theme | Rule Variation |
|
||||
|--------|-------|---------------|
|
||||
| 1 | "The Labyrinth" | High wall density maps, `vision_radius2: 36` (reduced from 49) |
|
||||
| 2 | "Energy Rush" | `energy_interval: 5` (doubled production), `spawn_cost: 2` (cheaper bots) |
|
||||
| 3 | "Fog of War" | `vision_radius2: 25` (heavily reduced), new optional `sonar` field in game state showing approximate enemy count per quadrant |
|
||||
| 4 | "The Colosseum" | `attack_radius2: 8` (extended range), open maps, aggressive meta |
|
||||
| 5 | "Shifting Sands" | New tile type `quicksand` in game state (bots that don't handle it treat it as open — they can enter but movement costs 2 turns) |
|
||||
|
||||
For season 5's `quicksand` example: the game state sends
|
||||
`{ "row": 15, "col": 20, "type": "quicksand" }` in a new `terrain` array.
|
||||
Old bots that don't read `terrain` still function — they walk through
|
||||
quicksand unknowingly (and get slowed). New bots that parse `terrain` can
|
||||
avoid quicksand tiles, gaining a strategic edge. This creates an incentive
|
||||
to update bots each season without *forcing* anyone to.
|
||||
|
||||
**Season config in the match protocol:**
|
||||
|
||||
The game state's `config` object already includes all tunable parameters.
|
||||
Seasonal changes are just different values:
|
||||
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"season_id": "s4",
|
||||
"season_name": "The Colosseum",
|
||||
"rules_version": 4,
|
||||
"rows": 60,
|
||||
"cols": 60,
|
||||
"max_turns": 500,
|
||||
"vision_radius2": 49,
|
||||
"attack_radius2": 8,
|
||||
"spawn_cost": 3,
|
||||
"energy_interval": 10,
|
||||
"special_tiles": ["quicksand"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Bots that read `config.attack_radius2` adapt automatically. Bots that
|
||||
hardcode `attack_radius2 = 5` still work but use stale assumptions.
|
||||
`special_tiles` is a new array listing any non-standard tile types in
|
||||
play — old bots that don't read it are unaffected.
|
||||
|
||||
**Season archive:**
|
||||
|
||||
Each completed season gets an archive page (`/season/{season_id}`):
|
||||
- Champion + top 10 + bracket results
|
||||
- Most improved bot (biggest rating gain)
|
||||
- Best newcomer (highest-rated bot registered this season)
|
||||
- Most watched match (by replay view count)
|
||||
- Evolution highlights (best evolved bot, most creative strategy)
|
||||
- Map of the season (highest engagement score)
|
||||
- All replays preserved and browsable
|
||||
|
||||
**Season championship bracket:**
|
||||
|
||||
In week 4, the top 8 bots enter a single-elimination bracket of best-of-7
|
||||
series (§13.7). The bracket is published on the season page with live
|
||||
updates as series complete.
|
||||
|
||||
```
|
||||
Quarterfinals:
|
||||
#1 SwarmBot vs #8 NewBot → SwarmBot (4-1)
|
||||
#4 GathererBot vs #5 RusherBot → RusherBot (4-3)
|
||||
#3 HunterBot vs #6 evo-go-g12 → HunterBot (4-2)
|
||||
#2 GuardianBot vs #7 evo-py-g8 → GuardianBot (4-0)
|
||||
|
||||
Semifinals:
|
||||
SwarmBot vs RusherBot → SwarmBot (4-2)
|
||||
HunterBot vs GuardianBot → HunterBot (4-3)
|
||||
|
||||
Finals:
|
||||
SwarmBot vs HunterBot → ???
|
||||
```
|
||||
|
||||
### 13.10 Bot Profile Cards
|
||||
|
||||
Auto-generated visual cards summarizing a bot's identity, stats, and
|
||||
character in a single shareable image.
|
||||
|
||||
**Card generation:**
|
||||
|
||||
The card is rendered as a PNG via OffscreenCanvas (in the browser on
|
||||
demand, or pre-rendered by the index rebuilder for top-50 bots).
|
||||
|
||||
**Card content:**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ │
|
||||
│ SwarmBot #3 │
|
||||
│ by alice Rating: 1820 │
|
||||
│ │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ Archetype: │ │
|
||||
│ │ FORMATION SWARM │ │
|
||||
│ │ │ │
|
||||
│ │ Season 4 · 142 games │ │
|
||||
│ └─────────────────────────┘ │
|
||||
│ │
|
||||
│ Win Rate 69% ████████░░ │
|
||||
│ vs Rushers 82% █████████░ │
|
||||
│ vs Turtles 45% ████░░░░░░ │
|
||||
│ │
|
||||
│ Signature: Eastern corridor │
|
||||
│ push on 4-player maps │
|
||||
│ │
|
||||
│ Rival: HunterBot (11-11-1) │
|
||||
│ │
|
||||
│ ⚔️ 847 kills 💎 2.1k energy │
|
||||
│ 🏰 23 captures 📈 +320 Elo │
|
||||
│ │
|
||||
│ aicodebattle.com │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Data sources (all from existing bot profile JSON):**
|
||||
|
||||
| Field | Source |
|
||||
|-------|--------|
|
||||
| Rating, rank | Leaderboard |
|
||||
| Archetype | Strategy classifier from behavioral features (§12 evolution meta) |
|
||||
| Win rate breakdown | D1 query: wins vs each archetype cluster |
|
||||
| Signature | Most statistically distinctive behavior vs population average |
|
||||
| Rival | From rival detection (§12.5) |
|
||||
| Kill/energy/capture stats | Aggregate from match_participants |
|
||||
|
||||
**"Signature" computation:**
|
||||
|
||||
For each bot, compare its behavioral features (aggression, economy,
|
||||
exploration, formation) to the population mean. The dimension where the
|
||||
bot deviates most is its signature. Combined with map-type analysis:
|
||||
|
||||
```
|
||||
if bot.aggression is 2σ above mean AND best_map_type == "4-player":
|
||||
signature = "Aggressive multi-front warfare on 4-player maps"
|
||||
if bot.economy is 1.5σ above mean AND bot.exploration > 80%:
|
||||
signature = "Full-map economic dominance"
|
||||
```
|
||||
|
||||
Template-generated from ~20 signature patterns.
|
||||
|
||||
**Sharing:**
|
||||
|
||||
- "Share Card" button on the bot profile page generates a PNG download
|
||||
- Direct URL: `https://aicodebattle.com/card/{bot_id}.png`
|
||||
- Served as a static PNG from R2 (pre-rendered for top-50 bots)
|
||||
- Or rendered on-demand via a Worker that reads the bot profile JSON,
|
||||
draws to Canvas (using `@cloudflare/workers-types` Canvas API or
|
||||
a pre-built image template), and returns the PNG
|
||||
- Open Graph tags on the URL so pasting it into Twitter/Discord/Slack
|
||||
shows the card as a rich preview:
|
||||
```html
|
||||
<meta property="og:image" content="https://aicodebattle.com/card/b_4e8c1d2f.png" />
|
||||
<meta property="og:title" content="SwarmBot — #3 Rated — AI Code Battle" />
|
||||
```
|
||||
- The card image includes the platform URL as a watermark, driving traffic
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue