- 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>
Replace os.ReadFile+os.WriteFile with io.Copy so large files (e.g. the 21MB
demo-replay-v2-6p.json) are never fully loaded into RAM during copyWebAssets.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a web-builder stage to the Dockerfile (Vite/TS build) and copy the
resulting dist/ into the runtime image at /app/web/dist. Call copyWebAssets
each build cycle so HTML/JS/CSS is merged into the output dir before wrangler
deploys — previously only JSON data files were uploaded, causing CF Pages to
serve 404 at the root.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
wrangler creates .wrangler/tmp relative to its working directory.
The container runs as non-root user acb with WORKDIR=/app (root-owned),
so mkdir /app/.wrangler/tmp fails with EACCES. Setting cmd.Dir=/tmp
gives wrangler a writable CWD while keeping the /data output path
(absolute) unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
matches.winner is an INTEGER (player slot), not a bot_id VARCHAR.
Fix two queries that compared mp.bot_id = m.winner (type mismatch)
to use mp.player_slot = m.winner.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
COALESCE(parent_ids, '[]'::json) fails because the column is JSONB
and PostgreSQL won't coerce json to jsonb. Change to '[]'::jsonb.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add s3.go with AWS SDK v2 S3Client wrapper for R2/B2 operations
- Implement listObjects, deleteObject, objectExists, uploadFile, copyObject, downloadObject
- Add s3_test.go with MockS3Client and comprehensive tests
- Wire promoteRecentReplaysForCycle() into build cycle in main.go
- Add fetchRecentMatchIDs() to query recent matches from PostgreSQL
- Add fetchExemptMatchIDs() to protect series/season/playlist matches from pruning
- Implement pruneR2CacheWithDB() for 10GB cap enforcement with exemptions
- Update go.mod with AWS SDK v2 dependencies
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add LLMBaseURL and LLMAPIKey config options for narrative generation
- Wire up LLM client to generateBlog() when LLM is configured
- Fix ParticipantData type usage in test files
- Simplify rivalry arc detection (remove alternation check)
- Fix type conversion in upset detection gap calculation
- Mark narrative engine as complete in PROGRESS.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Created narrative.go with story arc detection per plan §15.5
- Arc types: Rise, Fall, Rivalry, upset, evolution, comeback
- LLMClient for OpenAI-compatible API narrative generation
- generateLLMChronicles() using narrative engine
- Updated blog.go with LLM integration
- Template-based fallback when LLM unavailable
- Added tests in narrative_test.go
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add blog generation to Go index builder (cmd/acb-index-builder/blog.go):
- Weekly meta report generation with competitive analysis
- Story arc chronicles: rise stories, upsets, rivalries
- Blog index and individual post JSON generation
- Add blog page to web SPA (web/src/pages/blog.ts):
- Blog listing with type filters (all/meta-report/chronicle)
- Individual post view with markdown rendering
- Tag cloud and post metadata display
- Added /blog and /blog/:slug routes
- Add Blog link to navigation menu
- Add placeholder blog data files for initial content
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Canvas-rendered PNG card generation in cmd/acb-index-builder/cards.go
- 1200x630 images for social sharing (OG/Twitter)
- Rating tiers with color coding (gold/silver/bronze/green/gray)
- Win rate color coding (green/blue/yellow/red)
- Rank badges for top 100 bots
- Evolved bot badges with island indicator
- Add card upload to R2 warm cache and B2 cold archive
- Add Open Graph meta tags in web/app.html
- Add dynamic OG tag management in web/src/og-tags.ts
- Update bot profile page to set OG tags on load
- Add BuildTimeout config field (fixes test failures)
- Add comprehensive tests for card generation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Per plan §11.1, the index builder reads PostgreSQL and generates all JSON
index files for Cloudflare Pages deployment:
- main.go: Build cycle orchestration with configurable timeout, self-restart
- config.go: Environment-based configuration with sensible defaults
- db.go: PostgreSQL data fetching for bots, matches, series, seasons, predictions
- generator.go: JSON index generation (leaderboard, bots, matches, playlists)
- deploy.go: Cloudflare Pages deployment via wrangler, R2 warm cache pruning
- Dockerfile: Multi-stage build with Go + Node.js + wrangler CLI
- main_test.go: Tests for config, index generation, playlists
Index builder runs on 15-minute cycles, deploys to Pages every ~90 minutes,
and prunes R2 warm cache weekly to stay within 10GB free tier.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>