Commit graph

193 commits

Author SHA1 Message Date
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
c70729e298 feat(§14.1): per-bot debug telemetry public-visibility toggle
- Add filterReplayDebug() to server.go: strips debug fields from replay JSON
  for bots with debug_public=false; owner bypass via Bearer <api_secret>
- handleRegister and handleBotPatch already accept/persist debug_public
- PATCH /api/bot/{id} route lets owners toggle flag post-registration
- Registration form exposes debug_public checkbox (web/src/pages/register.ts)
- BotProfile and RegisterRequest types include debug_public (api-types.ts)
- Index builder reads and emits debug_public in /data/bots/{id}.json
- Replay viewer debug panel visibility controlled by server-filtered JSON
2026-04-22 18:21:08 -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
1b55d4dc51 feat(voting): add map voting UI widget to replay viewer (§14.6)
- Add MapID field to engine Config struct for inclusion in replay JSON
- Add map_id to TypeScript Config interface
- Add map voting panel to replay viewer sidebar with:
  - Map metadata display (dimensions, wall density, energy node count)
  - Thumbs up/down vote buttons wired to POST /api/vote/map
  - One vote per visitor enforcement (disables after voting)
  - Net vote count display with positive/negative coloring
  - Graceful fallback when map_id unavailable (local replays)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 18:08:55 -04:00
jedarden
77f713697e feat(web): add enriched replay badge to bot profile match history
§13.3: Display "Narrated" badge on enriched matches in bot profile
recent matches section, matching the match list page behavior.

- Add enriched badge rendering to renderMatchItem() in bot-profile.ts
- Add .enriched-badge CSS style (pink/magenta color to match
  playlist category styling)

The index builder already sets the Enriched flag via isMatchEnriched()
which checks R2 for commentary file existence. The match list page
(matches.ts) already displays this badge.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 18:00:07 -04:00
jedarden
b58e90a94f feat(deploy): register 10 Phase 13 expansion bots in docker-compose.bots.yml
Adds service definitions for Defender, Scout, Farmer, Pacifist, Phalanx,
Raider, Nomad, Opportunist, Assassin, and Kamikaze bots on ports 8087-8096.
Expands the bot field from 6 to 16 entries for rating diversity.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 17:55:56 -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
4dd91decad feat(bot): implement Kamikaze bot (JavaScript) — aggressive self-sacrifice archetype
Max-aggression strategy with unconditional attack: every unit charges the
nearest enemy, never retreats, and presses engagements within attack range.
Fixed self-collision bug by reserving all starting positions and freeing
them as bots move away. Falls back to enemy core rushing when no enemies
are visible, with exploratory spreading when no targets exist.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 17:11:28 -04:00
jedarden
75672f6a92 feat(web): add ambient activity awareness per §16.18
Favicon badge with numeric counter, tab title updates when backgrounded,
haptic pulse on mobile for key events, seasonal background color shift,
and 30s polling for new match/evolution activity.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 17:06:02 -04:00
jedarden
cecbc4a2a0 fix(bot): fix Opportunist retreat and attack pathfinding bugs
Two bugs in the Opportunist bot strategy:

1. retreatMove: when a lone bot is surrounded by enemies with no nearby
   allies, all passable directions scored below the -1 initial threshold
   due to flat enemy penalties. Fixed by scoring distance-from-enemies
   as a positive value (further = better) instead of a flat penalty,
   ensuring the bot always picks the safest direction.

2. attackMove: BFS could never reach enemy targets because the passable
   function excluded all enemy positions. The target IS an enemy, so the
   path was unreachable. Fixed by wrapping passable to treat the target
   position as passable during attack pathfinding.

All 19 tests now pass, including TestComputeMovesRetreat and
TestComputeMovesNearbyAdvantageAttack.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 17:03:02 -04:00
jedarden
e3e59396f3 chore: add .gitignore to assassin bot, remove build artifacts
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:56:23 -04:00
jedarden
f11d00a177 feat(bot): add Assassin bot (Rust) — decapitation archetype
All units rush the nearest enemy core via BFS, ignoring enemy units
and economy. No perimeter defense — full commitment to core destruction.
Targets persist across turns for consistent pathing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:55:33 -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
2df70c8ae0 feat(bot): add Nomad bot (Python) — constant relocation archetype
Mobile archetype that never camps. Picks a target region every ~20 turns
(random corner, opposite side, or enemy core), migrates all units toward
it, briefly engages enemies on arrival, then relocates again.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:48:12 -04:00
jedarden
53377d577f feat(bot): add Raider bot (Java) — hit-and-run harasser archetype
Implements a hit-and-run strategy that scouts for lone enemy bots,
attacks from flanking positions, then retreats after 1-2 engagement
turns to avoid reinforcements. Defends own core when under pressure.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:44:34 -04:00
jedarden
ed4be7a4d3 feat(bot): add Phalanx bot (Rust) — tight formation archetype
Formation-based combat bot that moves all units as a coordinated group:
- Circular mean centroid computation (toroidal-aware)
- Hexagonal packing formation slots with greedy slot assignment
- Rally mode when mean distance from centroid exceeds 3 cells
- Scored movement: formation cohesion + advance toward enemy concentration
- Attack range bonus when engaging enemies in formation
- Self-collision avoidance via claimed destination tracking
- 10 unit tests covering all core algorithms

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:43:39 -04:00
jedarden
62a5aa52ac feat(bot): add Scout bot (Python) — fog exploration archetype
Exploration-maximizing bot that maintains per-cell last-seen tick counters,
moves toward the stalest unobserved territory using forward-cone staleness
scoring, flees from enemies within extended combat range, and distributes
multiple bots across angular zones for maximum map coverage.

Archetype: Low Aggression, Low Economy, High Exploration, Low Formation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:40:35 -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
5362b6c011 feat(bot): add Farmer bot (Go) — economy-maximizer archetype
Economy-maximizing bot that prioritizes energy collection and spawning
while avoiding combat entirely. Seeks nearest uncontested energy via BFS,
flees enemies within 3 cells, avoids contested energy tiles, and stays
near active cores for maximum spawn throughput. Includes 12 unit tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:30:26 -04:00
jedarden
968af06522 feat(evolution): add progressive disclosure to generation log and below-fold sections per §16.15
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:22:01 -04:00
jedarden
3cefabb9ed feat(bot): add Defender bot (C#) — core-hugging perimeter archetype
Core-defense strategy: bots patrol evenly-spaced slots around their
own core, intercept enemies entering the perimeter radius, and never
chase past the perimeter. Falls back to energy gathering when cores
are lost.

MAP-Elites profile: Low Aggression, Low Economy, Low Exploration, High Formation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:19:08 -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
4a92539c6f feat(web): progressive disclosure for dense pages per §16.15
Leaderboard, bot directory, match history, and playlist pages now use
expandable rows/cards, IntersectionObserver lazy rendering, windowed
virtual scrolling (for 1000+ leaderboard entries), and batched "Show
more" affordances. All expand/collapse transitions respect
prefers-reduced-motion. Keyboard accessibility (Enter/Space to toggle)
and ARIA attributes (aria-expanded, aria-controls) added throughout.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:43:50 -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
da824f7360 fix(web): remove unused BATCH_SIZE constant in bots page
The bots page was refactored to use lazySection but left behind an
unused BATCH_SIZE = 50 constant, causing a TS6133 build error.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:17:20 -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
04927a76b0 feat(web): progressive disclosure — lazy-load, expand details, windowed lists per §16.15
Virtual list tracks expanded row heights for correct spacer calculations.
Leaderboard mobile cards use event delegation so toggles work inside lazy
sections. Mobile card details animate with max-height instead of display
toggle. Reduced-motion rules cover all expand/collapse patterns.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:06:55 -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
1451ca5a50 feat(web): mobile swipe-through playlist carousel per §16.16
TikTok-style full-screen vertical carousel for playlist matches on mobile.
Swipe up/down advances between replays, horizontal swipe reveals metadata
panel with match details. Director mode auto-adjusts speed based on action
density. Auto-advance with animated countdown ring after replay completion.
Desktop layout unaffected — carousel only activates on mobile (<768px).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 14:54:38 -04:00
jedarden
677fde5245 fix(engine): use core1 variable in spawn priority tiebreak test
The TestSpawnPriority_LowerIDBreaksTie test declared core1 but never
referenced it, causing a compile error. Added an assertion that
core1.LastSpawnedTurn remains 0 (confirming it didn't spawn).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 14:45:06 -04:00
jedarden
67d94cebbd feat(web): add theater component and playlist carousel
Commit the TheaterMode component (§16.17) and playlist carousel
referenced by recent commits. Includes bots page and playlists
page enhancements.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 14:31:08 -04:00
jedarden
4930d7a841 feat(web): integrate theater mode into replay viewer per §16.17
Wire the existing TheaterMode component into the replay page — adds
fullscreen toggle button on canvas, F key shortcut, auto-hiding
controls with 3s inactivity, vignette pulse on critical moments,
and mobile Fullscreen API support.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 14:28:54 -04:00
jedarden
5cf9a786d5 feat(web): progressive disclosure — lazy sections, expandable details per §16.15
Wire IntersectionObserver-based lazy rendering into bot profile (recent
matches below fold) and leaderboard (mobile cards). All three dense pages
(leaderboard, matches, bot-profile) now use expandable rows/cards for
secondary detail, windowed rendering for long lists, and keyboard-accessible
"Show more" affordances. Expand/collapse animations respect reduced-motion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 14:01:13 -04:00
jedarden
28f6d99bff feat(replay): smooth 400ms cross-fade between view modes per §16.11
Implement double-buffered canvas cross-fade when switching between
dots, Voronoi territory, and influence gradient views. Old layer fades
out while new layer fades in with ease-in-out cubic easing over 400ms.
Respects prefers-reduced-motion by snapping instantly when set.

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