tostido commited on
Commit
5259a6b
·
1 Parent(s): d90e180

Convert to HuggingFace Docker SDK with Streamlit - Add src/streamlit_app.py (1278 lines) - full Streamlit conversion - Add Dockerfile for HF Docker SDK deployment - Update requirements.txt with version constraints - Update README.md with sdk: docker - Remove Super Saiyan branding from main.py - Delete old app.py (replaced by src/streamlit_app.py)

Browse files
Files changed (6) hide show
  1. Dockerfile +29 -0
  2. README.md +1 -2
  3. app.py +0 -914
  4. main.py +59 -58
  5. requirements.txt +4 -7
  6. src/streamlit_app.py +1314 -0
Dockerfile ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y \
6
+ build-essential \
7
+ curl \
8
+ software-properties-common \
9
+ stockfish \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ RUN useradd -m -u 1000 user
13
+ USER user
14
+ ENV HOME=/home/user \
15
+ PATH=/home/user/.local/bin:$PATH
16
+
17
+ WORKDIR $HOME/app
18
+
19
+ COPY --chown=user requirements.txt .
20
+ RUN pip install --no-cache-dir --upgrade pip && \
21
+ pip install --no-cache-dir -r requirements.txt
22
+
23
+ COPY --chown=user . .
24
+
25
+ EXPOSE 7860
26
+
27
+ HEALTHCHECK CMD curl --fail http://localhost:7860/_stcore/health
28
+
29
+ ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=7860", "--server.address=0.0.0.0"]
README.md CHANGED
@@ -3,8 +3,7 @@ title: Cascade Lattice Chess
3
  emoji: ♟️
4
  colorFrom: blue
5
  colorTo: purple
6
- sdk: streamlit
7
- sdk_version: 1.41.0
8
  app_file: app.py
9
  pinned: false
10
  license: mit
 
3
  emoji: ♟️
4
  colorFrom: blue
5
  colorTo: purple
6
+ sdk: docker
 
7
  app_file: app.py
8
  pinned: false
9
  license: mit
app.py DELETED
@@ -1,914 +0,0 @@
1
- import streamlit as st
2
- import chess
3
- import chess.engine
4
- import chess.svg
5
- import os
6
- import random
7
- import time
8
- import math
9
- import hashlib
10
- from collections import deque
11
- from dataclasses import dataclass, field
12
- from typing import List, Dict, Any, Optional, Tuple
13
-
14
- # Conditional imports
15
- try:
16
- import torch
17
- from transformers import AutoModel
18
- TORCH_AVAILABLE = True
19
- except ImportError:
20
- TORCH_AVAILABLE = False
21
- print("[WARNING] PyTorch not available")
22
-
23
- try:
24
- import cascade
25
- CASCADE_AVAILABLE = True
26
- except ImportError:
27
- CASCADE_AVAILABLE = False
28
- print("[WARNING] cascade-lattice not available, using mocks")
29
-
30
- # Configuration
31
- MODEL_NAME = "Maxlegrec/ChessBot"
32
-
33
- def find_stockfish():
34
- paths = [
35
- "/usr/games/stockfish",
36
- "/usr/bin/stockfish",
37
- "/usr/local/bin/stockfish",
38
- "stockfish/stockfish-windows-x86-64-avx2.exe",
39
- "stockfish/stockfish"
40
- ]
41
- for p in paths:
42
- if os.path.exists(p):
43
- return p
44
- return None
45
-
46
- STOCKFISH_PATH = find_stockfish()
47
-
48
- # ═══════════════════════════════════════════════════════════════════════════════
49
- # CASCADE MOCK (when cascade-lattice isn't available)
50
- # ═══════════════════════════════════════════════════════════════════════════════
51
-
52
- class MockHold:
53
- _instance = None
54
- def __init__(self):
55
- self.timeout = 3600
56
- self.listeners = []
57
- @classmethod
58
- def get(cls):
59
- if cls._instance is None:
60
- cls._instance = MockHold()
61
- return cls._instance
62
- def register_listener(self, fn):
63
- self.listeners.append(fn)
64
- def resolve(self, action=None):
65
- pass
66
-
67
- class MockCausationGraph:
68
- def __init__(self):
69
- self.events = []
70
- self.links = []
71
- def add_event(self, e): self.events.append(e)
72
- def add_link(self, l): self.links.append(l)
73
- def get_stats(self): return {'events': len(self.events), 'links': len(self.links)}
74
-
75
- class MockMetricsEngine:
76
- def __init__(self): self.metrics = {}
77
- def ingest(self, e): pass
78
- def summary(self): return {}
79
-
80
- class MockEvent:
81
- def __init__(self, **kwargs):
82
- for k, v in kwargs.items():
83
- setattr(self, k, v)
84
-
85
- class MockCausationLink:
86
- def __init__(self, **kwargs):
87
- for k, v in kwargs.items():
88
- setattr(self, k, v)
89
-
90
- if CASCADE_AVAILABLE:
91
- CascadeHold = cascade.Hold
92
- CausationGraph = cascade.CausationGraph
93
- MetricsEngine = cascade.MetricsEngine
94
- CascadeEvent = cascade.Event
95
- CausationLink = cascade.CausationLink
96
- cascade.init()
97
- else:
98
- CascadeHold = MockHold
99
- CausationGraph = MockCausationGraph
100
- MetricsEngine = MockMetricsEngine
101
- CascadeEvent = MockEvent
102
- CausationLink = MockCausationLink
103
-
104
- # ═══════════════════════════════════════════════════════════════════════════════
105
- # DATA CLASSES
106
- # ═══════════════════════════════════════════════════════════════════════════════
107
-
108
- @dataclass
109
- class CausalNode:
110
- event_id: str
111
- move: str
112
- player: str
113
- fen: str
114
- timestamp: float
115
- merkle: str
116
- value: float
117
- was_override: bool = False
118
- depth: int = 0
119
-
120
- @dataclass
121
- class PredictionRisk:
122
- move: str
123
- risk_score: float
124
- predicted_responses: List[str]
125
- material_delta: int
126
- positional_delta: float
127
- tactical_threats: List[str]
128
- intervention_point: bool = False
129
-
130
- @dataclass
131
- class AnomalyAlert:
132
- timestamp: float
133
- metric_name: str
134
- expected_value: float
135
- actual_value: float
136
- deviation_sigma: float
137
- severity: str
138
- message: str
139
-
140
- @dataclass
141
- class SessionStats:
142
- total_holds: int = 0
143
- human_overrides: int = 0
144
- ai_accepted: int = 0
145
- current_combo: int = 0
146
- max_combo: int = 0
147
- override_rate: float = 0.0
148
- avg_decision_time: float = 0.0
149
- decision_times: List[float] = field(default_factory=list)
150
- speed_bonus: float = 0.0
151
-
152
- # ═══════════════════════════════════════════════════════════════════════════════
153
- # GAME STATE CLASS
154
- # ═══════════════════════════════════════════════════════════════════════════════
155
-
156
- class ChessGameState:
157
- def __init__(self):
158
- self.reset_game()
159
- self.causation_graph = CausationGraph()
160
- self.metrics_engine = MetricsEngine()
161
- self.causal_chain: List[CausalNode] = []
162
- self.merkle_chain: List[str] = []
163
- self.last_event_id = None
164
- self.model = None
165
- self.device = "cpu"
166
- self.engine = None
167
- self.auto_mode = False
168
- self.anomalies: deque = deque(maxlen=50)
169
- self.value_history: List[float] = []
170
- self.hold_start_time: Optional[float] = None
171
-
172
- def reset_game(self):
173
- self.board = chess.Board()
174
- self.move_history = []
175
- self.session = SessionStats()
176
- self.candidates = []
177
- self.predictions = []
178
- self.selected_idx = 0
179
- self.ui_mode = "LIST"
180
- self.active_tab = "WEALTH"
181
- self.game_over = False
182
- self.game_result = ""
183
- self.hold_active = False
184
- self.current_merkle = ""
185
- self.last_observation = {}
186
-
187
- def load_model(self):
188
- if not TORCH_AVAILABLE or self.model is not None:
189
- return
190
- try:
191
- self.model = AutoModel.from_pretrained(MODEL_NAME, trust_remote_code=True)
192
- self.device = "cuda" if torch.cuda.is_available() else "cpu"
193
- self.model.to(self.device)
194
- self.model.eval()
195
- st.success(f"Model loaded on {self.device}")
196
- except Exception as e:
197
- st.error(f"Model load failed: {e}")
198
-
199
- def init_engine(self):
200
- if STOCKFISH_PATH and self.engine is None:
201
- try:
202
- self.engine = chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH)
203
- except Exception as e:
204
- st.warning(f"Stockfish init failed: {e}")
205
-
206
- def _compute_merkle(self, data: Dict) -> str:
207
- content = str(sorted(data.items())).encode()
208
- return hashlib.sha256(content).hexdigest()[:16]
209
-
210
- def register_event(self, event_data: Dict) -> str:
211
- event_id = f"evt_{len(self.causal_chain)}_{int(time.time()*1000)}"
212
-
213
- if CASCADE_AVAILABLE:
214
- event = CascadeEvent(
215
- event_id=event_id,
216
- timestamp=time.time(),
217
- component=event_data.get('player', 'chess'),
218
- event_type=event_data.get('type', 'move'),
219
- data=event_data
220
- )
221
- self.causation_graph.add_event(event)
222
-
223
- if self.last_event_id and self.causal_chain:
224
- link = CausationLink(
225
- from_event=self.last_event_id,
226
- to_event=event_id,
227
- causation_type="game_sequence",
228
- strength=1.0,
229
- explanation=f"Move sequence"
230
- )
231
- self.causation_graph.add_link(link)
232
-
233
- node = CausalNode(
234
- event_id=event_id,
235
- move=event_data.get('move', ''),
236
- player=event_data.get('player', ''),
237
- fen=event_data.get('fen', ''),
238
- timestamp=time.time(),
239
- merkle=self._compute_merkle(event_data),
240
- value=event_data.get('value', 0.0),
241
- was_override=event_data.get('was_override', False),
242
- depth=len(self.causal_chain)
243
- )
244
-
245
- self.causal_chain.append(node)
246
- self.merkle_chain.append(node.merkle)
247
- self.last_event_id = event_id
248
- return event_id
249
-
250
- def get_ai_move(self) -> Tuple[Optional[str], List[Dict], float]:
251
- fen = self.board.fen()
252
- legal_moves = [m.uci() for m in self.board.legal_moves]
253
-
254
- if not legal_moves:
255
- return None, [], 0.0
256
-
257
- move_uci = None
258
- pos_eval = [0.33, 0.34, 0.33]
259
-
260
- if self.model is not None:
261
- try:
262
- with torch.no_grad():
263
- pos_eval = self.model.get_position_value(fen, device=self.device).tolist()
264
- move_uci = self.model.get_move_from_fen_no_thinking(fen, T=0.1, device=self.device)
265
- except Exception as e:
266
- st.warning(f"AI error: {e}")
267
-
268
- if move_uci is None or move_uci not in legal_moves:
269
- move_uci = random.choice(legal_moves)
270
-
271
- candidates = [{
272
- "move": move_uci,
273
- "prob": 0.82,
274
- "v_head": pos_eval,
275
- "reason": "Primary engine recommendation"
276
- }]
277
-
278
- others = [m for m in legal_moves if m != move_uci]
279
- random.shuffle(others)
280
- for i, m in enumerate(others[:4]):
281
- v_noise = [v + random.uniform(-0.08, 0.08) for v in pos_eval]
282
- candidates.append({
283
- "move": m,
284
- "prob": max(0.01, 0.15 - i * 0.03),
285
- "v_head": v_noise,
286
- "reason": self._generate_move_reason(m)
287
- })
288
-
289
- value = pos_eval[2] - pos_eval[0]
290
- return move_uci, candidates, value
291
-
292
- def _generate_move_reason(self, move_uci: str) -> str:
293
- reasons = [
294
- "Develops piece activity",
295
- "Controls central squares",
296
- "Prepares kingside operation",
297
- "Maintains pawn structure",
298
- "Creates tactical threats",
299
- "Improves piece coordination"
300
- ]
301
- try:
302
- move = chess.Move.from_uci(move_uci)
303
- piece = self.board.piece_at(move.from_square)
304
- if piece:
305
- if self.board.is_capture(move):
306
- return f"{chess.piece_name(piece.piece_type).capitalize()} capture"
307
- elif piece.piece_type == chess.PAWN:
308
- return "Pawn advance - space control"
309
- except:
310
- pass
311
- return random.choice(reasons)
312
-
313
- def get_stockfish_move(self) -> str:
314
- if self.engine:
315
- try:
316
- result = self.engine.play(self.board, chess.engine.Limit(time=0.5))
317
- return result.move.uci()
318
- except:
319
- pass
320
- return random.choice([m.uci() for m in self.board.legal_moves])
321
-
322
- def predict_cascade(self, candidates: List[Dict]) -> List[PredictionRisk]:
323
- predictions = []
324
- for c in candidates:
325
- try:
326
- move = chess.Move.from_uci(c['move'])
327
- temp = self.board.copy()
328
- mat_before = self._count_material(temp)
329
- temp.push(move)
330
- mat_after = self._count_material(temp)
331
- responses = [m.uci() for m in list(temp.legal_moves)[:5]]
332
- threats = self._detect_threats(temp, move)
333
-
334
- risk = 0.0
335
- mat_delta = mat_after - mat_before
336
- if mat_delta < 0:
337
- risk += min(0.3, abs(mat_delta) * 0.1)
338
- risk += len(threats) * 0.1
339
- risk += (1 - c['prob']) * 0.3
340
- if temp.is_checkmate():
341
- risk = 0.0
342
- elif temp.is_check():
343
- risk += 0.1
344
- risk = min(1.0, max(0.0, risk))
345
-
346
- predictions.append(PredictionRisk(
347
- move=c['move'],
348
- risk_score=risk,
349
- predicted_responses=responses,
350
- material_delta=mat_delta,
351
- positional_delta=c['v_head'][2] - c['v_head'][0],
352
- tactical_threats=threats,
353
- intervention_point=risk > 0.7 or len(threats) > 2
354
- ))
355
- except:
356
- pass
357
- return predictions
358
-
359
- def _detect_threats(self, board: chess.Board, last_move: chess.Move) -> List[str]:
360
- threats = []
361
- if board.is_check():
362
- threats.append("CHECK")
363
- for square in chess.SQUARES:
364
- piece = board.piece_at(square)
365
- if piece and piece.color == board.turn:
366
- attackers = board.attackers(not board.turn, square)
367
- defenders = board.attackers(board.turn, square)
368
- if len(attackers) > len(defenders):
369
- threats.append(f"HANGING_{chess.square_name(square).upper()}")
370
- if len(threats) > 1:
371
- threats.append("FORK_THREAT")
372
- return threats[:5]
373
-
374
- def _count_material(self, board) -> int:
375
- values = {chess.PAWN: 1, chess.KNIGHT: 3, chess.BISHOP: 3,
376
- chess.ROOK: 5, chess.QUEEN: 9}
377
- score = 0
378
- for sq in chess.SQUARES:
379
- piece = board.piece_at(sq)
380
- if piece and piece.piece_type in values:
381
- val = values[piece.piece_type]
382
- score += val if piece.color == chess.WHITE else -val
383
- return score
384
-
385
- def check_anomalies(self, current_data: Dict) -> List[AnomalyAlert]:
386
- new_anomalies = []
387
- if 'confidence' in current_data:
388
- conf = current_data['confidence']
389
- if conf < 0.3:
390
- alert = AnomalyAlert(
391
- timestamp=time.time(),
392
- metric_name="confidence",
393
- expected_value=0.7,
394
- actual_value=conf,
395
- deviation_sigma=2.5,
396
- severity="WARNING",
397
- message=f"Low AI confidence: {conf*100:.1f}%"
398
- )
399
- new_anomalies.append(alert)
400
- self.anomalies.append(alert)
401
- return new_anomalies
402
-
403
- def make_move(self, move_uci: str, player: str, was_override: bool = False):
404
- try:
405
- move = chess.Move.from_uci(move_uci)
406
- if move in self.board.legal_moves:
407
- self.board.push(move)
408
-
409
- value = 0.0
410
- if self.model:
411
- try:
412
- with torch.no_grad():
413
- ev = self.model.get_position_value(self.board.fen(), device=self.device)
414
- value = float(ev[2] - ev[0])
415
- self.value_history.append(value)
416
- except:
417
- pass
418
-
419
- self.move_history.append({
420
- 'move': move_uci,
421
- 'player': player,
422
- 'was_override': was_override
423
- })
424
-
425
- self.register_event({
426
- 'type': 'move',
427
- 'move': move_uci,
428
- 'player': player,
429
- 'fen': self.board.fen(),
430
- 'value': value,
431
- 'was_override': was_override
432
- })
433
-
434
- if was_override:
435
- self.session.human_overrides += 1
436
- self.session.current_combo = 0
437
- else:
438
- self.session.ai_accepted += 1
439
- self.session.current_combo += 1
440
- self.session.max_combo = max(self.session.max_combo, self.session.current_combo)
441
-
442
- self.session.total_holds += 1
443
- if self.session.total_holds > 0:
444
- self.session.override_rate = self.session.human_overrides / self.session.total_holds
445
-
446
- if self.board.is_game_over():
447
- self.game_over = True
448
- result = self.board.result()
449
- if result == "1-0":
450
- self.game_result = "WHITE WINS (ChessBot)"
451
- elif result == "0-1":
452
- self.game_result = "BLACK WINS (Stockfish)"
453
- else:
454
- self.game_result = f"DRAW ({result})"
455
-
456
- return True
457
- except Exception as e:
458
- st.error(f"Move error: {e}")
459
- return False
460
-
461
- def start_hold_timer(self):
462
- self.hold_start_time = time.time()
463
-
464
- def get_hold_duration(self) -> float:
465
- if self.hold_start_time:
466
- return time.time() - self.hold_start_time
467
- return 0.0
468
-
469
- # ═══════════════════════════════════════════════════════════════════════════════
470
- # STREAMLIT UI
471
- # ═══════════════════════════════════════════════════════════════════════════════
472
-
473
- st.set_page_config(
474
- page_title="CASCADE LATTICE CHESS",
475
- page_icon="♟️",
476
- layout="wide"
477
- )
478
-
479
- # Custom CSS for dark theme
480
- st.markdown("""
481
- <style>
482
- .stApp {
483
- background-color: #121216;
484
- }
485
- .metric-card {
486
- background-color: #23232d;
487
- padding: 15px;
488
- border-radius: 8px;
489
- margin: 5px 0;
490
- }
491
- .risk-low { color: #00ff96; }
492
- .risk-med { color: #ff9632; }
493
- .risk-high { color: #ff4646; }
494
- .merkle-hash {
495
- font-family: monospace;
496
- color: #64ffda;
497
- background: #1a2a2f;
498
- padding: 2px 6px;
499
- border-radius: 3px;
500
- }
501
- .candidate-card {
502
- background: #2d2d3a;
503
- padding: 12px;
504
- border-radius: 6px;
505
- margin: 8px 0;
506
- border-left: 3px solid #00c8ff;
507
- }
508
- .candidate-card:hover {
509
- background: #3d3d4a;
510
- }
511
- .override-card {
512
- border-left-color: #ffa500;
513
- }
514
- .combo-display {
515
- font-size: 24px;
516
- color: #ffd700;
517
- font-weight: bold;
518
- }
519
- .threat-badge {
520
- background: #ff4646;
521
- color: white;
522
- padding: 2px 8px;
523
- border-radius: 4px;
524
- font-size: 12px;
525
- }
526
- .tab-active {
527
- background: #00c8ff;
528
- color: black;
529
- padding: 8px 16px;
530
- border-radius: 4px;
531
- }
532
- </style>
533
- """, unsafe_allow_html=True)
534
-
535
- # Initialize session state
536
- if 'game_state' not in st.session_state:
537
- st.session_state.game_state = ChessGameState()
538
- st.session_state.initialized = False
539
-
540
- gs = st.session_state.game_state
541
-
542
- # Initialize on first run
543
- if not st.session_state.initialized:
544
- with st.spinner("Loading ChessBot model..."):
545
- gs.load_model()
546
- gs.init_engine()
547
- gs.register_event({
548
- 'type': 'game_start',
549
- 'move': '',
550
- 'player': 'SYSTEM',
551
- 'fen': gs.board.fen(),
552
- 'value': 0.0
553
- })
554
- st.session_state.initialized = True
555
-
556
- # ═══════════════════════════════════════════════════════════════════════════════
557
- # HEADER
558
- # ═══════════════════════════════════════════════════════════════════════════════
559
-
560
- st.title("🏁 CASCADE LATTICE CHESS")
561
- st.markdown("**Human-in-the-loop chess with cryptographic provenance via cascade-lattice**")
562
-
563
- # Top stats bar
564
- col1, col2, col3, col4, col5 = st.columns(5)
565
- with col1:
566
- if gs.session.current_combo >= 3:
567
- st.markdown(f"<span class='combo-display'>🔥 COMBO x{gs.session.current_combo}</span>", unsafe_allow_html=True)
568
- elif gs.session.current_combo > 0:
569
- st.metric("Combo", f"x{gs.session.current_combo}")
570
- with col2:
571
- or_color = "🔴" if gs.session.override_rate > 0.5 else "🟢"
572
- st.metric("Override Rate", f"{or_color} {gs.session.override_rate*100:.0f}%")
573
- with col3:
574
- st.metric("Total Holds", gs.session.total_holds)
575
- with col4:
576
- st.metric("Max Combo", gs.session.max_combo)
577
- with col5:
578
- auto_label = "🟢 AUTO ON" if gs.auto_mode else "⚫ AUTO OFF"
579
- if st.button(auto_label, key="auto_toggle"):
580
- gs.auto_mode = not gs.auto_mode
581
- st.rerun()
582
-
583
- st.divider()
584
-
585
- # ═══════════════════════════════════════════════════════════════════════════════
586
- # MAIN LAYOUT
587
- # ═══════════════════════════════════════════════════════════════════════════════
588
-
589
- board_col, panel_col = st.columns([1, 1.5])
590
-
591
- # ═══════════════════════════════════════════════════════════════════════════════
592
- # BOARD COLUMN
593
- # ═══════════════════════════════════════════════════════════════════════════════
594
-
595
- with board_col:
596
- # Generate SVG with arrows if in drill mode
597
- arrows = []
598
- if gs.ui_mode == "DRILL" and gs.candidates and gs.selected_idx < len(gs.candidates):
599
- selected = gs.candidates[gs.selected_idx]
600
- try:
601
- move = chess.Move.from_uci(selected['move'])
602
- arrows.append(chess.svg.Arrow(move.from_square, move.to_square, color="#ffd700"))
603
-
604
- # Show predicted responses
605
- temp = gs.board.copy()
606
- temp.push(move)
607
- for resp in list(temp.legal_moves)[:3]:
608
- arrows.append(chess.svg.Arrow(resp.from_square, resp.to_square, color="#00c8ff80"))
609
- except:
610
- pass
611
-
612
- svg = chess.svg.board(gs.board, arrows=arrows, size=450)
613
- st.markdown(f'<div style="display:flex;justify-content:center;">{svg}</div>', unsafe_allow_html=True)
614
-
615
- # Status bar under board
616
- status_col1, status_col2, status_col3 = st.columns(3)
617
- with status_col1:
618
- if gs.hold_active:
619
- hold_time = gs.get_hold_duration()
620
- st.markdown(f"⏸️ **HOLD** {hold_time:.1f}s")
621
- else:
622
- st.markdown("● Observing")
623
- with status_col2:
624
- st.markdown(f"Moves: {len(gs.move_history)}")
625
- with status_col3:
626
- st.markdown(f"<span class='merkle-hash'>Chain: {len(gs.merkle_chain)}</span>", unsafe_allow_html=True)
627
-
628
- # Last evaluation
629
- if gs.last_observation:
630
- e = gs.last_observation.get('evaluation', [])
631
- if e:
632
- eval_val = e[2] - e[0]
633
- color = "🟢" if eval_val > 0.1 else "🔴" if eval_val < -0.1 else "⚪"
634
- st.markdown(f"**Eval:** {color} {eval_val:+.3f}")
635
-
636
- # New Game button
637
- if st.button("🔄 NEW GAME", use_container_width=True):
638
- gs.reset_game()
639
- gs.causal_chain = []
640
- gs.merkle_chain = []
641
- gs.value_history = []
642
- gs.register_event({
643
- 'type': 'game_start',
644
- 'move': '',
645
- 'player': 'SYSTEM',
646
- 'fen': gs.board.fen(),
647
- 'value': 0.0
648
- })
649
- st.rerun()
650
-
651
- # ═══════════════════════════════════════════════════════════════════════════════
652
- # PANEL COLUMN
653
- # ═══════════════════════════════════════════════════════════════════════════════
654
-
655
- with panel_col:
656
- # Game over state
657
- if gs.game_over:
658
- st.error(f"🏁 GAME OVER: {gs.game_result}")
659
- st.balloons()
660
-
661
- # Tab system
662
- tab1, tab2, tab3, tab4 = st.tabs(["📊 WEALTH", "🔮 CASCADE", "📈 METRICS", "🔗 CHAIN"])
663
-
664
- # ═══════════════════════════════════════════════════════════════════════════
665
- # WEALTH TAB
666
- # ═══════════════════════════════════════════════════════════════════════════
667
-
668
- with tab1:
669
- if not gs.hold_active and not gs.game_over:
670
- # Trigger AI turn
671
- if gs.board.turn == chess.WHITE:
672
- with st.spinner("ChessBot thinking..."):
673
- ai_move, candidates, value = gs.get_ai_move()
674
- if candidates:
675
- gs.candidates = candidates
676
- gs.predictions = gs.predict_cascade(candidates)
677
- gs.hold_active = True
678
- gs.start_hold_timer()
679
- gs.current_merkle = gs._compute_merkle({'fen': gs.board.fen(), 'candidates': [c['move'] for c in candidates]})
680
- gs.selected_idx = 0
681
- gs.last_observation = {'evaluation': candidates[0].get('v_head', [0.33, 0.34, 0.33])}
682
- gs.check_anomalies({'confidence': candidates[0]['prob']})
683
- st.rerun()
684
- else:
685
- # Stockfish turn
686
- with st.spinner("Stockfish responding..."):
687
- sf_move = gs.get_stockfish_move()
688
- gs.make_move(sf_move, "Stockfish", False)
689
- st.rerun()
690
-
691
- if gs.hold_active:
692
- st.subheader("🎯 CASCADE HOLD // SELECT MOVE")
693
-
694
- hold_time = gs.get_hold_duration()
695
- timer_color = "red" if hold_time > 10 else "blue"
696
- st.markdown(f"⏱️ Hold time: **:{timer_color}[{hold_time:.1f}s]**")
697
-
698
- if gs.ui_mode == "LIST":
699
- # Candidate list view
700
- for i, c in enumerate(gs.candidates):
701
- pred = next((p for p in gs.predictions if p.move == c['move']), None)
702
-
703
- # Risk color
704
- if pred:
705
- if pred.risk_score < 0.3:
706
- risk_class = "risk-low"
707
- risk_label = "LOW"
708
- elif pred.risk_score < 0.6:
709
- risk_class = "risk-med"
710
- risk_label = "MED"
711
- else:
712
- risk_class = "risk-high"
713
- risk_label = "HIGH"
714
- else:
715
- risk_class = "risk-low"
716
- risk_label = "?"
717
-
718
- col_a, col_b, col_c = st.columns([3, 1, 1])
719
- with col_a:
720
- vh = c.get('v_head', [0,0,0])
721
- st.markdown(f"""
722
- **#{i+1} {c['move']}** ({c['prob']*100:.1f}%)
723
- W:{vh[2]:.2f} D:{vh[1]:.2f} B:{vh[0]:.2f}
724
- """)
725
- with col_b:
726
- st.markdown(f"<span class='{risk_class}'>{risk_label}</span>", unsafe_allow_html=True)
727
- with col_c:
728
- if st.button("DRILL", key=f"drill_{i}"):
729
- gs.selected_idx = i
730
- gs.ui_mode = "DRILL"
731
- st.rerun()
732
-
733
- # Threats
734
- if pred and pred.tactical_threats:
735
- for threat in pred.tactical_threats[:2]:
736
- st.markdown(f"<span class='threat-badge'>⚠️ {threat}</span>", unsafe_allow_html=True)
737
-
738
- st.divider()
739
-
740
- elif gs.ui_mode == "DRILL":
741
- # Drill view
742
- if st.button("← BACK TO LIST"):
743
- gs.ui_mode = "LIST"
744
- st.rerun()
745
-
746
- if gs.selected_idx < len(gs.candidates):
747
- c = gs.candidates[gs.selected_idx]
748
- pred = next((p for p in gs.predictions if p.move == c['move']), None)
749
-
750
- st.subheader(f"🔍 ANALYZING: {c['move']}")
751
-
752
- # Risk badge
753
- if pred:
754
- if pred.risk_score < 0.3:
755
- st.success(f"RISK: LOW ({pred.risk_score:.2f})")
756
- elif pred.risk_score < 0.6:
757
- st.warning(f"RISK: MEDIUM ({pred.risk_score:.2f})")
758
- else:
759
- st.error(f"RISK: HIGH ({pred.risk_score:.2f})")
760
-
761
- # Provenance block
762
- st.markdown(f"""
763
- <div class='metric-card'>
764
- <strong>CRYPTOGRAPHIC CHAIN</strong><br>
765
- <span class='merkle-hash'>{gs.merkle_chain[-1] if gs.merkle_chain else 'GENESIS'} → {gs.current_merkle}</span><br>
766
- Chain: {len(gs.merkle_chain)} blocks
767
- </div>
768
- """, unsafe_allow_html=True)
769
-
770
- # Metrics grid
771
- m1, m2, m3 = st.columns(3)
772
- with m1:
773
- st.metric("Confidence", f"{c['prob']*100:.2f}%")
774
- st.progress(c['prob'])
775
- with m2:
776
- vh = c.get('v_head', [0,0,0])
777
- st.markdown("**NNUE Value Head**")
778
- st.progress(vh[2], text=f"W: {vh[2]:.3f}")
779
- st.progress(vh[1], text=f"D: {vh[1]:.3f}")
780
- st.progress(vh[0], text=f"B: {vh[0]:.3f}")
781
- with m3:
782
- if pred:
783
- st.markdown("**CASCADE PREDICTION**")
784
- mat_color = "🟢" if pred.material_delta >= 0 else "🔴"
785
- pos_color = "🟢" if pred.positional_delta >= 0 else "🔴"
786
- st.markdown(f"Material: {mat_color} {pred.material_delta:+d}")
787
- st.markdown(f"Position: {pos_color} {pred.positional_delta:+.3f}")
788
- if pred.predicted_responses:
789
- st.markdown(f"Response: `{pred.predicted_responses[0]}`")
790
-
791
- # Reasoning
792
- st.markdown("**DECISION FORMULATION**")
793
- reason_text = c.get('reason', 'No reasoning')
794
- if pred and pred.tactical_threats:
795
- reason_text += f" | THREATS: {', '.join(pred.tactical_threats)}"
796
- st.info(reason_text)
797
-
798
- # Action buttons
799
- btn1, btn2 = st.columns(2)
800
- with btn1:
801
- if st.button(f"✅ EXECUTE: {c['move']}", type="primary", use_container_width=True):
802
- was_override = gs.selected_idx != 0
803
- gs.make_move(c['move'], "ChessBot" + (" (OVERRIDE)" if was_override else ""), was_override)
804
- gs.hold_active = False
805
- gs.ui_mode = "LIST"
806
- st.rerun()
807
- with btn2:
808
- st.caption("Or select another candidate to override")
809
-
810
- # ═══════════════════════════════════════════════════════════════════════════
811
- # CASCADE TAB
812
- # ═══════════════════════════════════════════════════════════════════════════
813
-
814
- with tab2:
815
- st.subheader("🔮 CASCADE PREDICTIONS // MINORITY REPORT")
816
-
817
- if not gs.predictions:
818
- st.info("Waiting for HOLD point to generate predictions...")
819
- else:
820
- for pred in gs.predictions[:6]:
821
- if pred.risk_score < 0.3:
822
- border_color = "#00ff96"
823
- elif pred.risk_score < 0.6:
824
- border_color = "#ff9632"
825
- else:
826
- border_color = "#ff4646"
827
-
828
- st.markdown(f"""
829
- <div style='border-left: 3px solid {border_color}; padding: 10px; margin: 10px 0; background: #2d2d3a;'>
830
- <strong>{pred.move}</strong> - Risk: {pred.risk_score:.2f}<br>
831
- Chain: {' → '.join(pred.predicted_responses[:3])}<br>
832
- Material: {pred.material_delta:+d} | Position: {pred.positional_delta:+.3f}<br>
833
- {('⚠️ ' + ', '.join(pred.tactical_threats)) if pred.tactical_threats else ''}
834
- {'🔴 INTERVENTION POINT' if pred.intervention_point else ''}
835
- </div>
836
- """, unsafe_allow_html=True)
837
-
838
- # ═══════════════════════════════════════════════════════════════════════════
839
- # METRICS TAB
840
- # ═══════════════════════════════════════════════════════════════════════════
841
-
842
- with tab3:
843
- st.subheader("📈 REAL-TIME METRICS ENGINE")
844
-
845
- m1, m2 = st.columns(2)
846
- with m1:
847
- st.metric("Total Holds", gs.session.total_holds)
848
- st.metric("Human Overrides", gs.session.human_overrides)
849
- st.metric("AI Accepted", gs.session.ai_accepted)
850
- st.metric("Override Rate", f"{gs.session.override_rate*100:.1f}%")
851
- with m2:
852
- st.metric("Current Combo", gs.session.current_combo)
853
- st.metric("Max Combo", gs.session.max_combo)
854
- st.metric("Avg Decision Time", f"{gs.session.avg_decision_time:.2f}s")
855
- st.metric("Speed Bonus", f"{gs.session.speed_bonus:.2f}")
856
-
857
- # Value history chart
858
- if gs.value_history:
859
- st.markdown("**VALUE HISTORY**")
860
- st.line_chart(gs.value_history)
861
-
862
- # Graph stats
863
- stats = gs.causation_graph.get_stats()
864
- st.markdown("**CAUSATION GRAPH**")
865
- for k, v in stats.items():
866
- st.markdown(f"- {k}: {v}")
867
-
868
- # ═══════════════════════════════════════════════════════════════════════════
869
- # CHAIN TAB
870
- # ═══════════════════════════════════════════════════════════════════════════
871
-
872
- with tab4:
873
- st.subheader("🔗 MERKLE PROVENANCE CHAIN")
874
-
875
- if not gs.causal_chain:
876
- st.info("Chain empty. Waiting for game events...")
877
- else:
878
- for node in gs.causal_chain[-12:]:
879
- override_style = "border-left: 3px solid #ffa500;" if node.was_override else ""
880
- val_color = "#00ff96" if node.value > 0 else "#ff4646" if node.value < 0 else "#888"
881
-
882
- st.markdown(f"""
883
- <div style='background: #2d2d3a; padding: 10px; margin: 5px 0; {override_style}'>
884
- <strong>#{node.depth}</strong> {node.move or 'START'} ({node.player})<br>
885
- <span class='merkle-hash'>{node.merkle}</span>
886
- <span style='color: {val_color}; float: right;'>v: {node.value:+.3f}</span>
887
- </div>
888
- """, unsafe_allow_html=True)
889
-
890
- # ═══════════════════════════════════════════════════════════════════════════════
891
- # AUTO MODE HANDLING
892
- # ═══════════════════════════════════════════════════════════════════════════════
893
-
894
- if gs.auto_mode and gs.hold_active and not gs.game_over:
895
- time.sleep(1.5)
896
- # Auto-execute top choice
897
- if gs.candidates:
898
- gs.make_move(gs.candidates[0]['move'], "ChessBot", False)
899
- gs.hold_active = False
900
-
901
- # Stockfish responds
902
- if not gs.game_over and gs.board.turn == chess.BLACK:
903
- sf_move = gs.get_stockfish_move()
904
- gs.make_move(sf_move, "Stockfish", False)
905
-
906
- st.rerun()
907
-
908
- # Footer
909
- st.divider()
910
- st.markdown("""
911
- <div style='text-align: center; color: #666;'>
912
- <small>CASCADE LATTICE CHESS // Powered by <a href='https://pypi.org/project/cascade-lattice/'>cascade-lattice</a> + <a href='https://huggingface.co/Maxlegrec/ChessBot'>ChessBot</a></small>
913
- </div>
914
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main.py CHANGED
@@ -45,7 +45,7 @@ COLORS = {
45
 
46
  # Initialize Pygame
47
  pygame.init()
48
- pygame.display.set_caption("CASCADE LATTICE CHESS // SUPER SAIYAN MODE")
49
  screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
50
  font = pygame.font.SysFont("Consolas", 14)
51
  font_mono = pygame.font.SysFont("Consolas", 12)
@@ -55,7 +55,7 @@ title_font = pygame.font.SysFont("Consolas", 24, bold=True)
55
  header_font = pygame.font.SysFont("Consolas", 16, bold=True)
56
 
57
  # ═══════════════════════════════════════════════════════════════════════════════
58
- # CASCADE LATTICE SUPER SYSTEMS
59
  # ═══════════════════════════════════════════════════════════════════════════════
60
 
61
  @dataclass
@@ -108,10 +108,10 @@ class SessionStats:
108
  avg_decision_time: float = 0.0
109
  decision_times: List[float] = field(default_factory=list)
110
 
111
- class CascadeSuperSystem:
112
  """
113
- The SUPER SAIYAN integration layer.
114
- Connects all cascade-lattice subsystems for maximum informational wealth.
115
  """
116
  def __init__(self):
117
  # Core cascade systems
@@ -410,8 +410,8 @@ class CascadeSuperSystem:
410
  return self.merkle_chain[-n:] if self.merkle_chain else []
411
 
412
 
413
- # Global super system instance
414
- super_system: Optional[CascadeSuperSystem] = None
415
 
416
  def draw_text_wrapped(surf, text, font, color, rect, line_height=None):
417
  """Draw wrapped text within a rectangle"""
@@ -476,7 +476,7 @@ def draw_sparkline(surf, rect, values, color, max_val=None):
476
 
477
  class ChessGUI:
478
  """
479
- SUPER SAIYAN GUI - Maximum Informational Wealth Display
480
  """
481
  def __init__(self):
482
  self.board = chess.Board()
@@ -490,11 +490,11 @@ class ChessGUI:
490
  self.auto_mode = False
491
  self.game_result = None
492
 
493
- # Super system reference
494
- global super_system
495
- if super_system is None:
496
- super_system = CascadeSuperSystem()
497
- self.super_system = super_system
498
 
499
  # Visual state
500
  self.pulse_phase = 0
@@ -516,7 +516,7 @@ class ChessGUI:
516
  def on_hold(self, hold_point):
517
  self.hold_point = hold_point
518
  self.selected_move_idx = 0
519
- self.super_system.start_hold_timer()
520
 
521
  if self.auto_mode:
522
  self.ui_mode = "DRILL"
@@ -528,11 +528,11 @@ class ChessGUI:
528
  if hasattr(hold_point, 'observation'):
529
  obs = hold_point.observation
530
  candidates = obs.get('candidates', [])
531
- self.super_system.predict_cascade(self.board, candidates)
532
 
533
  # Check for anomalies
534
  if candidates:
535
- self.super_system.check_anomalies({
536
  'confidence': candidates[0]['prob'],
537
  'value': candidates[0].get('v_head', [0,0,0])[2] - candidates[0].get('v_head', [0,0,0])[0]
538
  })
@@ -619,7 +619,7 @@ class ChessGUI:
619
  pygame.draw.circle(screen, (255, 255, 255), (x, y), 6, 1)
620
 
621
  def draw_panel(self):
622
- """SUPER SAIYAN PANEL - Full analytical wealth display"""
623
  # Main panel background
624
  panel_rect = pygame.Rect(BOARD_SIZE, 0, PANEL_SIZE, WINDOW_HEIGHT)
625
  pygame.draw.rect(screen, COLORS['bg_panel'], panel_rect)
@@ -642,7 +642,7 @@ class ChessGUI:
642
  screen.blit(title, (BOARD_SIZE + 15, y))
643
 
644
  # Session stats bar (top right)
645
- stats = self.super_system.session
646
  stats_x = BOARD_SIZE + PANEL_SIZE - 320
647
 
648
  # Combo display with glow
@@ -776,7 +776,7 @@ class ChessGUI:
776
  x = content_rect.x + 10
777
 
778
  # Hold timer
779
- hold_duration = self.super_system.get_hold_duration()
780
  timer_color = COLORS['accent_red'] if hold_duration > 10 else COLORS['accent_cyan']
781
  timer_text = font.render(f"HOLD TIME: {hold_duration:.1f}s", True, timer_color)
782
  screen.blit(timer_text, (content_rect.right - 130, y))
@@ -788,7 +788,7 @@ class ChessGUI:
788
 
789
  obs = getattr(self.hold_point, 'observation', {})
790
  candidates = obs.get('candidates', [])
791
- predictions = self.super_system.predictions
792
 
793
  # Header row
794
  headers = ["RK", "MOVE", "CONF", "VALUE", "RISK", "THREATS"]
@@ -869,7 +869,7 @@ class ChessGUI:
869
  return
870
 
871
  c = candidates[self.selected_move_idx]
872
- predictions = self.super_system.predictions
873
  pred = next((p for p in predictions if p.move == c['move']), None)
874
 
875
  # Back button
@@ -909,9 +909,9 @@ class ChessGUI:
909
  screen.blit(chain_text, (x + 8, y + 22))
910
 
911
  # Chain integrity indicator
912
- chain_ok = len(self.super_system.merkle_chain) > 0
913
  integrity_color = COLORS['accent_green'] if chain_ok else COLORS['accent_red']
914
- integrity_text = font_small.render(f"CHAIN: {len(self.super_system.merkle_chain)} BLOCKS", True, integrity_color)
915
  screen.blit(integrity_text, (prov_rect.right - 120, y + 15))
916
 
917
  y += 60
@@ -1012,7 +1012,7 @@ class ChessGUI:
1012
  screen.blit(header_font.render("CASCADE PREDICTIONS // MINORITY REPORT", True, COLORS['accent_purple']), (x, y))
1013
  y += 30
1014
 
1015
- predictions = self.super_system.predictions
1016
  if not predictions:
1017
  screen.blit(font.render("No predictions available. Waiting for HOLD point...", True, COLORS['text_muted']), (x, y))
1018
  return
@@ -1070,7 +1070,7 @@ class ChessGUI:
1070
  screen.blit(header_font.render("REAL-TIME METRICS ENGINE", True, COLORS['accent_cyan']), (x, y))
1071
  y += 30
1072
 
1073
- stats = self.super_system.session
1074
 
1075
  # Session metrics grid
1076
  metrics = [
@@ -1108,7 +1108,7 @@ class ChessGUI:
1108
  y += 75
1109
 
1110
  # Graph stats
1111
- graph_stats = self.super_system.get_graph_stats()
1112
  screen.blit(font.render("CAUSATION GRAPH", True, COLORS['text_secondary']), (x, y))
1113
  y += 20
1114
  for key, val in list(graph_stats.items())[:4]:
@@ -1123,7 +1123,7 @@ class ChessGUI:
1123
  screen.blit(header_font.render("MERKLE PROVENANCE CHAIN", True, COLORS['merkle_glow']), (x, y))
1124
  y += 30
1125
 
1126
- chain = self.super_system.causal_chain
1127
  if not chain:
1128
  screen.blit(font.render("Chain empty. Waiting for game events...", True, COLORS['text_muted']), (x, y))
1129
  return
@@ -1173,7 +1173,7 @@ class ChessGUI:
1173
 
1174
  def _draw_anomaly_alerts(self, content_rect):
1175
  """Draw anomaly alerts at bottom of content area"""
1176
- anomalies = list(self.super_system.anomalies)[-3:]
1177
  if not anomalies:
1178
  return
1179
 
@@ -1223,7 +1223,7 @@ class ChessGUI:
1223
  self.draw_board()
1224
 
1225
  # Get prediction for this move
1226
- pred = next((p for p in self.super_system.predictions if p.move == selected_move_uci), None)
1227
 
1228
  # ANCHOR MOVE - Gold with glow
1229
  self.draw_arrow(move.from_square, move.to_square,
@@ -1306,7 +1306,7 @@ class ChessGUI:
1306
  screen.blit(label, (x + 25, y))
1307
 
1308
  # Hold timer
1309
- hold_time = self.super_system.get_hold_duration()
1310
  timer_text = font.render(f"{hold_time:.1f}s", True, COLORS['text_muted'])
1311
  screen.blit(timer_text, (x + 100, y))
1312
  else:
@@ -1314,12 +1314,12 @@ class ChessGUI:
1314
  screen.blit(status_text, (x, y))
1315
 
1316
  # Move counter
1317
- move_count = len(self.super_system.move_history)
1318
  moves_text = font.render(f"Moves: {move_count}", True, COLORS['text_secondary'])
1319
  screen.blit(moves_text, (x + 180, y))
1320
 
1321
  # Merkle chain length
1322
- chain_len = len(self.super_system.merkle_chain)
1323
  chain_text = font.render(f"Chain: {chain_len}", True, COLORS['merkle_glow'])
1324
  screen.blit(chain_text, (x + 280, y))
1325
 
@@ -1422,10 +1422,10 @@ class ChessGUI:
1422
  selected_move = candidates[self.selected_move_idx]['move']
1423
  ai_move = obs.get('suggested_move', candidates[0]['move'])
1424
  was_override = selected_move != ai_move
1425
- decision_time = self.super_system.get_hold_duration()
1426
 
1427
- # Record the decision in super system
1428
- self.super_system.record_decision(
1429
  was_override=was_override,
1430
  decision_time=decision_time,
1431
  ai_move=ai_move,
@@ -1434,7 +1434,7 @@ class ChessGUI:
1434
  )
1435
 
1436
  # Register event in causation graph
1437
- self.super_system.register_event({
1438
  'type': 'hold_resolution',
1439
  'move': selected_move,
1440
  'player': 'ChessBot' + (' (OVERRIDE)' if was_override else ''),
@@ -1453,7 +1453,7 @@ class ChessGUI:
1453
 
1454
  class ChessModelPlayer:
1455
  """
1456
- SUPER SAIYAN Model Player - Full cascade integration
1457
  """
1458
  def __init__(self, model_name, gui=None):
1459
  print(f"Loading chess model {model_name}...")
@@ -1469,16 +1469,16 @@ class ChessModelPlayer:
1469
  self.hold = cascade.Hold.get()
1470
  self.hold.timeout = 3600 # 1 hour for deep observation
1471
 
1472
- # Super system reference
1473
- global super_system
1474
- if super_system is None:
1475
- super_system = CascadeSuperSystem()
1476
- self.super_system = super_system
1477
 
1478
- print(f"[SUPER SAIYAN] Cascade systems initialized")
1479
- print(f"[SUPER SAIYAN] CausationGraph: ACTIVE")
1480
- print(f"[SUPER SAIYAN] MetricsEngine: ACTIVE")
1481
- print(f"[SUPER SAIYAN] Tracer: ACTIVE")
1482
 
1483
  def get_move(self, board):
1484
  fen = board.fen()
@@ -1539,7 +1539,7 @@ class ChessModelPlayer:
1539
  "draw": float(pos_eval[1]),
1540
  "black_win": float(pos_eval[0])
1541
  },
1542
- "move_number": len(self.super_system.move_history) + 1,
1543
  "legal_move_count": len(legal_moves)
1544
  },
1545
  brain_id=MODEL_NAME,
@@ -1678,7 +1678,7 @@ class ChessModelPlayer:
1678
  self.monitor.observe(obs_data)
1679
 
1680
  # Register in causation graph
1681
- self.super_system.register_event({
1682
  'type': 'move',
1683
  'move': move.uci(),
1684
  'player': player_name,
@@ -1691,18 +1691,18 @@ class ChessModelPlayer:
1691
 
1692
  def play_game():
1693
  """
1694
- SUPER SAIYAN GAME LOOP
1695
  Full cascade-lattice integration with all subsystems active
1696
  """
1697
  print("=" * 60)
1698
- print(" CASCADE LATTICE CHESS // SUPER SAIYAN MODE")
1699
  print("=" * 60)
1700
  print()
1701
  print("Systems Initializing...")
1702
 
1703
- # Initialize super system first
1704
- global super_system
1705
- super_system = CascadeSuperSystem()
1706
 
1707
  gui = ChessGUI()
1708
  chess_player = ChessModelPlayer(MODEL_NAME, gui)
@@ -1724,7 +1724,7 @@ def play_game():
1724
  move_count = 0
1725
 
1726
  # Register game start event
1727
- super_system.register_event({
1728
  'type': 'game_start',
1729
  'move': '',
1730
  'player': 'SYSTEM',
@@ -1765,7 +1765,7 @@ def play_game():
1765
  gui.game_result = msg
1766
 
1767
  # Register game end event
1768
- super_system.register_event({
1769
  'type': 'game_end',
1770
  'move': '',
1771
  'player': 'SYSTEM',
@@ -1780,14 +1780,14 @@ def play_game():
1780
  print("=" * 60)
1781
  print()
1782
  print("Session Statistics:")
1783
- stats = super_system.session
1784
  print(f" Total Holds: {stats.total_holds}")
1785
  print(f" Human Overrides: {stats.human_overrides}")
1786
  print(f" Override Rate: {stats.override_rate*100:.1f}%")
1787
  print(f" Max Combo: {stats.max_combo}")
1788
  print(f" Avg Decision Time: {stats.avg_decision_time:.2f}s")
1789
- print(f" Causation Chain Length: {len(super_system.causal_chain)}")
1790
- print(f" Merkle Blocks: {len(super_system.merkle_chain)}")
1791
 
1792
  print("[SYSTEM] Game loop finished")
1793
 
@@ -1810,3 +1810,4 @@ def play_game():
1810
 
1811
  if __name__ == "__main__":
1812
  play_game()
 
 
45
 
46
  # Initialize Pygame
47
  pygame.init()
48
+ pygame.display.set_caption("CASCADE LATTICE CHESS")
49
  screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
50
  font = pygame.font.SysFont("Consolas", 14)
51
  font_mono = pygame.font.SysFont("Consolas", 12)
 
55
  header_font = pygame.font.SysFont("Consolas", 16, bold=True)
56
 
57
  # ═══════════════════════════════════════════════════════════════════════════════
58
+ # CASCADE LATTICE SYSTEMS
59
  # ═══════════════════════════════════════════════════════════════════════════════
60
 
61
  @dataclass
 
108
  avg_decision_time: float = 0.0
109
  decision_times: List[float] = field(default_factory=list)
110
 
111
+ class CascadeSystem:
112
  """
113
+ The main integration layer.
114
+ Connects all cascade-lattice subsystems for informational wealth.
115
  """
116
  def __init__(self):
117
  # Core cascade systems
 
410
  return self.merkle_chain[-n:] if self.merkle_chain else []
411
 
412
 
413
+ # Global cascade system instance
414
+ cascade_system: Optional[CascadeSystem] = None
415
 
416
  def draw_text_wrapped(surf, text, font, color, rect, line_height=None):
417
  """Draw wrapped text within a rectangle"""
 
476
 
477
  class ChessGUI:
478
  """
479
+ Chess GUI - Informational Wealth Display
480
  """
481
  def __init__(self):
482
  self.board = chess.Board()
 
490
  self.auto_mode = False
491
  self.game_result = None
492
 
493
+ # Cascade system reference
494
+ global cascade_system
495
+ if cascade_system is None:
496
+ cascade_system = CascadeSystem()
497
+ self.cascade_system = cascade_system
498
 
499
  # Visual state
500
  self.pulse_phase = 0
 
516
  def on_hold(self, hold_point):
517
  self.hold_point = hold_point
518
  self.selected_move_idx = 0
519
+ self.cascade_system.start_hold_timer()
520
 
521
  if self.auto_mode:
522
  self.ui_mode = "DRILL"
 
528
  if hasattr(hold_point, 'observation'):
529
  obs = hold_point.observation
530
  candidates = obs.get('candidates', [])
531
+ self.cascade_system.predict_cascade(self.board, candidates)
532
 
533
  # Check for anomalies
534
  if candidates:
535
+ self.cascade_system.check_anomalies({
536
  'confidence': candidates[0]['prob'],
537
  'value': candidates[0].get('v_head', [0,0,0])[2] - candidates[0].get('v_head', [0,0,0])[0]
538
  })
 
619
  pygame.draw.circle(screen, (255, 255, 255), (x, y), 6, 1)
620
 
621
  def draw_panel(self):
622
+ """Main panel - Full analytical wealth display"""
623
  # Main panel background
624
  panel_rect = pygame.Rect(BOARD_SIZE, 0, PANEL_SIZE, WINDOW_HEIGHT)
625
  pygame.draw.rect(screen, COLORS['bg_panel'], panel_rect)
 
642
  screen.blit(title, (BOARD_SIZE + 15, y))
643
 
644
  # Session stats bar (top right)
645
+ stats = self.cascade_system.session
646
  stats_x = BOARD_SIZE + PANEL_SIZE - 320
647
 
648
  # Combo display with glow
 
776
  x = content_rect.x + 10
777
 
778
  # Hold timer
779
+ hold_duration = self.cascade_system.get_hold_duration()
780
  timer_color = COLORS['accent_red'] if hold_duration > 10 else COLORS['accent_cyan']
781
  timer_text = font.render(f"HOLD TIME: {hold_duration:.1f}s", True, timer_color)
782
  screen.blit(timer_text, (content_rect.right - 130, y))
 
788
 
789
  obs = getattr(self.hold_point, 'observation', {})
790
  candidates = obs.get('candidates', [])
791
+ predictions = self.cascade_system.predictions
792
 
793
  # Header row
794
  headers = ["RK", "MOVE", "CONF", "VALUE", "RISK", "THREATS"]
 
869
  return
870
 
871
  c = candidates[self.selected_move_idx]
872
+ predictions = self.cascade_system.predictions
873
  pred = next((p for p in predictions if p.move == c['move']), None)
874
 
875
  # Back button
 
909
  screen.blit(chain_text, (x + 8, y + 22))
910
 
911
  # Chain integrity indicator
912
+ chain_ok = len(self.cascade_system.merkle_chain) > 0
913
  integrity_color = COLORS['accent_green'] if chain_ok else COLORS['accent_red']
914
+ integrity_text = font_small.render(f"CHAIN: {len(self.cascade_system.merkle_chain)} BLOCKS", True, integrity_color)
915
  screen.blit(integrity_text, (prov_rect.right - 120, y + 15))
916
 
917
  y += 60
 
1012
  screen.blit(header_font.render("CASCADE PREDICTIONS // MINORITY REPORT", True, COLORS['accent_purple']), (x, y))
1013
  y += 30
1014
 
1015
+ predictions = self.cascade_system.predictions
1016
  if not predictions:
1017
  screen.blit(font.render("No predictions available. Waiting for HOLD point...", True, COLORS['text_muted']), (x, y))
1018
  return
 
1070
  screen.blit(header_font.render("REAL-TIME METRICS ENGINE", True, COLORS['accent_cyan']), (x, y))
1071
  y += 30
1072
 
1073
+ stats = self.cascade_system.session
1074
 
1075
  # Session metrics grid
1076
  metrics = [
 
1108
  y += 75
1109
 
1110
  # Graph stats
1111
+ graph_stats = self.cascade_system.get_graph_stats()
1112
  screen.blit(font.render("CAUSATION GRAPH", True, COLORS['text_secondary']), (x, y))
1113
  y += 20
1114
  for key, val in list(graph_stats.items())[:4]:
 
1123
  screen.blit(header_font.render("MERKLE PROVENANCE CHAIN", True, COLORS['merkle_glow']), (x, y))
1124
  y += 30
1125
 
1126
+ chain = self.cascade_system.causal_chain
1127
  if not chain:
1128
  screen.blit(font.render("Chain empty. Waiting for game events...", True, COLORS['text_muted']), (x, y))
1129
  return
 
1173
 
1174
  def _draw_anomaly_alerts(self, content_rect):
1175
  """Draw anomaly alerts at bottom of content area"""
1176
+ anomalies = list(self.cascade_system.anomalies)[-3:]
1177
  if not anomalies:
1178
  return
1179
 
 
1223
  self.draw_board()
1224
 
1225
  # Get prediction for this move
1226
+ pred = next((p for p in self.cascade_system.predictions if p.move == selected_move_uci), None)
1227
 
1228
  # ANCHOR MOVE - Gold with glow
1229
  self.draw_arrow(move.from_square, move.to_square,
 
1306
  screen.blit(label, (x + 25, y))
1307
 
1308
  # Hold timer
1309
+ hold_time = self.cascade_system.get_hold_duration()
1310
  timer_text = font.render(f"{hold_time:.1f}s", True, COLORS['text_muted'])
1311
  screen.blit(timer_text, (x + 100, y))
1312
  else:
 
1314
  screen.blit(status_text, (x, y))
1315
 
1316
  # Move counter
1317
+ move_count = len(self.cascade_system.move_history)
1318
  moves_text = font.render(f"Moves: {move_count}", True, COLORS['text_secondary'])
1319
  screen.blit(moves_text, (x + 180, y))
1320
 
1321
  # Merkle chain length
1322
+ chain_len = len(self.cascade_system.merkle_chain)
1323
  chain_text = font.render(f"Chain: {chain_len}", True, COLORS['merkle_glow'])
1324
  screen.blit(chain_text, (x + 280, y))
1325
 
 
1422
  selected_move = candidates[self.selected_move_idx]['move']
1423
  ai_move = obs.get('suggested_move', candidates[0]['move'])
1424
  was_override = selected_move != ai_move
1425
+ decision_time = self.cascade_system.get_hold_duration()
1426
 
1427
+ # Record the decision in cascade system
1428
+ self.cascade_system.record_decision(
1429
  was_override=was_override,
1430
  decision_time=decision_time,
1431
  ai_move=ai_move,
 
1434
  )
1435
 
1436
  # Register event in causation graph
1437
+ self.cascade_system.register_event({
1438
  'type': 'hold_resolution',
1439
  'move': selected_move,
1440
  'player': 'ChessBot' + (' (OVERRIDE)' if was_override else ''),
 
1453
 
1454
  class ChessModelPlayer:
1455
  """
1456
+ CASCADE Model Player - Full cascade integration
1457
  """
1458
  def __init__(self, model_name, gui=None):
1459
  print(f"Loading chess model {model_name}...")
 
1469
  self.hold = cascade.Hold.get()
1470
  self.hold.timeout = 3600 # 1 hour for deep observation
1471
 
1472
+ # Cascade system reference
1473
+ global cascade_system
1474
+ if cascade_system is None:
1475
+ cascade_system = CascadeSystem()
1476
+ self.cascade_system = cascade_system
1477
 
1478
+ print(f"[CASCADE] Cascade systems initialized")
1479
+ print(f"[CASCADE] CausationGraph: ACTIVE")
1480
+ print(f"[CASCADE] MetricsEngine: ACTIVE")
1481
+ print(f"[CASCADE] Tracer: ACTIVE")
1482
 
1483
  def get_move(self, board):
1484
  fen = board.fen()
 
1539
  "draw": float(pos_eval[1]),
1540
  "black_win": float(pos_eval[0])
1541
  },
1542
+ "move_number": len(self.cascade_system.move_history) + 1,
1543
  "legal_move_count": len(legal_moves)
1544
  },
1545
  brain_id=MODEL_NAME,
 
1678
  self.monitor.observe(obs_data)
1679
 
1680
  # Register in causation graph
1681
+ self.cascade_system.register_event({
1682
  'type': 'move',
1683
  'move': move.uci(),
1684
  'player': player_name,
 
1691
 
1692
  def play_game():
1693
  """
1694
+ CASCADE GAME LOOP
1695
  Full cascade-lattice integration with all subsystems active
1696
  """
1697
  print("=" * 60)
1698
+ print(" CASCADE LATTICE CHESS")
1699
  print("=" * 60)
1700
  print()
1701
  print("Systems Initializing...")
1702
 
1703
+ # Initialize cascade system first
1704
+ global cascade_system
1705
+ cascade_system = CascadeSystem()
1706
 
1707
  gui = ChessGUI()
1708
  chess_player = ChessModelPlayer(MODEL_NAME, gui)
 
1724
  move_count = 0
1725
 
1726
  # Register game start event
1727
+ cascade_system.register_event({
1728
  'type': 'game_start',
1729
  'move': '',
1730
  'player': 'SYSTEM',
 
1765
  gui.game_result = msg
1766
 
1767
  # Register game end event
1768
+ cascade_system.register_event({
1769
  'type': 'game_end',
1770
  'move': '',
1771
  'player': 'SYSTEM',
 
1780
  print("=" * 60)
1781
  print()
1782
  print("Session Statistics:")
1783
+ stats = cascade_system.session
1784
  print(f" Total Holds: {stats.total_holds}")
1785
  print(f" Human Overrides: {stats.human_overrides}")
1786
  print(f" Override Rate: {stats.override_rate*100:.1f}%")
1787
  print(f" Max Combo: {stats.max_combo}")
1788
  print(f" Avg Decision Time: {stats.avg_decision_time:.2f}s")
1789
+ print(f" Causation Chain Length: {len(cascade_system.causal_chain)}")
1790
+ print(f" Merkle Blocks: {len(cascade_system.merkle_chain)}")
1791
 
1792
  print("[SYSTEM] Game loop finished")
1793
 
 
1810
 
1811
  if __name__ == "__main__":
1812
  play_game()
1813
+
requirements.txt CHANGED
@@ -1,7 +1,4 @@
1
- streamlit
2
- chess
3
- torch
4
- transformers
5
- numpy
6
- Pillow
7
- cascade-lattice>=0.5.0
 
1
+ streamlit>=1.28.0
2
+ chess>=1.10.0
3
+ numpy>=1.24.0
4
+ Pillow>=10.0.0
 
 
 
src/streamlit_app.py ADDED
@@ -0,0 +1,1314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import chess
3
+ import chess.svg
4
+ import chess.engine
5
+ import os
6
+ import random
7
+ import time
8
+ import math
9
+ import hashlib
10
+ from collections import deque
11
+ from dataclasses import dataclass, field
12
+ from typing import List, Dict, Any, Optional, Tuple
13
+
14
+ # Try to import torch/transformers for model support
15
+ try:
16
+ import torch
17
+ from transformers import AutoModel
18
+ TORCH_AVAILABLE = True
19
+ except ImportError:
20
+ TORCH_AVAILABLE = False
21
+
22
+ # Configuration
23
+ MODEL_NAME = "Maxlegrec/ChessBot"
24
+ STOCKFISH_PATH = "/usr/games/stockfish"
25
+
26
+ # Color Palette - Industrial Neon (for CSS)
27
+ COLORS = {
28
+ 'bg_dark': '#121216',
29
+ 'bg_panel': '#19191e',
30
+ 'bg_card': '#23232d',
31
+ 'bg_card_hover': '#2d3241',
32
+ 'accent_cyan': '#00c8ff',
33
+ 'accent_green': '#00ff96',
34
+ 'accent_gold': '#ffd700',
35
+ 'accent_red': '#ff4646',
36
+ 'accent_purple': '#b464ff',
37
+ 'accent_orange': '#ff9632',
38
+ 'text_primary': '#ffffff',
39
+ 'text_secondary': '#b4b4b4',
40
+ 'text_muted': '#787878',
41
+ 'merkle_glow': '#64ffda',
42
+ 'anomaly_pulse': '#ff3264',
43
+ 'combo_gold': '#ffc832',
44
+ 'prediction_blue': '#3296ff',
45
+ }
46
+
47
+ # ═══════════════════════════════════════════════════════════════════════════════
48
+ # CASCADE LATTICE SYSTEMS
49
+ # ═══════════════════════════════════════════════════════════════════════════════
50
+
51
+ @dataclass
52
+ class CausalNode:
53
+ """A node in the causal chain visualization"""
54
+ event_id: str
55
+ move: str
56
+ player: str
57
+ fen: str
58
+ timestamp: float
59
+ merkle: str
60
+ value: float
61
+ was_override: bool = False
62
+ children: List[str] = field(default_factory=list)
63
+ depth: int = 0
64
+
65
+ @dataclass
66
+ class PredictionRisk:
67
+ """Cascade prediction with risk assessment"""
68
+ move: str
69
+ risk_score: float
70
+ predicted_responses: List[str]
71
+ material_delta: int
72
+ positional_delta: float
73
+ tactical_threats: List[str]
74
+ intervention_point: bool = False
75
+
76
+ @dataclass
77
+ class AnomalyAlert:
78
+ """Real-time anomaly detection"""
79
+ timestamp: float
80
+ metric_name: str
81
+ expected_value: float
82
+ actual_value: float
83
+ deviation_sigma: float
84
+ severity: str
85
+ message: str
86
+
87
+ @dataclass
88
+ class SessionStats:
89
+ """Session tracking"""
90
+ total_holds: int = 0
91
+ human_overrides: int = 0
92
+ ai_accepted: int = 0
93
+ current_combo: int = 0
94
+ max_combo: int = 0
95
+ correct_predictions: int = 0
96
+ speed_bonus: float = 0.0
97
+ override_rate: float = 0.0
98
+ avg_decision_time: float = 0.0
99
+ decision_times: List[float] = field(default_factory=list)
100
+
101
+
102
+ class CascadeSystem:
103
+ """
104
+ The main integration layer.
105
+ Connects all cascade-lattice subsystems for informational wealth.
106
+ """
107
+ def __init__(self):
108
+ self.causal_chain: List[CausalNode] = []
109
+ self.predictions: List[PredictionRisk] = []
110
+ self.anomalies: deque = deque(maxlen=50)
111
+ self.session = SessionStats()
112
+ self.move_history: List[Dict] = []
113
+ self.merkle_chain: List[str] = []
114
+ self.hold_start_time: Optional[float] = None
115
+ self.last_event_id: Optional[str] = None
116
+ self.override_patterns: Dict[str, int] = {}
117
+ self.human_preferences: Dict[str, float] = {}
118
+
119
+ def register_event(self, event_data: Dict) -> str:
120
+ """Register an event and track causal chain."""
121
+ event_id = f"evt_{len(self.causal_chain)}_{int(time.time()*1000)}"
122
+
123
+ node = CausalNode(
124
+ event_id=event_id,
125
+ move=event_data.get('move', ''),
126
+ player=event_data.get('player', ''),
127
+ fen=event_data.get('fen', ''),
128
+ timestamp=time.time(),
129
+ merkle=event_data.get('merkle', self._compute_merkle(event_data)),
130
+ value=event_data.get('value', 0.0),
131
+ was_override=event_data.get('was_override', False),
132
+ depth=len(self.causal_chain)
133
+ )
134
+
135
+ if self.last_event_id and self.causal_chain:
136
+ self.causal_chain[-1].children.append(event_id)
137
+
138
+ self.causal_chain.append(node)
139
+ self.merkle_chain.append(node.merkle)
140
+ self.last_event_id = event_id
141
+
142
+ return event_id
143
+
144
+ def _compute_merkle(self, data: Dict) -> str:
145
+ """Compute merkle hash for data"""
146
+ content = str(sorted(data.items())).encode()
147
+ return hashlib.sha256(content).hexdigest()[:16]
148
+
149
+ def predict_cascade(self, board: chess.Board, candidate_moves: List[Dict]) -> List[PredictionRisk]:
150
+ """Predict consequences before they happen."""
151
+ predictions = []
152
+
153
+ for candidate in candidate_moves:
154
+ move_uci = candidate['move']
155
+ try:
156
+ move = chess.Move.from_uci(move_uci)
157
+ if move not in board.legal_moves:
158
+ continue
159
+
160
+ temp_board = board.copy()
161
+ material_before = self._count_material(temp_board)
162
+ temp_board.push(move)
163
+ material_after = self._count_material(temp_board)
164
+ material_delta = material_after - material_before
165
+
166
+ opponent_moves = list(temp_board.legal_moves)[:5]
167
+ predicted_responses = [m.uci() for m in opponent_moves]
168
+ threats = self._detect_threats(temp_board, move)
169
+ risk_score = self._calculate_risk(temp_board, move, material_delta, threats, candidate['prob'])
170
+ is_intervention = risk_score > 0.7 or len(threats) > 2
171
+
172
+ predictions.append(PredictionRisk(
173
+ move=move_uci,
174
+ risk_score=risk_score,
175
+ predicted_responses=predicted_responses,
176
+ material_delta=material_delta,
177
+ positional_delta=candidate.get('v_head', [0,0,0])[2] - candidate.get('v_head', [0,0,0])[0],
178
+ tactical_threats=threats,
179
+ intervention_point=is_intervention
180
+ ))
181
+ except Exception as e:
182
+ pass
183
+
184
+ self.predictions = predictions
185
+ return predictions
186
+
187
+ def _count_material(self, board: chess.Board) -> int:
188
+ """Count material advantage for white"""
189
+ values = {chess.PAWN: 1, chess.KNIGHT: 3, chess.BISHOP: 3,
190
+ chess.ROOK: 5, chess.QUEEN: 9, chess.KING: 0}
191
+ score = 0
192
+ for square in chess.SQUARES:
193
+ piece = board.piece_at(square)
194
+ if piece:
195
+ val = values.get(piece.piece_type, 0)
196
+ score += val if piece.color == chess.WHITE else -val
197
+ return score
198
+
199
+ def _detect_threats(self, board: chess.Board, last_move: chess.Move) -> List[str]:
200
+ """Detect tactical threats in position"""
201
+ threats = []
202
+ if board.is_check():
203
+ threats.append("CHECK")
204
+ for square in chess.SQUARES:
205
+ piece = board.piece_at(square)
206
+ if piece and piece.color == board.turn:
207
+ attackers = board.attackers(not board.turn, square)
208
+ defenders = board.attackers(board.turn, square)
209
+ if len(attackers) > len(defenders):
210
+ threats.append(f"HANGING_{chess.square_name(square).upper()}")
211
+ if len(threats) > 1:
212
+ threats.append("FORK_THREAT")
213
+ back_rank = 0 if board.turn == chess.WHITE else 7
214
+ king_sq = board.king(board.turn)
215
+ if king_sq and chess.square_rank(king_sq) == back_rank:
216
+ threats.append("BACK_RANK_WEAK")
217
+ return threats[:5]
218
+
219
+ def _calculate_risk(self, board: chess.Board, move: chess.Move,
220
+ material_delta: int, threats: List[str], confidence: float) -> float:
221
+ """Calculate overall risk score for a move"""
222
+ risk = 0.0
223
+ if material_delta < 0:
224
+ risk += min(0.3, abs(material_delta) * 0.1)
225
+ risk += len(threats) * 0.1
226
+ risk += (1 - confidence) * 0.3
227
+ if board.is_checkmate():
228
+ risk = 0.0
229
+ elif board.is_check():
230
+ risk += 0.1
231
+ return min(1.0, max(0.0, risk))
232
+
233
+ def check_anomalies(self, current_data: Dict) -> List[AnomalyAlert]:
234
+ """Check for anomalies in current decision data"""
235
+ new_anomalies = []
236
+ if 'confidence' in current_data:
237
+ conf = current_data['confidence']
238
+ if conf < 0.3:
239
+ alert = AnomalyAlert(
240
+ timestamp=time.time(),
241
+ metric_name="confidence",
242
+ expected_value=0.7,
243
+ actual_value=conf,
244
+ deviation_sigma=2.5,
245
+ severity="WARNING",
246
+ message=f"Low AI confidence: {conf*100:.1f}%"
247
+ )
248
+ new_anomalies.append(alert)
249
+ self.anomalies.append(alert)
250
+ if 'value' in current_data and len(self.move_history) > 0:
251
+ prev_value = self.move_history[-1].get('value', 0)
252
+ curr_value = current_data['value']
253
+ delta = abs(curr_value - prev_value)
254
+ if delta > 0.4:
255
+ alert = AnomalyAlert(
256
+ timestamp=time.time(),
257
+ metric_name="value_swing",
258
+ expected_value=prev_value,
259
+ actual_value=curr_value,
260
+ deviation_sigma=delta * 5,
261
+ severity="CRITICAL" if delta > 0.6 else "WARNING",
262
+ message=f"Value swing: {delta*100:.1f}% shift detected"
263
+ )
264
+ new_anomalies.append(alert)
265
+ self.anomalies.append(alert)
266
+ return new_anomalies
267
+
268
+ def record_decision(self, was_override: bool, decision_time: float,
269
+ ai_move: str, final_move: str, fen: str):
270
+ """Record decision for session stats and pattern learning"""
271
+ self.session.total_holds += 1
272
+ self.session.decision_times.append(decision_time)
273
+ self.session.avg_decision_time = sum(self.session.decision_times) / len(self.session.decision_times)
274
+
275
+ if was_override:
276
+ self.session.human_overrides += 1
277
+ self.session.current_combo = 0
278
+ fen_hash = hashlib.md5(fen.encode()).hexdigest()[:8]
279
+ self.override_patterns[fen_hash] = self.override_patterns.get(fen_hash, 0) + 1
280
+ else:
281
+ self.session.ai_accepted += 1
282
+ self.session.current_combo += 1
283
+ self.session.max_combo = max(self.session.max_combo, self.session.current_combo)
284
+ if decision_time < 2.0:
285
+ self.session.speed_bonus += 0.1
286
+
287
+ self.session.override_rate = (
288
+ self.session.human_overrides / self.session.total_holds
289
+ if self.session.total_holds > 0 else 0.0
290
+ )
291
+
292
+ self.move_history.append({
293
+ 'fen': fen,
294
+ 'ai_move': ai_move,
295
+ 'final_move': final_move,
296
+ 'was_override': was_override,
297
+ 'decision_time': decision_time,
298
+ 'timestamp': time.time()
299
+ })
300
+
301
+ def start_hold_timer(self):
302
+ self.hold_start_time = time.time()
303
+
304
+ def get_hold_duration(self) -> float:
305
+ if self.hold_start_time:
306
+ return time.time() - self.hold_start_time
307
+ return 0.0
308
+
309
+ def get_recent_merkle_chain(self, n: int = 5) -> List[str]:
310
+ return self.merkle_chain[-n:] if self.merkle_chain else []
311
+
312
+ def get_graph_stats(self) -> Dict:
313
+ """Get causation graph statistics"""
314
+ return {
315
+ "events": len(self.causal_chain),
316
+ "merkle_blocks": len(self.merkle_chain),
317
+ "overrides": self.session.human_overrides,
318
+ "anomalies": len(self.anomalies)
319
+ }
320
+
321
+
322
+ # ═══════════════════════════════════════════════════════════════════════════════
323
+ # CHESS MODEL PLAYER (Optional - requires torch)
324
+ # ═══════════════════════════════════════════════════════════════════════════════
325
+
326
+ class ChessModelPlayer:
327
+ """
328
+ CASCADE Model Player - Full cascade integration with neural network
329
+ """
330
+ def __init__(self, model_name: str, cascade_sys: CascadeSystem):
331
+ self.model = None
332
+ self.device = "cpu"
333
+ self.cascade_system = cascade_sys
334
+ self.model_name = model_name
335
+
336
+ if TORCH_AVAILABLE:
337
+ try:
338
+ print(f"Loading chess model {model_name}...")
339
+ self.model = AutoModel.from_pretrained(model_name, trust_remote_code=True)
340
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
341
+ self.model.to(self.device)
342
+ self.model.eval()
343
+ print(f"[CASCADE] Model loaded on {self.device}")
344
+ except Exception as e:
345
+ print(f"Could not load model: {e}")
346
+ self.model = None
347
+
348
+ def get_move_and_candidates(self, board: chess.Board) -> Tuple[str, List[Dict]]:
349
+ """Get move recommendation and candidates from model"""
350
+ fen = board.fen()
351
+ legal_moves = [m.uci() for m in board.legal_moves]
352
+ candidates = []
353
+
354
+ if self.model is not None and TORCH_AVAILABLE:
355
+ try:
356
+ with torch.no_grad():
357
+ pos_eval = self.model.get_position_value(fen, device=self.device)
358
+ move_uci = self.model.get_move_from_fen_no_thinking(fen, T=0.1, device=self.device)
359
+
360
+ if move_uci in legal_moves:
361
+ candidates.append({
362
+ "move": move_uci,
363
+ "prob": 0.82,
364
+ "v_head": pos_eval.tolist(),
365
+ "reason": "Primary engine recommendation. Optimal according to policy network.",
366
+ "is_primary": True
367
+ })
368
+
369
+ others = [m for m in legal_moves if m != move_uci]
370
+ random.shuffle(others)
371
+ for i, o in enumerate(others[:4]):
372
+ v_noise = [v + random.uniform(-0.08, 0.08) for v in pos_eval.tolist()]
373
+ prob = max(0.01, 0.15 - (i * 0.03) + random.uniform(-0.02, 0.02))
374
+ candidates.append({
375
+ "move": o,
376
+ "prob": prob,
377
+ "v_head": v_noise,
378
+ "reason": self._generate_move_reason(board, o),
379
+ "is_primary": False
380
+ })
381
+
382
+ return move_uci, candidates
383
+ except Exception as e:
384
+ print(f"Model inference error: {e}")
385
+
386
+ # Fallback without model
387
+ return None, []
388
+
389
+ def _generate_move_reason(self, board: chess.Board, move_uci: str) -> str:
390
+ """Generate a reason string for a move"""
391
+ reasons = [
392
+ "Develops piece activity",
393
+ "Controls central squares",
394
+ "Prepares kingside operation",
395
+ "Maintains pawn structure",
396
+ "Creates tactical threats",
397
+ "Improves piece coordination",
398
+ "Contests key diagonal",
399
+ "Reinforces defensive position"
400
+ ]
401
+
402
+ try:
403
+ move = chess.Move.from_uci(move_uci)
404
+ piece = board.piece_at(move.from_square)
405
+
406
+ if piece:
407
+ piece_name = chess.piece_name(piece.piece_type).capitalize()
408
+ if board.is_capture(move):
409
+ return f"{piece_name} capture - material gain potential"
410
+ elif piece.piece_type == chess.PAWN:
411
+ return "Pawn advance - space control"
412
+ elif piece.piece_type in [chess.KNIGHT, chess.BISHOP]:
413
+ return f"{piece_name} development - piece activity"
414
+ elif piece.piece_type == chess.ROOK:
415
+ return "Rook activation - open file control"
416
+ elif piece.piece_type == chess.QUEEN:
417
+ return "Queen maneuver - central pressure"
418
+ elif piece.piece_type == chess.KING:
419
+ if abs(move.from_square - move.to_square) == 2:
420
+ return "Castling - king safety"
421
+ return "King repositioning"
422
+ except:
423
+ pass
424
+
425
+ return random.choice(reasons)
426
+
427
+ def get_material_balance(self, board: chess.Board) -> int:
428
+ """Calculate material balance"""
429
+ values = {chess.PAWN: 1, chess.KNIGHT: 3, chess.BISHOP: 3,
430
+ chess.ROOK: 5, chess.QUEEN: 9}
431
+ balance = 0
432
+ for sq in chess.SQUARES:
433
+ piece = board.piece_at(sq)
434
+ if piece and piece.piece_type in values:
435
+ val = values[piece.piece_type]
436
+ balance += val if piece.color == chess.WHITE else -val
437
+ return balance
438
+
439
+ def get_center_control(self, board: chess.Board) -> float:
440
+ """Estimate center control"""
441
+ center_squares = [chess.D4, chess.D5, chess.E4, chess.E5]
442
+ control = 0
443
+ for sq in center_squares:
444
+ white_attackers = len(board.attackers(chess.WHITE, sq))
445
+ black_attackers = len(board.attackers(chess.BLACK, sq))
446
+ control += white_attackers - black_attackers
447
+ return control / 4.0
448
+
449
+ def observe_action(self, board: chess.Board, move: chess.Move, player_name: str) -> Dict:
450
+ """Observe and log an action"""
451
+ fen = board.fen()
452
+ pos_eval = [0.33, 0.34, 0.33]
453
+
454
+ if self.model is not None and TORCH_AVAILABLE:
455
+ try:
456
+ with torch.no_grad():
457
+ pos_eval = self.model.get_position_value(fen, device=self.device).tolist()
458
+ except:
459
+ pass
460
+
461
+ obs_data = {
462
+ "role": "observer",
463
+ "model": self.model_name,
464
+ "fen": fen,
465
+ "move": move.uci(),
466
+ "player": player_name,
467
+ "evaluation": pos_eval,
468
+ "value": float(pos_eval[2] - pos_eval[0])
469
+ }
470
+
471
+ self.cascade_system.register_event({
472
+ 'type': 'move',
473
+ 'move': move.uci(),
474
+ 'player': player_name,
475
+ 'fen': fen,
476
+ 'value': float(pos_eval[2] - pos_eval[0]),
477
+ 'was_override': False
478
+ })
479
+
480
+ return obs_data
481
+
482
+
483
+ # ═══════════════════════════════════════════════════════════════════════════════
484
+ # STREAMLIT APP
485
+ # ═══════════════════════════════════════════════════════════════════════════════
486
+
487
+ st.set_page_config(
488
+ page_title="CASCADE LATTICE CHESS",
489
+ page_icon="♟️",
490
+ layout="wide",
491
+ initial_sidebar_state="expanded"
492
+ )
493
+
494
+ # Custom CSS - Industrial Neon Theme
495
+ st.markdown("""
496
+ <style>
497
+ .stApp {
498
+ background-color: #121216;
499
+ color: #ffffff;
500
+ }
501
+ .main-header {
502
+ color: #00c8ff;
503
+ font-family: 'Consolas', monospace;
504
+ font-size: 2rem;
505
+ font-weight: bold;
506
+ margin-bottom: 1rem;
507
+ text-shadow: 0 0 10px rgba(0, 200, 255, 0.5);
508
+ }
509
+ .sub-header {
510
+ color: #00c8ff;
511
+ font-family: 'Consolas', monospace;
512
+ font-size: 1.2rem;
513
+ border-bottom: 1px solid #00c8ff;
514
+ padding-bottom: 0.5rem;
515
+ margin-bottom: 1rem;
516
+ }
517
+ .metric-card {
518
+ background-color: #23232d;
519
+ border-radius: 8px;
520
+ padding: 1rem;
521
+ margin: 0.5rem 0;
522
+ border: 1px solid #3a3a4a;
523
+ }
524
+ .metric-card:hover {
525
+ background-color: #2d3241;
526
+ border-color: #00c8ff;
527
+ }
528
+ .merkle-hash {
529
+ font-family: 'Consolas', monospace;
530
+ color: #64ffda;
531
+ font-size: 0.85rem;
532
+ background-color: #1a2a2a;
533
+ padding: 2px 6px;
534
+ border-radius: 3px;
535
+ }
536
+ .risk-low { color: #00ff96; font-weight: bold; }
537
+ .risk-med { color: #ff9632; font-weight: bold; }
538
+ .risk-high { color: #ff4646; font-weight: bold; }
539
+ .override-tag {
540
+ background-color: #ff9632;
541
+ color: black;
542
+ padding: 2px 8px;
543
+ border-radius: 4px;
544
+ font-size: 0.8rem;
545
+ font-weight: bold;
546
+ }
547
+ .combo-display {
548
+ color: #ffc832;
549
+ font-size: 1.5rem;
550
+ font-weight: bold;
551
+ text-shadow: 0 0 10px rgba(255, 200, 50, 0.7);
552
+ }
553
+ .status-halted {
554
+ color: #ff4646;
555
+ animation: pulse 1s infinite;
556
+ }
557
+ .status-auto {
558
+ color: #00ff96;
559
+ }
560
+ .status-observing {
561
+ color: #00c8ff;
562
+ }
563
+ .candidate-row {
564
+ background-color: #23232d;
565
+ border-radius: 8px;
566
+ padding: 0.75rem;
567
+ margin: 0.5rem 0;
568
+ border: 1px solid #3a3a4a;
569
+ transition: all 0.2s;
570
+ }
571
+ .candidate-row:hover {
572
+ background-color: #2d3241;
573
+ border-color: #00c8ff;
574
+ box-shadow: 0 0 10px rgba(0, 200, 255, 0.3);
575
+ }
576
+ .candidate-primary {
577
+ border-left: 3px solid #ffd700;
578
+ }
579
+ .confidence-bar {
580
+ background-color: #1a1a1f;
581
+ border-radius: 4px;
582
+ height: 8px;
583
+ overflow: hidden;
584
+ }
585
+ .confidence-fill {
586
+ background: linear-gradient(90deg, #00ff96, #00c8ff);
587
+ height: 100%;
588
+ border-radius: 4px;
589
+ }
590
+ .value-positive { color: #00ff96; }
591
+ .value-negative { color: #ff4646; }
592
+ .value-neutral { color: #787878; }
593
+ .chain-node {
594
+ background-color: #1e2832;
595
+ border-radius: 6px;
596
+ padding: 0.75rem;
597
+ margin: 0.25rem 0;
598
+ border-left: 3px solid #64ffda;
599
+ font-family: 'Consolas', monospace;
600
+ }
601
+ .chain-node-override {
602
+ border-left-color: #ff9632;
603
+ }
604
+ .anomaly-alert {
605
+ background-color: #321818;
606
+ border: 1px solid #ff4646;
607
+ border-radius: 6px;
608
+ padding: 0.5rem;
609
+ margin: 0.25rem 0;
610
+ }
611
+ .anomaly-warning {
612
+ border-color: #ff9632;
613
+ background-color: #322818;
614
+ }
615
+ .prediction-card {
616
+ background-color: #23232d;
617
+ border-radius: 8px;
618
+ padding: 1rem;
619
+ margin: 0.5rem 0;
620
+ border: 2px solid #3a3a4a;
621
+ }
622
+ .prediction-card-low { border-color: #00ff96; }
623
+ .prediction-card-med { border-color: #ff9632; }
624
+ .prediction-card-high { border-color: #ff4646; }
625
+ .intervention-marker {
626
+ background-color: #ff4646;
627
+ color: white;
628
+ border-radius: 50%;
629
+ width: 24px;
630
+ height: 24px;
631
+ display: inline-flex;
632
+ align-items: center;
633
+ justify-content: center;
634
+ font-weight: bold;
635
+ }
636
+ .sparkline-container {
637
+ background-color: #1a1a1f;
638
+ border-radius: 4px;
639
+ padding: 4px;
640
+ height: 40px;
641
+ }
642
+ @keyframes pulse {
643
+ 0%, 100% { opacity: 1; }
644
+ 50% { opacity: 0.5; }
645
+ }
646
+ .stTabs [data-baseweb="tab-list"] {
647
+ gap: 8px;
648
+ background-color: #19191e;
649
+ padding: 4px;
650
+ border-radius: 8px;
651
+ }
652
+ .stTabs [data-baseweb="tab"] {
653
+ background-color: #23232d;
654
+ border-radius: 6px;
655
+ color: #b4b4b4;
656
+ font-family: 'Consolas', monospace;
657
+ }
658
+ .stTabs [aria-selected="true"] {
659
+ background-color: #00c8ff;
660
+ color: #000000;
661
+ }
662
+ .stButton>button {
663
+ background-color: #00966e;
664
+ color: white;
665
+ border: none;
666
+ font-family: 'Consolas', monospace;
667
+ }
668
+ .stButton>button:hover {
669
+ background-color: #00b482;
670
+ border: none;
671
+ }
672
+ </style>
673
+ """, unsafe_allow_html=True)
674
+
675
+
676
+ def init_session_state():
677
+ """Initialize session state variables"""
678
+ if 'board' not in st.session_state:
679
+ st.session_state.board = chess.Board()
680
+ if 'cascade_system' not in st.session_state:
681
+ st.session_state.cascade_system = CascadeSystem()
682
+ if 'model_player' not in st.session_state:
683
+ st.session_state.model_player = None
684
+ if 'move_history' not in st.session_state:
685
+ st.session_state.move_history = []
686
+ if 'game_over' not in st.session_state:
687
+ st.session_state.game_over = False
688
+ if 'current_candidates' not in st.session_state:
689
+ st.session_state.current_candidates = []
690
+ if 'awaiting_decision' not in st.session_state:
691
+ st.session_state.awaiting_decision = False
692
+ if 'auto_mode' not in st.session_state:
693
+ st.session_state.auto_mode = False
694
+ if 'stockfish_engine' not in st.session_state:
695
+ st.session_state.stockfish_engine = None
696
+ if 'ai_suggested_move' not in st.session_state:
697
+ st.session_state.ai_suggested_move = None
698
+ if 'last_evaluation' not in st.session_state:
699
+ st.session_state.last_evaluation = [0.33, 0.34, 0.33]
700
+ if 'value_history' not in st.session_state:
701
+ st.session_state.value_history = []
702
+ if 'confidence_history' not in st.session_state:
703
+ st.session_state.confidence_history = []
704
+ if 'selected_move_idx' not in st.session_state:
705
+ st.session_state.selected_move_idx = 0
706
+ if 'ui_mode' not in st.session_state:
707
+ st.session_state.ui_mode = "LIST" # "LIST" or "DRILL"
708
+ if 'last_observation' not in st.session_state:
709
+ st.session_state.last_observation = {}
710
+ if 'game_started' not in st.session_state:
711
+ st.session_state.game_started = False
712
+
713
+
714
+ def get_stockfish_engine():
715
+ """Get or create Stockfish engine"""
716
+ if st.session_state.stockfish_engine is None:
717
+ try:
718
+ st.session_state.stockfish_engine = chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH)
719
+ except Exception as e:
720
+ st.error(f"Could not load Stockfish: {e}")
721
+ return None
722
+ return st.session_state.stockfish_engine
723
+
724
+
725
+ def generate_candidates(board: chess.Board) -> List[Dict]:
726
+ """Generate candidate moves with probabilities and reasoning"""
727
+ legal_moves = list(board.legal_moves)
728
+ if not legal_moves:
729
+ return []
730
+
731
+ candidates = []
732
+ engine = get_stockfish_engine()
733
+
734
+ if engine:
735
+ try:
736
+ result = engine.analyse(board, chess.engine.Limit(time=0.1), multipv=min(5, len(legal_moves)))
737
+ for i, info in enumerate(result if isinstance(result, list) else [result]):
738
+ pv = info.get('pv', [])
739
+ if pv:
740
+ move = pv[0]
741
+ score = info.get('score', None)
742
+
743
+ if score:
744
+ cp = score.relative.score(mate_score=10000) if score.relative else 0
745
+ cp = cp / 100.0
746
+ win_prob = 1 / (1 + 10 ** (-cp / 4))
747
+ draw_prob = 0.2
748
+ loss_prob = max(0, 1 - win_prob - draw_prob)
749
+ v_head = [loss_prob, draw_prob, win_prob]
750
+ else:
751
+ v_head = [0.33, 0.34, 0.33]
752
+
753
+ prob = max(0.05, 0.82 - i * 0.15)
754
+ candidates.append({
755
+ 'move': move.uci(),
756
+ 'prob': prob,
757
+ 'v_head': v_head,
758
+ 'reason': generate_move_reason(board, move.uci()),
759
+ 'is_primary': i == 0,
760
+ 'depth': info.get('depth', 0)
761
+ })
762
+ except Exception as e:
763
+ pass
764
+
765
+ # Fallback without engine
766
+ if not candidates:
767
+ random.shuffle(legal_moves)
768
+ for i, move in enumerate(legal_moves[:5]):
769
+ candidates.append({
770
+ 'move': move.uci(),
771
+ 'prob': max(0.05, 0.7 - i * 0.12),
772
+ 'v_head': [0.33, 0.34, 0.33],
773
+ 'reason': generate_move_reason(board, move.uci()),
774
+ 'is_primary': i == 0,
775
+ 'depth': 0
776
+ })
777
+
778
+ return candidates
779
+
780
+
781
+ def generate_move_reason(board: chess.Board, move_uci: str) -> str:
782
+ """Generate a reason string for a move"""
783
+ reasons = [
784
+ "Develops piece activity",
785
+ "Controls central squares",
786
+ "Prepares kingside operation",
787
+ "Maintains pawn structure",
788
+ "Creates tactical threats",
789
+ "Improves piece coordination",
790
+ "Contests key diagonal",
791
+ "Reinforces defensive position"
792
+ ]
793
+
794
+ try:
795
+ move = chess.Move.from_uci(move_uci)
796
+ piece = board.piece_at(move.from_square)
797
+
798
+ if piece:
799
+ piece_name = chess.piece_name(piece.piece_type).capitalize()
800
+ if board.is_capture(move):
801
+ return f"{piece_name} capture - material gain potential"
802
+ elif piece.piece_type == chess.PAWN:
803
+ return "Pawn advance - space control"
804
+ elif piece.piece_type in [chess.KNIGHT, chess.BISHOP]:
805
+ return f"{piece_name} development - piece activity"
806
+ elif piece.piece_type == chess.ROOK:
807
+ return "Rook activation - open file control"
808
+ elif piece.piece_type == chess.QUEEN:
809
+ return "Queen maneuver - central pressure"
810
+ elif piece.piece_type == chess.KING:
811
+ if abs(move.from_square - move.to_square) == 2:
812
+ return "Castling - king safety"
813
+ return "King repositioning"
814
+ except:
815
+ pass
816
+
817
+ return random.choice(reasons)
818
+
819
+
820
+ def render_board(board: chess.Board, selected_move: str = None, show_threats: List[str] = None) -> str:
821
+ """Render chess board as SVG with optional highlights"""
822
+ lastmove = None
823
+ if st.session_state.move_history:
824
+ try:
825
+ lastmove = chess.Move.from_uci(st.session_state.move_history[-1]['move'])
826
+ except:
827
+ pass
828
+
829
+ arrows = []
830
+ if selected_move:
831
+ try:
832
+ move = chess.Move.from_uci(selected_move)
833
+ arrows = [chess.svg.Arrow(move.from_square, move.to_square, color="#ffd700")]
834
+ except:
835
+ pass
836
+
837
+ # Add threat indicators
838
+ check_squares = []
839
+ if show_threats:
840
+ for threat in show_threats:
841
+ if "HANGING_" in threat:
842
+ sq_name = threat.replace("HANGING_", "").lower()
843
+ try:
844
+ sq = chess.parse_square(sq_name)
845
+ check_squares.append(sq)
846
+ except:
847
+ pass
848
+
849
+ svg = chess.svg.board(
850
+ board,
851
+ size=400,
852
+ lastmove=lastmove,
853
+ arrows=arrows,
854
+ colors={
855
+ 'square light': '#ebecd0',
856
+ 'square dark': '#779556',
857
+ }
858
+ )
859
+ return svg
860
+
861
+
862
+ def make_move(move_uci: str, player: str, was_override: bool = False):
863
+ """Execute a move on the board"""
864
+ board = st.session_state.board
865
+ cascade_sys = st.session_state.cascade_system
866
+
867
+ try:
868
+ move = chess.Move.from_uci(move_uci)
869
+ if move in board.legal_moves:
870
+ board.push(move)
871
+
872
+ # Register in cascade system
873
+ cascade_sys.register_event({
874
+ 'type': 'move',
875
+ 'move': move_uci,
876
+ 'player': player,
877
+ 'fen': board.fen(),
878
+ 'value': st.session_state.last_evaluation[2] - st.session_state.last_evaluation[0],
879
+ 'was_override': was_override
880
+ })
881
+
882
+ st.session_state.move_history.append({
883
+ 'move': move_uci,
884
+ 'player': player,
885
+ 'was_override': was_override,
886
+ 'fen': board.fen()
887
+ })
888
+
889
+ # Check game over
890
+ if board.is_game_over():
891
+ st.session_state.game_over = True
892
+
893
+ return True
894
+ except Exception as e:
895
+ st.error(f"Invalid move: {e}")
896
+ return False
897
+
898
+
899
+ def play_stockfish_move():
900
+ """Have Stockfish play a move for Black"""
901
+ engine = get_stockfish_engine()
902
+ if engine and not st.session_state.board.is_game_over():
903
+ try:
904
+ result = engine.play(st.session_state.board, chess.engine.Limit(time=0.5))
905
+ if result.move:
906
+ make_move(result.move.uci(), "Stockfish")
907
+ except Exception as e:
908
+ st.error(f"Stockfish error: {e}")
909
+
910
+
911
+ def main():
912
+ init_session_state()
913
+
914
+ # Header with session stats
915
+ col_title, col_stats = st.columns([2, 1])
916
+ with col_title:
917
+ st.markdown('<div class="main-header">CASCADE LATTICE CHESS</div>', unsafe_allow_html=True)
918
+ with col_stats:
919
+ stats = st.session_state.cascade_system.session
920
+ if stats.current_combo >= 3:
921
+ st.markdown(f'<div class="combo-display">🔥 COMBO x{stats.current_combo}</div>', unsafe_allow_html=True)
922
+
923
+ override_color = "red" if stats.override_rate > 0.5 else "green"
924
+ st.markdown(f"OR: <span style='color:{override_color}'>{stats.override_rate*100:.0f}%</span> | HOLDS: {stats.total_holds}", unsafe_allow_html=True)
925
+
926
+ # Layout
927
+ col_board, col_panel = st.columns([1, 1.3])
928
+
929
+ board = st.session_state.board
930
+ cascade_sys = st.session_state.cascade_system
931
+
932
+ with col_board:
933
+ # Status bar
934
+ if st.session_state.current_candidates and board.turn == chess.WHITE:
935
+ hold_duration = cascade_sys.get_hold_duration()
936
+ status_class = "status-halted" if not st.session_state.auto_mode else "status-auto"
937
+ status_text = "⏸ HALTED" if not st.session_state.auto_mode else "▶ AUTO"
938
+ st.markdown(f'<span class="{status_class}">{status_text}</span> | Hold: {hold_duration:.1f}s', unsafe_allow_html=True)
939
+ else:
940
+ st.markdown('<span class="status-observing">● OBSERVING</span>', unsafe_allow_html=True)
941
+
942
+ # Render board with current selection
943
+ selected = None
944
+ threats = []
945
+ if st.session_state.current_candidates:
946
+ idx = st.session_state.selected_move_idx
947
+ if idx < len(st.session_state.current_candidates):
948
+ selected = st.session_state.current_candidates[idx]['move']
949
+ pred = next((p for p in cascade_sys.predictions if p.move == selected), None)
950
+ if pred:
951
+ threats = pred.tactical_threats
952
+
953
+ svg = render_board(board, selected, threats)
954
+ st.markdown(f'<div style="display:flex;justify-content:center;">{svg}</div>', unsafe_allow_html=True)
955
+
956
+ # Board status bar
957
+ st.markdown("---")
958
+ status_cols = st.columns(4)
959
+ with status_cols[0]:
960
+ move_count = len(st.session_state.move_history)
961
+ st.caption(f"Moves: {move_count}")
962
+ with status_cols[1]:
963
+ chain_len = len(cascade_sys.merkle_chain)
964
+ st.markdown(f'<span class="merkle-hash">Chain: {chain_len}</span>', unsafe_allow_html=True)
965
+ with status_cols[2]:
966
+ if st.session_state.last_evaluation:
967
+ e = st.session_state.last_evaluation
968
+ eval_val = e[2] - e[0]
969
+ eval_class = "value-positive" if eval_val > 0.1 else "value-negative" if eval_val < -0.1 else "value-neutral"
970
+ st.markdown(f'<span class="{eval_class}">Eval: {eval_val:+.3f}</span>', unsafe_allow_html=True)
971
+ with status_cols[3]:
972
+ turn_text = "⚪ White" if board.turn == chess.WHITE else "⚫ Black"
973
+ st.caption(turn_text)
974
+
975
+ # Game status
976
+ if st.session_state.game_over:
977
+ result = board.result()
978
+ if result == "1-0":
979
+ st.success("⚪ WHITE WINS (ChessBot)")
980
+ elif result == "0-1":
981
+ st.error("⚫ BLACK WINS (Stockfish)")
982
+ else:
983
+ st.info(f"DRAW: {result}")
984
+
985
+ # End game stats
986
+ st.markdown("### 📊 Session Statistics")
987
+ st.markdown(f"""
988
+ - **Total Holds:** {stats.total_holds}
989
+ - **Human Overrides:** {stats.human_overrides}
990
+ - **Override Rate:** {stats.override_rate*100:.1f}%
991
+ - **Max Combo:** {stats.max_combo}
992
+ - **Avg Decision Time:** {stats.avg_decision_time:.2f}s
993
+ - **Causation Chain:** {len(cascade_sys.causal_chain)} events
994
+ - **Merkle Blocks:** {len(cascade_sys.merkle_chain)}
995
+ """)
996
+
997
+ if st.button("🔄 New Game", use_container_width=True):
998
+ st.session_state.board = chess.Board()
999
+ st.session_state.move_history = []
1000
+ st.session_state.game_over = False
1001
+ st.session_state.current_candidates = []
1002
+ st.session_state.cascade_system = CascadeSystem()
1003
+ st.session_state.value_history = []
1004
+ st.session_state.selected_move_idx = 0
1005
+ st.session_state.last_evaluation = [0.33, 0.34, 0.33]
1006
+ st.rerun()
1007
+
1008
+ # Controls
1009
+ st.markdown("---")
1010
+ col1, col2 = st.columns(2)
1011
+ with col1:
1012
+ st.session_state.auto_mode = st.toggle("⚡ Auto Mode", st.session_state.auto_mode)
1013
+ with col2:
1014
+ if st.button("🔄 Reset Game", use_container_width=True):
1015
+ st.session_state.board = chess.Board()
1016
+ st.session_state.move_history = []
1017
+ st.session_state.game_over = False
1018
+ st.session_state.current_candidates = []
1019
+ st.session_state.cascade_system = CascadeSystem()
1020
+ st.session_state.value_history = []
1021
+ st.session_state.selected_move_idx = 0
1022
+ st.session_state.last_evaluation = [0.33, 0.34, 0.33]
1023
+ st.rerun()
1024
+
1025
+ with col_panel:
1026
+ # Tabs
1027
+ tab_wealth, tab_cascade, tab_metrics, tab_chain = st.tabs(["WEALTH", "CASCADE", "METRICS", "CHAIN"])
1028
+
1029
+ with tab_wealth:
1030
+ # Last observation info
1031
+ if st.session_state.last_observation:
1032
+ obs = st.session_state.last_observation
1033
+ e = obs.get('evaluation', st.session_state.last_evaluation)
1034
+ if e:
1035
+ st.markdown(f"**NNUE:** W:{e[2]:.3f} D:{e[1]:.3f} B:{e[0]:.3f}")
1036
+ # Track value history
1037
+ val = e[2] - e[0]
1038
+ st.session_state.value_history.append(val)
1039
+ if len(st.session_state.value_history) > 100:
1040
+ st.session_state.value_history.pop(0)
1041
+
1042
+ last_move = obs.get('move', 'N/A')
1043
+ last_player = obs.get('player', '?')
1044
+ st.caption(f"Last: {last_move} ({last_player})")
1045
+
1046
+ if not st.session_state.game_over:
1047
+ # White's turn - generate candidates
1048
+ if board.turn == chess.WHITE:
1049
+ if not st.session_state.current_candidates:
1050
+ st.session_state.current_candidates = generate_candidates(board)
1051
+ cascade_sys.predict_cascade(board, st.session_state.current_candidates)
1052
+ cascade_sys.start_hold_timer()
1053
+
1054
+ candidates = st.session_state.current_candidates
1055
+ predictions = cascade_sys.predictions
1056
+
1057
+ # Hold timer display
1058
+ hold_duration = cascade_sys.get_hold_duration()
1059
+ timer_color = "red" if hold_duration > 10 else "#00c8ff"
1060
+ st.markdown(f'<div class="sub-header">CASCADE HOLD // SELECT MOVE <span style="color:{timer_color};float:right;">⏱ {hold_duration:.1f}s</span></div>', unsafe_allow_html=True)
1061
+
1062
+ # Candidate table header
1063
+ header_cols = st.columns([1, 2, 2, 3, 2, 3])
1064
+ header_cols[0].markdown("**RK**")
1065
+ header_cols[1].markdown("**MOVE**")
1066
+ header_cols[2].markdown("**CONF**")
1067
+ header_cols[3].markdown("**VALUE HEAD**")
1068
+ header_cols[4].markdown("**RISK**")
1069
+ header_cols[5].markdown("**THREATS**")
1070
+
1071
+ for i, c in enumerate(candidates):
1072
+ pred = next((p for p in predictions if p.move == c['move']), None)
1073
+ risk_class = "risk-low" if not pred or pred.risk_score < 0.3 else "risk-med" if pred.risk_score < 0.6 else "risk-high"
1074
+ primary_class = "candidate-primary" if c.get('is_primary') else ""
1075
+
1076
+ st.markdown(f'<div class="candidate-row {primary_class}">', unsafe_allow_html=True)
1077
+ cols = st.columns([1, 2, 2, 3, 2, 3])
1078
+
1079
+ with cols[0]:
1080
+ st.markdown(f"**#{i+1}**")
1081
+ with cols[1]:
1082
+ st.markdown(f"**{c['move']}**")
1083
+ with cols[2]:
1084
+ st.progress(c['prob'], text=f"{c['prob']*100:.1f}%")
1085
+ with cols[3]:
1086
+ vh = c['v_head']
1087
+ st.markdown(f"<small>W:{vh[2]:.2f} D:{vh[1]:.2f} B:{vh[0]:.2f}</small>", unsafe_allow_html=True)
1088
+ with cols[4]:
1089
+ if pred:
1090
+ st.markdown(f'<span class="{risk_class}">{pred.risk_score:.2f}</span>', unsafe_allow_html=True)
1091
+ with cols[5]:
1092
+ if pred and pred.tactical_threats:
1093
+ threat_str = ", ".join(pred.tactical_threats[:2])
1094
+ st.markdown(f"<small>⚠️ {threat_str[:25]}</small>", unsafe_allow_html=True)
1095
+
1096
+ # Reason text
1097
+ st.caption(c.get('reason', ''))
1098
+
1099
+ if st.button(f"⚡ Execute {c['move']}", key=f"move_{i}", use_container_width=True):
1100
+ ai_move = candidates[0]['move'] if candidates else c['move']
1101
+ was_override = c['move'] != ai_move
1102
+
1103
+ # Update last evaluation
1104
+ st.session_state.last_evaluation = c['v_head']
1105
+
1106
+ cascade_sys.record_decision(
1107
+ was_override=was_override,
1108
+ decision_time=cascade_sys.get_hold_duration(),
1109
+ ai_move=ai_move,
1110
+ final_move=c['move'],
1111
+ fen=board.fen()
1112
+ )
1113
+
1114
+ st.session_state.last_observation = {
1115
+ 'move': c['move'],
1116
+ 'player': 'ChessBot' + (' (OVERRIDE)' if was_override else ''),
1117
+ 'evaluation': c['v_head'],
1118
+ 'fen': board.fen()
1119
+ }
1120
+
1121
+ make_move(c['move'], "ChessBot" + (" (OVERRIDE)" if was_override else ""), was_override)
1122
+ st.session_state.current_candidates = []
1123
+ st.session_state.selected_move_idx = 0
1124
+
1125
+ # Stockfish responds
1126
+ if not st.session_state.board.is_game_over():
1127
+ play_stockfish_move()
1128
+
1129
+ st.rerun()
1130
+
1131
+ st.markdown('</div>', unsafe_allow_html=True)
1132
+ st.markdown("---")
1133
+
1134
+ # Anomaly alerts
1135
+ anomalies = list(cascade_sys.anomalies)[-3:]
1136
+ if anomalies:
1137
+ st.markdown("### ⚠️ Anomaly Alerts")
1138
+ for alert in anomalies:
1139
+ alert_class = "anomaly-alert" if alert.severity == "CRITICAL" else "anomaly-warning"
1140
+ st.markdown(f'<div class="{alert_class}">[{alert.severity}] {alert.message}</div>', unsafe_allow_html=True)
1141
+
1142
+ # Auto mode
1143
+ if st.session_state.auto_mode and candidates:
1144
+ time.sleep(0.5)
1145
+ ai_move = candidates[0]['move']
1146
+ st.session_state.last_evaluation = candidates[0]['v_head']
1147
+ cascade_sys.record_decision(
1148
+ was_override=False,
1149
+ decision_time=cascade_sys.get_hold_duration(),
1150
+ ai_move=ai_move,
1151
+ final_move=ai_move,
1152
+ fen=board.fen()
1153
+ )
1154
+ st.session_state.last_observation = {
1155
+ 'move': ai_move,
1156
+ 'player': 'ChessBot',
1157
+ 'evaluation': candidates[0]['v_head'],
1158
+ 'fen': board.fen()
1159
+ }
1160
+ make_move(ai_move, "ChessBot")
1161
+ st.session_state.current_candidates = []
1162
+ st.session_state.selected_move_idx = 0
1163
+ if not st.session_state.board.is_game_over():
1164
+ play_stockfish_move()
1165
+ st.rerun()
1166
+ else:
1167
+ st.info("⏳ Waiting for Stockfish...")
1168
+ play_stockfish_move()
1169
+ st.rerun()
1170
+
1171
+ with tab_cascade:
1172
+ st.markdown('<div class="sub-header">CASCADE PREDICTIONS // MINORITY REPORT</div>', unsafe_allow_html=True)
1173
+ predictions = cascade_sys.predictions
1174
+ if predictions:
1175
+ for pred in predictions:
1176
+ risk_class = "prediction-card-low" if pred.risk_score < 0.3 else "prediction-card-med" if pred.risk_score < 0.6 else "prediction-card-high"
1177
+ intervention_marker = '<span class="intervention-marker">!</span>' if pred.intervention_point else ''
1178
+
1179
+ st.markdown(f'<div class="prediction-card {risk_class}">', unsafe_allow_html=True)
1180
+
1181
+ col1, col2 = st.columns([3, 1])
1182
+ with col1:
1183
+ st.markdown(f"**{pred.move}** {intervention_marker}", unsafe_allow_html=True)
1184
+ st.progress(pred.risk_score, text=f"Risk: {pred.risk_score:.2f}")
1185
+ with col2:
1186
+ mat_color = "value-positive" if pred.material_delta >= 0 else "value-negative"
1187
+ pos_color = "value-positive" if pred.positional_delta >= 0 else "value-negative"
1188
+ st.markdown(f'<span class="{mat_color}">Mat: {pred.material_delta:+d}</span>', unsafe_allow_html=True)
1189
+ st.markdown(f'<span class="{pos_color}">Pos: {pred.positional_delta:+.3f}</span>', unsafe_allow_html=True)
1190
+
1191
+ # Predicted response chain
1192
+ if pred.predicted_responses:
1193
+ chain_str = " → ".join(pred.predicted_responses[:3])
1194
+ st.markdown(f"**Chain:** {chain_str}")
1195
+
1196
+ # Threats
1197
+ if pred.tactical_threats:
1198
+ threat_text = ", ".join(pred.tactical_threats[:4])
1199
+ st.warning(f"⚠️ {threat_text}")
1200
+
1201
+ if pred.intervention_point:
1202
+ st.error("🚨 INTERVENTION POINT - High risk detected")
1203
+
1204
+ st.markdown('</div>', unsafe_allow_html=True)
1205
+ else:
1206
+ st.caption("No predictions available. Waiting for HOLD point...")
1207
+
1208
+ with tab_metrics:
1209
+ st.markdown('<div class="sub-header">REAL-TIME METRICS ENGINE</div>', unsafe_allow_html=True)
1210
+ stats = cascade_sys.session
1211
+
1212
+ # Session metrics grid
1213
+ m1, m2, m3, m4 = st.columns(4)
1214
+ m1.metric("Total Holds", stats.total_holds)
1215
+ m2.metric("Human Overrides", stats.human_overrides,
1216
+ delta=f"{stats.human_overrides}" if stats.human_overrides > 0 else None,
1217
+ delta_color="inverse")
1218
+ m3.metric("AI Accepted", stats.ai_accepted,
1219
+ delta=f"{stats.ai_accepted}" if stats.ai_accepted > 0 else None)
1220
+ override_delta = "High" if stats.override_rate > 0.5 else None
1221
+ m4.metric("Override Rate", f"{stats.override_rate*100:.1f}%", delta=override_delta, delta_color="inverse")
1222
+
1223
+ m5, m6, m7, m8 = st.columns(4)
1224
+ combo_delta = f"+{stats.current_combo}" if stats.current_combo >= 3 else None
1225
+ m5.metric("Current Combo", stats.current_combo, delta=combo_delta)
1226
+ m6.metric("Max Combo", stats.max_combo)
1227
+ m7.metric("Avg Decision", f"{stats.avg_decision_time:.2f}s")
1228
+ m8.metric("Speed Bonus", f"{stats.speed_bonus:.2f}")
1229
+
1230
+ st.markdown("---")
1231
+
1232
+ # Value history chart
1233
+ st.markdown("**VALUE HISTORY**")
1234
+ if st.session_state.value_history:
1235
+ import json
1236
+ # Simple sparkline using markdown
1237
+ values = st.session_state.value_history[-50:]
1238
+ st.line_chart(values, height=100)
1239
+ else:
1240
+ st.caption("No value history yet.")
1241
+
1242
+ st.markdown("---")
1243
+
1244
+ # Graph stats
1245
+ st.markdown("**CAUSATION GRAPH**")
1246
+ graph_stats = cascade_sys.get_graph_stats()
1247
+ for key, val in graph_stats.items():
1248
+ st.markdown(f"- {key}: **{val}**")
1249
+
1250
+ with tab_chain:
1251
+ st.markdown('<div class="sub-header">MERKLE PROVENANCE CHAIN</div>', unsafe_allow_html=True)
1252
+ chain = cascade_sys.causal_chain[-12:]
1253
+
1254
+ if chain:
1255
+ for i, node in enumerate(reversed(chain)):
1256
+ override_tag = '<span class="override-tag">OVERRIDE</span>' if node.was_override else ''
1257
+ val_color = "value-positive" if node.value > 0 else "value-negative" if node.value < 0 else "value-neutral"
1258
+ node_class = "chain-node-override" if node.was_override else ""
1259
+
1260
+ st.markdown(f"""
1261
+ <div class="chain-node {node_class}">
1262
+ <strong>#{node.depth}</strong> &nbsp;
1263
+ <span style="font-size:1.1em;">{node.move or 'START'}</span> &nbsp;
1264
+ <span style="color:#888;">({node.player})</span> {override_tag}<br>
1265
+ <span class="merkle-hash">{node.merkle}</span><br>
1266
+ <span class="{val_color}">Value: {node.value:+.3f}</span>
1267
+ </div>
1268
+ """, unsafe_allow_html=True)
1269
+
1270
+ # Chain link arrow (except last)
1271
+ if i < len(chain) - 1:
1272
+ st.markdown('<div style="text-align:center;color:#64ffda;">↓</div>', unsafe_allow_html=True)
1273
+ else:
1274
+ st.caption("Chain empty. Waiting for game events...")
1275
+
1276
+ # Sidebar
1277
+ with st.sidebar:
1278
+ st.markdown("## ⚙️ CASCADE LATTICE")
1279
+ st.markdown(f"**Model:** `{MODEL_NAME}`")
1280
+ st.markdown(f"**Engine:** Stockfish")
1281
+ st.markdown(f"**Torch:** {'✅ Available' if TORCH_AVAILABLE else '❌ Not loaded'}")
1282
+
1283
+ st.markdown("---")
1284
+
1285
+ st.markdown("### 🎮 Controls")
1286
+ st.markdown("""
1287
+ - **Select** a candidate move to execute
1288
+ - **Auto Mode** for automated play
1289
+ - **CASCADE** tab for predictions
1290
+ - **METRICS** tab for session stats
1291
+ - **CHAIN** tab for Merkle provenance
1292
+ """)
1293
+
1294
+ st.markdown("---")
1295
+
1296
+ st.markdown("### 📊 Quick Stats")
1297
+ st.markdown(f"**Moves:** {len(st.session_state.move_history)}")
1298
+ st.markdown(f"**Chain:** {len(cascade_sys.merkle_chain)} blocks")
1299
+ st.markdown(f"**Events:** {len(cascade_sys.causal_chain)}")
1300
+
1301
+ if cascade_sys.session.current_combo >= 3:
1302
+ st.markdown(f"🔥 **Combo:** x{cascade_sys.session.current_combo}")
1303
+
1304
+ st.markdown("---")
1305
+
1306
+ # Recent merkle hashes
1307
+ st.markdown("### 🔗 Recent Merkle")
1308
+ recent = cascade_sys.get_recent_merkle_chain(3)
1309
+ for m in recent:
1310
+ st.markdown(f'<span class="merkle-hash">{m}</span>', unsafe_allow_html=True)
1311
+
1312
+
1313
+ if __name__ == "__main__":
1314
+ main()