app/player_rating.h

the Kirin rating: how strong the board thinks you are (L4).

WHAT THIS IS NOT is the most important thing about it.

It is not the account rating. The server refuses to rate a solo game, and it is right to (chess-web/worker/games.ts

: “Solo games are never rated — you cannot

farm a rating off your own engine”). A competitive ladder you can climb by beating a machine you configured yourself is not a ladder. So nothing here ever reaches the leaderboard, and the two numbers must never be conflated in the UI.

What it IS: a private, on-device estimate of the player’s strength, kept for the two things a board actually needs it for —

  1. giving someone who plays alone a number that moves. Most Kirin games are played against the engine in a living room with no account and no network, and “you have improved” is the whole reason a person keeps playing.

  2. aiming the opponent (adaptive difficulty, PLAN §11): to pick an engine strength that makes for an even game, the board has to have an opinion about how strong you are.

Both survive with no Lichess account and no Wi-Fi, which is why the rating lives here — on the board — and not in the cloud (PLAN §7: “We never depend on Lichess

to know how strong a player is”).

Two separate ratings, because they measure different things and a good tactician is not automatically a good player: play (against the engine) and puzzles (against Lichess’s puzzle ratings). Lichess keeps them apart too.

Glicko-2, deliberately, and with the same (rating, rd, vol) triple the server’s ratings

table stores: a board is played in bursts and then left alone for three weeks, and a system with no notion of uncertainty handles that badly — plain Elo would treat a rusty player’s first game back as gospel. RD says “I am no longer

sure”, moves fast when it is unsure, and slows down once it is confident.

Pure: no Qt, no hardware, no network. The store is a single small JSON file.

namespace kirin

Variables

constexpr double kEngineOpponentRd = 50.0
constexpr double kPuzzleOpponentRd = 50.0
constexpr double kTau = 0.5
constexpr double kMinRd = 30.0
constexpr double kMaxRd = 350.0
struct Rating
#include <player_rating.h>

Public Functions

inline int display() const
inline bool provisional() const

Public Members

double rating = 1500.0
double rd = 350.0
double vol = 0.06
int games = 0

Public Static Attributes

static constexpr double kProvisionalRd = 110.0
static constexpr int kProvisionalGames = 5
struct RatingResult
#include <player_rating.h>

Public Members

double opponentRating = 1500.0
double opponentRd = 50.0
double score = 0.5
class RatingStore
#include <player_rating.h>

Public Functions

explicit RatingStore(std::string path = "")
inline const std::string &path() const
inline const Rating &play() const
inline const Rating &puzzles() const
void recordEngineGame(int opponentElo, double score)
void recordPuzzle(int puzzleRating, bool win)
bool load()
bool save() const
void reset()

Public Static Functions

static std::string defaultPath()

Private Members

std::string path_
Rating play_
Rating puzzles_