Complete D1 database schema and migrations for Phase 6

- Add missing tables: predictions, predictor_stats, map_votes,
  replay_feedback, series, series_games, seasons
- Add evolution fields to bots table (evolved, island, generation,
  parent_ids, description)
- Add additional fields to maps table (player_count, energy_nodes,
  wall_density, status, engagement_score)
- Create migrations/0001_initial.sql for D1 migrations
- Update wrangler.toml with migrations_dir config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-03-24 09:52:53 -04:00
parent 23186b77e1
commit 24859669fc
4 changed files with 427 additions and 3 deletions

View file

@ -29,7 +29,12 @@
### Remaining Phase 6 Work
- [ ] Cloudflare Pages project creation and deployment
- [ ] D1 database schema and migrations
- [x] D1 database schema and migrations
- Complete schema.sql with all tables from plan
- Added: predictions, predictor_stats, map_votes, replay_feedback, series, series_games, seasons
- Added evolution fields to bots table (evolved, island, generation, parent_ids)
- Created migrations/0001_initial.sql for D1 migrations
- Updated wrangler.toml with migrations_dir config
- [ ] R2 bucket creation and custom domain
- [ ] Worker API deployment via Wrangler
- [ ] DNS configuration
@ -132,6 +137,9 @@ ai-code-battle/
├── worker-api/
│ ├── package.json # npm dependencies
│ ├── wrangler.toml # Cloudflare Worker config
│ ├── schema.sql # Complete D1 schema (all tables)
│ ├── migrations/ # D1 migration files
│ │ └── 0001_initial.sql
│ └── src/
│ ├── index.ts # Router + cron dispatcher
│ ├── types.ts # TypeScript types

View file

@ -0,0 +1,264 @@
-- Migration: 0001_initial
-- Description: Initial database schema for AI Code Battle
-- Created: 2025-03-24
-- ============================================
-- Core Tables
-- ============================================
-- Bots table: stores registered bots
CREATE TABLE IF NOT EXISTS bots (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
owner_id TEXT NOT NULL,
endpoint_url TEXT NOT NULL,
api_key_hash TEXT NOT NULL,
rating REAL NOT NULL DEFAULT 1500.0,
rating_deviation REAL NOT NULL DEFAULT 350.0,
rating_volatility REAL NOT NULL DEFAULT 0.06,
evolved INTEGER NOT NULL DEFAULT 0,
island TEXT,
generation INTEGER,
parent_ids TEXT,
description TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
last_health_check TEXT,
health_status TEXT DEFAULT 'unknown',
matches_played INTEGER NOT NULL DEFAULT 0,
matches_won INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_bots_owner ON bots(owner_id);
CREATE INDEX IF NOT EXISTS idx_bots_rating ON bots(rating DESC);
CREATE INDEX IF NOT EXISTS idx_bots_evolved ON bots(evolved);
-- Matches table: stores match metadata
CREATE TABLE IF NOT EXISTS matches (
id TEXT PRIMARY KEY,
status TEXT NOT NULL DEFAULT 'pending',
winner_id TEXT,
turns INTEGER,
end_reason TEXT,
map_id TEXT NOT NULL,
scores_json TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
started_at TEXT,
completed_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_matches_status ON matches(status);
CREATE INDEX IF NOT EXISTS idx_matches_created ON matches(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_matches_map ON matches(map_id);
-- Match participants: links bots to matches
CREATE TABLE IF NOT EXISTS match_participants (
id TEXT PRIMARY KEY,
match_id TEXT NOT NULL,
bot_id TEXT NOT NULL,
player_index INTEGER NOT NULL,
score INTEGER NOT NULL DEFAULT 0,
status TEXT,
rating_before REAL NOT NULL,
rating_after REAL,
rating_deviation_before REAL NOT NULL,
rating_deviation_after REAL,
FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE,
FOREIGN KEY (bot_id) REFERENCES bots(id) ON DELETE CASCADE,
UNIQUE(match_id, bot_id),
UNIQUE(match_id, player_index)
);
CREATE INDEX IF NOT EXISTS idx_match_participants_match ON match_participants(match_id);
CREATE INDEX IF NOT EXISTS idx_match_participants_bot ON match_participants(bot_id);
-- Jobs table: match execution jobs for workers
CREATE TABLE IF NOT EXISTS jobs (
id TEXT PRIMARY KEY,
match_id TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
worker_id TEXT,
claimed_at TEXT,
heartbeat_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
completed_at TEXT,
error_message TEXT,
FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status);
CREATE INDEX IF NOT EXISTS idx_jobs_worker ON jobs(worker_id);
CREATE INDEX IF NOT EXISTS idx_jobs_heartbeat ON jobs(heartbeat_at);
-- Rating history: tracks rating changes over time
CREATE TABLE IF NOT EXISTS rating_history (
id TEXT PRIMARY KEY,
bot_id TEXT NOT NULL,
match_id TEXT NOT NULL,
rating_before REAL NOT NULL,
rating_after REAL NOT NULL,
rating_deviation REAL NOT NULL,
recorded_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (bot_id) REFERENCES bots(id) ON DELETE CASCADE,
FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_rating_history_bot ON rating_history(bot_id);
CREATE INDEX IF NOT EXISTS idx_rating_history_time ON rating_history(recorded_at DESC);
-- Maps table: stores generated maps
CREATE TABLE IF NOT EXISTS maps (
id TEXT PRIMARY KEY,
width INTEGER NOT NULL,
height INTEGER NOT NULL,
player_count INTEGER NOT NULL DEFAULT 2,
walls TEXT NOT NULL,
spawns TEXT NOT NULL,
cores TEXT NOT NULL,
energy_nodes TEXT NOT NULL,
wall_density REAL NOT NULL DEFAULT 0.15,
status TEXT NOT NULL DEFAULT 'active',
engagement_score REAL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_maps_status ON maps(status);
CREATE INDEX IF NOT EXISTS idx_maps_player_count ON maps(player_count);
CREATE INDEX IF NOT EXISTS idx_maps_engagement ON maps(engagement_score DESC);
-- Bot secrets: stores API keys for bots (separate for security)
CREATE TABLE IF NOT EXISTS bot_secrets (
bot_id TEXT PRIMARY KEY,
api_key_hash TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (bot_id) REFERENCES bots(id) ON DELETE CASCADE
);
-- ============================================
-- Prediction System
-- ============================================
-- Predictions: visitor predictions on match outcomes
CREATE TABLE IF NOT EXISTS predictions (
id TEXT PRIMARY KEY,
match_id TEXT NOT NULL,
predictor_id TEXT NOT NULL,
predictor_name TEXT,
predicted_bot_id TEXT NOT NULL,
correct INTEGER,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE,
FOREIGN KEY (predicted_bot_id) REFERENCES bots(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_predictions_match ON predictions(match_id);
CREATE INDEX IF NOT EXISTS idx_predictions_predictor ON predictions(predictor_id);
-- Predictor stats: aggregate prediction accuracy
CREATE TABLE IF NOT EXISTS 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
);
CREATE INDEX IF NOT EXISTS idx_predictor_stats_rating ON predictor_stats(rating DESC);
-- ============================================
-- Map Voting
-- ============================================
-- Map votes: community voting on map quality
CREATE TABLE IF NOT EXISTS map_votes (
id TEXT PRIMARY KEY,
map_id TEXT NOT NULL,
voter_id TEXT NOT NULL,
vote INTEGER NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (map_id) REFERENCES maps(id) ON DELETE CASCADE,
UNIQUE(map_id, voter_id)
);
CREATE INDEX IF NOT EXISTS idx_map_votes_map ON map_votes(map_id);
-- ============================================
-- Replay Feedback
-- ============================================
-- Replay feedback: community annotations on replays
CREATE TABLE IF NOT EXISTS replay_feedback (
id TEXT PRIMARY KEY,
match_id TEXT NOT NULL,
turn INTEGER NOT NULL,
type TEXT NOT NULL,
body TEXT NOT NULL,
author TEXT NOT NULL,
upvotes INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_feedback_match ON replay_feedback(match_id, turn);
CREATE INDEX IF NOT EXISTS idx_feedback_type ON replay_feedback(type);
CREATE INDEX IF NOT EXISTS idx_feedback_upvotes ON replay_feedback(upvotes DESC);
-- ============================================
-- Multi-Game Series
-- ============================================
-- Series: best-of-N match series between two bots
CREATE TABLE IF NOT EXISTS series (
id TEXT PRIMARY KEY,
bot_a_id TEXT NOT NULL,
bot_b_id TEXT NOT NULL,
format INTEGER NOT NULL,
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 DEFAULT (datetime('now')),
completed_at TEXT,
FOREIGN KEY (bot_a_id) REFERENCES bots(id) ON DELETE CASCADE,
FOREIGN KEY (bot_b_id) REFERENCES bots(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_series_status ON series(status);
CREATE INDEX IF NOT EXISTS idx_series_bots ON series(bot_a_id, bot_b_id);
CREATE INDEX IF NOT EXISTS idx_series_season ON series(season_id);
-- Series games: individual games within a series
CREATE TABLE IF NOT EXISTS series_games (
series_id TEXT NOT NULL,
game_number INTEGER NOT NULL,
match_id TEXT,
map_id TEXT NOT NULL,
winner INTEGER,
PRIMARY KEY (series_id, game_number),
FOREIGN KEY (series_id) REFERENCES series(id) ON DELETE CASCADE,
FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_series_games_match ON series_games(match_id);
-- ============================================
-- Seasonal Rotations
-- ============================================
-- Seasons: seasonal leaderboards with rule variations
CREATE TABLE IF NOT EXISTS seasons (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
theme TEXT NOT NULL,
rules_version INTEGER NOT NULL DEFAULT 1,
started_at TEXT NOT NULL,
ended_at TEXT,
champion_id TEXT,
status TEXT NOT NULL DEFAULT 'active',
FOREIGN KEY (champion_id) REFERENCES bots(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_seasons_status ON seasons(status);
CREATE INDEX IF NOT EXISTS idx_seasons_dates ON seasons(started_at, ended_at);

View file

@ -1,16 +1,25 @@
-- AI Code Battle D1 Schema
-- Phase 4: Match Orchestration
-- Complete schema with all tables from the implementation plan
-- ============================================
-- Core Tables (Phase 4)
-- ============================================
-- Bots table: stores registered bots
CREATE TABLE IF NOT EXISTS bots (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
name TEXT NOT NULL UNIQUE,
owner_id TEXT NOT NULL,
endpoint_url TEXT NOT NULL,
api_key_hash TEXT NOT NULL,
rating REAL NOT NULL DEFAULT 1500.0,
rating_deviation REAL NOT NULL DEFAULT 350.0,
rating_volatility REAL NOT NULL DEFAULT 0.06,
evolved INTEGER NOT NULL DEFAULT 0,
island TEXT,
generation INTEGER,
parent_ids TEXT,
description TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
last_health_check TEXT,
@ -21,6 +30,7 @@ CREATE TABLE IF NOT EXISTS bots (
CREATE INDEX IF NOT EXISTS idx_bots_owner ON bots(owner_id);
CREATE INDEX IF NOT EXISTS idx_bots_rating ON bots(rating DESC);
CREATE INDEX IF NOT EXISTS idx_bots_evolved ON bots(evolved);
-- Matches table: stores match metadata
CREATE TABLE IF NOT EXISTS matches (
@ -30,6 +40,7 @@ CREATE TABLE IF NOT EXISTS matches (
turns INTEGER,
end_reason TEXT,
map_id TEXT NOT NULL,
scores_json TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
started_at TEXT,
completed_at TEXT
@ -37,6 +48,7 @@ CREATE TABLE IF NOT EXISTS matches (
CREATE INDEX IF NOT EXISTS idx_matches_status ON matches(status);
CREATE INDEX IF NOT EXISTS idx_matches_created ON matches(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_matches_map ON matches(map_id);
-- Match participants: links bots to matches
CREATE TABLE IF NOT EXISTS match_participants (
@ -45,6 +57,7 @@ CREATE TABLE IF NOT EXISTS match_participants (
bot_id TEXT NOT NULL,
player_index INTEGER NOT NULL,
score INTEGER NOT NULL DEFAULT 0,
status TEXT,
rating_before REAL NOT NULL,
rating_after REAL,
rating_deviation_before REAL NOT NULL,
@ -97,12 +110,21 @@ CREATE TABLE IF NOT EXISTS maps (
id TEXT PRIMARY KEY,
width INTEGER NOT NULL,
height INTEGER NOT NULL,
player_count INTEGER NOT NULL DEFAULT 2,
walls TEXT NOT NULL,
spawns TEXT NOT NULL,
cores TEXT NOT NULL,
energy_nodes TEXT NOT NULL,
wall_density REAL NOT NULL DEFAULT 0.15,
status TEXT NOT NULL DEFAULT 'active',
engagement_score REAL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_maps_status ON maps(status);
CREATE INDEX IF NOT EXISTS idx_maps_player_count ON maps(player_count);
CREATE INDEX IF NOT EXISTS idx_maps_engagement ON maps(engagement_score DESC);
-- Bot secrets: stores API keys for bots (separate for security)
CREATE TABLE IF NOT EXISTS bot_secrets (
bot_id TEXT PRIMARY KEY,
@ -110,3 +132,132 @@ CREATE TABLE IF NOT EXISTS bot_secrets (
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (bot_id) REFERENCES bots(id) ON DELETE CASCADE
);
-- ============================================
-- Prediction System (Section 13.5)
-- ============================================
-- Predictions: visitor predictions on match outcomes
CREATE TABLE IF NOT EXISTS predictions (
id TEXT PRIMARY KEY,
match_id TEXT NOT NULL,
predictor_id TEXT NOT NULL,
predictor_name TEXT,
predicted_bot_id TEXT NOT NULL,
correct INTEGER,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE,
FOREIGN KEY (predicted_bot_id) REFERENCES bots(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_predictions_match ON predictions(match_id);
CREATE INDEX IF NOT EXISTS idx_predictions_predictor ON predictions(predictor_id);
-- Predictor stats: aggregate prediction accuracy
CREATE TABLE IF NOT EXISTS 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
);
CREATE INDEX IF NOT EXISTS idx_predictor_stats_rating ON predictor_stats(rating DESC);
-- ============================================
-- Map Voting (Section 13.6)
-- ============================================
-- Map votes: community voting on map quality
CREATE TABLE IF NOT EXISTS map_votes (
id TEXT PRIMARY KEY,
map_id TEXT NOT NULL,
voter_id TEXT NOT NULL,
vote INTEGER NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (map_id) REFERENCES maps(id) ON DELETE CASCADE,
UNIQUE(map_id, voter_id)
);
CREATE INDEX IF NOT EXISTS idx_map_votes_map ON map_votes(map_id);
-- ============================================
-- Replay Feedback (Section 13.6)
-- ============================================
-- Replay feedback: community annotations on replays
CREATE TABLE IF NOT EXISTS replay_feedback (
id TEXT PRIMARY KEY,
match_id TEXT NOT NULL,
turn INTEGER NOT NULL,
type TEXT NOT NULL,
body TEXT NOT NULL,
author TEXT NOT NULL,
upvotes INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_feedback_match ON replay_feedback(match_id, turn);
CREATE INDEX IF NOT EXISTS idx_feedback_type ON replay_feedback(type);
CREATE INDEX IF NOT EXISTS idx_feedback_upvotes ON replay_feedback(upvotes DESC);
-- ============================================
-- Multi-Game Series (Section 14.7)
-- ============================================
-- Series: best-of-N match series between two bots
CREATE TABLE IF NOT EXISTS series (
id TEXT PRIMARY KEY,
bot_a_id TEXT NOT NULL,
bot_b_id TEXT NOT NULL,
format INTEGER NOT NULL,
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 DEFAULT (datetime('now')),
completed_at TEXT,
FOREIGN KEY (bot_a_id) REFERENCES bots(id) ON DELETE CASCADE,
FOREIGN KEY (bot_b_id) REFERENCES bots(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_series_status ON series(status);
CREATE INDEX IF NOT EXISTS idx_series_bots ON series(bot_a_id, bot_b_id);
CREATE INDEX IF NOT EXISTS idx_series_season ON series(season_id);
-- Series games: individual games within a series
CREATE TABLE IF NOT EXISTS series_games (
series_id TEXT NOT NULL,
game_number INTEGER NOT NULL,
match_id TEXT,
map_id TEXT NOT NULL,
winner INTEGER,
PRIMARY KEY (series_id, game_number),
FOREIGN KEY (series_id) REFERENCES series(id) ON DELETE CASCADE,
FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_series_games_match ON series_games(match_id);
-- ============================================
-- Seasonal Rotations (Section 14.9)
-- ============================================
-- Seasons: seasonal leaderboards with rule variations
CREATE TABLE IF NOT EXISTS seasons (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
theme TEXT NOT NULL,
rules_version INTEGER NOT NULL DEFAULT 1,
started_at TEXT NOT NULL,
ended_at TEXT,
champion_id TEXT,
status TEXT NOT NULL DEFAULT 'active',
FOREIGN KEY (champion_id) REFERENCES bots(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_seasons_status ON seasons(status);
CREATE INDEX IF NOT EXISTS idx_seasons_dates ON seasons(started_at, ended_at);

View file

@ -7,6 +7,7 @@ compatibility_flags = ["nodejs_compat"]
binding = "DB"
database_name = "acb-db"
database_id = "placeholder-will-be-set-on-deploy"
migrations_dir = "migrations"
[vars]
ENVIRONMENT = "development"