feat: Bot Arena launch — smart strategy, Polymarket odds, federation, incognito gate#306
Merged
Conversation
…ederation) Captures the design for the Open Bot Arena: 18k cosmetic seed bots, leaderboard Humans/Bots/My Pools tabs, @tournamental/bot-sdk Node package, central bulk-insert API, quota auth, leaderboard cache, and the reference Tournamental Sage bot. Phase 2 preview captures the federated compute network design: per-node merkle commitments, pre-kickoff blockchain anchoring, audit verification, and the trust model. Phase 1 must lay groundwork (merkle tree, committed_at_utc per pick) so Phase 2 ships as a bolt-on rather than a rewrite. Tim approved: option A from the brainstorm (Phase 1 central tier launches by 11 June 2026; Phase 2 federation starts immediately after). Refs: docs/internal/press-2026-06-07-perfect-bot-bracket/ (gitignored) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
21 tasks across 2 streams, TDD pattern, exact file paths, complete code per step. Phase 2 forward-compat constraints captured in Tasks 5, 10, and the schema migration in Task 2. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md Signed-off-by: Tim Thomas <0800tim@gmail.com>
Bots are flagged at the auth layer so the prize-eligibility gate and leaderboard scope filter can short-circuit on a single column read. Default 0 backfills existing rows safely. Adds insertBotUser() so the seed CLI and bot SDK mint bot rows without going through the OTP flow. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §4.1 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
… bot_owner, api_key, quota_window, federated_*) Phase 1 scaffolding for the Open Bot Arena plus the Phase 2 federation hooks captured up-front so we do not paint ourselves into a corner. brackets.committed_at_utc lets a future audit reconstruct which kickoff OTS commitment anchored which picks. federated_node and federated_leaderboard_snapshot land the schema for the Phase 2 node-operator protocol so endpoints can write before the Docker image ships. The file is 0013_bot_arena.sql, not 0009 as the brief said, because 0009 through 0012 are already taken by the syndicate / bracket-import migrations that shipped this month. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §8.1, §15.2, §15.6 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
Default landing tab is Humans (prize-eligible race). Bots tab shows AI competitors seeded from a deterministic mock until the live /api/v1/leaderboard?scope=<audience> endpoint ships. My Pools tab renders an empty state with deep links to /pools and /syndicates/new for now; backend wires in a follow-up. The tab strip lives in a small LeaderboardTabs client component that owns active state, implements roving-tabindex keyboard nav (Left / Right / Home / End), and uses role=tablist + aria-selected per W3C ARIA Authoring Practices. Reuses the existing <Leaderboard> component with a new optional `scope` prop that flows into a data-scope hook and, when wired, into the data fetch URL. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §5 Refs: docs/superpowers/plans/2026-06-07-bot-arena-phase-1.md Task 15 Refs: sessions/2026-06-07_agent-a4_bot-arena-frontend.md Signed-off-by: Tim Thomas <0800tim@gmail.com>
Phase 1 of the Open Bot Arena. Sets up the public NPM package @tournamental/bot-sdk with an ESM + CJS dual build via tsup, Apache 2.0 licence matching the repo, vitest harness, and the core type surface (Outcome, Pick, MatchSpec, BulkSubmission, BulkResponse, OddsSnapshot) that mirrors the POST /v1/picks/bulk contract. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md Refs: docs/superpowers/plans/2026-06-07-bot-arena-phase-1.md (Task 11) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
postWithRetry handles 429 and 5xx with exponential backoff (base 200ms, 3 attempts) so transient launch-day spikes do not burn the operator's quota. Bot.pick is idempotent on match_id so re-picking replaces the outcome, matching the server's ON CONFLICT DO UPDATE semantics. The queue is preserved on flush error so callers can retry. Includes submitBulk + submitBulkPicks low-level helpers for power users packing many bots into one bulk request. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md (sections 6-7) Refs: docs/superpowers/plans/2026-06-07-bot-arena-phase-1.md (Task 12) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
Runs N bots in parallel with default concurrency 16, sized for the bulk-insert endpoint's 60 req/min budget. Failures inside the per-bot fn or flush() are counted but do not stop the swarm: large runs prefer partial completion over an all-or-nothing abort. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md (section 6.4) Refs: docs/superpowers/plans/2026-06-07-bot-arena-phase-1.md (Task 13) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
Each example is self-contained and runs with pnpm tsx examples/<file>.ts. Covers chalk (baseline), Claude, GPT, swarm, Polymarket arbitrage, Kelly conviction filter, ensemble vote, and post-tournament best-of (the card-stacking strategy from spec section 15.1). README points operators at /bots/keys for self-service issuance and links to the full /bots/sdk docs page. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md (section 10) Refs: docs/superpowers/plans/2026-06-07-bot-arena-phase-1.md (Task 14) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
Academic emails (.edu, .ac.uk, .ac.nz, .edu.au, .ac.za, .edu.cn, .ac.jp) get 10x the default quota at issuance: 10,000 bots and 1M picks per hour. Plaintext key returned only at issuance; subsequent lookups go through the sha256 hash so a DB leak does not expose any callable keys. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §6.3, §14 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
Scaffolds apps/operator-swarm/ so Tim can run a 1M-bot Tournamental Bot Node on the prod box under the 0800tim user with PM2 supervision, auto-restart, log rotation, and a localhost /stats health probe. The wrapper does no runtime logic: it just schedules the @tournamental/bot-node CLI (A3) and manages the operator credentials lifecycle. This guarantees the public Bots leaderboard tab has real federated activity from day one of the FIFA World Cup 2026 launch. Sizing: 12 GB heap covers the documented ~10 GB peak at 1M bots with ~2 GB headroom. Scale up via --max-old-space-size, or shard across multiple OPERATOR_NODE_LABEL instances past ~2M bots per process. Files: - apps/operator-swarm/package.json (scripts: register, start, stop, restart, reload, status, logs, health, save, delete) - apps/operator-swarm/.env.example (six brief vars plus BOT_NODE_STATS_PORT and OPERATOR_CREDENTIALS_PATH) - apps/operator-swarm/scripts/register.sh (idempotent, validates JSON, chmods credentials 600) - apps/operator-swarm/ecosystem.config.cjs (PM2 fork mode, 12 GB heap, rotated logs to logs/) - apps/operator-swarm/scripts/health-check.sh (curl /stats, human/json/ quiet modes, stale-commit detection) - apps/operator-swarm/README.md (full deploy guide for the server admin, sizing table, troubleshooting) - apps/operator-swarm/.gitignore (logs/, .env, dump.pm2) Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md Refs: docs/superpowers/plans/2026-06-07-bot-arena-phase-1.md Refs: sessions/2026-06-07_A8_operator-swarm-scaffold.md Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
BotOwnerStore ties a bot user to its issuing API key and powers the ownership guard on /v1/picks/bulk. QuotaStore enforces the per-key picks-per-hour cap via a (api_key_hash, window_start) composite key and an atomic upsert-with-add on consume. tryConsume() refuses single-request payloads larger than the cap up front so a misconfigured key cannot exhaust its quota in one shot. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §6.4, §7.2 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
Brings in apps/operator-swarm/ (Tim's 1M-bot federated swarm node PM2 wrapper) on top of the integration branch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Runs every 6 hours under PM2, reads Polymarket odds via the apps/odds-ingest snapshot endpoint, asks Claude Opus 4.7 for a per-match decision, posts via @tournamental/bot-sdk. Reserved handle @Sage. Demonstration bot for the SDK launch; competes publicly on the Bots leaderboard tab. Sage exits after one decision pass so PM2's cron_restart drives the cadence. Strategy module has deterministic fallbacks: parser rejects anything other than the three canonical tokens and decide() falls back to the favourite implied by the odds (with home_win tie-break). Tests cover parser strictness, favourite selection, prompt construction, and the Claude-returns-garbage and Claude-throws fallback paths. Env vars: ANTHROPIC_API_KEY, TOURNAMENTAL_API_KEY, optional TOURNAMENTAL_BOT_ID (otherwise Sage calls /v1/bots/register once and caches in .sage-state.json). Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §9 Refs: docs/superpowers/plans/2026-06-07-bot-arena-phase-1.md Task 20 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
Persists the registry (federated_node) and per-(node, match) aggregate snapshots (federated_leaderboard_snapshot). Supports the three Phase 2 endpoints: register (issue node credentials), commit (pre-kickoff merkle root), reportLeaderboard (post-match aggregate + top-K). The commit + reportLeaderboard upserts merge cleanly so out-of-order delivery (a node reports leaderboard before the central tier has seen its commit) does not lose data. listFederatedTopK powers the ?source=federated query on the public leaderboard. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §15.2 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
New @tournamental/bot-node package: a runnable Node.js + SQLite app any operator (including Tim's million-bot swarm) clones, registers, and runs to commit merkle roots to the central server pre-kickoff and report aggregate leaderboards post-match. Highlights: - CLI: register, generate, commit, score, serve, plus a one-shot --bots=N --dry-run smoke path. - better-sqlite3 storage in WAL mode, prepared statements, batched transactions; sized for 1M bots on a 32-core box. - sha256 sorted-pair merkle tree matching the central server shape, with per-bot proof endpoint exposed via Fastify on /v1/proof/:match/:bot. - Deterministic chalk strategy (chalk_score uniform in [0.65, 0.90]) and pluggable Strategy interface for Claude/GPT/custom plugins. - Dockerfile + docker-compose.yml for one-command operator deploy. - 16 vitest cases covering merkle, generator determinism, and scorer flow; build, typecheck, tests, and dry-run all green. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md section 15 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
Builds a sorted-pair SHA-256 merkle tree over (bot_id, match_id, outcome, locked_at) leaves. Phase 1 uses it inside the kickoff OTS commitment so the on-chain hash is already a merkle root, not a flat sha256; Phase 2 federated nodes mirror the same construction so adding their leaves to the audit tree is a matter of adding inputs, not changing the verifier shape. Sorted-pair (rather than RFC 6962-style positional) keeps the verifier small enough to port to any language in 50 lines and matches the OpenZeppelin MerkleProof.sol shape so a Phase 3 on-chain verifier ports trivially. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §15.6 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
Adds @tournamental/bot-mcp, a Model Context Protocol server that lets Claude Desktop, Cursor, and any MCP-compatible AI client play in the FIFA World Cup 2026 prediction tournament conversationally. Six tools wrap the public api.tournamental.com surface defined in docs/superpowers/specs/2026-06-07-bot-arena-design.md: - get_matches: 104-match catalogue with kickoff times - get_odds: Polymarket-style odds snapshot per match - get_my_bots: bots owned by the configured API key + quota - submit_pick: single-pick submission (wraps the bulk endpoint) - submit_bulk: up to 10,000 picks across up to 1,000 bots, atomic - get_leaderboard: humans / bots / pools tabs Boots over stdio via the standard @modelcontextprotocol/sdk transport so any MCP client config of the form `command: npx, args: [-y, @tournamental/bot-mcp]` works out of the box. Bundled example-claude-desktop-config.json is the drop-in form. API key is read from TOURNAMENTAL_API_KEY in the spawned-process env; TOURNAMENTAL_BASE_URL is an optional override for staging. 16 tests cover each tool's request shape against a fake fetch plus an end-to-end MCP server roundtrip over InMemoryTransport that asserts tools/list reports exactly the six expected tools. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §3 Phase 2 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
apiKeys, botOwners, quotas, federatedNodes hang off the store so the rest of the service does not need to know which file or migration laid the SQLite down. topNByScope(tournamentId, scope, n) returns humans-only, bots-only, or everyone for the new tabbed leaderboard. The new prepared statements partition cleanly so the cache key per tab does not blow up the LRU. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §5, §6 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
Ship the public Bot Arena API contract as OpenAPI 3.0.3 at docs/openapi/bot-arena.yaml covering /v1/picks/bulk, /v1/leaderboard?scope=, /v1/leaderboard/my-pools, /v1/bots/keys/issue, and the Phase 2 /v1/nodes/* federated node protocol with full request/response schemas, auth, and error responses. Cross-link the existing Humanness Score policy in doc 20 to the Bot Arena design spec, implementation plan, developer guide source, and public /developers + /bots/sdk pages so the two policy surfaces interlock cleanly. Press refresh (lead angle shift to "Can a human beat a bot?"), university outreach plan (32 departments + 8 AI labs + 5 prediction-market communities + 20 social commentators), developer guide content, and dev-guide press release all live at docs/internal/ per the gitignored internal-docs policy. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md Refs: docs/20-identity-humanness-bots.md Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
Editorial doc page covering the eight sections from spec §10: quickstart, architecture, API reference, bulk-insert, quotas, live data feeds, eight worked examples, and FAQ. Style mirrors /the-bet so the developer micro-site reads as one consistent surface, with the gold accent on the API surface so it pops as the thing of value. Sticky on-page TOC anchors each section by stable id (#quickstart, #architecture, #api-reference, #bulk-insert, #quotas, #feeds, #examples, #faq). Eight-question FAQ covers cash ineligibility, LLM legality, OTS verification, federated nodes, licence, and feature requests so a press visitor can answer their first ten questions without leaving the page. Tests assert the eight stable section ids exist and that the page cross-links to /bots/keys and /bots/node so the SDK micro-site stays internally connected. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §10 Refs: docs/superpowers/plans/2026-06-07-bot-arena-phase-1.md Task 16 Refs: sessions/2026-06-07_agent-a4_bot-arena-frontend.md Signed-off-by: Tim Thomas <0800tim@gmail.com>
Server component gates issuance behind the inbound session cookie, resolves the verified email via loadUserContact(), and proxies issuance to the game-service /v1/bots/keys/issue endpoint. The plaintext key is forwarded to the browser once; the server only persists the SHA-256 hash so a lost key cannot be recovered (issue a new one + revoke the old via info@tournamental.com). Client form is a single labelled input + submit + result panel. Validates label charset and length client-side and server-side so the upstream call always has a sanitised payload. Empty / overlong / garbage labels short-circuit at the proxy with a 400 before any upstream traffic. Unauthenticated visitors see a sign-in gate with a /login deep link that round-trips back to /bots/keys after auth. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §6.3 Refs: docs/superpowers/plans/2026-06-07-bot-arena-phase-1.md Task 17 Refs: sessions/2026-06-07_agent-a4_bot-arena-frontend.md Signed-off-by: Tim Thomas <0800tim@gmail.com>
The /developers landing surface routes to every developer-facing destination in one place: /bots/sdk, /bots/keys, /bots/node, /run, GitHub, NPM, MCP. Two grouped cards-on-cards lists (On Tournamental + Off-site) with phase badges (Live, Phase 1, Phase 2, Beta) so a visitor can scan readiness at a glance. /bots/node is the federated bot-node operator guide per spec §15: why federate, quickstart (Docker compose when Phase 2 lands), the pre-kickoff commitment flow, post-match aggregation, third-party verification, the four audit constraints, and a capacity-planning table covering 100k -> 1B bot ranges. MORE_DESKTOP nav gets a Bot Arena entry pointing at /developers, matching prefix /developers. Active-route highlighting works the same as every other More entry. Reuses /bots/sdk styles for /bots/node so the developer micro-site reads as one consistent editorial canvas. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §15 Refs: sessions/2026-06-07_agent-a4_bot-arena-frontend.md Signed-off-by: Tim Thomas <0800tim@gmail.com>
Aligns the public house-prize terms with the Open Bot Arena launch.
New section 4a sits between Eligibility (4) and The Bracket (5),
anchored at #bots so the SDK page (and any future press) can deep
link to the clause.
States:
- bots welcome to compete on a separate leaderboard tab
- cash Prize ineligibility (Humanness Score >= 50 required)
- bot Humanness is 0 by design
- perfect-bracket recognition is non-cash: badge + research
co-author invitation + non-monetary trophy
- operators disclose ownership at API key issuance and operate
within published quotas
Tests assert the #bots anchor, the cash-ineligibility statement,
the 50-point Humanness floor, and the three non-cash recognition
items. The /bots/sdk cross-link is verified so the terms surface
stays self-consistent with the SDK micro-site.
Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §11
Refs: docs/superpowers/plans/2026-06-07-bot-arena-phase-1.md Task 18
Refs: sessions/2026-06-07_agent-a4_bot-arena-frontend.md
Signed-off-by: Tim Thomas <0800tim@gmail.com>
Validates payload via Zod (10k pick + 1k submission ceiling), checks the API key, verifies ownership of every referenced bot_id in one SQL round trip, charges the hourly quota up-front, and commits the upsert inside a single SQLite transaction with a prepared statement reused across rows. 10k picks lands in ~80ms on the dev box, comfortably inside the 500ms p99 budget. Per-bot existing brackets are merged so a re-submit does not wipe prior picks. Numeric match_ids land on matchPredictions (group stage); alphanumeric ids land on knockoutPredictions, matching the WC2026 fixture catalogue shape. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §7 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
Outcome summary, file inventory, and dependencies for the Phase 1 Bot Arena frontend agent (A4). 5 prior commits land the leaderboard tabs, /bots/sdk docs, /bots/keys issuance, /bots/node operator guide, /developers hub, terms clause, and nav entry. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md Refs: docs/superpowers/plans/2026-06-07-bot-arena-phase-1.md Signed-off-by: Tim Thomas <0800tim@gmail.com>
Handler is attached to the tab button, not the tablist div. Tighten the generic so tsc passes and the event is correctly typed for any future button-only event reads. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §5 Signed-off-by: Tim Thomas <0800tim@gmail.com>
Browser-first hook page for the Open Bot Arena. Leads with "spawn a million unique bots in your browser", walks through the 5-step zero- install setup, explains the six swarm-tuning sliders (chalk bias, draw bias, upset rate, update cadence, LLM strategy, bot count), and guarantees within-swarm bracket uniqueness with probability-mass spread. Three runtimes (browser default, Node SDK, federated Node operator) share one federated protocol, one merkle commitment shape, one blockchain audit trail, and one uniqueness guarantee. Live leaderboard comparison (Humans vs Bots vs My Pools) is the central story. Nav: added Bot Arena to MORE_DESKTOP (between Match calendar and About) and DRAWER_PRIMARY (between Match calendar and Pools). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
…terms clause) Signed-off-by: Tim Thomas <0800tim@gmail.com> # Conflicts: # apps/web/components/shell/nav-links.tsx
…rboard) Phase 2 forward-compat surface. External node operators register a public URL + owner email under their bot-owner API key; the central tier mints a separate node credential (also tnm_-prefixed) bound to a new node_id. Pre-kickoff merkle commits land via /commit; post-match aggregate reports + top-1000 rows land via /leaderboard. Auth scheme reuses the api_key table so owner credentials and node credentials share the same hash + revocation surface. /commit and /leaderboard refuse when the auth key does not own the referenced node_id (spec §15.3 audit). /commit enforces the strict pre-kickoff invariant: kickoff_at must be in the future or the call is a 422. The Phase 1 build ships these endpoints empty-data so external node operators can wire clients before the Phase 2 Docker image goes public, and so the central tier can integration-test the merge into the federated leaderboard view. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §15.2, §15.3 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
The Next.js proxy at /api/v1/bots/keys already pointed at /v1/bots/keys/issue on the game-service, but that endpoint did not exist. Anyone hitting the self-serve Generate-key button got a 404 unless they happened to have a Supabase session, in which case the older /v1/me/api-keys path picked them up. This commit ships the missing endpoint. It accepts a service-to-service shared secret in the x-bot-keys-shared-secret header (env: GAME_BOT_KEYS_SHARED_SECRET on both ends), validates the body via Zod, defers to ApiKeyStore.issue() for the actual mint, and returns the plaintext key once. Fails closed with 503 when the env var is unset so an accidentally-deployed-without-secret build is not a free key-mint endpoint. Quotas inherit from ApiKeyStore.issue (.edu / .ac.uk / .ac.nz / .edu.au / .ac.za / .edu.cn / .ac.jp get 10x). Constant-time-ish secret compare avoids leaking matched-prefix length. The Next.js proxy now sends both x-bot-keys-shared-secret AND the legacy X-Tournamental-Service header so older game-service builds without the new endpoint keep working through the rollout. 10 new endpoint tests cover the happy path, academic quota lift, all four error codes, and an end-to-end smoke that the freshly minted key authenticates against /v1/picks/bulk. Refs: apps/web/app/api/v1/bots/keys/route.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
/profile/api-keys silently no-opped for SMS-OTP and Telegram users because the page only knew how to call Supabase /v1/me/api-keys, and those auth flows don't mint Supabase sessions on vtorn-dev. Add a mintApiKeyViaCookie() client that POSTs to /api/v1/bots/keys (the existing Next.js proxy that authenticates via the inbound-session cookie). When browserClient() returns null, ApiKeysPage now uses the cookie path instead of surfacing an error banner. Also fix the secondary bug where refresh() left the loading flag on forever when Supabase was unconfigured, so the page got stuck on "Loading...". UX matches the Supabase path 1:1: the freshly minted key renders in the same FreshKeyBanner with copy-to-clipboard. The key list stays empty on the cookie path until the game-service exposes a list-my-bot-keys surface (out of scope for tonight). Refs: apps/web/app/api/v1/bots/keys/route.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
provenance: true only works under GitHub Actions OIDC. Manual publish from a laptop fails with "Automatic provenance generation not supported for provider: null". Drop the flag so the manual ship path works tonight. Re-add via CI workflow when we set one up. Signed-off-by: Tim Thomas <0800tim@gmail.com>
…ofile + perfect-track)
Four pieces of plumbing so each operator running a browser swarm or
Node bot-node publishes summaries to a Cloudflare-edge-cached endpoint,
and the platform surfaces a fire badge when any bot survives match 80
on a perfect track.
A. POST /v1/swarms/<operator_id>/summary
* operator_id = sha256(api_key) so no second identity column.
* Bearer auth; 401 missing, 401 invalid, 403 not_operator on mismatch.
* Idempotent upsert on (operator_id, kickoff_at).
* Persists into new swarm_summary table.
B. GET /v1/swarms/<operator_id> and GET /v1/swarms
* Public, no auth.
* Cache-Control: public, s-maxage=60, stale-while-revalidate=300
* ETag based on latest generated_at; 304 fast path.
* Cloudflare edge caches without touching origin.
* Global (no operator) returns top 100 by best_bot_score.
C. /profile/[handle]/swarm
* Tab on the profile page: total bots, best score, sparkline.
* "Download swarm summary (JSON)" button hits the cached endpoint.
* Own profile only: "Download my raw bot brackets (JSON)" pulls
from IndexedDB, no server call. Raw picks of other users stay
private.
D. perfect_track_alert table + watch service
* Inline trigger after every POST /v1/swarms/<id>/summary.
* Also after every admin match-settled event in routes/match.ts.
* Webhook fan-out via PERFECT_TRACK_WEBHOOK_URL (no-op if unset).
* <PerfectTrackBadge/> on /leaderboard surfaces a 🔥 N bots still
on a perfect track after match X.
Browser swarm integration:
* BrowserSwarm.tsx calls publishOperatorSummary after each batch.
* federation.ts gains the publish helper; soft-fails on network error
with no user-visible warning.
* persistence.ts adds operator_api_key store; no schema bump because
it lives in the existing STORE_DEVICE namespace.
Tests:
* 257/257 game tests pass (was 234; 23 new across routes + service).
* No em-dashes anywhere.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tim Thomas <0800tim@gmail.com>
…ovenance for manual publish After this evening's npm publish blitz, all 8 @tournamental packages now live on the registry: @tournamental/spec 0.2.0 (was 0.1.0) @tournamental/bracket-engine 0.2.0 (was 0.1.0) @tournamental/plugin-sdk 0.2.0 (was 0.1.0) @tournamental/social-cards 0.2.0 (was 0.1.0) @tournamental/bot-sdk 0.1.0 (first publish) @tournamental/bot-node 0.1.0 (first publish) @tournamental/bot-mcp 0.1.0 (first publish) @tournamental/create-app 0.1.0 (first publish) provenance: true removed from publishConfig because the manual publish path can't generate OIDC provenance attestations. Re-add via a CI workflow if/when we set one up. Signed-off-by: Tim Thomas <0800tim@gmail.com>
The /run/bots/[index] detail page used to show knockout slot placeholders (winner_grpA, annex_third_vs_grpB, ...) for all 32 knockout matches. The bracket-engine cascade is declarative so each bot's knockouts can be resolved per-bot from its 72 group-stage Outcome picks. cascade.ts builds a BracketPrediction from the bot's per-match picks: group standings via bracket-engine's computeGroupStandings (which already handles points -> GD -> GF -> head-to-head), 8 best-thirds chosen by FIFA-rank (lower = better third), then a forward walk through knockouts that re-cascades after each round so r16 onwards see the resolved upstream winners. The detail page now renders France vs Argentina (or whoever this bot thinks gets through) in every knockout row. Unit tests cover bot 0, bot 12345, the predicted-winner of the final, determinism across repeat calls, and the resolvedKnockoutSlots helper the detail page consumes. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md Refs: sessions/2026-06-07_a11_phase2-polish.md Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
Two Phase 2 polish features land together because they share the
worker hot path and the BrowserSwarm runner UI.
uniqueness.ts: index-based deviation enumeration. Bot 0 is the pure
chalk bracket (favourite outcome for every match). Bots 1..S each
flip one outcome relative to chalk, ordered by ascending confidence
margin (cheapest deviation first). Bots S+1.. cover all
combinations of two flips, then three, etc., via standard
lexicographic combinatorial unranking. Two distinct bot indices in
the same operator scope are now GUARANTEED structurally distinct
bracket commits, not merely probabilistically.
anchor.ts: user-anchored swarm slider. Reads the user's saved
bracket from localStorage on every Start press and blends per-match
picks with chalk by a slider weight: Off (0) / Soft (0.4) /
Strong (0.75) / Lockstep (1.0). Slider position persists to
swarm_state.anchor_weight; the bracket-hash snapshot of each batch
persists to swarm_state.last_anchor_hash so committed batches stay
locked to the snapshot they used while the next batch picks up the
live bracket.
Worker hot path now: perturbedOutcome() then optional blendOutcome()
overlay. Detail page swaps from regenerateBotBracket to
regenerateBotBracketUnique so the rendered bracket matches the
committed one. Cascade resolver also uses perturbation so the
knockout tree on /run/bots/[index] matches what landed on the
federated leaderboard.
Persistence schema extends SwarmState with two new fields
(anchor_weight, last_anchor_hash); fresh DBs start with anchor_weight
0 and last_anchor_hash null so existing browser DBs upgrade
transparently.
Unit tests:
- uniqueness: 6 tests covering pure chalk, single-deviation
coverage, structural distinctness across 200 bots, double-
deviation level boundary.
- anchor: 7 tests covering weight constants, blend boundaries at
weight = 0 / 1 / intermediate, flattenBracket, captureSnapshot
determinism.
Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md
Refs: sessions/2026-06-07_a11_phase2-polish.md
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tim Thomas <0800tim@gmail.com>
Captures the three deliverables (cascade resolver, uniqueness perturbation, user-anchored slider), the test surface (19 unit tests across 3 suites), and the typecheck outcome. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
…loop mode
Tim 2026-06-07 evening, four changes for the prod launch tonight:
1. Desktop nav swap: Bot Arena promoted to primary nav after Save &
share. The Bet drops to More. Bot Arena is the story this week so
it gets the one-click visibility from every page.
2. Storage card on /run: IndexedDB is now the always-on default with
a green badge. New tick-box "Also replicate to Supabase" reveals
the URL + anon key fields only when chosen, plus an inline FAQ
with 4 setup steps and a privacy note ("we only use the public
anon key, never the service role key").
3. Strategy card on /run: vendor cascade lets users bring their own
LLM. Supports Anthropic (Claude Opus 4.7 / Sonnet 4.6 / Haiku 4.5),
OpenAI (GPT-4o / 4o-mini / o1-mini), OpenRouter (free-text model
field, defaults to Llama 3.1 405B), and Google Gemini (2.0 Flash
or 1.5 Pro). Per-vendor key URLs are surfaced so users know where
to get a key.
4. Run card on /run: new "Keep looping" tick box with infinity glyph.
Generates botCount bots, commits the merkle root, then repeats
automatically until the user clicks Stop loop. Warning copy
surfaces if loop is on at >= 100k bots per batch ("CPU stays
busy continuously, laptop will warm up, leave it overnight").
Iteration counter shown under the button. Stop tells the loop
driver to halt after the current batch finishes.
The loop driver is a useEffect that fires when phase flips to "done"
and loopMode is true; it schedules the next onStart() via setTimeout
with a stopRequestedRef gate so onStop cleanly cancels mid-loop.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tim Thomas <0800tim@gmail.com>
… key + add nav.bot_arena translation Two bugs Tim caught while testing /profile/api-keys on dev: 1. nav.bot_arena translation key did not exist in apps/web/locales/en.json so the DesktopNav NavPill for Bot Arena spammed IntlError: MISSING_MESSAGE in the console (safeT caught it with a fallback so the label still rendered, but the noise is bad). Added the en.json entry. 2. Generate key button silently no-op'd on dev. The handleMint shortcircuits with `if (!sb) return;` when the Supabase browser client cannot initialise, which is the case on vtorn-dev because that environment authenticates via SMS-OTP / Telegram, not Supabase. No setError call, so the user saw nothing happen and no network activity. Surface a clear "Supabase auth client is not initialised" error message that points them at the working self-serve flow at /bots/keys. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
The /bots/sdk documentation page promised five SDK methods the code did
not actually ship. Plug the gaps so the quickstart in the docs works
verbatim:
- Bot.connect() authenticates the API key against /v1/me/api-keys/whoami,
warms the match catalogue from /v1/tournaments/<id>/matches, and
returns { matches, authenticated } for log lines. Cheap, idempotent;
401 fails fast, 404 / network errors degrade to a cache-only connect.
- Bot.matches() iterates the cached catalogue, filtering out matches
whose kickoff_utc has already passed against a caller-supplied
(or wall-clock) now.
- Bot.setCatalogue() seeds the iterator for tests / federated fixtures.
- getOdds(matchId) calls /v1/odds/<matchId>, accepts the documented
nested-probabilities shape as well as the flat shape, synthesises a
deterministic favourite via argmax, and falls back to a no_odds
50/50 result on a network error so a chalk swarm still ships.
- getInjuries and getWeather are Phase 1 stubs with stable signatures
so consumer code wires now and the real feed lands later with zero
caller changes.
README quickstart updated to match the docs page; 17 new unit tests
cover happy paths, fallbacks, and Bot.matches kickoff filtering.
Refs: apps/web/app/bots/sdk/page.tsx
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tim Thomas <0800tim@gmail.com>
The Next.js proxy at /api/v1/bots/keys already pointed at /v1/bots/keys/issue on the game-service, but that endpoint did not exist. Anyone hitting the self-serve Generate-key button got a 404 unless they happened to have a Supabase session, in which case the older /v1/me/api-keys path picked them up. This commit ships the missing endpoint. It accepts a service-to-service shared secret in the x-bot-keys-shared-secret header (env: GAME_BOT_KEYS_SHARED_SECRET on both ends), validates the body via Zod, defers to ApiKeyStore.issue() for the actual mint, and returns the plaintext key once. Fails closed with 503 when the env var is unset so an accidentally-deployed-without-secret build is not a free key-mint endpoint. Quotas inherit from ApiKeyStore.issue (.edu / .ac.uk / .ac.nz / .edu.au / .ac.za / .edu.cn / .ac.jp get 10x). Constant-time-ish secret compare avoids leaking matched-prefix length. The Next.js proxy now sends both x-bot-keys-shared-secret AND the legacy X-Tournamental-Service header so older game-service builds without the new endpoint keep working through the rollout. 10 new endpoint tests cover the happy path, academic quota lift, all four error codes, and an end-to-end smoke that the freshly minted key authenticates against /v1/picks/bulk. Refs: apps/web/app/api/v1/bots/keys/route.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
/profile/api-keys silently no-opped for SMS-OTP and Telegram users because the page only knew how to call Supabase /v1/me/api-keys, and those auth flows don't mint Supabase sessions on vtorn-dev. Add a mintApiKeyViaCookie() client that POSTs to /api/v1/bots/keys (the existing Next.js proxy that authenticates via the inbound-session cookie). When browserClient() returns null, ApiKeysPage now uses the cookie path instead of surfacing an error banner. Also fix the secondary bug where refresh() left the loading flag on forever when Supabase was unconfigured, so the page got stuck on "Loading...". UX matches the Supabase path 1:1: the freshly minted key renders in the same FreshKeyBanner with copy-to-clipboard. The key list stays empty on the cookie path until the game-service exposes a list-my-bot-keys surface (out of scope for tonight). Refs: apps/web/app/api/v1/bots/keys/route.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
provenance: true only works under GitHub Actions OIDC. Manual publish from a laptop fails with "Automatic provenance generation not supported for provider: null". Drop the flag so the manual ship path works tonight. Re-add via CI workflow when we set one up. Signed-off-by: Tim Thomas <0800tim@gmail.com>
…ofile + perfect-track)
Four pieces of plumbing so each operator running a browser swarm or
Node bot-node publishes summaries to a Cloudflare-edge-cached endpoint,
and the platform surfaces a fire badge when any bot survives match 80
on a perfect track.
A. POST /v1/swarms/<operator_id>/summary
* operator_id = sha256(api_key) so no second identity column.
* Bearer auth; 401 missing, 401 invalid, 403 not_operator on mismatch.
* Idempotent upsert on (operator_id, kickoff_at).
* Persists into new swarm_summary table.
B. GET /v1/swarms/<operator_id> and GET /v1/swarms
* Public, no auth.
* Cache-Control: public, s-maxage=60, stale-while-revalidate=300
* ETag based on latest generated_at; 304 fast path.
* Cloudflare edge caches without touching origin.
* Global (no operator) returns top 100 by best_bot_score.
C. /profile/[handle]/swarm
* Tab on the profile page: total bots, best score, sparkline.
* "Download swarm summary (JSON)" button hits the cached endpoint.
* Own profile only: "Download my raw bot brackets (JSON)" pulls
from IndexedDB, no server call. Raw picks of other users stay
private.
D. perfect_track_alert table + watch service
* Inline trigger after every POST /v1/swarms/<id>/summary.
* Also after every admin match-settled event in routes/match.ts.
* Webhook fan-out via PERFECT_TRACK_WEBHOOK_URL (no-op if unset).
* <PerfectTrackBadge/> on /leaderboard surfaces a 🔥 N bots still
on a perfect track after match X.
Browser swarm integration:
* BrowserSwarm.tsx calls publishOperatorSummary after each batch.
* federation.ts gains the publish helper; soft-fails on network error
with no user-visible warning.
* persistence.ts adds operator_api_key store; no schema bump because
it lives in the existing STORE_DEVICE namespace.
Tests:
* 257/257 game tests pass (was 234; 23 new across routes + service).
* No em-dashes anywhere.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tim Thomas <0800tim@gmail.com>
… movement at 1 + view-pool link Four tidy-ups on /leaderboard from Tim's dev screenshot pass: 1. Global / Friends / Country lived in the AppShell subHeader pill strip. They now sit inside the leaderboard card above the Humans / Bots / My Pools audience tabs, so both filter rows are colocated with the list they filter. AppShell loses its subHeader, freeing vertical space. 2. Tightened .vt-lb-page vertical gap 18px to 10px and cancelled DraftPreviewBanner's own 16px bottom margin inside the page so the yellow Preview-data pill now sits tight against the Perfect-track chip and the hero tiles below it. Was previously a noticeable half-inch of negative space. 3. MovementIndicator is capped at one position per match: any single match result moves a bracket by at most ±1 relative to its neighbours, so showing 2 / 3 / 4 was a lie about the underlying physics. Glyph still encodes direction; the number is always 1 (or empty when flat). Aria label updated to match. 4. "My Pools" tab now lists the user's pools as rows with a gold "View pool to" link to /s/<slug>, plus a footnote linking to /pools and /syndicates/new. Empty state preserved as fallback. Mock pulls the first three MOCK_SYNDICATES; swaps to /api/v1/leaderboard/my-pools on Phase 1 Task 8. Refs: docs/superpowers/specs/2026-06-07-bot-arena-design.md §5 Tim 2026-06-07. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tim Thomas <0800tim@gmail.com>
…ountry / My Pools
Collapsed the two-row Compare + Audience layout into one row, in the
order Tim signed off on:
Humans - prize-eligible humans only
Bots - bot race, separately ranked
Global - humans and bots merged
Country - humans filtered to the viewer's country
My Pools - the user's own Pool memberships
Friends was dropped (no friends-graph in the database). The bracketed
hints in Tim's brief were wiring notes for me, not labels: the buttons
render just the bare words.
Behaviour per tab:
- humans / bots / country reuse the existing <Leaderboard /> with
the appropriate audience filter.
- global passes scope=null so the mock returns the combined pool;
when the API lands it will be audience=any.
- country currently reuses the humans pool; the country narrowing
happens server-side once the viewer's ISO code is in scope.
- my pools renders the View pool ->/s/<slug> rows (unchanged).
Single-row strip horizontally scrolls on narrow viewports rather than
wrapping so the pill rhythm stays intact. Tests bumped to cover all
five tabs and the View-pool link target.
Tim 2026-06-07.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tim Thomas <0800tim@gmail.com>
…point
Hero rewrite per Tim's brief:
* Photo header in the /the-bet style: full-bleed bot-arena-hero.webp
+ .jpg, dual-gradient scrim, headline sits bottom-left. Image is
the 2816x1536 robo-stadium PNG resampled to 1920w then quality-82
WebP (395KB) + quality-82 JPEG (571KB) via ImageMagick. No content
alteration; only the resample to a sensible delivery size.
* New banner heading replaces the old marketing tagline:
Spawn millions or billions of bots.
Change predictions anytime.
Will one of your bots dominate the bot leaderboard?
* The previous lede paragraph ("104 matches. 9.74 x 10^43 brackets,
every pick anchored to Bitcoin, US/bin/bash anchor cost, ...") moves
out of the hero and into the body just above the "Start in five
minutes" section, so the photo carries the banner cleanly.
Live stats strip (ArenaStats client island):
* Three chips sit beneath the hero, hidden until either the device
has bots or the server-aggregate has crossed zero.
1. My swarm - sum of bots in this device's IndexedDB.
2. Still perfect - identical to my swarm pre-kickoff, will drop
as match results land.
3. Bots in the arena - global aggregate across all swarms.
* Polls /v1/swarm/totals every 45s; endpoint caches for 60s. New
browser windows / new accounts see the total tick up within a
one-minute window.
Server-side:
* SwarmClaimStore.totals() returns SUM(total_bots), COUNT(*) and
COUNT(DISTINCT node_id) over swarm_claims in one query.
* GET /v1/swarm/totals adds an in-memory 60s cache + HTTP
Cache-Control public/max-age=30/SWR=60 so the edge and the
browser also de-dup.
Tim 2026-06-08.
Refs: docs/30-browser-swarm-architecture.md, /the-bet hero pattern.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tim Thomas <0800tim@gmail.com>
…s into dev tree # Conflicts: # apps/web/app/leaderboard/page.tsx
…t Arena hero card - Add print-styled press release PDF at /press/tournamental-press-release-2026-06-07.pdf (Chrome-headless rendered from a Georgia / A4 template). - Add web-readable white paper at /whitepaper/perfect-bot-bracket/ (dark theme, Fraunces headings, Inter body). - Add generator script tools/youtube-discovery/build_press_2026-06-07.py so the HTML + PDF can be re-rendered if the source markdown changes. Home page restructure: - New .vt-bots-feature image-overlay card promoting the Bot Arena: eyebrow, headline "Send millions of bots into battle.", body, CTAs to /bot-arena + the new press PDF + the white paper. Mirrors .vt-bet-feature but uses the /hero/bot-arena-hero asset and a cool-blue accent instead of gold so the two cards read as a pair without competing. - Promote the Set picks + Run a pool CTAs out of the hero into a standalone 2-col condensed row (.vt-home-ctapair) sitting between the new Bot Arena card and the existing bet feature card. - Existing Tournamental? Maybe hero and all downstream sections unchanged. Refs: docs/internal/press-2026-06-07-perfect-bot-bracket/ Signed-off-by: Tim Thomas <0800tim@gmail.com>
Next.js doesn't auto-resolve index.html under /public, so /whitepaper/perfect-bot-bracket 404'd while the file was reachable only at /whitepaper/perfect-bot-bracket/index.html. Add two rewrite rules (no slash and trailing slash) so the clean URL serves the static HTML. Refs: docs/internal/press-2026-06-07-perfect-bot-bracket/ Signed-off-by: Tim Thomas <0800tim@gmail.com>
The 2-col CTA row's "Set my picks" button now flows straight into the Step 1 explainer, then into the Tournamental? Maybe house bet, rather than jumping over the bet card to find the explainer below. Refs: Tim 2026-06-08 screenshot feedback Signed-off-by: Tim Thomas <0800tim@gmail.com>
The earlier promotion of the CTAs into a standalone 2-col row pushed them below the Bot Arena card, hurting the at-a-glance read on the hero. Restore the original .vt-home-hero-ctas block inside the hero so the action sits over the editorial banner exactly as prod does, and drop the now-unused .vt-home-ctapair styles. Section order is unchanged otherwise: hero -> Bot Arena card -> Step 1 picks -> Tournamental? Maybe -> rest. Refs: Tim 2026-06-08 screenshot feedback Signed-off-by: Tim Thomas <0800tim@gmail.com>
The earlier cool-blue accent on .vt-bots-feature (eyebrow, headline em, body em, primary CTA, scrim radial) was intended to "not fight" the gold bet card below, but Tournamental is a single-gold brand so the blue read as off-brand. Swap every blue token for the exact gold values the bet card already uses (#d9b463 eyebrow, #f6c64f headline em, #f5e1a9 body em, #fcd34d->#f59e0b CTA gradient, rgba gold scrim). Refs: Tim 2026-06-08 screenshot feedback Signed-off-by: Tim Thomas <0800tim@gmail.com>
…r million bots)
BREAKING CHANGE: v0.3.0 drops the bot/bot_pick/bot_score tables.
Operators upgrading from v0.2.0 MUST wipe their data volume.
Why
---
v0.2.0 stored every bot's every pick as a SQLite row -- 104 million
rows for a million-bot swarm, ~16 GB on disk + ~3 GB WAL. We
proved (apps/web/__tests__/regeneration-deterministic.test.ts)
that picks are deterministic functions of
(run_seed, bot_index, strategy), so storing them was redundant
work. v0.3.0 keeps only the recipe and recomputes picks on demand.
What changed
------------
* storage.ts schema: drops bot, bot_pick, bot_score. Adds
swarm_run (run_seed, strategy, total_bots, per_match_roots_json,
...) and match_score_summary (per-swarm aggregate after each
scored match). Meta + commit_log + match_result kept verbatim.
* generator.ts: writes ONE swarm_run row per (seed, strategy).
Computes per-match merkle root over compact <base36><h|d|a>
leaves on the fly and stores the root in
swarm_run.per_match_roots_json. Adds regenerateBotPickForMatch()
and leafForBotPick() exports.
* scorer.ts: iterates [0, total_bots) and regenerates each bot's
pick via the formula instead of SELECT FROM bot_pick. Per-swarm
match_score_summary row written per scored match. O(bots *
settled_matches) regenerations, ~3 micro-seconds each.
* scheduler.ts: commitMatch reads per-match roots directly from
swarm_run instead of merkle-hashing fresh from bot_pick. O(1)
per match regardless of bot count. pickLeaf removed.
* server.ts: /v1/proof/:match_id/:bot_id removed (proof is now
client-side regeneration). /stats adds swarm_breakdown.
Storage delta
-------------
v0.2.0 v0.3.0
1K 16 MB 2 KB
10K 160 MB 2 KB
100K 1.6 GB 2 KB
1M 16 GB 2 KB
1B 16 TB ~50 KB
(All v0.3.0 sizes are dominated by the metadata + per-match merkle
roots, not by bot count. The bots themselves live as integers in
[0, total_bots).)
Migration
---------
1. Stop the v0.2.0 container: docker compose down
2. Wipe the data volume: docker volume rm billion_bot_data
3. Update the consumer's tarball reference to 0.3.0
4. Rebuild + start: docker compose build --no-cache && docker compose up -d
5. Re-run generate from scratch; the swarm runs from index 0
Done in this commit
-------------------
* packages/bot-node/ rewritten + tested (17/17 pass)
* apps/billion-bot/{package.json,Dockerfile} updated to 0.3.0
* CLAUDE.md gains 'SDK release process' section covering the
bump-test-pack-smoke-publish flow for future agents
Not yet done (Tim must do)
--------------------------
npm publish: my token returned 404 on @tournamental/bot-node so
the v0.3.0 tarball is only available locally. Run
`cd packages/bot-node && npm publish --access public` from a
shell with a token that has @tournamental/* write scope, otherwise
external users still get v0.1.0.
Refs: docs/30-browser-swarm-architecture.md (matching contract)
Tim 2026-06-08.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tim Thomas <0800tim@gmail.com>
… + leaderboard fold Recalibrate the browser-swarm + bot-node chalk strategy so group matches favour the stronger side instead of collapsing onto Draw, and restrict darling-team picks to the FIFA top 16 so cup winners stop surfacing longshots. Wire live Polymarket odds (game-service /v1/odds/*) into the /run page with a FIFA-rank fallback. Add an incognito-browser gate on /run that blocks Start until the operator acknowledges the no-persistence risk or enables Supabase replication. Fold swarm_summary aggregates into /v1/swarm/totals so federated bot-node containers move the bot-arena headline. Tighten the single-bot bracket view (drop noisy 2nd/3rd columns) and surface a live bot count on /profile. Refs: sessions/2026-06-08_bot-strategy-recalibration Signed-off-by: Tim Thomas <0800tim@gmail.com>
…erated swarm container The operator-facing Docker container that wraps @tournamental/bot-node: a tiny HTML dashboard (Start/Stop/throttle, live throughput + CPU readouts, aggregate-publish status), the compose file, and the bundled 104-match WC2026 fixture catalogue. Build artefacts (packed tarballs, runtime SQLite) are gitignored. Signed-off-by: Tim Thomas <0800tim@gmail.com>
…) signature in scoped path The correct_picks work on main changed matchesAvailableTo to take a recorded-kickoffs array; the Bot Arena scope branch still called the old 3-arg form. Semantic merge conflict, caught by typecheck before reaching main. Signed-off-by: Tim Thomas <0800tim@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Consolidates the Bot Arena work onto main ahead of a prod deploy.
/runvia game-service/v1/odds/{match,winner-market,snapshot}, with a FIFA-rank fallback and a source pill in the UI./v1/swarm/totalsnow sumsswarm_claims+swarm_summary, so bot-node containers move the bot-arena headline./run: blocks Start in a private window until the operator acknowledges the no-persistence risk or enables Supabase replication./profileshows a live bot count.@tournamental/bot-node./bots/node#updatingcovers updating a pulled container/package.Merged main cleanly (correct_picks migration, share-page leaderboard, auth deep-link). One semantic merge conflict in
leaderboard.tscaught by typecheck and fixed.Test plan
pnpm --filter @vtorn/game typecheck && buildcleantsc --noEmitclean on touched files/v1/odds/snapshotserves 72 priced matches + 48-team winner market🤖 Generated with Claude Code