The prior test drove the proxying app via Hono's in-process app.request()
harness, which has no real Node socket — so it could only assert the header
was non-empty-or-absent, not that the real client IP round-trips. Spin the
proxying app up with @hono/node-server (real loopback socket) and assert the
upstream actually receives 127.0.0.1/::1, exercising the same
remoteAddress lookup arenaProxy uses in production.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Forward client IP via x-forwarded-for/x-real-ip so the canonical arena's
per-IP rate limiting isn't collapsed to one bucket per node.
- 30s AbortSignal.timeout on non-stream requests; SSE fight streams
(/api/fights/:id/stream) are exempt (long-lived by design).
- On upstream fetch failure, log and return 502 {error} instead of a
buffered hang or a 500 stack trace.
- fights.ts: set X-Accel-Buffering: no on the SSE stream response so an
nginx-fronted arena (nginx-proxy-manager) doesn't buffer live fight events.
- docker-compose.yml: document ARENA_UPSTREAM_URL / TRUSTED_PROXY (commented,
no active value set here — the canonical arena gets its own compose file
in a later plan).
TDD: added the SSE/XFF/502 tests, confirmed the 502 test failed against the
prior implementation, then implemented to green (9/9 arena-proxy tests,
17/17 combined with rate-limit.test.ts).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hono middleware that forwards /api/* to ARENA_UPSTREAM_URL when set, with
standalone mode (env unset) and /api/health untouched. Verified end-to-end
against a real second HTTP server: register a bot upstream, read it back
through the proxy, method/query/JSON body forwarded unchanged, inbound Host
header dropped, response content-encoding/content-length stripped.
TDD: 6 tests written first and confirmed failing (module didn't exist),
then implemented to green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds docker-compose.arena.yml (registry image only, no build, no payment
env, no TRUSTED_PROXY since no reverse proxy fronts it) and
docs/arena-deployment.md (full runbook: host paths, port, secret handling,
seed decision, NPM/TLS upgrade path, teardown) for the one canonical public
arena at http://146.59.87.168:9100 (user decision 2026-07-30: no DNS/TLS
this phase, plain HTTP on the raw port; TLS is a later env-only upgrade).
Deployed + verified on VPS2: healthy on-host and off-host, seeded with a
full read-only copy of archi-dev-box's botfights.db (115 bots/102,440
fights; source untouched, mtime verified unchanged).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
k.go('arcade') schedules the scene callback on frameEnd, not synchronously.
start() was called before fighters existed, causing "Cannot read properties
of undefined (reading 'obj')". Fix: await a readiness promise that resolves
once the scene callback has created the fighters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
wireSSE() was only called for human fights in onMounted, so bot fights
(webhook/poll mode) never received round_start/round_end events. The fight
ran server-side but the frontend showed nothing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The creator was being registered with webhookUrl='http://human.local/' and
isHuman=true. Now uses poll.local and isHuman=false. The auto-upgrade logic
on login also converts any existing creator record from human to bot mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Bot registered immediately after name check (poll mode default)
- bot_id + secret available in bot-setup step with COPY GUIDE + CREDENTIALS
- Webhook mode updates URL on existing bot instead of re-registering
- Collapsible guide preview with credentials injected
- Resets guide content when switching webhook/polling mode
- Fix secret text overflow with break-all on profile page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove setup guide from pre-registration bot-setup step (no credentials yet)
- Add collapsible guide preview to ready step with real bot_id/secret injected
- "COPY GUIDE + CREDENTIALS" copies the full guide with credentials embedded
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add /docs/* route to serve markdown setup guides
- Add md to MIME map and SPA catch-all exclusion
- Normalize BOTFIGHTS.md placeholders to YOUR_BOT_ID/YOUR_BOT_SECRET
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- downloadSetupGuide() now triggers a real file download with bot_id/secret injected
- Add POST /api/auth/regenerate-secret endpoint (JWT auth, 3/hour rate limit)
- Add "Download Setup Guide" section to BotProfilePage with secret regeneration flow
- Old secret immediately invalidated on regeneration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add botfights system user/group, chown /app, USER directive
- Add HEALTHCHECK using /api/health endpoint (30s interval, 5s timeout)
- Container now runs as non-root for security hardening
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Source pattern verification tests:
- BUG-6: webhook calls wrapped in Promise.all (parallel, not sequential)
- BUG-7: SSE maps (spectatorCounts, fightReactions, ssePerIp) cleaned on disconnect
- BUG-4: no raw setTimeout in game code (all use trackedTimeout)
- BUG-F2: no silent .catch(() => {}) in frontend source
43 regression tests total, all passing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix E2E Playwright config: correct port 5173→9101, increase webServer timeout
- Fix signup-bot and signup-human specs: add missing nsec backup step
- Implement scoreCreativeAnswer() heuristic for creative round scoring
- Pass DB ELO to generateMockBotResponse for non-MOCK_BOTS integration tests
- Update challenges tests: all 16 types are now factual (no creative types)
- Fix lifecycle test flakiness: widen ELO correlation tolerance, add timeout
- All 11 E2E specs pass, 770 unit/integration tests pass
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Soak: 1000 random fights verify zero crashes, ELO bell curve around
1200, and bounded heap growth. Stress: 50 concurrent queue joins
verify no races, no duplicates, correct rejoin behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
17 tests covering: simultaneous bet placement, draw refund mechanics,
bet validation bounds, extreme ELO odds, Cashu token rejection, and
escrow lifecycle leak prevention.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests concurrent same-name registration (exactly one succeeds),
case-insensitive name collisions, expired JWT rejection, NIP-98
pubkey mismatch, and duplicate pubkey prevention.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8 new tests: unicode/emoji/diacritics in bot names rejected, special
chars rejected, same webhook URL allowed, pubkey hex validation,
missing required fields rejected.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds tests for both-bots-at-0-HP tiebreaker asymmetry (botA penalized
first) and verifies ELO can go negative when 0-rated bot loses.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Simulates both bots timing out every round for 10 rounds. Verifies
zero damage per round, HP unchanged at 200, draw outcome.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- FightViewer: store canvas event handlers and remove them before
replacing canvas elements, preventing detached DOM/closure leaks
- FightViewer: clean up canvas listeners on unmount
- tts.ts: clear _staticLoading dedup map after precache completes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cache already uses lastAccess timestamps and LRU eviction. Added
test-only exports and 6 tests verifying: timestamp tracking, access
updates, LRU eviction of oldest entry, recently accessed entries
survive eviction, and eviction is not FIFO.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added audioTimeout() to audio/context.ts — tracked timer set cleared on
scene destroy via clearAllAudioTimers(). Converted 20 sfx.ts + 3 voice.ts
raw setTimeout calls to audioTimeout. Converted FightScene playEntrance
timeout to trackedTimeout. Remaining setTimeout in tts.ts worker layer
and music.ts (already tracked via setMusicTimeout) are self-managing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shutdown now: stops background fight loop, waits up to 15s for active
fights to drain (polling every 500ms), clears human + poll pending
challenges, clears bet escrow. Added clearEscrow() to betting.ts and
5 tests verifying cleanup functions and correct shutdown order.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shutdown now: 1) stops background fight loop, 2) waits up to 15s for
active fights to finish, 3) cancels pending human + poll challenges,
4) clears bet escrow. Added clearEscrow() to betting.ts. Tests verify
each cleanup function and shutdown sequence ordering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 3 tests for cleanupOrphanedFights: verifies db.update sets
status='cancelled' with endedAt on stale live fights, returns 0
on success, and propagates DB errors correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add sanitizeError() helper that strips file paths, stack traces, SQLite
errors, and system errors from messages before returning them to clients.
Applied to all route-level catch blocks in payments, queue, fights, and
admin routes. Includes 12 tests for the sanitizer and static analysis
test verifying no route files leak raw err.message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All individual indexes from plan already existed. Added missing composite
index that covers WHERE is_active=1 ORDER BY elo_rating DESC pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Log timing for each round phase: webhook calls, scoring, DB operations,
and total round time. Also log finalize transaction time. Uses logger
with 'perf' category for easy filtering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add test confirming disconnect-wallet deletes walletConnections rows
and sets hasWallet=false on the bot record.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verify random IV (same plaintext encrypts differently), ciphertext
format (iv:authTag:encrypted), auth tag tamper detection, encrypted
data tamper detection, empty string handling, and unicode support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add tests confirming production error handler sanitizes all internal
errors (ENOENT, stack traces, file paths). Add static analysis test
verifying no route file passes err.stack to c.json() responses.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>