The worker was only configured with B2 (ARMOR) credentials, so replays were
being uploaded to B2 but not to R2. The index builder's B2→R2 promotion
cycle only covers the last 24 hours, so older replays were not available
in R2, causing 404 errors when clicking "Watch Replay".
This fix adds R2 credentials to the worker deployment, allowing it to
upload replays directly to R2 (hot cache) in addition to B2 (cold archive).
This makes replays immediately available for viewing without waiting for
the promotion cycle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The bug fix was already applied in commit 9bcbd56. Updated notes to reflect:
- Fix is already in the codebase (using p.Won instead of p.BotID == m.WinnerID)
- Index builder will automatically regenerate static JSON on next 15m cycle
- No manual intervention required - production deployment will pick up the fix automatically
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The code fix for the winner badge bug has been applied. The index
builder needs to be run in an environment with Go installed to
regenerate static JSON files with correct winner badges.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The code fix was already applied in commit 6fe778b. This commit
adds documentation noting that the index builder needs to be run
to regenerate static JSON with correct winner badges.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The WinnerID field is a player-slot integer as string (e.g. "2"), not a bot_id.
The SQL query already computes the correct winner status in p.Won field.
Fixed in 3 functions:
- matchToSummary: Changed Won: p.BotID == m.WinnerID to Won: p.Won
- buildPlaylistMatch: Changed Won: p.BotID == m.WinnerID to Won: p.Won
- ratingUpsetMagnitude: Use p.Won to identify winner instead of comparing with m.WinnerID
- maxScoreDiff: Use p.Won to identify winner instead of comparing with m.WinnerID
- isEvolutionBreakthrough: Find winner using p.Won before checking if evolved
This fixes the issue where 984/1000 prod matches had winner_id set but all participants showed won: false.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Matches stopped being created after 2026-05-09. The iad-acb kubeconfig on
ex44 has expired credentials, preventing cluster access for diagnosis.
Created comprehensive diagnostic documentation covering:
- Cluster architecture and components (matchmaker, workers)
- Step-by-step diagnostic procedures for kubectl access
- Pod status checks and log analysis commands
- Database verification queries
- Service restart procedures
- Known issues from prior incidents (R2 credentials corruption)
Next steps:
1. Renew iad-acb token from Rackspace Spot UI
2. Update kubeconfig on ex44
3. Execute diagnostic commands to identify root cause
4. Restart services as needed
5. Verify match creation resumes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix TestBuildNarrativePrompt_Comeback to check for current ELO
instead of old rating (comeback arc shows bottom 25%→top 25%)
- Fix TestDetectRivalryArcs to use 10+ matches (grudge match spec)
instead of only 5 matches
Story arc detection (per §3.7 chronicles):
✓ Comeback bots: recovered from bottom 25% to top 25%
✓ Grudge matches: same pair meets 10+ times
✓ Underdog victories: bottom-10 beats top-10
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Fix /docs/api route to go directly to API docs page instead of redirecting
- Fix docs.ts link to point to /compete/docs/api instead of itself
- Add download button for replay-schema-v1.json in API docs
- Reorder router routes to ensure /docs/api is matched before /docs
The API documentation at /docs/api now correctly shows the OpenAPI-style
endpoint documentation for all static JSON file paths on Pages, R2, and B2,
including the versioned replay format specification.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Added comprehensive keyboard shortcuts overlay modal and missing shortcuts
for the replay viewer:
- New keyboard shortcuts overlay (activated by ? or / key) showing all
available shortcuts in an accessible modal dialog
- Added missing shortcuts: V (cycle view mode), E (toggle event timeline),
C (toggle commentary), Shift+arrows (jump 10 turns)
- Updated visible shortcuts list in sidebar to include all shortcuts
- Modal is fully accessible with ARIA roles, keyboard navigation, and
respects prefers-reduced-motion
- All shortcuts are WCAG 2.1 Level AA compliant
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Evolver writes live.json to R2 every cycle. Observatory page polls and
renders live feed + lineage tree + meta shift chart.
- Added ACB_R2_UPLOAD_ENABLED env var to enable automatic R2 upload during run loop
- CycleState tracks real-time evolution cycle status (generation, phase, candidate, validation, evaluation)
- Export() now includes cycle info when cycleState is provided
- runCycle() integrated with live observatory exports at each phase transition
- exportLiveQuiet() for mid-cycle status updates without verbose logging
- Fixed function signature mismatches for exportLiveQuiet calls
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The win probability sparkline component is now fully integrated:
1. Worker (engine/winprob.go): Monte Carlo rollout computes per-turn win
probabilities, detectCriticalMoments identifies turns where win prob
shifts >15% with template-based descriptions.
2. Replay storage (engine/replay.go): win_prob and critical_moments arrays
stored in replay JSON, written by match worker after each match.
3. Web component (web/src/components/win-prob.ts): WinProbSparkline class
renders the graph with critical moment markers (dashed vertical lines),
click-to-scrub interaction, and current turn indicator.
4. Replay page integration (web/src/pages/replay.ts): initWinProb() sets up
the sparkline with player colors, legend, prev/next critical moment
navigation buttons, and keyboard shortcuts ([/]).
The sparkline displays one line per player with area fill gradient,
percentage labels (0%, 50%, 100%), critical moment diamonds with
delta labels, and updates in real-time as the replay plays.
- Client-side event extraction from replay turn data
- Icon ribbon overlaid on replay viewer timeline
- Click-to-jump to event moment
- Computed events: mass death (5+ bots), spawn wave (3+ spawns),
momentum shift (win prob crosses 50%), critical moment (>15% shift)
- Energy milestone detection (3+ energy collected)
- Hover tooltips with event descriptions
- Updated icons matching plan §14.8 specification
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per §14.10 of the plan, implemented shareable bot profile cards:
- Canvas-rendered PNG cards (800x450) with bot stats and branding
- Open Graph tags for social sharing (og:image points to /r2/cards/{bot_id}.png)
- "Share Card" button on bot profile page downloads the card as PNG
- Card displays: name, rating, rank badge, owner, archetype, win rate, stats
- Evolved badge, signature move, and recent rival info
- Responsive styles for desktop and mobile
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add optional debug field in move response schema with extended telemetry:
- values: key-value pairs for debug display (metrics, state flags)
- heatmap: 2D grid overlay for visualization (threat maps, influence maps)
Engine changes:
- Add Values and Heatmap fields to DebugInfo struct in bot_http.go
- Add DebugHeatmap struct with name and 2D data array
Web viewer changes:
- Extend DebugInfo interface in types.ts with values and heatmap
- Implement heatmap rendering with blue→red gradient overlay
- Add getHeatmapColor helper for normalized value visualization
- Update debug panel to display values as key-value table
- Show heatmap info with name and dimensions
Schema updates:
- Add DebugHeatmap definition to replay-schema-v1.json
- Extend DebugInfo with values and heatmap properties
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- All 5 presets implemented: Landscape (1920×1080), Square (1080×1080), Portrait (1080×1920), GIF compact (640×360), GIF square (480×480)
- MP4/WebM export via MediaRecorder API
- Custom GIF encoder with LZW compression and 256-color palette
- Share panel with Twitter, Reddit, Discord integration
- Web Share API support for native sharing
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- WASM game engine (cmd/acb-wasm/) with loadState/step/runMatch API
- Pre-compiled 6 strategy bots to WASM (web/public/wasm/bots/)
- In-browser sandbox with Monaco editor (web/src/pages/sandbox.ts)
- WASM upload mode with interface validation
- Opponent selector (up to 3 opponents for 2-4 player matches)
- Replay viewer integration with fog-of-war toggle and view modes
- All tests pass, go vet clean, npm build succeeds
The reveal() function was trying to return the result of setXP()
which returns void. Fixed by setting XP first, then returning
the threshold value.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Fastify HTTP server with HMAC-SHA256 authentication
- Full TypeScript type definitions for game protocol
- Grid utilities: toroidal distance, BFS, neighbors
- HMAC signing/verification via Node.js crypto
- Multi-stage Dockerfile for production builds
- GitHub Actions workflow for CI/CD
- Placeholder strategy that moves toward energy
- ES modules with Node.js 20+ support
Add .gitignore to exclude Rust build artifacts from version control.
This matches the pattern used by other starter kits and keeps the
repository clean of build outputs.
- Add Clone derive to AppState for axum compatibility
- Import Digest trait from sha2 for hash computation
- Use String instead of &str in response headers for lifetime safety
- Add Position import to grid.rs module
- Make Position Copy for easier cloning
- Replace constant_time_eq with custom hmac_equal function
- Add musl-dev to Dockerfile for Alpine build compatibility
The Rust starter kit now compiles and builds successfully with
cargo check and Docker, matching the requirements from plan §5.3
and §12 (Phase 2).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Complete Go starter kit for AI Code Battle with:
- main.go: HTTP server with HMAC authentication, placeholder computeMoves()
- game/ package: Shared utilities (types, auth, grid) for reuse
- types.go: Game state types, Direction constants, Position, etc.
- auth.go: HMAC-SHA256 signing/verification with timestamp validation
- grid.go: Toroidal distance, BFS pathfinding, neighbor functions
- Tests: Comprehensive test coverage for grid and auth utilities
- Dockerfile: Multi-stage build with Go 1.24-alpine
- README: Complete documentation with examples and protocol reference
The starter kit provides a minimal working bot that holds position
by default. Participants implement their strategy in computeMoves()
using the provided grid utilities.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Flask HTTP server (~130 lines) with HMAC-SHA256 signing, game state
type definitions, stub strategy, and Dockerfile.
- Flask-based /turn and /health endpoints
- HMAC-SHA256 request verification and response signing
- Type-annotated compute_moves() stub (holds all bots in place)
- Grid utilities: toroidal distance, BFS, neighbor enumeration
- README with quickstart, protocol spec, and customization guide
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Added tests for:
- TestNextScheduledTime: verifies correct calculation of next scheduled
run time across various scenarios (same-day future, same-day past,
different weekdays, edge cases around midnight)
- TestWeeklyScheduleEnvParsing: validates environment variable parsing
for the WEEKDAY:HH:MM format, including valid and invalid inputs
These tests ensure the weekly automated map evolution ticker (§14.6)
correctly schedules evolution runs at the configured time.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wire up the acb-map-evolver to run automatically on a weekly schedule
(Sunday 03:00 UTC by default) from the evolver deployment.
The map evolution ticker:
- Waits until the next scheduled time (weekday:hour:minute UTC)
- Runs acb-map-evolver --once to evolve maps for all player counts
- Repeats every 7 days
The schedule can be configured via ACB_MAP_EVOLUTION_SCHEDULE env var
(format: WEEKDAY:HH:MM, e.g., "0:03:00" for Sunday 03:00 UTC).
Enable via ACB_MAP_EVOLUTION_ENABLED=true or --enable-map-evolution flag.
Per plan §14.6: the weekly map evolution loads engagement scores,
runs MAP-Elites evolution, promotes high-scoring variants, and updates
the active map pool in the database.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Implement runWeeklyLoop() function that waits for scheduled time and
runs evolution for all player counts (2, 3, 4, 6) weekly
- Add --weekly flag to enable weekly mode (default: Sunday 03:00 UTC)
- Add --weekly-schedule flag for custom schedule (WEEKDAY:HH:MM format)
- Add ACB_WEEKLY_SCHEDULE env var for configuration
feat(acb-evolver): add weekly map evolution ticker
- Add MapEvolutionEnabled and MapEvolutionSchedule to RunConfig
- Add --enable-map-evolution flag to acb-evolver run subcommand
- Add startMapEvolutionTicker() goroutine that runs weekly
- Ticker executes acb-map-evolver --once to trigger map breeding
- Configurable via ACB_MAP_EVOLUTION_ENABLED and ACB_MAP_EVOLUTION_SCHEDULE
This integrates map evolution into the bot evolver's deployment,
allowing weekly automated map evolution based on engagement scores
as specified in plan §14.6.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add starters/php/ with complete starter kit for AI Code Battle:
- index.php: HTTP server with HMAC verification, routing for /turn and /health
- strategy.php: Stub compute_moves() function with example energy-seeking logic
- game.php: Game state types (GameState, Position, VisibleBot, etc.) and grid utilities (toroidal_manhattan, toroidal_chebyshev, bfs, neighbors, cardinal_steps)
- Dockerfile: Alpine-based PHP 8.4 container
- README.md: Quickstart documentation with local/Docker run instructions
- composer.json: Minimal composer config (no external dependencies)
Follows same contract as other starters:
- Listens on port 8080 (BOT_PORT env var)
- POST /turn: Receives game state JSON, returns moves JSON
- GET /health: Health check endpoint
- HMAC-SHA256 signature verification on requests/responses
Reference implementation: bots/guardian/ (GuardianBot in PHP)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The --once mode was implemented but the command-line flag was not being
parsed. This commit adds the flag parsing and help text for --once, which
enables the weekly automated map evolution run from the evolver.
The evolver's weekly ticker (run.go) calls acb-map-evolver --once to
trigger map evolution on Sundays at 03:00 UTC as specified in plan §14.6.
Per plan §13.3, implements user-requested AI replay commentary with:
- HMAC bot authentication via shared_secret
- Rate limiting: 5 requests/day per bot
- Match validation (exists and completed)
- Idempotency via enrichment_requested_at column
- Enqueues to Valkey for acb-enrichment service
- Returns 202 Accepted with estimated wait time
Also adds:
- AllowN() method to ratelimit package for multi-token checks
- enrichment_requested_at column to matches table (idempotency)
- enrichLtr rate limiter (5/day per bot)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add 'featured' boolean column to series table for weekly featured series
- Add tickFeaturedSeries ticker that runs Friday 20:00 UTC to create bo5 featured series
- Featured series: query top 20 bots by rating, select 4 rivalry pairs by ELO proximity
- Best-of-7 championship bracket already implemented via createChampionshipBracket
- Add FeaturedSchedSecs config (default: 3600s check interval)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Update the map engagement scoring formula to match plan §14.6:
- score = win_prob_crossings * 3.0 + critical_moments * 2.0 +
resource_contest_turns * 1.5 + survival_turns * 0.5
New metrics computed from replay data:
- resource_contest_turns: turns where energy is contested by multiple players
- survival_turns: turns where all players have at least one bot alive
The old formula used map_coverage_pct, closeness, and turn_pct which
did not match the specification.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Change filter from 'idea'/'mistake' to 'insight'/'idea' (mapping to 'hint'/'strategy' from plan §13.6)
- Increase upvote threshold from 3 to 10 for higher quality signals
- The evolver consumes community_hints.json for LLM prompt context
Implements §16.15 progressive disclosure system that reveals advanced
UI features gradually based on user engagement (XP tracked via localStorage).
Features:
- XP tracking system stored in localStorage
- reveal(featureKey) / isRevealed(featureKey) API
- 9 XP-gated features (event timeline, view modes, follow camera, etc.)
- Action-based features (predictions, sandbox, embed) unlocked by specific user actions
- Power user override to show all controls
- Engagement tracking (30+ second replay watch = 1 XP)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The rating recovery CLI mode (-mode=recalc-ratings) was using
glicko2Tau (0.5) instead of glicko2DefaultSigma (0.06) for the
default sigma value when resetting ratings. This caused the reset
sigma to be ~8x higher than the schema-defined default.
Added glicko2DefaultSigma constant (0.06) and updated ResetAllRatings
and recalcRatings to use it correctly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements the rating recovery procedure specified in plan §12.3.
Running 'go run ./cmd/acb-worker -mode=recalc-ratings' will:
1. Reset all bot ratings to Glicko-2 defaults (mu=1500, phi=350, sigma=0.06)
2. Fetch all completed matches from the database in chronological order
3. Replay each match to recompute Glicko-2 ratings from scratch
4. Update the bots table with the recalculated ratings
This is needed for disaster recovery when ratings are corrupted or lost.
Database functions added:
- ResetAllRatings: resets all bot ratings to defaults
- GetAllCompletedMatches: fetches completed matches chronologically with participants
- UpdateAllRatings: bulk updates all bot ratings in a single transaction
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add sitemap.xml generation as a final pass in the index builder. The
sitemap covers all public pages: home, leaderboard, bots list, bot
profiles, matches list, featured replays, seasons, rivalries,
predictions, and docs.
- Add SiteURL config field (ACB_SITE_URL env var, defaults to
https://aicodebattle.com)
- Add generateSitemap() function with proper XML encoding
- Add SitemapURL and Sitemap types for XML marshaling
- Call generateSitemap() at the end of generateAllIndexes()
- Write sitemap.xml to output directory alongside leaderboard.json
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add comprehensive JSON Schema for replay format (v1) as specified in
plan §15.2. This enables third-party tooling to validate and understand
replay files programmatically.
Schema documents:
- Root replay object (format_version, match_id, config, timestamps)
- Match result (winner, reason, scores, stats)
- Player information
- Map data (walls, cores, energy nodes)
- Turn-by-turn state (bots, cores, energy, scores, events)
- Optional win probability curve and critical moments
- Event types (bot_spawned, bot_died, energy_collected, core_captured,
combat_death, collision_death)
- Debug telemetry for bot reasoning visualization
All fields include descriptions, types, constraints, and examples.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements plan §3.9 requirement for INV-6 invariant verification.
The test runs thousands of random scenarios across various grid
dimensions (30x30 to 200x200) and multiple random seeds to verify
that no bot, energy, core, or wall position ever has coordinates
outside the valid bounds [0, rows) x [0, cols).
Test coverage:
- Random wall placement with potentially out-of-bounds input
- 1000 random Wrap() calls with positions far outside bounds
- Move() operations from edge and corner positions in all directions
- Neighbors() and VisibleFrom() return value validation
The test uses a manual random-seed loop approach for maximum
control and reproducibility, testing 6 grid sizes × 10 seeds
for comprehensive coverage of the toroidal wrapping invariant.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add map_engagement_test.go with tests for:
- Win prob dependency in map engagement (lead changes counted)
- Critical moments dependency in engagement score
- Empty/nil replay handling
- Complete ComputeWinProbability + SetWinProbability flow
This confirms the existing implementation already correctly:
- Computes win probability via Monte Carlo rollout (100 iterations)
- Sets win_prob and critical_moments on replay before serialization
- Calculates map engagement score from win_prob_crossings and critical_moments
- Writes engagement score to maps table via UpdateMapEngagement
Task: bf-qps
- Add engine.CalculateMapEngagement() to compute map engagement scores from replay data (win_prob_crossings, critical_moments, map_coverage_pct, closeness, turn_pct)
- Add DBClient.UpdateMapEngagement() to update map engagement using rolling average
- Worker now calculates and writes map engagement scores after each match
- Add test to verify win_prob array is non-empty in produced replays
This implements the win probability Monte Carlo array storage in replay JSON
feature. The engine already called ComputeWinProbability() in MatchRunner.Run(),
so this commit adds the missing map engagement tracking.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Energy node placement now uses a tiered radius distribution: 30% in the
contested central zone (0.05-0.20 from center), 40% in the mid-zone
(0.20-0.40), and 30% in the home zone (0.40-0.60). Previously nodes were
placed uniformly at 0.20-0.70, letting bots farm their home quadrant
indefinitely without crossing the midline.
After cellular automata wall generation, a 3-wide corridor is carved from
each core straight to the map center, plus a 5x5 open arena at the center
tile. This creates lanes that funnel bots into contact — replicating the key
mechanic that drove frequent fights in the original AI Challenge Ants game,
where symmetric food spawning near the midfield forced both colonies to
expand outward and collide.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>