root_policy.py
python · 128 lines · Exact bytes
Version & provenance · data-cm7
Recorded SHA-256 4c9daa4621205902a84c382908ae0e2b3eff62b236c33d77a6995ea8c62dc722Rendered SHA-256 4c9daa4621205902a84c382908ae0e2b3eff62b236c33d77a6995ea8c62dc722
Apache-2.0 original project code; dependencies retain their own licenses. License notes. Line links are stable within this snapshot. CM-7 completed implementation source; original and public rendered hashes recorded. Host-specific paths use /work placeholders; inspect reproduction instructions before running. Separate Rust helper retains its GPL license.
1"""Conservative rule-aware Syzygy policy with explicit unresolved intervals.
2
3No unknown-history root is exact-labelled. A conservative DTZ margin with no
4repeated position can certify outcome; borderline cases use interval search.
5A bounded game tree can finish at an irreversible zero-counter state,
6where five-valued WDL is exact under claimable 50-move chess. Cursed/blessed WDL
7map to draw. At a limit, interval bounds keep uncertainty rather than guess.
8"""
9from dataclasses import dataclass
10from inventory_inputs import c
11
12class Unresolved(Exception):pass
13
14@dataclass(frozen=True)
15class Interval:
16 lo:int
17 hi:int
18 reason:str
19 @property
20 def exact(self):return self.lo==self.hi
21
22def wdl_outcome(wdl):
23 if type(wdl) is not int or wdl not in (-2,-1,0,1,2):raise ValueError('invalid WDL')
24 return 1 if wdl==2 else -1 if wdl==-2 else 0
25
26def check_probe(wdl,dtz):
27 wdl_outcome(wdl)
28 if type(dtz) is not int:raise ValueError('invalid DTZ')
29 if (wdl==0 and dtz!=0) or (wdl!=0 and ((dtz>0)-(dtz<0))!=((wdl>0)-(wdl<0))):
30 raise ValueError('WDL/DTZ sign inconsistency')
31 if abs(wdl)==1 and abs(dtz)<=100:raise ValueError('cursed/blessed DTZ range')
32 if abs(wdl)==2 and not 1<=abs(dtz)<=100:raise ValueError('unconditional DTZ range')
33
34def negate(v):return Interval(-v.hi,-v.lo,'negated:'+v.reason)
35
36def validate_history(board,history_known):
37 if not history_known:raise Unresolved('unknown repetition history')
38 if len(board.move_stack)<board.halfmove_clock:raise Unresolved('history shorter than reversible halfmove window')
39 b=board.copy(stack=True);moves=[]
40 for _ in range(board.halfmove_clock):moves.append(b.pop())
41 if b.halfmove_clock!=0:raise Unresolved('history does not reach zero-counter boundary')
42 for move in reversed(moves):
43 if not move or move not in b.legal_moves or b.is_game_over(claim_draw=False):raise ValueError('invalid historical move')
44 b.push(move)
45 if b.fen(en_passant='fen')!=board.fen(en_passant='fen'):raise ValueError('historical clock/state mismatch')
46
47def claims(board):
48 return board.can_claim_fifty_moves() or board.can_claim_threefold_repetition()
49
50def no_repetition_since_zeroing(board):
51 if len(board.move_stack)<board.halfmove_clock:return False
52 b=board.copy(stack=True);keys=set()
53 for n in range(board.halfmove_clock+1):
54 key=c.position_key(b)
55 if key in keys:return False
56 keys.add(key)
57 if n<board.halfmove_clock:b.pop()
58 return True
59
60class Solver:
61 def __init__(self,tb,max_nodes=20000):self.tb=tb;self.max_nodes=max_nodes;self.nodes=0
62 def value(self,board,depth):
63 if self.nodes>=self.max_nodes:return Interval(-1,1,'node_budget')
64 self.nodes+=1
65 if board.is_checkmate():return Interval(-1,-1,'checkmate')
66 if board.is_game_over(claim_draw=False):return Interval(0,0,'automatic_draw')
67 wdl=self.tb.probe_wdl(board);dtz=self.tb.probe_dtz(board);check_probe(wdl,dtz)
68 raw=wdl_outcome(wdl)
69 # Draw-only extra rules cannot turn a zero-counter draw into a loss or win.
70 if raw==0:return Interval(0,0,'tablebase_draw')
71 if board.halfmove_clock==0:return Interval(raw,raw,'zeroing_boundary')
72 claim=claims(board)
73 if raw==-1 and claim:return Interval(0,0,'claim_saves_loss')
74 # Conservative margin (stricter than positive DTZ+clock<=99); no
75 # historical twofold anywhere in the reversible window. See cited
76 # Stockfish Syzygy outcome bounds, not an engine score or mate distance.
77 if 2*abs(dtz)+board.halfmove_clock<100 and no_repetition_since_zeroing(board):
78 return Interval(raw,raw,'conservative_dtz_margin_no_repetition')
79 bounds=Interval(0,1,'limit') if raw==1 else Interval(-1,0,'limit')
80 if depth<=0 or self.nodes>=self.max_nodes:return bounds
81 low=0 if claim else -1;high=0 if claim else -1
82 moves=list(board.legal_moves)
83 # Zeroing/mating checks can certify a branch cheaply; ordering isn't a label.
84 moves.sort(key=lambda m:(not board.is_zeroing(m),m.uci()))
85 for move in moves:
86 board.push(move)
87 v=negate(self.value(board,depth-1))
88 board.pop()
89 low=max(low,v.lo);high=max(high,v.hi)
90 if low==1:return Interval(1,1,'proved_winning_child')
91 low=max(low,bounds.lo);high=min(high,bounds.hi)
92 if low>high:raise ValueError('tablebase/tree contradiction')
93 return Interval(low,high,'tree')
94
95def root_policy(tb,board,*,history_known=False,depth=2,max_nodes=20000):
96 c.check_board(board)
97 validate_history(board,history_known)
98 if board.castling_rights or len(board.piece_map())>5:raise ValueError('unsupported board')
99 if board.is_game_over(claim_draw=False):raise ValueError('terminal root')
100 if type(depth) is not int or depth<0 or type(max_nodes) is not int or max_nodes<1:raise ValueError('invalid bound')
101 original=board.fen(en_passant='fen');stack=len(board.move_stack)
102 solver=Solver(tb,max_nodes);wdl=tb.probe_wdl(board);dtz=tb.probe_dtz(board);check_probe(wdl,dtz)
103 root_bound=solver.value(board,0)
104 entries=[]
105 for move in sorted(board.legal_moves,key=lambda m:m.uci()):
106 child=board.copy(stack=True);zero=child.is_zeroing(move);child.push(move)
107 v=negate(solver.value(child,depth))
108 if v.lo>root_bound.hi:raise ValueError('child contradicts independent root bound')
109 v=Interval(v.lo,min(v.hi,root_bound.hi),v.reason+';root_upper_bound')
110 entries.append(dict(move=move.uci(),lo=v.lo,hi=v.hi,reason=v.reason,zeroing=zero,
111 child_halfmove_clock=child.halfmove_clock))
112 claim=claims(board)
113 lo=max([x['lo'] for x in entries]+([0] if claim else []));hi=max([x['hi'] for x in entries]+([0] if claim else []))
114 lo=max(lo,root_bound.lo);hi=min(hi,root_bound.hi)
115 if lo>hi:raise ValueError('root contradiction')
116 exact=lo==hi
117 tied=[x['move'] for x in entries if x['lo']==x['hi']==lo] if exact else []
118 unresolved_ties=[x['move'] for x in entries if x['lo']!=x['hi'] and x['hi']>=lo] if exact else [x['move'] for x in entries if x['lo']!=x['hi']]
119 claim_optimal=claim and exact and lo==0
120 ready=exact and not unresolved_ties and bool(tied) and not claim_optimal
121 if board.fen(en_passant='fen')!=original or len(board.move_stack)!=stack:raise AssertionError('mutated caller board')
122 return dict(outcome_interval=[lo,hi],root_outcome_exact=exact,preserving_moves=tied,
123 complete_preserving_move_set=exact and not unresolved_ties,unresolved_ties=unresolved_ties,
124 draw_claim_available=claim,draw_claim_optimal=claim_optimal,move_only_ready=ready,
125 raw_wdl=wdl,raw_dtz=dtz,halfmove_clock=board.halfmove_clock,history_known=True,
126 moves=entries,nodes=solver.nodes,depth=depth,node_budget=max_nodes,
127 method='bounded interval tree; zeroing WDL; conservative DTZ margin with verified repetition window')
128