With many simultaneous games, AI turns start in bursts and exceed the
in-flight cap. That was counted as an AI failure, so after 5 bursts the
breaker paused the AI for 30 s and every AI seat played random moves.
- Only real failures (timeouts, lost connection, malformed/illegal
answers, unexpected errors) count towards the breaker; the in-flight
cap and "overloaded"/"expired" answers from the AI service do not
- AI_MAX_INFLIGHT default 32 -> 64 (>= MAX_ACTIVE_GAMES, matches the AI
service's AI_MAX_QUEUE)
- Test that load shedding keeps the breaker closed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Running games always continue; new work is refused when there is no
capacity; abuse is rejected immediately.
- Docker: CPU/memory/pids caps per container (overridable in .env);
uvicorn message size, queue and concurrency limits
- Server abuse limits: connections total/per IP (close 1013), message
rate per connection (token bucket, close 1008 on floods), lobby
creation rate per IP
- Load monitor: event loop lag, CPU of the own cgroup quota and
throttling, with hysteresis (normal/busy/overloaded, severe lag
overloads immediately)
- Admission control: create_lobby and start_game refused while
overloaded or MAX_ACTIVE_GAMES are running; server_status message
broadcast on changes; /health reports load but stays ok
- AI bridge: timeout sent with requests, in-flight cap, circuit breaker
- AI service: bounded workers and queue ("overloaded"), drops expired
requests, degrades to the heuristic agent when busy or too slow
- Website: busy notices, Create/Start disabled when not accepting
games, slow retry when the server is full
- Tests for limiters, load levels, admission control and AI service
limits; docs updated
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sum of all bids may never equal the number of cards each player
holds in the round; the last bidder has to adjust their bid. This was an
optional "not equal" lobby setting and is now a fixed rule of the game
engine, so it applies to humans, AI players and training alike.
- Remove GameOptions.not_equal, the set_options field, the lobby toggle
and the --not-equal training flag
- Explain the rule in the lobby and show the blocked bid while bidding
- Drop the option from the AI observation encoding
- Tests check that no round ends with bids summing to the cards in hand
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Caddy forwards /wizard/api/* and /wizard/website/* with the prefix
stripped, so the server received /ws instead of /api/ws and the website
connected to /api/ws on the domain root.
- Server routes are now /ws and /health (was /api/ws, /api/health)
- Website derives the websocket URL from its own location (../api/ws),
giving /wizard/api/ws in production and /api/ws in development
- Vite dev proxy strips /api like the production proxy
- nginx back to the plain SPA config since Caddy strips the prefix
- /internal/ai rejects requests that came through the reverse proxy,
in addition to the shared secret
- Docs updated for the prefixed deployment
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>