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>
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>
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>
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>
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>
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>
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>
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>
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>
- Worker resolves open predictions after writing match results (resolvePredictions + upsertPredictorStats)
- API endpoints: POST /api/predict, GET /api/predictions/open, GET /api/predictions/history
- Frontend /watch/predictions page with polling, prediction submission, and history display
- predictor_stats table tracks streaks and accuracy per predictor
- Series format selection: fix threshold from >200 to >=200 for bo3 eligibility
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Evolution page: live polling (10s), activity feed, candidate tracking,
statistics section, island overview with live.json schema
- Series page: detailed series view with game-by-game results
- Seasons page: season list with status and champion display
- Predictions page: enhanced prediction UI with open matches
- API types: add CycleInfo, Candidate, ActivityEntry, Totals for live.json
- Embed: improved embeddable replay widget
- Mobile CSS: responsive breakpoints and bottom tab bar
- Exporter: enhanced live.json generation with full cycle/candidate data
- Matchmaker: series scheduling support with config
- Worker: additional database queries for series/season data
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Upload replays to B2 (Backblaze) instead of R2 for cold archive storage
- Write match results directly to PostgreSQL instead of HTTP API
- Perform Glicko-2 rating updates in worker after match completion
- Update config: ACB_R2_* env vars → ACB_B2_*
- Remove obsolete api_test.go (tested removed HTTP client)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cleanup of superseded code that no longer matches the architecture:
Removed:
- worker-api/ - Cloudflare Worker with D1, superseded by K8s-based matchmaker + direct PostgreSQL
- cmd/acb-indexer/ - TypeScript index builder, superseded by Go cmd/acb-index-builder/
- cluster-configuration/ - K8s manifests belong in ardenone-cluster repo
Gutted cmd/acb-api/:
- Removed registration, job claim/result endpoints (deferred for v1)
- Removed dead code: predictions.go, seasons.go, series.go, register.go, jobs.go, glicko2.go
- API is now a stub with only health/ready endpoints
- Matchmaker and workers handle the core loop without it
Updated PROGRESS.md to reflect current architecture.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a metrics HTTP server to acb-worker exposing Prometheus text format
at /metrics, plus /health and /ready K8s probe endpoints. Tracks counters
(matches, errors, jobs, replays, polls, heartbeats) and histograms
(match duration, replay upload duration, replay size). Instruments the
full worker execution flow. Fixes .gitignore binary patterns to use
root-anchored paths so cmd/ subdirectories aren't incorrectly excluded.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Dockerfile for acb-worker match execution container
- Add docker-compose.bots.yml for orchestrating all 6 strategy bots
- Add docker-compose.workers.yml for worker and indexer deployment
- Add .env.example documenting all required environment variables
- Add DEPLOYMENT.md with deployment guide and troubleshooting
- Update PROGRESS.md with Phase 6 progress
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Worker polls Cloudflare Worker API for pending match jobs
- Claims jobs and executes matches using the game engine
- Uploads replays to R2 via S3-compatible API
- Sends heartbeats during match execution
- Submits results back to Worker API
- Includes retry logic with exponential backoff
- API client tests for job coordination endpoints
Also fixes glicko2.ts: export g() and E() functions for testing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>