Bot Ladder
The bot ladder is the player-facing ranking system for games against AI bots. It runs separately from any PvP rating — your bot ladder rating does not affect your PvP rating, and vice versa. Ladder data is opt-in: a user must explicitly enable it before their games count.
Levels
Section titled “Levels”Ten strength levels, defined in
apps/game-web/src/pages/profile/bot-ladder/lib/levels.ts:32 and sourced
from the authoritative catalog at packages/katago-server/src/bot-level.ts:142.
| Code | Display band | Approx strength |
|---|---|---|
lvl_30k | Beginner | 30 kyu |
lvl_20k | Beginner | 20 kyu |
lvl_10k | Beginner | 10 kyu |
lvl_5k | Beginner | 5 kyu |
lvl_1k | Intermediate | 1 kyu |
lvl_5d | Intermediate | 5 dan |
lvl_1d | Intermediate | 1 dan |
lvl_3d | Advanced | 3 dan |
lvl_7d | Advanced | 7 dan |
lvl_10d | Elite | 10 dan |
Each level config bundles a KataGo model, a visits budget, a family
(band), and a baseline Elo used by BotRankService to compute deltas.
REST API
Section titled “REST API”All paths are /api-prefixed (see REST API for the
gateway-wide table). The bot-ladder subset:
| Method | Path | Purpose |
|---|---|---|
| GET | /game/bot/ladder/me | { rating, optIn, today } — current user state. |
| POST | /game/bot/ladder/opt-in | { optIn: boolean } — toggle ladder participation. |
| GET | /game/bot/ladder/history?limit=50 | Most recent rating deltas. |
Example: read my ladder state
Section titled “Example: read my ladder state”GET /api/game/bot/ladder/meCookie: better-auth.session_token=<...>{ "rating": 1482, "optIn": true, "today": { "gamesPlayed": 3, "wins": 2, "losses": 1, "energyUsed": 12 }}Example: toggle opt-in
Section titled “Example: toggle opt-in”POST /api/game/bot/ladder/opt-inContent-Type: application/jsonCookie: better-auth.session_token=<...>
{ "optIn": false }Data model
Section titled “Data model”Drizzle schema lives next to the rest of weiqi-server’s tables.
botRatings— one row per user. Columns:userId,rating,botRatingOptIn,gamesPlayed,wins,losses,lastPlayedAt, …botRankHistory— append-only. One row per rating change. Columns:userId,rating(post-update),opponentLevel,expected,actual,createdAt, …
The rating column on botRatings is denormalised — botRankHistory is the
authoritative audit log.
Rating math
Section titled “Rating math”BotRankService uses classic Elo with a per-level expected score:
expected = 1 / (1 + 10 ^ ((opponentRating - userRating) / 400))delta = K * (actual - expected)K is fixed at 32 for bot games. The opponent rating comes from
BOT_LEVELS[opponentLevel].rating — a stable, level-specific baseline that
does not change as more users play.
Daily cap
Section titled “Daily cap”BotDailyUsageService.getUsage returns today’s usage against the user’s
entitlement (packages/authz):
BOTS_HINT_TOP3,BOTS_LIVE_EVAL,BOTS_REVIEW_FULL— feature gates.dailyGameCap— total bot games per UTC day for free tier.- PRO tier has no daily cap and full hint access.
Real-time updates
Section titled “Real-time updates”The player profile page
(apps/game-web/src/pages/profile/bot-ladder/bot-ladder.page.tsx:39) shows
a live RatingChart and a HistoryTable. Both consume the useBotLadder
hook (apps/game-web/src/entities/bot-game/hooks/useBotLadder.ts), which in
turn talks to botGameApi (src/entities/bot-game/api/bot-game.api.ts:46)
over a plain fetch.
Socket.IO is not used on the ladder page — the data set is small and the user reloads infrequently. The page is rendered with SSR + hydration; the initial rating comes from the server, updates poll every 30 s.
Hints and reviews
Section titled “Hints and reviews”Hindsight is via the same endpoints as a normal bot game, with an entitlement check:
/game/bot/games/:id/hint— requiresBOTS_HINT_TOP3(free) or higher./game/bot/games/:id/review— full KataGo review. RequiresBOTS_REVIEW_FULL. Backed byrunFullReview; results are cached onboard_ai_reviews.
A free-tier player with the daily cap exhausted still sees the page, but the “play” button is replaced with an upgrade prompt.
Front-end types
Section titled “Front-end types”The shared entity types live in
apps/game-web/src/entities/bot-game/types/bot-game.ts:1-178:
export type BotLevelCode = "lvl_30k" | "lvl_20k" | "lvl_10k" | "lvl_5k" | "lvl_1k" | "lvl_5d" | "lvl_1d" | "lvl_3d" | "lvl_7d" | "lvl_10d";
export type BotLadderRating = { rating: number; optIn: boolean; today: { gamesPlayed: number; wins: number; losses: number; energyUsed: number };};These types are referenced from the docs whenever a bot-related endpoint is documented, so they stay in sync with the actual client API.