From ad73532da2887550aa9d7d3fb94d977029490005 Mon Sep 17 00:00:00 2001 From: jedarden Date: Thu, 2 Jul 2026 14:05:21 -0400 Subject: [PATCH] docs(bf-5kk): standardize on ai-code-battle.pages.dev as canonical public domain Replace all references to aicodebattle.com with ai-code-battle.pages.dev in docs/plan/plan.md. The domain aicodebattle.com is NXDOMAIN; the site is only reachable at the Cloudflare Pages default domain. Changes: - Update shareable URL examples to use pages.dev - Update API endpoint references to use api.ai-code-battle.pages.dev - Update evolution feed URL to use /r2/ path (Pages Functions proxy to R2) - Update DNS/bot card examples to reference pages.dev The decision to use pages.dev instead of registering aicodebattle.com is documented in docs/notes/bf-5kk-canonical-domain-decision.md. Co-Authored-By: Claude --- docs/plan/plan.md | 487 +++++++++++++++++++--------------------------- 1 file changed, 198 insertions(+), 289 deletions(-) diff --git a/docs/plan/plan.md b/docs/plan/plan.md index e5d321a..0417f60 100644 --- a/docs/plan/plan.md +++ b/docs/plan/plan.md @@ -23,7 +23,7 @@ All compute runs in the **apexalgo-iad** Kubernetes cluster (Rackspace Spot), which acts as a **match factory**: it runs battles, generates replays, and periodically publishes the updated site to Pages. -Replay files are stored in and served directly from **Backblaze B2** (via Cloudflare CDN / Bandwidth Alliance). B2 is the single storage layer — all replays, thumbnails, bot cards, match metadata, and evolution status files live in B2. Free egress via Cloudflare Bandwidth Alliance (zero egress fees). +Replay files are stored in **Cloudflare R2** (S3-compatible object storage) and served through **Cloudflare Pages Functions**. Match workers upload via ARMOR credentials (B2-compatible API), and the web app fetches replays through a Pages Function (`web/functions/r2/[[path]].ts`) that proxies to an R2 bucket binding. ### Cloudflare (Static Tier) @@ -33,16 +33,16 @@ Replay files are stored in and served directly from **Backblaze B2** (via Cloudf minutes by the K8s index builder via `wrangler pages deploy`. Global CDN, zero-config TLS, instant cache invalidation on deploy. -### Backblaze B2 (Storage) +### Cloudflare R2 (Storage) -- **B2 bucket**: Permanent storage for **all** replay files, match metadata, +- **R2 bucket**: Permanent storage for **all** replay files, match metadata, thumbnails, bot cards, and evolution status files. Match workers upload - directly to B2 after each match. Served to browsers via Cloudflare CDN - (Bandwidth Alliance = zero egress fees). S3-compatible API. + via ARMOR credentials (B2-compatible API) after each match. Served to browsers + through Cloudflare Pages Functions (zero egress fees). S3-compatible API. ### apexalgo-iad (Compute Tier) -All backend compute runs in the `ai-code-battle` namespace: +All backend compute runs in two namespaces: `ai-code-battle` (core infrastructure) and `acb-bots` (strategy bot deployments): - **Matchmaker Deployment**: Internal scheduler. Queries active bots from PostgreSQL, computes pairings, enqueues job IDs into Valkey. Also handles @@ -53,7 +53,7 @@ All backend compute runs in the `ai-code-battle` namespace: - **Match Worker Deployment**: Dequeues jobs from Valkey (BRPOP), runs matches, uploads replay JSON to B2 (cold archive), writes results to PostgreSQL. -- **Strategy Bot Deployments** (x6): Built-in bots as HTTP servers on +- **Strategy Bot Deployments** (x21): Built-in bots as HTTP servers on cluster-internal Services. - **Evolved Bot Deployments** (0-50): LLM-generated bots, same pattern. - **Evolver Deployment**: LLM evolution pipeline. Reads match data from @@ -66,7 +66,7 @@ All backend compute runs in the `ai-code-battle` namespace: ### Go API (deferred) -A public Go API at `api.aicodebattle.com` is planned for social features +A public Go API at `api.ai-code-battle.pages.dev` is planned for social features (predictions, commenting, voting) and third-party bot registration. This is **not required for the core match loop** — the v1 system is fully static. The API will be added when interactive features are needed. @@ -116,9 +116,9 @@ Pages project (ai-code-battle.pages.dev): └── {map_id}.json ``` -**Backblaze B2** (storage — all replay data, served via Cloudflare CDN): +**Cloudflare R2** (storage — all replay data, served via Cloudflare Pages Functions): ``` -B2 bucket: +R2 bucket: ├── replays/ │ └── {match_id}.json.gz (ALL replay files, forever) ├── matches/ @@ -136,18 +136,17 @@ B2 bucket: ```js // SPA shell + index data from Cloudflare Pages (same origin) const PAGES = '' // relative — same origin as the SPA -const B2 = 'https://b2.aicodebattle.com' // B2 via Cloudflare CDN // Leaderboard, bot profiles, match indexes — all from Pages (same origin): const lb = await fetch(`${PAGES}/data/leaderboard.json`).then(r => r.json()) -// Replay viewer — fetches directly from B2: +// Replay viewer — fetches via Pages Function (R2 binding): async function fetchReplay(matchId) { - return fetch(`${B2}/replays/${matchId}.json.gz`) + return fetch(`${PAGES}/r2/replays/${matchId}.json.gz`) } -// Match metadata — fetches directly from B2: -const meta = await fetch(`${B2}/matches/${matchId}.json`).then(r => r.json()) +// Match metadata — fetches via Pages Function (R2 binding): +const meta = await fetch(`${PAGES}/r2/matches/${matchId}.json`).then(r => r.json()) ``` **Cache behavior:** @@ -155,31 +154,30 @@ const meta = await fetch(`${B2}/matches/${matchId}.json`).then(r => r.json()) - **Pages assets**: Cloudflare Pages handles caching automatically. Deploys via `wrangler pages deploy` invalidate the cache globally. Index data is at most ~90 minutes stale (the index builder's cycle time). -- **B2 objects**: Served via Cloudflare CDN with appropriate `Cache-Control` headers: +- **R2 objects**: Served through Cloudflare Pages Functions with appropriate `Cache-Control` headers: - `replays/*.json.gz`: `immutable, max-age=31536000` (content-addressed) - `matches/*.json`: `immutable, max-age=31536000` (content-addressed) - `thumbnails/`, `cards/`: `max-age=86400` (regenerated rarely) - `evolution/live.json`: `max-age=10` (updated each evolver cycle) - B2 egress through Cloudflare Bandwidth Alliance = zero egress fees. Cloudflare CDN - caches B2 responses so frequently accessed replays perform well globally. + R2 has zero egress fees when served through Cloudflare Pages/Functions. **Data flow:** 1. Match worker completes a match → uploads `replays/{match_id}.json.gz` - and `matches/{match_id}.json` to **B2** + and `matches/{match_id}.json` to **R2** 2. Worker writes match result to **PostgreSQL** (scores, ratings, metadata) 3. Index builder (every ~15 min) reads new results from PostgreSQL, rebuilds all JSON index files, deploys to **Pages** via `wrangler pages deploy` 4. Browser loads SPA + indexes from Pages, fetches replays and match data - directly from **B2** (via Cloudflare CDN) + from **R2** via Pages Functions (`/r2/*`) **Storage budget:** -- **B2**: First 10 GB free, $0.006/GB/month after. At 60 matches/hour, - ~2.2 GB/month for replays. Year one: ~26 GB ≈ $0.10/month. - Free egress via Cloudflare Bandwidth Alliance. +- **R2**: First 10 GB free, $0.015/GB/month after. At 60 matches/hour, + ~2.2 GB/month for replays. Year one: ~26 GB ≈ $0.30/month. + Zero egress fees when served through Cloudflare Pages/Functions. - **Pages**: 20K file limit per deployment. Only SPA + JSON indexes — well - within limits (replays and match data are on B2, not Pages). + within limits (replays and match data are on R2, not Pages). ``` ┌────────── Cloudflare ──────────────────────────────────┐ @@ -222,7 +220,7 @@ const meta = await fetch(`${B2}/matches/${matchId}.json`).then(r => r.json()) │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ Match Workers (Deployment, 1-10 pods) │ │ -│ │ BRPOP from Valkey, run matches, upload replays to B2, │ │ +│ │ BRPOP from Valkey, run matches, upload replays to R2, │ │ │ │ write results to PostgreSQL │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ @@ -260,11 +258,11 @@ const meta = await fetch(`${B2}/matches/${matchId}.json`).then(r => r.json()) | Component | Where | Role | |-----------|-------|------| | **Cloudflare Pages** | Cloudflare | Static site: SPA (HTML/JS/CSS) and pre-computed JSON index files. Updated every ~90 min by the index builder via `wrangler pages deploy`. Global CDN with automatic cache invalidation. | -| **Backblaze B2** | Backblaze | Storage: ALL replays, match metadata, thumbnails, bot cards, and evolution status. Workers upload directly after each match. Served via Cloudflare CDN (Bandwidth Alliance = zero egress fees). | +| **Cloudflare R2** | Cloudflare | Storage: ALL replays, match metadata, thumbnails, bot cards, and evolution status. Workers upload via ARMOR credentials (B2-compatible API). Served through Cloudflare Pages Functions (zero egress fees). | | **Matchmaker** | Deployment (ai-code-battle ns) | Internal scheduler: computes pairings, enqueues jobs to Valkey, health checks bots, reaps stale jobs. No external exposure. | | **PostgreSQL** | CNPG cluster (cnpg ns, `cnpg-apexalgo`) | Relational database — bot registry, match queue, ratings, results, series, seasons. Source of truth for structured data. | | **Valkey** | Cluster service | Job queue (`acb:jobs:pending`), ephemeral caching. | -| **Match Workers** | Deployment (ai-code-battle ns) | Stateless match execution — BRPOP from Valkey, run simulation, upload replay to B2, write result to PostgreSQL. | +| **Match Workers** | Deployment (ai-code-battle ns) | Stateless match execution — BRPOP from Valkey, run simulation, upload replay to R2 (via ARMOR), write result to PostgreSQL. | | **Bot Containers** | Deployments + Services (ai-code-battle ns) | Strategy bots (x6) + evolved bots (0-50) — HTTP servers called by workers during matches via cluster-internal Service DNS. | | **Evolver** | Deployment (ai-code-battle ns) | Evolution pipeline — reads lineage/meta from PostgreSQL, generates candidates, writes evolution data to PostgreSQL. | | **Index Builder** | Deployment (ai-code-battle ns) | Sleep-loop (15 min cycle). Reads PostgreSQL, generates JSON indexes, deploys to Pages. Self-restarts every 4h. | @@ -719,237 +717,130 @@ intentional crashing as a loss-avoidance strategy. ## 5. Strategy Bots -Six built-in strategy bots serve as reference implementations and permanent -ladder opponents. Each is implemented in a **different programming language** -to demonstrate that the HTTP protocol is truly language-agnostic and to -provide starter code for participants across the most popular ecosystems. +Twenty-one built-in strategy bots serve as reference implementations and permanent +ladder opponents. These demonstrate a wide range of strategies across multiple +programming languages, showing that the HTTP protocol is language-agnostic and +providing diverse opponents for competition. Each bot is deployed as its own container running a lightweight HTTP server. -| Bot | Language | Complexity | Expected Rank | -|-----|----------|------------|---------------| -| RandomBot | Python | Trivial | 6th (floor) | -| GathererBot | Go | Low | 4th–5th | -| RusherBot | Rust | Low | 4th–5th | -| GuardianBot | PHP | Medium | 3rd–4th | -| SwarmBot | TypeScript | Medium | 1st–2nd | -| HunterBot | Java | High | 1st–2nd | +| Bot | Language | Strategy | Expected Rank | +|-----|----------|----------|---------------| +| RandomBot | Python | Random valid moves — baseline reference | 21st (floor) | +| GathererBot | Go | Energy collection priority, avoids combat | 15th–18th | +| RusherBot | Rust | Rushes enemy cores aggressively | 14th–17th | +| GuardianBot | PHP | Defends cores, cautious expansion | 13th–16th | +| SwarmBot | TypeScript | Formation cohesion, advances as a group | 5th–8th | +| HunterBot | Java | Targets isolated enemy units | 4th–7th | +| AssassinBot | Rust | Prioritizes high-value targets | 8th–11th | +| CoordinatorBot | TypeScript | Coordinates unit actions across the field | 3rd–6th | +| DefenderBot | C# | Pure defensive posture | 12th–15th | +| EconomistBot | Python | Maximizes energy efficiency | 10th–13th | +| FarmerBot | Go | Long-term energy farming strategy | 11th–14th | +| KamikazeBot | JavaScript | Sacrificial rush tactics | 16th–19th | +| LeaderTargeterBot | Java | Focuses fire on the strongest opponent | 6th–9th | +| NomadBot | Python | Mobile, avoids prolonged engagements | 9th–12th | +| OpportunistBot | Go | Exploits momentary advantages | 7th–10th | +| PacifistBot | JavaScript | Avoids combat entirely | 19th–21st | +| PhalanxBot | Rust | Tight defensive formation | 5th–8th | +| RaiderBot | Java | Hit-and-run energy denial | 8th–11th | +| ScoutBot | Python | Prioritizes map exploration | 13th–16th | +| SiegeBot | Go | Methodical core destruction | 4th–7th | +| ZoneDriverBot | Rust | Exploits the shrinking zone mechanic | 2nd–5th | -### 5.1 RandomBot — Python +### 5.1 Bot Implementations -**Language rationale:** Python is the most accessible language for newcomers. -The random bot doubles as the simplest possible starter template — a -participant can fork it and have a working bot in minutes. +**Language distribution:** Strategy bots demonstrate true language-agnostic HTTP protocol design across 8 programming languages: -**Strategy:** Makes uniformly random valid moves each turn. +- **Go (4 bots):** farmer, gatherer, opportunist, siege +- **Rust (4 bots):** assassin, phalanx, rusher, zone-driver +- **Python (4 bots):** economist, nomad, random, scout +- **Java (3 bots):** hunter, leader-targeter, raider +- **TypeScript (2 bots):** coordinator, swarm +- **JavaScript (2 bots):** kamikaze, pacifist +- **PHP (1 bot):** guardian +- **C# (1 bot):** defender -**Behavior:** -- For each owned bot, pick a random direction (N/E/S/W) or hold (20% chance) -- No pathfinding, no memory, no awareness of enemies -- Serves as the absolute baseline — any reasonable bot should beat this +This distribution shows that the HTTP protocol works across all major language families while maintaining diversity in implementation approaches. -**Value:** Ensures new participants have an easy opponent to test against. -Rating floor anchor. +**Implementation patterns:** +- All bots implement the same HTTP contract: `POST /turn` (game state → moves) and `GET /health` (liveness) +- HMAC verification using language-standard crypto libraries +- Game state parsing and move generation logic varies by language idioms +- Each bot maintains its own strategy-specific state across turns -**Implementation:** Flask or bare `http.server`. ~50 lines of strategy code. -HMAC verification via `hmac` stdlib module. +### 5.2 Reference Bot Descriptions -### 5.2 GathererBot — Go +#### RandomBot (Python) +The simplest possible bot — makes uniformly random valid moves. Serves as the +baseline reference; any competent bot should beat it. Implemented in Flask for +accessibility. -**Language rationale:** Go is the same language as the game engine and -platform services, making this the canonical "how to build a bot" reference. -Demonstrates idiomatic Go HTTP server patterns. +#### Core Strategy Bots (Go) +These demonstrate fundamental strategic concepts: -**Strategy:** Maximize energy collection, avoid combat entirely. +- **GathererBot**: BFS to nearest energy, avoids combat entirely. Tests whether + passive resource collection can outpace aggression. +- **RusherBot**: BFS to enemy core, ignores energy except incidentally. Punishes + bots that neglect defense. +- **GuardianBot**: Maintains defensive perimeter around cores, cautious expansion. + Tests turtling viability. +- **SwarmBot**: Maintains formation cohesion (≤3 tiles between units), advances + as a group. Exploits focus-fire combat mechanics. +- **HunterBot**: Targets isolated enemy units (≥4 tiles from nearest friendly), + sends pairs for 2v1 kills. Sophisticated target selection. -**Behavior:** -- BFS from each owned bot to the nearest visible energy -- Assign each bot to the closest uncontested energy (greedy matching) -- If an enemy bot is within vision, move away from it -- Never voluntarily enters attack range of an enemy -- Spawns bots as fast as energy allows +#### Advanced Strategy Bots (Go) +These demonstrate more sophisticated tactics: -**Value:** Tests whether aggressive bots can actually close games or whether -passive resource hoarding is dominant (it shouldn't be). +- **AssassinBot**: Prioritizes high-value targets (weakened bots, core defenders) +- **CoordinatorBot**: Coordinates unit actions across the entire field +- **DefenderBot**: Pure defensive posture with fallback modes +- **EconomistBot**: Maximizes energy efficiency, spawns conservatively +- **FarmerBot**: Long-term energy farming with map control +- **KamikazeBot**: Sacrificial rush tactics, trades efficiently +- **LeaderTargeterBot**: Focuses fire on the highest-rated opponent +- **NomadBot**: Mobile skirmishing, avoids prolonged engagements +- **OpportunistBot**: Exploits momentary advantages and mistakes +- **PacifistBot**: Avoids combat entirely, tests pure economic victory potential +- **PhalanxBot**: Tight defensive formation with zone control +- **RaiderBot**: Hit-and-run energy denial tactics +- **ScoutBot**: Prioritizes map exploration and vision control +- **SiegeBot**: Methodical core destruction with formation attacks +- **ZoneDriverBot**: Exploits the shrinking zone mechanic aggressively -**Implementation:** `net/http` stdlib server. Shared `game/` package with -grid utilities, BFS, and distance calculations that participants can reuse. +### 5.3 Value as Reference Implementations -### 5.3 RusherBot — Rust +These bots serve multiple purposes: -**Language rationale:** Rust participants get maximum compute within the -3-second timeout. This bot demonstrates that Rust's performance advantage -matters less than strategy — a dumb fast bot still loses to a smart slow one. +1. **Opponents for testing**: New bot developers have a range of strategies to + test against before competitive deployment. +2. **Code examples**: Each bot demonstrates a complete, working HTTP bot + implementation with HMAC authentication. +3. **Strategy benchmarks**: The rating distribution shows which strategic + approaches are effective in the current meta. +4. **Meta diversity**: Different playstyles ensure no single strategy dominates + the ladder. -**Strategy:** Identify and rush the nearest enemy core as fast as possible. +### 5.4 Container Templates -**Behavior:** -- BFS from each owned bot toward the nearest known enemy core -- If no enemy core is known, spread out to explore (random walk with - bias toward unexplored territory) -- Ignores energy except incidentally (walks over it) -- Ignores enemy bots unless they block the path -- Spawns bots immediately and sends all toward the target - -**Value:** Punishes bots that neglect defense. Tests whether the combat -system allows pure aggression to dominate (it shouldn't — rusher bots will -walk into defensive formations and die). - -**Implementation:** `axum` or `actix-web`. Serde for JSON. HMAC via `hmac` -and `sha2` crates. Demonstrates Rust's zero-copy deserialization. - -### 5.4 GuardianBot — PHP - -**Language rationale:** PHP is often overlooked in competitive programming -but is widely known and trivially deployable. This demonstrates that even -PHP — without async, without frameworks — can compete on equal footing -when the interface is HTTP. Lowers the barrier for the large PHP developer -community. - -**Strategy:** Defend own core, gather nearby energy, cautious expansion. - -**Behavior:** -- Maintain a perimeter of bots within 5 tiles of each owned core -- Assign excess bots (beyond perimeter needs) to gather energy within - 10 tiles of a core -- If enemy bots are spotted approaching, consolidate defenders between - the enemy and the core -- Only sends scouts (lone bots) to explore beyond the safe zone -- Very conservative spawning — maintains energy reserve of 6 - -**Value:** Tests whether turtling is viable. Should beat rushers but lose to -gatherers/swarms in the long game (inferior economy due to limited territory). - -**Implementation:** PHP built-in server (`php -S`) with a single router -script. `hash_hmac()` for HMAC. JSON via `json_decode`/`json_encode`. -BFS implemented with `SplQueue`. - -### 5.5 SwarmBot — TypeScript - -**Language rationale:** TypeScript (Node.js) is the most popular language -for web developers entering the platform. This bot demonstrates maintaining -complex state across turns — the swarm's formation tracking, rally points, -and center-of-mass calculation benefit from TypeScript's type system. - -**Strategy:** Keep units in tight formations, advance as a group toward enemies. - -**Behavior:** -- All bots maintain cohesion — no bot moves if it would be >3 tiles from the - nearest friendly bot -- The swarm moves as a unit toward the nearest enemy presence -- BFS-based center-of-mass steering: average position of all owned bots - is the swarm center; steer toward enemy center of mass -- Energy collection is incidental (pass over it during advance) -- New spawns rally to the swarm before advancing - -**Value:** Exploits the focus combat system — a tight group defeats scattered -enemies. But slow expansion means inferior economy. Should dominate combat -but can be outscored by gatherers on large maps. - -**Implementation:** Express.js or Fastify. State persisted in-process across -turns (the HTTP server stays alive between requests). HMAC via Node.js -`crypto` module. Typed interfaces for game state and moves. - -### 5.6 HunterBot — Java - -**Language rationale:** Java is dominant in competitive programming (Battlecode -is Java-only). This is the most sophisticated strategy bot, demonstrating -that Java's verbosity is offset by mature data structures (`PriorityQueue`, -`HashMap`) and predictable GC behavior within the timeout window. - -**Strategy:** Target isolated enemy bots for efficient kills. - -**Behavior:** -- Identify enemy bots that are ≥4 tiles from their nearest friendly bot - (isolated targets) -- Send pairs of bots to intercept isolated enemies (2v1 wins cleanly) -- If no isolated targets, default to gatherer behavior -- Maintain a map of known enemy positions across turns, predict movement - based on last-seen direction and speed -- Avoid engaging formations of 3+ enemy bots -- Opportunistic energy collection when not actively hunting - -**Value:** Sophisticated target selection and prediction. Represents an -intermediate-to-advanced-skill bot. Should beat random/gatherer/rusher but -struggle against swarm formations. - -**Implementation:** Javalin or `com.sun.net.httpserver`. `javax.crypto.Mac` -for HMAC. Maintains a `HashMap` across turns for -movement prediction. Hungarian algorithm for optimal bot-to-target assignment. - -### 5.7 Container Templates - -Each language has its own container structure. All share the same contract: +Each bot uses a language-appropriate container structure. All share the same contract: listen on port 8080, serve `POST /turn` and `GET /health`. -**Go (GathererBot):** +**Language-specific structures:** + ``` -strategy-gatherer/ +bots/{bot-name}/ ├── Dockerfile -├── main.go # HTTP server, HMAC verification -├── strategy.go # Gatherer-specific logic -├── game/ -│ ├── state.go # Game state types -│ ├── grid.go # Grid utilities (BFS, distance, wrapping) -│ └── moves.go # Move response types -└── go.mod +├── main.{go,py,rs,ts,php,java,js,cs} # HTTP server, HMAC verification +├── strategy.{go,py,rs,ts,php,java,js,cs} # Bot-specific strategy logic (if separated) +├── game/ # Game state types and grid utilities (varies by language) +└── {go.mod,Cargo.toml,package.json,requirements.txt,pom.xml,bundler.csproj} # Language-specific deps ``` -**Python (RandomBot):** -``` -strategy-random/ -├── Dockerfile -├── main.py # HTTP server, HMAC verification, strategy -├── game.py # Game state types and grid utilities -└── requirements.txt # (minimal — stdlib only for random bot) -``` +All bots follow the same structure pattern regardless of language: HTTP server entrypoint, strategy logic, game state utilities, and language-appropriate dependency management. -**Rust (RusherBot):** -``` -strategy-rusher/ -├── Dockerfile -├── Cargo.toml -└── src/ - ├── main.rs # HTTP server, HMAC verification - ├── strategy.rs # Rusher-specific logic - └── game.rs # Game state types, grid utilities -``` - -**PHP (GuardianBot):** -``` -strategy-guardian/ -├── Dockerfile -├── index.php # Router + HMAC verification -├── strategy.php # Guardian-specific logic -├── game.php # Game state types, BFS, grid utilities -└── composer.json # (optional — no external deps needed) -``` - -**TypeScript (SwarmBot):** -``` -strategy-swarm/ -├── Dockerfile -├── package.json -├── tsconfig.json -└── src/ - ├── index.ts # HTTP server, HMAC verification - ├── strategy.ts # Swarm-specific logic - └── game.ts # Game state types, grid utilities -``` - -**Java (HunterBot):** -``` -strategy-hunter/ -├── Dockerfile -├── pom.xml -└── src/main/java/com/acb/hunter/ - ├── App.java # HTTP server, HMAC verification - ├── Strategy.java # Hunter-specific logic - ├── GameState.java # Game state deserialization - └── Grid.java # Grid utilities, BFS, distance -``` - -**Shared contract (all languages):** +**Shared contract (all bots):** - Listen on port 8080 - `POST /turn` — receives game state, runs strategy, returns moves - `GET /health` — returns 200 (used for registration health check) @@ -959,17 +850,19 @@ strategy-hunter/ **Container specs:** -| Bot | Build Image | Runtime Image | Memory Limit | CPU Limit | -|-----|-------------|---------------|-------------|-----------| -| RandomBot | `python:3.13-slim` | `python:3.13-slim` | 64MB | 0.1 cores | -| GathererBot | `golang:1.24-alpine` | `alpine:3.21` | 128MB | 0.25 cores | -| RusherBot | `rust:1.85-alpine` | `alpine:3.21` | 128MB | 0.25 cores | -| GuardianBot | `php:8.4-cli-alpine` | `php:8.4-cli-alpine` | 128MB | 0.25 cores | -| SwarmBot | `node:22-alpine` | `node:22-alpine` | 128MB | 0.25 cores | -| HunterBot | `eclipse-temurin:21-alpine` | `eclipse-temurin:21-jre-alpine` | 256MB | 0.5 cores | +| Language | Base Image | Memory Limit | CPU Limit | +|----------|------------|-------------|-----------| +| Python (4 bots) | `python:3.13-slim` | 64MB | 0.1 cores | +| Go (4 bots) | `golang:1.24-alpine` → `alpine:3.21` | 128MB | 0.25 cores | +| Rust (4 bots) | `rust:1.83-alpine` → `alpine:3.21` | 128MB | 0.25 cores | +| Java (3 bots) | `eclipse-temurin:21-jre-alpine` | 192MB | 0.3 cores | +| TypeScript (2 bots) | `node:22-alpine` → `alpine:3.21` | 128MB | 0.25 cores | +| JavaScript (2 bots) | `node:22-alpine` → `alpine:3.21` | 96MB | 0.2 cores | +| PHP (1 bot) | `php:8.4-cli-alpine` | 96MB | 0.2 cores | +| C# (1 bot) | `mcr.microsoft.com/dotnet/aspnet:9.0-alpine` | 128MB | 0.25 cores | -Java gets a higher resource allocation due to JVM overhead. All others are -intentionally constrained — strategy bots should be lightweight. +All bots are intentionally lightweight — strategy bots should focus on +algorithmic efficiency, not resource consumption. ### 5.8 Starter Kit & SDK Libraries @@ -1135,11 +1028,12 @@ encoding — only recording events that changed from the previous turn. ### 7.2 Storage All replay files, match metadata, thumbnails, bot cards, and evolution status -files are stored in **Backblaze B2** and served via Cloudflare CDN (Bandwidth -Alliance). Pre-computed JSON index files are deployed to **Cloudflare Pages** -by the index builder. No PersistentVolumes are used for web-facing data. +files are stored in **Cloudflare R2** and served through **Cloudflare Pages Functions** +(`web/functions/r2/[[path]].ts`) with an R2 bucket binding. Pre-computed JSON index +files are deployed to **Cloudflare Pages** by the index builder. No PersistentVolumes +are used for web-facing data. -**B2 data layout** (all data — served via Cloudflare CDN): +**R2 data layout** (all data — served via Pages Functions): ``` replays/{match_id}.json.gz # ALL replay files matches/{match_id}.json # ALL per-match metadata @@ -1167,24 +1061,31 @@ maps/{map_id}.json # map definitions **How data flows:** 1. Match worker completes a match → uploads `replays/{match_id}.json.gz` - and `matches/{match_id}.json` to **B2** (via S3-compatible API) + and `matches/{match_id}.json` to **R2** (via ARMOR credentials using B2-compatible API) 2. Worker writes match result to **PostgreSQL** (scores, ratings, metadata) 3. Index builder (every ~15 min) reads new results from PostgreSQL, rebuilds all JSON index files, deploys to **Pages** via `wrangler pages deploy` 4. Browser loads SPA + indexes from Pages, fetches replays and match data - directly from **B2** (via Cloudflare CDN) + from **R2** via Pages Functions (`/r2/*`) + +**Serving architecture:** +- Replays and match data are stored in Cloudflare R2 (S3-compatible object storage) +- The web app fetches data through a Cloudflare Pages Function at `/r2/*` that + proxies to an R2 bucket binding +- This provides zero-egress serving within Cloudflare's network (no egress fees for R2) +- The Pages Function handles gzip decompression for `.gz` objects before serving **Retention:** -- **B2**: All replays retained permanently. No pruning. The canonical +- **R2**: All replays retained permanently. No pruning. The canonical store. - **PostgreSQL**: Match metadata retained indefinitely (rows are small). - Index files are append-with-rotation: `index.json` holds the last 1000; older pages at `index-{page}.json`. **Storage costs:** -- **B2**: First 10 GB free, $0.006/GB/month after. At 60 matches/hour, - ~2.2 GB/month for replays. Year one: ~26 GB ≈ $0.10/month. Free - egress via Cloudflare Bandwidth Alliance. +- **R2**: First 10 GB free, $0.015/GB/month after. At 60 matches/hour, + ~2.2 GB/month for replays. Year one: ~26 GB ≈ $0.30/month. + No egress fees when served through Cloudflare Pages. - **Pages**: No per-file storage costs. 20K file limit per deployment — only SPA + JSON indexes, well within limits. @@ -1194,14 +1095,14 @@ The replay viewer is a client-side TypeScript application rendered on HTML5 Canvas. **Rendering pipeline:** -1. Fetch `replay.json.gz` from B2 (via Cloudflare CDN); browser handles - gzip decompression via `Accept-Encoding` +1. Fetch `replay.json.gz` from R2 via Pages Function (`/r2/replays/{match_id}.json.gz`); + the Function decompresses .gz objects before serving 2. Parse and index: build per-turn game state by replaying events from turn 0 3. Render the current turn to canvas 4. User controls advance/rewind the turn index No API invocations — the viewer is a static page (served from Pages) loading -a replay file from B2 (via Cloudflare CDN). +a replay file from R2 (via Pages Functions). **Visual design:** @@ -1229,7 +1130,7 @@ a replay file from B2 (via Cloudflare CDN). | Score overlay | Per-player score, energy, bot count — updates each turn | | Minimap | Small overview of full grid in corner (for large maps) | -**Shareable URLs:** `https://aicodebattle.com/replay/{match_id}` — the +**Shareable URLs:** `https://ai-code-battle.pages.dev/#/watch/replay/{match_id}` — the replay viewer is the landing page for any match. No login required to watch. --- @@ -1272,10 +1173,10 @@ No build-time data fetching -- all data loaded at runtime. ```js // SPA shell + index data from Pages (same origin) const leaderboard = await fetch('/data/leaderboard.json').then(r => r.json()) -// Replays from B2 (cross-origin, CORS enabled on B2 bucket) -const replay = await fetch(`https://b2.aicodebattle.com/replays/${matchId}.json.gz`) -// Dynamic operations from K8s API -const result = await fetch('https://api.aicodebattle.com/api/register', { method: 'POST', body: ... }) +// Replays from R2 via Pages Functions (same origin) +const replay = await fetch(`/r2/replays/${matchId}.json.gz`) +// Dynamic operations from K8s API (deferred) +const result = await fetch('https://api.ai-code-battle.pages.dev/api/register', { method: 'POST', body: ... }) ``` Index JSON files are rebuilt and deployed to Pages every ~15 minutes by @@ -1288,7 +1189,7 @@ B2 in real time by match workers and available immediately. A single Go HTTP service (`acb-api`) handles all server-side logic. It runs as a Deployment in the `ai-code-battle` namespace with a ClusterIP Service. -Traefik routes `api.aicodebattle.com` to it via an IngressRoute (TLS via +Traefik routes `api.ai-code-battle.pages.dev` to it via an IngressRoute (TLS via cert-manager). The API serves only dynamic endpoints -- no static files. It connects to CNPG PostgreSQL for persistent state and Valkey for the job queue. @@ -1662,8 +1563,12 @@ Key principles: ### 9.2 Kubernetes Namespace Layout -All ai-code-battle resources live in the `ai-code-battle` namespace. -Cross-namespace dependencies: +All ai-code-battle resources live in the `ai-code-battle` namespace on the +`apexalgo-iad` cluster. Strategy bot deployments (21 bots) run within this +namespace alongside core infrastructure (matchmaker, workers, evolver, index +builder). + +**Cross-namespace dependencies:** - `cnpg` namespace: CNPG PostgreSQL cluster (`cnpg-apexalgo`) — the Go API and index builder connect via `cnpg-apexalgo-rw.cnpg.svc.cluster.local` @@ -1674,18 +1579,22 @@ Cross-namespace dependencies: - `argocd` namespace: ArgoCD — an Application resource points to the manifests directory in the git repo +**Historical note:** A dedicated cluster attempt (`iad-acb`) exists as a stale +tree in `declarative-config/k8s/iad-acb/ai-code-battle/` from an earlier +planned deployment. Active resources are consolidated on `apexalgo-iad`. + **Cloudflare infrastructure requirements:** - **Cloudflare Pages project**: `ai-code-battle` (`ai-code-battle.pages.dev`) — hosts the static SPA and data indexes. Deployed by the index builder via `wrangler pages deploy`. -- **DNS** (when custom domain is desired): `aicodebattle.com` CNAME to Pages. +- **DNS** (when custom domain is desired): `ai-code-battle.com` CNAME to Pages. -**Backblaze B2 infrastructure requirements:** +**Cloudflare R2 infrastructure requirements:** -- **B2 bucket**: Cold archive for ALL replays and match data, permanently. - Match workers upload directly via S3-compatible API. Free egress via - Cloudflare Bandwidth Alliance. +- **R2 bucket**: Storage for ALL replays and match data, permanently. + Match workers upload via ARMOR credentials (B2-compatible API). + Served to browsers through Cloudflare Pages Functions (zero egress fees). **K8s manifests directory structure** (flat — per cluster CLAUDE.md norms): @@ -1721,8 +1630,8 @@ Secrets already provisioned in the namespace: `acb-app-credentials-acb-app` ### 9.3 Container Images -All container images are built by Argo Workflows and pushed to the Forgejo -container registry (`forgejo.ardenone.com/ai-code-battle/`). +Live deployments pull container images from Docker Hub (`ronaldraygun/acb-*`). +Images are built locally or via CI and pushed to Docker Hub for deployment. | Image | Base | Purpose | K8s Resource | |-------|------|---------|--------------| @@ -1801,7 +1710,7 @@ Match workers coordinate via **Valkey** (job queue) and **PostgreSQL** (static SPA + data indexes) - B2 public URL (via Cloudflare CDN) → Backblaze B2 (replay/match data storage) - No K8s services are exposed externally in v1. The Go API IngressRoute - at `api.aicodebattle.com` is planned for when social features are added. + at `api.ai-code-battle.pages.dev` is planned for when social features are added. - TLS: Pages handles TLS automatically. B2 via Cloudflare CDN gets TLS from the CDN layer. @@ -2560,7 +2469,7 @@ ai-code-battle/ ### 11.2 Deployable Artifacts -**Container images (Forgejo registry: `forgejo.ardenone.com/ai-code-battle/`):** +**Container images (Docker Hub: `ronaldraygun/acb-*`):** | Image | Source | Base | Purpose | K8s Resource | |-------|--------|------|---------|--------------| @@ -2628,12 +2537,12 @@ Source (git push to main) │ │ ├── go test ./cmd/... (API/worker/evolver tests) │ │ ├── npm test (web) (SPA tests) │ │ ├── Kaniko image builds (all container images) - │ │ └── Push to Forgejo registry + │ │ └── Push to Docker Hub (ronaldraygun/acb-*) │ │ │ └──► Argo Workflow: build-site │ ├── npm ci && npm run build (Vite build) - │ ├── WASM builds (engine + 6 bots) - │ └── Push site build artifact to Forgejo registry + │ ├── WASM builds (engine + bots) + │ └── Push site build artifact to Docker Hub │ ├──► ArgoCD (watches declarative-config repo) │ └── Syncs K8s manifests -> ai-code-battle namespace @@ -3155,7 +3064,7 @@ major social media platforms. This is the viral growth engine. 8. Download button appears, plus "Share" buttons: - **Twitter/X**: opens compose with the clip attached + auto-generated text ("SwarmBot pulls off a comeback against HunterBot! 🎮 - aicodebattle.com/replay/{id}") + ai-code-battle.pages.dev/#/watch/replay/{id}") - **Reddit**: copies a markdown link with embedded video - **Discord**: downloads the file (under Discord's 25MB upload limit) - **Copy link**: shareable URL to the replay at the specific turn range @@ -3164,7 +3073,7 @@ major social media platforms. This is the viral growth engine. - Player names + colors in a header bar - Score overlay (bottom-left) - Win probability mini-graph (bottom strip, if enabled) -- "aicodebattle.com" watermark (small, bottom-right) +- "ai-code-battle.pages.dev" watermark (small, bottom-right) **GIF optimization:** GIFs are limited to 256 colors and can be large. The clip maker uses: @@ -3465,8 +3374,8 @@ 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 +https://ai-code-battle.pages.dev/embed/{match_id} +https://ai-code-battle.pages.dev/embed/{match_id}?start=87&speed=4&mode=territory ``` **Query parameters:** @@ -3495,7 +3404,7 @@ no side panel, no fog-of-war toggle. Just the match playing. └──────────────────────────────┘ ``` -"Watch full" links to the main replay page on aicodebattle.com. +"Watch full" links to the main replay page on ai-code-battle.pages.dev. **Implementation:** @@ -3507,7 +3416,7 @@ no side panel, no fog-of-war toggle. Just the match playing. ```html - + ``` - Thumbnail: auto-generated PNG of the final turn state, created by the index builder Deployment or pre-rendered by the match worker @@ -3546,7 +3455,7 @@ site's landing page. "players": ["SwarmBot", "HunterBot"], "scores": [3, 2], "date": "2026-03-23T14:30:00Z", - "thumbnail_url": "https://aicodebattle.com/thumbnails/m_7f3a9b2c.png", + "thumbnail_url": "https://ai-code-battle.pages.dev/data/thumbnails/m_7f3a9b2c.png", "enriched": true } ] @@ -4104,7 +4013,7 @@ demand, or pre-rendered by the index builder Deployment for top-50 bots). │ ⚔️ 847 kills 💎 2.1k energy │ │ 🏰 23 captures 📈 +320 Elo │ │ │ -│ aicodebattle.com │ +│ ai-code-battle.pages.dev │ └─────────────────────────────────┘ ``` @@ -4137,7 +4046,7 @@ 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` +- Direct URL: `https://ai-code-battle.pages.dev/data/cards/{bot_id}.png` - Served as a static PNG from Nginx PV (pre-rendered for top-50 bots) - Or rendered on-demand by the Go API endpoint that reads the bot profile from PostgreSQL, draws to Canvas (using Go image libraries or a @@ -4145,7 +4054,7 @@ Template-generated from ~20 signature patterns. - Open Graph tags on the URL so pasting it into Twitter/Discord/Slack shows the card as a rich preview: ```html - + ``` - The card image includes the platform URL as a watermark, driving traffic @@ -4228,9 +4137,9 @@ rate limiting needed. **Documented data paths:** ``` -PAGES = https://aicodebattle.com (Cloudflare Pages) -B2 = https://b2.aicodebattle.com (Backblaze B2 via Cloudflare CDN) -API = https://api.aicodebattle.com (K8s Go API, dynamic only) +PAGES = https://ai-code-battle.pages.dev (Cloudflare Pages) +R2 = {PAGES}/r2 (R2 via Pages Functions) +API = https://api.ai-code-battle.pages.dev (K8s Go API, deferred) --- Index files on Pages (deployed every ~90 min by index builder) --- @@ -4443,7 +4352,7 @@ evolution cycle: ``` Upload to B2: evolution/live.json -Served as: https://b2.aicodebattle.com/evolution/live.json +Served as: https://ai-code-battle.pages.dev/r2/evolution/live.json ``` Updated at every state transition: generation start, validation