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>
Added getDecodedToken validation that rejects malformed tokens with
400 before any DB lookups. Tests cover empty, non-base64, truncated,
and random base64 tokens.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add rateLimit(1_000, 30) middleware to GET /poll endpoint
- Fix Challenge type errors in human-responses test files (missing baseDamage)
- Add rate-limit unit test verifying 429 after exceeding limit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
better-sqlite3 driver is synchronous — .get() does NOT need await.
Added tests for unknown pubkey (404) and missing pubkey (400) on join.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Frontend: vitest.config.ts with vue plugin + jsdom, dummy component test
- Server: in-memory SQLite test DB factory + Hono testClient helper + smoke test
- CI: add pnpm audit and server coverage threshold steps
- Root: vitest workspace config for multi-project test discovery
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Scoring: both-wrong is now a DRAW — equal scores (3/3), no winner,
symmetric minimal damage. Garbage answers no longer beat reasonable
ones just by being faster. Both-wrong narrations reflect the draw.
Entrance: removed duplicate announceDeepIntro() call from FightViewer
(was already called inside playEntrance). Removed _resetPositions()
after entrance (entrance already places fighters at home positions,
the extra reset caused a visible snap/reset).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merged bot-setup and choose-connection into one actionable step.
Users now see mode picker, download button for the correct guide
(BOTFIGHTS-WEBHOOK.md or BOTFIGHTS-POLLING.md), and safety info
all on one screen instead of two filler steps with no actions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
buildNip98Token checked window.nostr before secretKeyHex, so
"Generate New Identity" would sign with the browser extension's key
after saving. Now explicit keys always take priority over extensions.
Also made setup flow mode-aware: webhook users get BOTFIGHTS-WEBHOOK.md,
polling users get BOTFIGHTS-POLLING.md with matching copy prompts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Polling bots have no external script running during practice mode,
so the poll would time out giving empty answers. Now overrides the
webhook URL to human.local so the browser UI handles challenges.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Allow huggingface.co in CSP connect-src (fixes Kokoro TTS model download)
- Add registerSW.js route (fixes PWA service worker 404)
- Add _resetPositions() safety after entrance (fixes invisible fighters)
- Fight end sequence works without canvas scene (KO/overlays/log always play)
- Pre-fight instructions in battle log for human players
- NIP-55 visibility sync and cleanup handlers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When window.nostr isn't available (common on mobile Chrome where
extensions can't inject), fall back to NIP-55 nostrsigner: intent
URIs. This opens Amber/Primal directly to sign a NIP-98 event,
then redirects back with the signed event for JWT authentication.
- Build nostrsigner: URI with unsigned NIP-98 event + callback URL
- Process NIP-55 callback on page mount (extract signed event from URL)
- Auto-detect Android to show "SIGN IN WITH AMBER / PRIMAL" label
- Reduced window.nostr polling from 3s to 2s before NIP-55 fallback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>