coolguy b9fc5693a4 Normalize PUCT Q + add mirror-descent policy target
Codex follow-up diagnostics identified two MCTS+training-loop issues that
together cap finetune-from-BC at the heuristic ceiling:

1. PUCT Q is in raw score units (~±100 for value_scale=100), but the
   exploration bonus c_puct * prior * sqrt(N) / (1+n) is on order of 1-10
   for our parameter ranges. Result: a single bad backup pushes q_eff
   well below the bonus floor and that action is effectively never
   visited again. With only 50 sims/move this is catastrophic for the
   policy-improvement operator. Fix: divide q_eff by config.q_scale
   (default 100, configurable) inside _select_action. Backups and value
   targets remain in raw score units; only the selection signal is
   normalized. AlphaZero canonical convention.

2. The current kl_anchor_beta path adds KL(current || ref) directly to
   the loss. That preserves BC but prevents improvement (gradient
   actively pulls policy back to reference). The standard regularized
   policy improvement operator is to mix the target instead:
     pi_target = softmax(alpha * log(pi_mcts) + (1-alpha) * log(pi_ref))
   Anneal alpha from low (rely on BC) to high (rely on MCTS) over
   training. Network learns to follow the regularized target, which
   stays near BC early but lets MCTS-discovered improvements through
   later.

Config additions:
- mcts.q_scale (default 100.0): PUCT Q divisor
- training.md_target_ref_ckpt: reference policy path (alternative to kl_anchor)
- training.md_target_alpha_start / _end / _iters: linear alpha schedule

Both Python mcts.py and Cython mcts.pyx updated; parity test passes.
Tests: 19/19.

Hypothesis: with normalized PUCT the network can actually explore and
exploit prior knowledge competently at 50 sims, and the mirror-descent
target lets self-play improvement happen while BC anchors the trajectory.
This is the operator-side fix that c9 (no anchor, collapsed) and c10/c11
(loss-side KL anchor, preserved-but-stuck) both missed.
2026-05-11 16:31:02 +09:00
2026-05-08 16:22:52 +09:00
2026-05-07 15:42:30 +09:00

coolrl-lost-cities

Focused Lost Cities extraction from the legacy coolrl repository.

The current implementation starts with the classic two-player card game:

  • classic 5-expedition rules by default
  • Python/Cython game engine
  • env wrapper
  • random, discard-only, and safe-heuristic bots
  • core rule, scoring, mask, env, canonical-state, bot, and GUI smoke tests

Training code, Deep CFR, learned-policy evaluation, GUI, and web client are intentionally outside the first port.

Development

uv run pytest tests/games/classic
uv run lost-cities-classic

For future GUI work, install the optional GUI dependencies:

uv sync --extra gui

Run the classic pygame GUI:

uv run lost-cities-classic-gui --mode pvc --bot safe-heuristic

The GUI uses the in-process Cython game engine.

Basic Usage

from coolrl_lost_cities.games.classic import GameState, build_bot, classic_config

state = GameState.new_game(classic_config(seed=1))
bot = build_bot("random", seed=1)

while not state.terminal:
    state.apply_action(bot.act(state))

print(state.total_score(0), state.total_score(1))

See classic port notes for the current direction.

S
Description
Lost Cities
Readme
7.7 MiB
Languages
Python 73.3%
Cython 21.7%
Julia 4.8%
Shell 0.2%