- Add matches_today and active_bots fields to LiveData Totals (evolver)
- Query matches table for COUNT(*) WHERE completed_at >= today
- Query bots table for COUNT(*) WHERE status = 'active'
- Add fields to index builder EvolutionMeta struct
- Update homepage to render "X matches today · Y bots active · Gen #Z evolving"
- Add CSS styling for .home-live-stats section
Closes: bf-4m8mo
Index builder:
- Add slog import for structured logging
- Improve fetchEvolutionMeta to return empty meta instead of error when programs table is empty
- Add logging to show evolution system status (running vs not initialized)
- Add logging in generateEvolutionMeta to show when evolution data is written
Evolver:
- Add automatic schema initialization and population seeding in RunEvolutionLoop
- Programs table is now automatically seeded with 6 initial strategy bots on startup
- Log seeding status to indicate whether programs table was already initialized
These changes ensure the evolution system properly initializes when deployed
and provides better visibility into its status via structured logging.
Closes: bf-4zde
- Add island_populations: program count per island
- Add best_ratings: top 10 evolved bots with bot_id, name, rating, island, language
- Add total_promoted and promotion_rate: all-time promotion statistics
- Queries programs table and joins with bots table for current ratings
Closes: bf-1cxv
Adds MetaIndex struct and generateMetaIndex function to create
data/meta/index.json listing all available meta data files
(archetypes.json, rivalries.json) with descriptions.
Also adds the new file to the R2 warm cache upload list.
Closes: bf-66rk
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The plan previously specified ZoneShrinkStep=2, but the engine uses
ZoneShrinkStep=1 (per commit 0577fcd). The value of 1 was found to
improve combat density because it matches bot movement speed (1 tile/turn).
A value of 2 caused the zone to shrink faster than bots could move,
killing them before combat could occur.
Updated zone parameters table and rationale in §3.7.1.
Closes: bf-3mrj
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Modified bundleWarmAssetsForCycle to return the deduplicated match IDs
of warm-set replays, and added a call to promoteRecentReplays in the
build cycle. This implements plan §8.2.5: index builder promotes recent
replays from B2 cold archive to R2 warm cache.
Acceptance criteria for plan-gap bead bf-jfmo:
- /r2/replays/{id}.json.gz will return valid gzipped replay JSON once
the Cloudflare Pages R2 bucket binding (ACB_BUCKET) is configured
- R2 bucket exists with replay objects after each build cycle
- Index builder has R2 S3 client configured (credentials via env vars)
Closes: bf-jfmo
Per bead bf-3e60: instead of copying B2->R2 (warm cache), bundle warm-set
replays, thumbnails, cards, and evolution live.json directly into the Pages
deploy directory as static assets (dist/data/). This serves replays same-origin,
eliminating R2 dependency and 404 errors.
Changes:
- Add B2Client interface for testable B2 operations
- Add bundleWarmReplays(): copies replays/*.json.gz from B2 to dist/data/replays/
- Add bundleWarmThumbnails(): copies thumbnails/*.png from B2 to dist/data/thumbnails/
- Add bundleWarmCards(): copies cards/*.png from B2 to dist/data/cards/
- Add bundleEvolutionLive(): copies evolution/live.json from B2 to dist/data/evolution/
- Replace promoteRecentReplaysForCycle() with bundleWarmAssetsForCycle()
- Remove R2 pruning logic from main loop (no longer needed)
- Add unit tests for all bundling functions with mock B2 client
Replays are served gzipped (as-is from B2) to keep deploy size under Pages'
25MB file limit. Frontend will gunzip client-side (separate bead bf-5cwi).
All tests pass (go test ./...).
Closes: bf-3e60
Plan §15.4 Live Evolution Observatory requires data/evolution/meta.json
and data/evolution/lineage.json files, which the web platform expects
but the index-builder was not generating.
- Add EvolutionMeta type (generation, promoted_today, top_10_count, updated_at)
- Add LineageNode type (full program lineage with parent relationships)
- Add fetchEvolutionMeta() to query evolver database for stats
- Add fetchLineage() to query evolver programs table for lineage tree
- Add generateEvolutionMeta() to write data/evolution/meta.json
- Add generateLineage() to write data/evolution/lineage.json
- Wire generation into generateAllIndexes()
- Add files to R2 upload list
The implementation gracefully handles missing evolver database by
returning empty/placeholder data, allowing the index-builder to run
without evolution data while still producing valid JSON files.
Closes: bf-6cp0
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>
- 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>
- 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
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>
Adds combat_turns metric (distinct turns where ≥1 bot died from enemy
focus-fire, excluding self-collisions). Worker computes it after each
match; index builder sorts matches/index.json and the new most-combat
playlist descending by it, and bumps interest score for combat-heavy
matches so they surface in highlights.
Also switches homepage featured replay default view from influence to
standard so the actual bot-on-bot combat is visible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_worker.js static file approach fails — Cloudflare rejects it when uploaded
as a static asset. Instead, copy web/functions/ into the image and set
wrangler CWD to /app/web/ so it discovers functions/ and uploads the Pages
Functions bundle correctly on every deploy cycle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Every index-builder deploy was overwriting the production Pages deployment
without functions (wrangler ran from /tmp, no functions/ dir visible).
Compiling functions/ to dist/_worker.js during the Docker web-builder stage
means the worker is always included in every Pages deploy, regardless of CWD.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Verified /watch/replays shows real completed matches (not just demo)
- Match cards display bot names, turn count, winner badges, map ID
- 'Watch Replay' links point to real match IDs (m_test_*)
- Curated playlists render with real data (featured, comebacks, upsets, etc.)
- Pagination/infinite scroll works via IntersectionObserver
- Mobile testing on Pixel 6 via ADB: layout responsive, touch targets usable
- Created MATCH_LIST_TEST_RESULTS.md with full verification details
- Thumbnails not implemented (clean UI without broken images due to R2 issues)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The AWS SDK requires a valid AWS region name even when using custom
S3-compatible endpoints (ARMOR/B2). Using "auto" as the region causes
an error: "Invalid region: region was not a valid DNS name."
This fixes the replay upload pipeline which was failing with the
invalid region error. Replays should now upload successfully to B2
via the ARMOR proxy.
Related to ai-code-battle-o43: Replay viewer verification task.
Verification results:
1. ✅ /data/blog/index.json exists and has 1 post (meta-week-13-season-1)
2. ✅ Individual post pages load correctly at /blog/{slug}
3. ✅ Blog post JSON structure matches frontend expectations (content_md field)
4. ✅ Tags and filters implemented in UI (All, Meta Reports, Chronicles buttons)
5. ✅ Blog page builds successfully (blog-D4QMd11d.js included in build)
Current state: Blog infrastructure is fully implemented with:
- LLM-powered narrative generation (blog.go, narrative.go)
- Story arc detection (rise, fall, rivalry, upset, evolution milestones)
- Weekly meta report generation with ELO movers, strategy analysis
- Chronicles for story arcs (rivalry, upset, rise/fall, evolution)
- Tag-based filtering and search
Note: Current blog content is placeholder/template-based. Meaningful
match commentary will be generated when:
- ACB_LLM_BASE_URL and ACB_LLM_API_KEY are configured in index-builder
- Real match data exists in PostgreSQL database
- Story arcs are detected from rating history and match results
The main leaderboard SPA is now served at / (index.html) and the
standalone replay viewer lives at /replay.html. This removes the
_redirects workaround in index-builder that patched over the inverted
entry points.
- Rename web/app.html → web/index.html (main SPA)
- Rename web/index.html → web/replay.html (standalone viewer)
- Update vite.config.ts: main→index.html, replay→replay.html
- Remove _redirects injection from deploy.go verifyMergedOutput
- Update pages.json routes and README dev URL
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The replay viewer was baked into index.html (served at /) while the
leaderboard app was at /app.html. Add a _redirects file so visitors
landing on / get redirected to the main leaderboard app.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add formatSeasonChampionshipContext helper to inject season progress,
championship bracket positioning, and seed lines into LLM prompts.
Add §13.2 critical moment / turning point summary to the match-of-the-week
section of buildSpotlightPrompt. These complete the §15.1/§15.5 alignment
for structured contextual match data in sports-journalism-style prompts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Restore detailed system prompt constant framing the LLM as a sports
journalist covering an emergent bot league, with specific guidance on
ELO deltas, rivalry context, head-to-head records, and scouting-style
lineage framing. Enrich per-arc prompts with critical moment summaries
(§13.2), community tactical hints, ELO before/after deltas, and
head-to-head records. Fix rivalry arc to include ELO context for both
bots. Ensure fall arc shows both wins and losses in key match listings.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Generates contextual turning-point descriptions for matches used in blog
narratives and rivalry chronicles (§13.2). Summarizes close scores, ELO
upsets, non-standard end conditions, and marathon matches.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two functions referenced in generateLLMChronicle were undefined:
- getCurrentSeasonTheme: returns the active season's theme string
- buildHeadToHeadFromArc: computes W/L head-to-head records for a bot
against all opponents from match data, enriching LLM narrative prompts
Also improves the sports journalist system prompt with more detailed
coverage style guidance for better narrative quality.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add data/meta/rivalries.json to R2 upload list in uploadMetaJSONToR2
- Add attachCommunityHints() to narrative.go to enrich story arcs with
highest-upvote community tactical hints (upvotes >= 3, idea/mistake types)
- Fix detectRivalryArcs() key separator from "-" to "|" to avoid UUID
hyphen collisions when parsing bot ID pairs
- Fix partitionBots() call sites in bot_strategies_phase13.go to use
struct field access (.friendly, .enemy) matching updated return type
generator.go already contains generateArchetypes, generateCommunityHints,
and generateMatchFeedback (all called from generateAllIndexes). main.go
uploads all four outputs to R2 on every build cycle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add transcript panel with turn-by-turn summaries generated from replay events
- Each turn shows: player moves, combat, deaths, captures, energy collection, spawns, win probability
- Add 'T' key shortcut to toggle transcript panel
- Panel supports three view modes: All Turns, ±10 Turns from Current, Recent 20 Turns
- Click on transcript entry to jump to that turn
- Current turn is highlighted in transcript with smooth scroll
- Panel content is selectable/copyable for screen reader users
- Transcript generation logic already existed in replay-viewer.ts; this adds the UI
- Transcript button slides in from right side of screen
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remove unused encoding/json and net/http imports from cmd/acb-evolver/run.go
that caused build failure. Include other pre-dispatch changes from prior work.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add ARIA live region announcement during auto-playback using detailed transcript text
- Transcript panel shows turn-by-turn summaries with current turn highlighting
- T key toggles transcript panel (collapsible UI)
- Panel content is selectable/copyable text for screen reader users
- Fix build errors in clip-maker.ts (remove unused lastExportBlob references)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add ReplayPlayer to type imports in replay-viewer.ts
- Add explicit type annotation for entry parameter in replay.ts transcript map
- Fixes TypeScript compilation errors for §15.3 screen reader transcript feature
- SeasonID and RulesVersion already present in engine/types.go Config struct
- Worker already populates from active season row via DB join
- Config embedded in VisibleState sent to bots each turn (including turn 0)
- All starter kits (go, python, rust, java, csharp) already expose and log fields
- Add season_id/rules_version logging to JavaScript starter on turn 0
- TypeScript Config interface already includes season_id and rules_version
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds generatePredictionsOpen to acb-index-builder, writing:
- data/predictions/open.json — upcoming predictable matches
Queries pending matches that are "predictable":
- Both bots in top 20
- Rivalry matches (3+ previous h2h matches)
- Series matches
- Evolved bot vs top-10 human-written bot
Output schema:
- updated_at — timestamp of generation
- matches — array of {match_id, bot_a, bot_b, a_rating, b_rating, open_until, head_to_head_record}
Limits to next 10 matches to keep file small. Refreshes every ~15 min by the index builder.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add crane CLI to the runtime Dockerfile so the index builder can pull the
latest SPA shell from the Forgejo container registry on each cycle. The
existing syncSiteBuild logic checks for a newer image digest, extracts
the dist/ assets via crane export, and overlays generated JSON data files
on top before deploying to Cloudflare Pages.
- Dockerfile: install go-containerregistry crane binary (v0.20.2)
- sitebuild.go: new file with syncSiteBuild, craneDigest, craneExport,
digest caching, fallback to baked-in /app/web/dist
- main.go: wire initCraneAuth at startup, replace hardcoded webDistDir
with syncSiteBuild call in runBuildCycle
- sitebuild_test.go: 18 tests for extractRegistry, digest caching,
fallback logic, crane auth config, and copyWebAssets overlay behavior
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds generateMapsIndex to acb-index-builder, writing:
- maps/index.json — active/probation/classic maps grouped by player count
- maps/{map_id}.json — full map definition with walls, cores, energy_nodes
Queries maps.map_json column for geometry; adds RawJSON field to MapData
and updates fetchMaps to select it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implementation complete:
1. engine/thumbnail.go - New thumbnail rendering package
- GenerateMatchThumbnail() creates 640x360 PNG thumbnails
- Renders grid, bots, cores, walls, energy with player colors
- SelectThumbnailTurn() chooses most interesting turn
- Pure Go stdlib image rendering (no canvas required)
2. cmd/acb-worker - Upload thumbnails to B2 alongside replays
- uploadThumbnail() generates PNG and uploads to B2
- Key: thumbnails/{match_id}.png, content-type: image/png
- Called after match completion, non-blocking on failure
3. cmd/acb-index-builder/deploy.go - Promote thumbnails to R2
- promoteRecentReplays() copies both replays AND thumbnails from B2 to R2
- Thumbnails promoted to warm cache alongside replay promotion
4. cmd/acb-index-builder/generator.go - Populate thumbnail URLs
- buildPlaylistMatch() now includes thumbnail_url field
- URL pattern: https://r2.aicodebattle.com/thumbnails/{match_id}.png
- Enables playlist cards and embed OG tags to show preview images
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace random 2-player pairing with the full §6.1 algorithm:
- Seed selection: bot with oldest last-match timestamp (tiebreak: lowest bot ID)
- Format selection: seed's least-played player count among {2, 3, 4, 6}
- Opponent selection: Pareto 80%/16-rank skill proximity + oldest last-pairing
with seed + fewest 24h games for game-count balance
- Map selection: least-recently-used active map for the chosen player count,
with map_scores.last_used_at updated after each match
- Random player slot assignment for all participant counts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add metrics server startup and HTTP middleware to acb-api, generation
counter metric to evolver, and R2 cache size metric to index builder.
Also remove dead measureR2CacheSize reference from index builder main.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The RecencyBoost test now uses balanced 5-5 splits for both pairs so
recency is the sole differentiating factor (previously one pair was 10-0
which conflated balance and recency). Also wires Prometheus build
duration metric in main loop.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add Exploration and Formation axis definitions with feature extraction
from source code pattern matching (exploration/formation indicators)
- Extend Grid key from (x,y) to (x,y,z,w) with 3⁴=81-cell behavior grid
- Update bin assignment, promotion gate, and persistence (JSON snapshot)
- Add Slice() for 2-D dashboard visualization across any axis pair
- Migration: old 2-D archives project at z=middle, w=middle
- Update cross-pollination to pad 2-element behavior vectors to 4
- Add Prometheus metrics to matchmaker (bot crashes, stale job count)
- Add rivalry detection to index builder (data/meta/rivalries.json)
- Web: batched bot list loading, leaderboard keyboard accessibility,
improved ARIA attributes on match/playlist cards
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add debug telemetry UI to replay viewer with player toggles,
priority-based target markers, and stacked reasoning boxes.
Fix undefined generateTestImage in main_test.go.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Covers buildFirstMatchPerBot, isNewBotDebutFast, buildPairFrequency,
isRivalryMatchFast, and integration test for playlist generation with
the optimized lookups.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Fix deploy.go to query actual table names (series_games not series_matches,
join through series_games for seasons instead of non-existent season_matches)
- Add playlist_matches table to exempt match IDs from R2 pruning
- Pre-build lookup maps for O(1) playlist match filtering instead of O(n²)
- Enhance home page featured replay to prefer AI-enriched matches
- Add enrichment test coverage (shouldEnrich criteria validation)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implement auto-curated playlists in the index builder: 12 playlist types
(closest finishes, upsets, comebacks, marathons, rivalry classics, etc.)
with weekly highlight curation. Add DB persistence, R2 pruning exemptions,
frontend pages, and AI commentary enrichment pipeline.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The buildSpotlightPrompt function accepted a rivalries parameter but never
used it. This adds top rivalry data to the LLM prompt so the generated
Counter-Strategy Spotlight can reference active rivalries. Test updated to
verify rivalry data appears in prompt output.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>