Commit graph

103 commits

Author SHA1 Message Date
jedarden
3ae35ea00a test(web): verify match list page renders cards with real matches
- 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>
2026-04-25 11:42:47 -04:00
jedarden
09fced7dfe fix(worker,index-builder): use us-east-1 region for S3-compatible endpoints
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.
2026-04-25 11:07:08 -04:00
jedarden
cd30484e8c verify(blog): verify blog page generates and renders AI match commentary posts
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
2026-04-25 10:40:36 -04:00
jedarden
e601fecc04 fix(worker): update B2 client for S3-compatible API (ARMOR/B2)
Remove custom endpoint resolver and use AWS SDK's standard approach
for S3-compatible endpoints:
- Use config.WithRegion("auto") for custom endpoints
- Set BaseEndpoint directly via s3.NewFromConfig options
- Add UsePathStyle for B2 compatibility

This fixes the 'Invalid region: region was not a valid DNS name' error
that was preventing replay uploads. The deployment manifest already
sets ACB_B2_REGION to empty string to avoid conflicts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 10:38:41 -04:00
jedarden
41c7223f8a refactor(web): promote app.html to index.html as homepage
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>
2026-04-25 08:51:52 -04:00
jedarden
5d89fbd885 fix(index-builder): add _redirects to route / → app.html
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>
2026-04-25 07:22:06 -04:00
jedarden
992fa1d573 fix(worker): crash cooldown passes time.Time not time.Duration to pq
Passing time.Duration (int64 nanoseconds) as $2 in NOW() + $2 caused
PostgreSQL to interpret the nanosecond value as seconds, setting
cooldown_until to year ~59066 instead of +30 minutes.

Fix: pre-compute time.Now().Add(CrashCooldownDuration) and pass the
resulting time.Time — pq encodes it as a proper timestamptz.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 18:07:29 -04:00
jedarden
4f45670066 fix(worker): use seasons.id instead of seasons.season_id in ClaimJob
The seasons table was recreated with id BIGSERIAL (not season_id VARCHAR).
The ClaimJob query was still referencing s.season_id (stale column name).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 17:39:24 -04:00
jedarden
38ae4c6303 fix(worker): use winner identity for Glicko-2 pairwise scoring
Raw game scores (capture points) are tied in most matches since the
winner is determined by an energy/bots-alive tiebreaker. This caused
Glicko-2 delta=0, leaving rating_mu frozen at 1500 for all bots.

Now winner gets 1.0, non-winners 0.0, draws 0.5 — correct pairwise
win/loss signal for Glicko-2 convergence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 17:29:36 -04:00
jedarden
ea2f1b50b7 fix(matchmaker): stale-reaper queries claimed not running status
Jobs remain in 'claimed' status until completed — the reaper was
querying 'running' (which is the match status, not job status) so
stale claimed jobs were never recycled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 17:24:57 -04:00
jedarden
9c5eb57fdd fix(evolver): correct GROUP BY in island stats query
b.bot_id was selected without being in the GROUP BY clause or wrapped
in an aggregate, causing a Postgres error on live export. Replaced with
a correlated subquery that finds the highest-rated bot per island.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 07:04:37 -04:00
jedarden
b7990ad475 fix(docker): add COPY ratelimit/ to acb-api Dockerfile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 18:09:53 -04:00
jedarden
d0087a3241 fix(docker): add COPY metrics/ to all service Dockerfiles
The metrics package is a local module dependency imported by all services
but was missing from every Dockerfile's build context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 18:00:16 -04:00
jedarden
002f945298 fix(matchmaker): use inclusive boundary for classic promotion age check
90 days == 3 calendar months exactly in March/April, causing
TestThreeMonthAgeCheck to fail. The intent is >= 3 months old.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 17:45:36 -04:00
jedarden
0813e36297 fix(evolver): wire Nash mixture and meta weaknesses into LLM prompts, fix 4-D diversity
- Add NashMixture and MetaWeaknesses fields to meta.Description and
  compute them from island population proportions (§10.2 PSRO)
- Update behaviorDistance to support N-D vectors for 4-D MAP-Elites
  grid (aggression, economy, exploration, formation)
- Wire NashMixture/MetaWeaknesses through FromMetaDescription converter
  so they actually reach the LLM prompt (was dead code before)
- Align LLM prompt with plan §15.1/§15.5: correct combat rules
  (focus-fire), fog of war, HTTP protocol section, Nash mixture target
- Fix diversity normalization from sqrt(2) (2-D) to 2.0 (4-D max)
- Rename handleUIFeedback to handleCreateFeedback (§13.6 naming)
- Update tests for new fields and corrected prompt text

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 01:22:19 -04:00
jedarden
be66af1ad5 fix(narrative): add season championship context and §13.2 critical moments to spotlight prompt
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>
2026-04-22 20:08:49 -04:00
jedarden
922ef5cd5a fix(narrative): align LLM prompts with plan §15.1/§15.5 sports-journalism spec
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>
2026-04-22 19:40:49 -04:00
jedarden
ae8eb0465e feat(narrative): add critical moment summaries to key match extraction
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>
2026-04-22 19:08:50 -04:00
jedarden
fddcc0ba34 fix(index-builder): add missing getCurrentSeasonTheme and buildHeadToHeadFromArc
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>
2026-04-22 19:01:03 -04:00
jedarden
7978ebbab3 feat(§15.2): generate and stream static meta JSON files to R2
- 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>
2026-04-22 18:46:27 -04:00
jedarden
60b83a02d9 feat(§15.3): implement screen reader transcript for replay viewer
- 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>
2026-04-22 18:42:49 -04:00
jedarden
38f14e1997 fix: remove unused imports in evolver, misc pre-dispatch changes
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>
2026-04-22 18:32:46 -04:00
jedarden
a509b70800 feat(§15.3): implement screen reader transcript for replay viewer
- 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>
2026-04-22 18:30:18 -04:00
jedarden
98a9f645c4 feat(evolver): update retirement ticker interval to daily (§10.8)
Changed RetirementCheckInterval from 1 hour to 24 hours to align
with the 7-day low-rating rule specified in §10.8. The retirement
automation is already fully implemented:

- startRetirementTicker: runs periodic checks (now daily)
- EnforcePolicy: retires bots below rating threshold (800) for 7
  consecutive days, enforces 50-bot population cap
- queryConsecutiveLowRating: uses rating_history table to track
  consecutive days below threshold
- RetireBot: handles K8s manifest deletion via declarative-config
- TestEnforcePolicy_CapEnforcement: integration test for cap enforcement

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 18:23:03 -04:00
jedarden
88bd70640a fix(types): add missing ReplayPlayer import and type annotation for transcript feature
- 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
2026-04-22 18:20:56 -04:00
jedarden
3b94b7eccb feat(matchmaker): add map fairness monitoring and auto-retirement (§14.6)
Implements the full map lifecycle audit as a hourly ticker in the
matchmaker:

1. updateMapFairnessStats: recompute per-slot win counts from completed
   matches into the map_fairness table
2. flagUnfairMaps: flag maps where any slot deviates >10pp from expected
   (1/N) across 80+ matches → status='probation'
3. retireDislikedMaps: force-retire maps with >20 net negative votes
4. pruneLowEngagementMaps: monthly bottom-10% engagement prune per tier
5. promoteClassicMaps: top-5 all-time engagement, 3+ months → 'classic'

Matchmaker already filters retired maps and gives probation maps 50%
reduced selection probability in selectMapLRU.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 18:19:24 -04:00
jedarden
6c1f031071 feat(config): add season_id + rules_version to Config per §4.2
- 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>
2026-04-22 18:09:26 -04:00
jedarden
4d1f5f976d feat(index): implement §14.5 predictions/open.json static file generation
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>
2026-04-22 17:53:48 -04:00
jedarden
32d7dd07e7 feat(index-builder): merge latest site build artifact before Pages deploy (§8.4, §11.3)
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>
2026-04-22 17:52:03 -04:00
jedarden
b2e9ba8319 fix(matchmaker): correct priority inversion in bestCandidate opponent selection
The bestCandidate function allowed game count to override pairing recency
when both candidates had non-zero LastPairedAt values. Per §6.1, pairing
recency must be the primary criterion with game count only breaking ties.

Also adds 10 tests covering: never-paired preference, oldest-pairing
preference, game count tiebreaking, Pareto distribution verification,
multi-opponent selection, and regression test for the priority inversion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 17:46:44 -04:00
jedarden
f4352c6304 feat(evolver): add workflow completion polling to promoter
Per plan §10.8 (deployment pipeline) and §9.8 (Argo Workflows):

- Add waitForWorkflowCompletion() that polls Argo Workflow API
- Add getWorkflowStatus() to fetch workflow phase/status
- Update Promote() to wait for workflow completion before inserting bot record
- Update Promote() to wait for K8s deployment readiness (waitForDeployment)
- Update triggerArgoWorkflow() to return workflow name for polling
- Add acb-evolved-bot-deploy-workflowtemplate.yml to manifests

The promotion flow now:
1. Writes bot source to bots/evolved/<bot_name>/
2. Commits and pushes source to git
3. Triggers Argo WorkflowTemplate
4. Waits for workflow completion (build + manifest commit)
5. Waits for K8s deployment to be ready
6. Inserts bot record into bots table
7. Updates programs table with bot_id/bot_name

This ensures evolved bots have running containers before being marked active.
2026-04-22 17:46:33 -04:00
jedarden
b4a975f5bf feat(index): implement §7.2/§15.2 maps/index.json and maps/{map_id}.json generation
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>
2026-04-22 17:46:27 -04:00
jedarden
89560e5ec4 feat(index): implement match thumbnail PNG generation (§7.2, §14.3)
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>
2026-04-22 17:42:36 -04:00
jedarden
35c705fa66 fix(api): remove unused time import from rotatekey_test.go
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 17:40:06 -04:00
jedarden
7df2fad568 feat(api): wire voteLtr rate limiter for upvote endpoint (§13.6)
Add dedicated 10/hour-per-IP rate limiter for POST /api/feedback/{id}/upvote,
separate from the 20/hour feedback submission limiter. Wired in main.go init,
server_test.go helper, and RegisterRoutes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 17:37:34 -04:00
jedarden
477a54c548 feat(matchmaker): implement §6.1 Pareto skill-proximity + LRU pairing algorithm
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>
2026-04-22 17:35:00 -04:00
jedarden
7e131d310f feat(api): add token-bucket rate limiting to public endpoints
Adds ratelimit package with per-IP and per-key HTTP middleware.
Applied to register (5/hr), feedback (20/hr), predict (60/hr),
and job submission (5/day) endpoints. Includes metrics counter
for rejected requests and periodic bucket cleanup goroutine.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:52:29 -04:00
jedarden
5a1130c77a feat(bot): add Pacifist bot (JavaScript) — non-aggressive attrition archetype
PacifistBot never attacks; it survives by maximizing distance from enemies
and retreating toward own core when cornered. Pure evasion strategy that
wins via opponent elimination by third parties.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:32:50 -04:00
jedarden
7f2407ed00 feat: add Prometheus metrics instrumentation across services
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>
2026-04-22 16:16:03 -04:00
jedarden
fff795ba6c fix(index-builder): improve rivalry recency test isolation + add metrics
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>
2026-04-22 16:07:18 -04:00
jedarden
7a0de02059 feat(evolver): persist cross-pollination state to Postgres per §10.2
Add crosspoll_state table to persist per-island generation counters
across evolver restarts. Load state on startup and save after each
cross-pollination check. Add persistence pattern and translation
structure tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:04:15 -04:00
jedarden
582b4c010d fix(worker): remove unused net/http import in acb-worker
Pre-existing issue blocking go vet and go test.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:55:45 -04:00
jedarden
80334c6e34 feat(evolver): expand MAP-Elites from 2-D to 4-D grid per §10.2
- 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>
2026-04-22 15:44:39 -04:00
jedarden
e90d2e37c9 test(evolver): integration tests for cross-pollination logic per §10.2
Adds mock store/LLM implementations and tests for CheckAndPollinate:
generation boundaries, fitness penalties, translation triggers,
multi-boundary catch-up, and empty island handling.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:26:18 -04:00
jedarden
c56cc8bae6 fix(matchmaker): multi-match crash cooldown (3 strikes / 30 min) per §4.5 + §6.1
Add crash_strikes and cooldown_until columns to bots table. Worker
increments strikes on crash (cooldown at 3), resets on success.
Matchmaker excludes cooldown bots from pairing, series scheduling,
and championship brackets. Fix erroneous cooldown filter on series
table in finalizeCompletedSeries (column only exists on bots).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:22:12 -04:00
jedarden
fb707b8461 test: integration tests for multi-match crash cooldown (3 strikes / 30 min) per §4.5 + §6.1
The crash cooldown system was already implemented across engine, worker,
and matchmaker. This adds comprehensive integration tests that verify:

- Single crash does not trigger cooldown
- Two crashes do not trigger cooldown
- Three consecutive crashes trigger 30-min cooldown
- Successful match resets strike counter
- Interleaved crash/success resets counter correctly
- Cooldown extends on repeated crashes while on cooldown
- Matchmaker eligibility query excludes bots on active cooldown
- Matchmaker eligibility query includes bots with expired cooldown
- Full end-to-end flow: 3 crashes → excluded → cooldown expires → re-pair

Tests use ACB_TEST_DATABASE_URL env var for PostgreSQL integration tests
and skip gracefully when not configured.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:14:03 -04:00
jedarden
d43cf83471 feat(evolver): island cross-pollination every 50 generations per §10.2
Adds cross-pollination logic that copies the top program from each island
to a random other island every 50 generations. When source and target
islands use different languages, the LLM translates the code. Generation
boundaries are tracked per-island to prevent duplicate events.

- New crosspoll package with boundary detection, migration, and LLM translation
- Added MaxGenerationByIsland DB query for generation counter tracking
- Integrated into RunEvolutionLoop with observability logging
- Tests for boundary logic, translation prompts, and target selection

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:13:27 -04:00
jedarden
c618f0b7a1 feat(worker): gzip replay compression at upload per §7.1
Worker now gzip-compresses replays before uploading to B2 with
key replays/{match_id}.json.gz and Content-Encoding: gzip.
Updated B2 client Upload to accept contentEncoding parameter.
Fixed downstream web consumers (matches, bot-profile, playlists)
to reference .json.gz URLs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:00:09 -04:00
jedarden
81940a1598 fix(api): fix getEnv stub and Dockerfile Go version for deployment readiness
The getEnv() function in server.go always returned the default value,
preventing ACB_R2_ENDPOINT/ACB_B2_ENDPOINT from being read at runtime.
Also updated Dockerfile from golang:1.24 to golang:1.25 to match go.mod.

K8s manifests for acb-evolver and acb-api already exist in
declarative-config/k8s/iad-acb/ai-code-battle/ (added Apr 21).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 13:29:45 -04:00
jedarden
f6ce4588f4 fix(api): rename /api/ui-feedback to /api/feedback per plan §13.6
The community feedback endpoint was registered as /api/ui-feedback in
the Go API but the plan and annotation.ts client both use /api/feedback.
Rename the route and update agentation-overlay.ts to match. Add a
route-level test asserting the canonical path and that the old path
returns 404.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 13:22:47 -04:00