Mobility and Passed Pawn Contracts
The playground evaluation model ablation (fortemate/dicechess-training#12, #14) tests
position-evaluation models against the baseline KcpFeatures (kcp-13). Strategy
research highlights three position properties that no previous feature schema exposed:
expected wasted rolls (dice faces whose piece type has no move), pawn blockage,
and tempo as separate own and opponent mobility counts rather than a single signed
difference.
In addition, the endgame motivates a passed-pawn block: pawn promotion in Dice Chess depends on rolling a pawn face, and a passed pawn’s value increases sharply as it nears the back rank.
Following the principle of train == serve, training enrichment and serving inference share the exact engine implementation to prevent drift.
Shared-Core Primitives
Section titled “Shared-Core Primitives”Two primitives in dicechess.engine.search compute the underlying positional features:
PieceMobility
Section titled “PieceMobility”Exposes pseudo-legal move counts per moving piece type in dice order (Pawn, Knight,
Bishop, Rook, Queen, King):
package dicechess.engine.search
object PieceMobility: def counts(state: GameState, color: Color): Array[Int] def count(state: GameState, color: Color, pieceType: PieceType): Int def total(state: GameState, color: Color): IntMoves are computed on state.withActiveColor(color) with castling dice present
(King and Rook dice in pool), exactly matching how RichFeatures.mobility_diff
computes its operands. For every position and color:
PassedPawns
Section titled “PassedPawns”Exposes passed-pawn detection and rank advancement:
package dicechess.engine.search
object PassedPawns: def count(state: GameState, color: Color): Int def maxRank(state: GameState, color: Color): Int def countAndMaxRank(state: GameState, color: Color): (Int, Int) def bitboard(state: GameState, color: Color): Bitboard- Definition: Uses the standard chess definition: a pawn is passed if there are no opposing pawns on the same file or on adjacent files ahead of the pawn along its direction of advance.
- Max rank advancement: Counted from that side’s own back rank as 0:
- White pawn on rank : advancement is .
- Black pawn on rank : advancement is .
- When a side has no passed pawn, the value is .
- Perspective: Evaluated from the explicit
colorargument, independent of active color and dice pool.
Versioned Feature Contracts
Section titled “Versioned Feature Contracts”1. kcp-mobility-27-v1
Section titled “1. kcp-mobility-27-v1”Extends KcpFeatures (13 columns) with per-piece move counts and normalized PDI:
| Index | Column Name | Source / Definition | Range |
|---|---|---|---|
| 0–12 | p_diff … queen_capture_danger | Identical to KcpFeatures.extract | float32 |
| 13 | own_moves_p | Own pseudo-legal pawn moves | |
| 14 | own_moves_n | Own pseudo-legal knight moves | |
| 15 | own_moves_b | Own pseudo-legal bishop moves | |
| 16 | own_moves_r | Own pseudo-legal rook moves | |
| 17 | own_moves_q | Own pseudo-legal queen moves | |
| 18 | own_moves_k | Own pseudo-legal king moves | |
| 19–24 | opp_moves_p … opp_moves_k | Opponent pseudo-legal move counts | |
| 25 | own_pdi | PieceDiversity.count(state, color) / 5f | 0.0 to 1.0 |
| 26 | opp_pdi | PieceDiversity.count(state, opponent) / 5f | 0.0 to 1.0 |
2. kcp-mobility-pawns-31-v1
Section titled “2. kcp-mobility-pawns-31-v1”Extends kcp-mobility-27-v1 with the passed-pawn block:
| Index | Column Name | Source / Definition | Range |
|---|---|---|---|
| 0–26 | Prefix (27 columns) | Identical to KcpMobilityFeatures.extract | float32 |
| 27 | own_passed_pawns | Number of passed pawns for color | 0 to 8 |
| 28 | opp_passed_pawns | Number of passed pawns for opponent | 0 to 8 |
| 29 | own_passed_max_rank | Most advanced passed pawn rank from back rank | 0 to 6 |
| 30 | opp_passed_max_rank | Opponent most advanced passed pawn rank | 0 to 6 |
Mover-Canonical Symmetry
Section titled “Mover-Canonical Symmetry”Both contracts are mover-canonical: evaluating state from color perspective
produces the identical vector as evaluating Symmetry.colorFlip(state) from
color.opponent perspective. When evaluated for the side to move (state.activeColor),
the vector matches that of the color-flipped state evaluated for its own mover.
Strategic Interpretation
Section titled “Strategic Interpretation”Expected Wasted Rolls
Section titled “Expected Wasted Rolls”In Dice Chess, a turn rolls 3 dice from a uniform 6-sided die . If a side has zero pseudo-legal moves for a piece type (and therefore zero legal moves), any die face showing cannot be used for that type.
For a single die roll, the probability that the rolled piece type has no move is:
Across the 3 dice rolled in a turn, by linearity of expectation, the expected number of wasted dice is:
The per-piece move counts own_moves_* and opp_moves_* directly expose whether each
piece type is mobile () or blocked ().
Pawn Blockage and Separate Tempo
Section titled “Pawn Blockage and Separate Tempo”- Pawn Blockage: When
own_moves_p == 0, friendly pawns are completely locked or absent. In Dice Chess, rolling pawn dice when pawns are blocked forces the player to burn turns or forgo actions. - Separate Tempo: Rather than collapsing mobility into a signed scalar
(
mobility_diff = own - opp), exposingown_moves_*andopp_moves_*separately allows non-linear models (neural nets) to evaluate asymmetries—for example, high mobility for both sides (tactical volatility) versus low mobility for both sides (closed deadlock).
Schema Versioning Rule
Section titled “Schema Versioning Rule”[!IMPORTANT] Any change in column order, normalization, perspective, or semantic definition requires a new schema identifier (e.g.
kcp-mobility-27-v2) and a corresponding newly trained model. Existing schemas must remain immutable to preserve reproducibility and avoid silent evaluation failures.
JMH Benchmarks
Section titled “JMH Benchmarks”Microbenchmarks on OpenJDK 25 (Temurin 25.0.4) compare extraction overhead across the
5 standard benchmark positions (initial, kiwipete, endgame, castling, promotion):
| Benchmark | Position | Average Time () |
|---|---|---|
passedPawnsSummary | initial / kiwipete / endgame / castling / promotion | 0.004 – 0.012 |
pieceMobilityCounts | initial / kiwipete / endgame / castling / promotion | 0.101 – 0.436 |
kcp (13 cols) | promotion / endgame / castling / initial / kiwipete | 79 – 17,664 |
kcpMobility27 | promotion / endgame / castling / initial / kiwipete | 82 – 17,466 |
kcpMobilityPawns31 | promotion / endgame / castling / initial / kiwipete | 78 – 17,149 |
The 216-outcome DFS search for king/queen capture probabilities in kcp dominates
total extraction time (hundreds of microseconds to tens of milliseconds). The additional
per-type move counting () and passed-pawn bitboard probe ()
add under overhead on complex positions (e.g. on kiwipete’s )
and under on the fastest sparse positions ( on promotion’s ).