From 24859669fce497026fc1a14212455bcee60b93fc Mon Sep 17 00:00:00 2001 From: jedarden Date: Tue, 24 Mar 2026 09:52:53 -0400 Subject: [PATCH] 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 --- PROGRESS.md | 10 +- worker-api/migrations/0001_initial.sql | 264 +++++++++++++++++++++++++ worker-api/schema.sql | 155 ++++++++++++++- worker-api/wrangler.toml | 1 + 4 files changed, 427 insertions(+), 3 deletions(-) create mode 100644 worker-api/migrations/0001_initial.sql diff --git a/PROGRESS.md b/PROGRESS.md index 44bb324..ba80d8b 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -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 diff --git a/worker-api/migrations/0001_initial.sql b/worker-api/migrations/0001_initial.sql new file mode 100644 index 0000000..ae14c25 --- /dev/null +++ b/worker-api/migrations/0001_initial.sql @@ -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); diff --git a/worker-api/schema.sql b/worker-api/schema.sql index d8506c0..96176db 100644 --- a/worker-api/schema.sql +++ b/worker-api/schema.sql @@ -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); diff --git a/worker-api/wrangler.toml b/worker-api/wrangler.toml index 840a0cb..bffb02a 100644 --- a/worker-api/wrangler.toml +++ b/worker-api/wrangler.toml @@ -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"