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>
Test all 7 admin endpoints (stats, bots, deactivate, activate,
reset-elo, fights, backup) reject non-creator pubkeys and missing
pubkeys with 403. Verifies global middleware guard works correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduce login and nostr/session rate limits from 30 to 10 requests per
minute per IP to prevent brute-force attacks. Add tests verifying 429
response after exceeding the limit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace manual XOR loop with Node's native crypto.timingSafeEqual for
constant-time secret comparison. Add tests verifying identical error
messages for wrong secrets and <1ms response time variance across 100
requests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
X-Forwarded-For, X-Real-IP, and CF-Connecting-IP headers were
blindly trusted, allowing attackers to bypass rate limiting by
spoofing different IPs. Now only trusted when TRUSTED_PROXY env
var is configured. Falls back to Node.js socket remoteAddress.
Add tests verifying proxy headers are ignored without TRUSTED_PROXY
and respected when it is set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create server/src/lib/validators.ts with reusable schemas for all API
inputs (auth, fights, bets, payments, tournaments, queue, docs).
Import and use in all route handlers, replacing inline validation.
Add formatZodError helper for user-friendly error messages.
77 test cases in validators.test.ts cover valid, invalid, boundary,
and attack inputs (SQL injection, XSS, prototype pollution).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove ^ prefix from all 33 dependencies across root, server, and
frontend package.json files. Lockfile regenerated and verified with
pnpm install --frozen-lockfile.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Human morph sprites were generated without archetype and customization
params, causing fallback to generic sprites instead of the bot's actual
appearance.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added isVerifyingWebhook state with spinner and disabled button during
webhook test. Improved error message to guide user on retry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changed from fixed 1.5s setInterval to recursive setTimeout with
exponential backoff on consecutive errors. Resets to 1.5s on success.
Added 2 tests verifying backoff escalation and reset behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bot registration (3 tests), human registration (1 test), fight replay
(2 tests for JS error checking), and leaderboard (3 tests for rendering).
All use Playwright with text-based selectors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Set up Playwright with Chromium, dev server auto-start, test helpers
for seeding bots and programmatic auth. Added smoke spec that verifies
homepage and leaderboard load without crashes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
14 integration tests covering full fight lifecycle with real in-memory
DB: SSE event ordering, polling bot challenge/response flow, human
player response submission, and 3 concurrent fights without interference.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8 tests covering: complete fight lifecycle, HP progression, ELO
updates, concurrent fight prevention, round data validity, ELO
conservation, status transitions, and win streak tracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical finding: when all bots answer correctly, even a 50ms speed
advantage wins 95.4% of fights. At 100ms+ gap it's 100% deterministic.
ELO separation reaches 450+ after just 50 fights. Speed completely
dominates the "all correct" meta.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Simulated 1000 fights with 80% accuracy and equal speed. First-to-lead
wins 60.4% of decided fights, confirming combo system is balanced.
No decay or comeback mechanics needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When both bots answer wrong, the one with higher checkAnswer confidence
(closer to correct) gets +1 point advantage. Rewards trying over
timing out. Equally wrong remains a pure draw.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When both bots are correct, the one with higher checkAnswer confidence
(exact match 1.0 vs fuzzy match 0.8) gets up to +1.0 bonus points.
This rewards precise answers over approximate ones, adding another
competitive dimension beyond pure speed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both-correct fights could almost never produce critical hits because
max margin at typical speed differences was ~2.2, far below threshold
of 4. Lowering to 3 yields ~15% critical hit rate (target 10-20%),
making speed differences produce more exciting fight dynamics.
Research findings: simulated 1000 fights with both bots answering
correctly. Critical rate went from 0% to 15.3% with new threshold.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Multi-turn simulation, authority impersonation, encoding tricks
(Base64, ROT13), fake JSON system prompts, red team framing,
reward manipulation, inverted instruction logic. All difficulty: hard.
Zero audit failures after addition.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Test roundToDifficulty: rounds 1-2 easy, 3-4 medium, 5+ hard
- Test pickChallenge difficulty filtering works with round numbers
- Audit prompt difficulty tags across all 16 challenge types
- 8 types lack hard prompts, 3 lack medium prompts
- 82.7% of prompts are untagged (no difficulty attribute)
- Report written to loop/difficulty-distribution.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- checkAnswer now returns highest score across all accepted answers
instead of first match, fixing 95 false-low-confidence results
- Skip string containment for purely numeric strings to prevent
false positives like "1000" matching inside "10000"
- Preserve decimal points in normalize() (42.0 no longer becomes 420)
- Use word-boundary regex for number matching in responses
- Fix 47 wrong choices scoring too high (comma-formatted numbers,
verbose choices matching terse answers)
- Fix 17 prompts where no choice matched any accepted answer
- Challenge audit now reports zero failures across all 1472 prompts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fetchNostrProfile now uses Promise.allSettled to query all relays
concurrently. Aggregates results with latest-created_at-wins strategy
instead of stopping at the first relay that responds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Module re-evaluation during HMR reset autoRestoreRan to false, causing
duplicate auth-restore API calls. Now persists flag on globalThis.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
useNostr (9), useFightCache (5), useOnlineStatus (4) composable tests.
Added fake-indexeddb dev dependency for IDB tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Only invalidates __alltime__ and current season cache keys on fight completion,
preserving historical season caches. Test verifies selective behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Global app.onError handler already in app.ts catches all unhandled route
exceptions. Production mode returns "Internal server error" only.
Tests verify no stack traces or file paths leak in responses.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Both bots timing out every round: verified draw with zero damage
- Perfect victory (10-0): verified correct ELO calculation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
blacklistJwt() adds token to in-memory blacklist until its natural expiry.
verifyJwt() checks blacklist before signature verification.
Cleanup interval removes expired entries every 10 minutes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Documents finding: no replay protection in NIP-98 verification.
Token replay within 120s window succeeds (mitigated by JWT issuance being idempotent).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests the actual rateLimit middleware with production mode via dynamic import.
Covers: under-limit allows, over-limit returns 429, window reset, per-IP
isolation, and poll endpoint config (30 req/1s window).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Catches runtime errors in child components, displays user-friendly
error message with reload button. Wired into App.vue wrapping
router-view. Tests verify error capture and button rendering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Timeout and WebSocket errors reject with proper Error objects
- Caller catches and falls through to poll-based confirmation
- Preimage undefined check prevents calling confirm with no preimage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feedbackTimer, timerHandle, and pollHandle are all cleared in
onUnmounted. Test confirms cleanup pattern works correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
13 silent catch handlers replaced with descriptive console.warn logging
across 6 frontend files. No silent error swallowing remains.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SSE now always attempts reconnection when fight isn't finished,
regardless of isLive.value. Uses exponential backoff (1s, 2s, 4s,
max 8s). Moved sseRetries to outer scope to persist across reconnects.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Validates challenge type against the full CHALLENGE_TYPES enum before
processing. Invalid types now return 400 instead of silently falling
back to speed_blitz.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Production now requires JWT_SECRET env var. Added comprehensive JWT
tests: creation, verification, expiry, tampered payload, tampered
signature, and malformed token rejection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>