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>
Two root causes prevented bots from making any moves:
1. SignRequest signing string included timestamp ({match_id}.{turn}.{timestamp}.{hash})
but all bots implement verifySignature without timestamp ({match_id}.{turn}.{hash}).
Fixed by dropping timestamp from the signing string; X-ACB-Timestamp header is still
sent for clock-skew checks but not in the HMAC.
2. The API stores bot secrets AES-GCM encrypted (184 hex chars) in the DB. The worker
was passing the ciphertext directly as the HMAC key, while bots use their plaintext
k8s secret (64 hex chars). Fixed by decrypting in the worker using ACB_ENCRYPTION_KEY.
Also tightens the home page winner filter to exclude winner_id="0" stalemates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove the Choose File / file input from the Load Replay panel entirely
- Hide the Load Replay panel when a ?url= param is already in the route,
so navigating from a match link goes straight to the replay with no
form in the way
- Update no-replay overlay text: "Loading replay…" vs "Enter a replay URL"
- Remove the fileInput change listener (file uploads no longer supported)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Filter the match pool to those with winner_id set before selecting
the featured replay, so a stalemate is never shown as the hero replay.
Falls back to any completed match if no decided matches exist.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace native alert() calls in replay.ts URL/file load error handlers
with inline error display in the no-replay div. Add combat attack
direction visualization to §16.9 of the plan: engine emits combat_death
events with killer bot list; viewer draws directed arrows on kills.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Playlist card images were pointing at /replays/{id}.jpg which returns
the SPA shell HTML. Changed to /r2/thumbnails/{id}.png which routes
through the Pages Function proxy to R2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace /replays/{id}.json.gz with /r2/replays/{id}.json.gz in all pages
(home, matches, bot-profile, playlists, feedback). The /replays/ path is not
served by Cloudflare Pages — it falls back to the SPA shell causing
"Unexpected token '<', <!DOCTYPE..." JSON parse errors. The R2 proxy at
/r2/ is the correct path for replay content.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Router: strip query string from hash path before route matching, and merge
hash query params (e.g. ?url=) into the params passed to route handlers.
Add /watch/replay route (without :id) so ?url= links work without a path ID.
Embed: fall back to demo replay when the match replay is not found in R2/B2
instead of showing "Failed to fetch" (handles test match IDs with no replay).
App: extend skeleton and PIP checks to match /watch/replay (with or without :id).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The aicodebattle.com domain was never registered. Replace all hardcoded
https://r2.aicodebattle.com references with the /r2/ relative path, served
by a Cloudflare Pages Function that reads from the acb-data R2 bucket via
binding. Adds web/functions/r2/[[path]].ts and web/wrangler.toml.
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>
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
Add perspective dropdown (Omniscient + per-player) that filters the
replay view to a single player's fog of war, hiding cells/bots outside
their vision radius. Add minimap canvas in the corner showing the full
grid with walls, energy, cores, bots, fog overlay, and a viewport
rectangle. Clicking the minimap pans the main canvas and zooms in.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
§16.13: Picture-in-Picture replay mini-player
When navigating back to a replay page where PIP was active, the
restoration logic was creating duplicate canvas elements (the
placeholder from the new DOM and the restored canvas from PIP).
Changes:
- Remove placeholder canvas before inserting restored PIP canvas
- Set 'replay-canvas' ID on restored canvas for TheaterMode and other consumers
- Use consistent 'actualCanvas' variable throughout initialization
The full PIP flow now works:
1. User starts replay on /watch/replay/:id
2. Clicks nav link → canvas reparents to floating mini-player
3. Playback continues uninterrupted
4. Click "return" → canvas reparents back to inline wrapper
5. Replay resumes at same tick
Co-Authored-By: Claude Opus 4.7 <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>
- 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>
§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>
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>
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>
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>
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 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>
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>
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>
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>
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>
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>
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>
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>
Staging manifests for sync to declarative-config/k8s/apexalgo-iad/ai-code-battle/:
- acb-evolver: Deployment + ServiceAccount with LLM/PG/R2 secrets
- acb-api: Deployment + Service + IngressRoute for api.ai-code-battle.ardenone.com
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three perceived-performance features:
- Preload on hover: internal links prefetch target JSON data after 150ms
hover debounce using <link rel=prefetch>. Touch events prefetch
immediately.
- Skeleton screens: every async page shows a shimmer-animated placeholder
matching the final content layout (leaderboard rows, bot profile card,
replay canvas, playlist grid, etc.) instead of generic "Loading..." text.
- Instant back-cache: back/forward navigation restores scroll position and
cached HTML from an in-memory LRU cache (8 pages), making back navigation
0ms.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
Sync annotations to both the canvas renderer (spatial markers) and the
event timeline (colored badge markers) so user feedback appears in both
the replay canvas and the timeline ribbon.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add director.ts component with action density computation, speed schedule
generation, and eased speed transitions. Integrate into replay viewer with
Director option in speed selector, target duration presets (30s/1min/2min/5min),
speed indicator display, and scrubbing pause/resume.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Working tree had removed the annotation component imports while the
initAnnotations function and ANNOTATION_OVERLAY_STYLES template
reference still depended on them, causing TS build failures.
- Fix TS6133: rename _matchId → matchId in AnnotationOverlay and use
this.matchId in filter so the field is actually read
- Align ANNOTATION_TAGS in feedback.ts to four types from plan §8.3:
insight, mistake, idea, highlight (matching components/annotation.ts)
- Update LS_KEY to acb_annotations_v2 to avoid stale-format conflicts
- Fix duplicate import block in api-types.ts (re-exported evolution types)
- Remove unused debugPanelChevron ref in replay.ts; add annotation
imports for AnnotationOverlay and createAnnotationForm
The AI commentary generation backend (enrichment.go) and client-side
subtitle display (replay-viewer.ts, embed.ts, home.ts) were already
complete in prior commits.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Playlist curation per §10 is fully implemented in the index builder:
- generatePlaylists() writes /data/playlists/index.json and {slug}.json
- curateWeeklyHighlights() selects best-of-week by upsets, elite
clashes, marathon turns, and closest finishes (last 7 days)
- persistGeneratedPlaylists() upserts to playlists/playlist_matches DB tables
- /data/playlists/ stub files seeded for all 12 curated collections
Replay viewer improvements shipped alongside:
- WinProbPoint refactored from {p0,p1} to {probs: number[]} for N players
- renderWinProbSparkline draws one line per player with matching colors
- replay.ts updated to build probs[] from replay.win_prob arrays
- Dynamic legend generated from replay.players instead of hardcoded P0/P1
New annotation overlay component (§16.8):
- AnnotationOverlay: timeline track, per-turn list, canvas markers
- createAnnotationForm: type selector, author, body, localStorage + API
- ANNOTATION_OVERLAY_STYLES: self-contained CSS for the overlay
Evolver: add mutations_per_hour metric to Totals (live.json §14)
Types: consolidate evolution types into types.ts, re-export from api-types.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the flat horizontal playlist row with a curated layout:
- Top 3 featured playlists (Best of Week, Biggest Upsets, Closest Finishes)
displayed as distinct visual sections in a 2:1:1 grid
- "Best of Week" highlighted with a primary accent style
- Remaining playlists shown in a scrollable "More Collections" row
- "Browse all →" header link routes to /watch/playlists
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix nav/home breakpoints from 768px → 639px to match design system
- Add leaderboard mobile card view with tap-to-expand stats and full-stats link
- Add canvas wrapper aspect-ratio:1 on phone (fills full width, square viewport)
- Add commentary text scroll on mobile, win-prob header stacking
- Replay viewer: mobile controls, pinch-to-zoom, tap-to-play, swipe scrub,
floating view-mode toggle, debug telemetry slide-up sheet (already in place)
- Sandbox: desktop-required message with link already implemented
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove duplicate SWR cache (use shared fetchers from api-types.ts)
- Add territory view mode to featured replay embed
- Use demo replay fallback when no live matches available
- Compact layout with tighter spacing for 1080p above-the-fold
- Add missing placeholder data files: evolution/meta.json, seasons/index.json
- Fix unused import in cmd/acb-index-builder/s3_test.go
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>
- 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>
PredictionHistoryEntry was imported but never used, and API_BASE was
declared but never read. These caused tsc to fail with TS6196/TS6133.
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>