The simulator has two execution modes with the same game-rule path:
- Debug/default:
Simulator(cards)retains its existing public API and records a complete replay after every action. - Training:
Simulator(cards, record_replay=False)keeps the same rules, validation, event ordering, rewards, and terminal detection, but does not serialize debug replay snapshots.step_fast()executes that same action path without producing the unused defensive state clone returned by the publicstep()API.SelfPlayRunneruses this mode automatically.
GameState.clone_for_search() remains the isolation boundary for MCTS
branches. MCTS now keeps legal actions and their priors at expansion time,
then clones and executes a successor only when PUCT selects it. Thus an
unvisited legal action never allocates a full independent state; when it is
visited, it uses the exact same Action.execute() rule implementation.
Run the API-level profiler from the repository root:
D:\KardsAI\training-venv-py312\Scripts\python.exe tools\profile_simulator.py --games 12 --max-steps 300 --seed 71
D:\KardsAI\training-venv-py312\Scripts\python.exe tools\profile_simulator.py --games 12 --max-steps 300 --seed 71 --no-replay --training-pathMeasured on the local machine before the MCTS lazy-child change (same random games, 1,469 steps):
| Path | Games/s | Steps/s | CPU | Main elapsed work |
|---|---|---|---|---|
| Public debug API | 6.34 | 776.5 | 97.5% | replay 1.28s, state clones 0.35s |
| Training path | 44.35 | 5,429.2 | 98.2% | action validation/generation |
This is a 7.0x end-to-end speed-up for identical games through the two
execution modes. tracemalloc on an 8-game / 925-step run reduced peak
Python allocations from 36.56 MiB to 4.99 MiB. The post-change CUDA MCTS
benchmark (benchmark_selfplay.py --episodes 1 --simulations 64 --max-actions 300 --workers 1) measured 0.1174 games/s, compared with 0.0577 games/s
before lazy MCTS child generation in this checkout.
The native rule engine pre-parses card data when CardDatabase is loaded and
caches the resulting rule objects by card ID. Card text is not parsed in the
turn loop. High-frequency action generation now also reads cached static
action-shape facts rather than repeatedly scanning each card rule.
The remaining primary cost in neural MCTS is deliberate search work:
independent state branches, action validation, and policy/value inference.
For higher GPU use, run independent self-play workers through the existing
SelfPlayRunner.run_parallel() / training worker configuration. Each worker
has its own simulator state and seed; static card definitions are not mutated.
tools/benchmark_training.py reports the four training-relevant rates in one
run: public random simulation, clone-free step_fast, MCTS self-play, and
raw network inference. It prints games/s, steps/s, and legal candidate
actions/s (network prints inference and batch rates).
MCTS now uses prior-ordered progressive widening. All legal actions are still generated by the normal validator and remain eligible; the high-prior subset is materialised first and the allowed width grows with square-root visits. A virtual-visit accounting field is included in PUCT so future parallel tree selection does not select the same pending branch repeatedly.
VectorizedSelfPlay provides independent simulator lanes with a shared
BatchedInference queue. It is intended for GPU-backed MCTS: rule states
remain local to each lane while policy/value requests from concurrent lanes
are coalesced into GPU batches.