#!/usr/bin/env python3 """Independent v11 public-rules reconstruction; no network or match participation.""" import hashlib import json from collections import Counter from datetime import datetime, timezone from pathlib import Path ROOT = Path(__file__).resolve().parent COLORS = {1: 'red', 2: 'blue', 3: 'neutral'} def count(board): c = Counter(board) return {name: c[state] for state, name in COLORS.items()} def birth_color(neighbors): votes = Counter(x for x in neighbors if x not in (0, 3)) if not votes: return 3 most = max(votes.values()) winners = [x for x, n in votes.items() if n == most] return winners[0] if len(winners) == 1 else 3 def tick(board, width, height): out = [0] * len(board) for y in range(height): for x in range(width): i = y * width + x neighbors = [board[((y + dy) % height) * width + ((x + dx) % width)] for dy in (-1, 0, 1) for dx in (-1, 0, 1) if (dx, dy) != (0, 0)] live = sum(v != 0 for v in neighbors) if board[i]: out[i] = board[i] if live in (2, 3) else 0 elif live == 3: out[i] = birth_color(neighbors) return out def seed(board, width, height, placements, budget=None): claimed = {} for color, points in placements.items(): assert len(set(map(tuple, points))) == len(points), 'duplicate placement' if budget is not None: assert len(points) == budget, 'wrong seed budget' for x, y in points: assert 0 <= x < width and 0 <= y < height, 'out of bounds' i = y * width + x assert board[i] == 0, 'seed on prior live cell' claimed.setdefault(i, []).append(color) out = board.copy() for i, colors in claimed.items(): out[i] = colors[0] if len(colors) == 1 else 3 return out def evolve(board, width, height, generations): frames = [board.copy()] for _ in range(generations): frames.append(tick(frames[-1], width, height)) return frames def canonical_commitment(match_id, round_number, points, nonce): payload = {'match_id': match_id, 'round': round_number, 'placements': sorted(points, key=lambda p: (p[1], p[0])), 'nonce': nonce} return hashlib.sha256(json.dumps(payload, separators=(',', ':'), ensure_ascii=False).encode()).hexdigest() def rule_checks(): assert birth_color([1, 3, 3]) == 1 assert birth_color([1, 2, 3]) == 3 assert birth_color([3, 3, 3]) == 3 assert birth_color([1, 2, 2]) == 2 assert birth_color([1, 2, 4]) == 3 # A blinker crosses the horizontal toroidal seam. board = [0] * 25 for x in (4, 0, 1): board[2 * 5 + x] = 1 after = tick(board, 5, 5) assert {i for i, v in enumerate(after) if v} == {5, 10, 15} # A surviving neutral cell keeps its color even between two red cells. board[10] = 3 assert tick(board, 5, 5)[10] == 3 assert seed([0] * 25, 5, 5, {1: [[0, 0]], 2: [[0, 0]]}, 1)[0] == 3 # Neutral parents count toward birth, but not toward the allegiance vote. board = [0] * 25 for x, color in ((1, 1), (2, 3), (3, 3)): board[5 + x] = color assert tick(board, 5, 5)[12] == 1 for placements, previous, budget in [({1: [[0, 0], [0, 0]]}, [0]*25, 2), ({1: [[5, 0]]}, [0]*25, 1), ({1: [[0, 0]]}, [1]+[0]*24, 1), ({1: []}, [0]*25, 1)]: try: seed(previous, 5, 5, placements, budget) except AssertionError: pass else: raise AssertionError('illegal placement accepted') return 13 def verify(): rules = json.loads((ROOT/'public-rules.json').read_text()) match = json.loads((ROOT/'public-match.json').read_text()) transcript = json.loads((ROOT/'public-transcript.json').read_text())['transcript'] assert rules['version'] == 11 assert transcript['match_id'] == match['id'] assert transcript['rounds'] == match['history'] assert transcript['state_hash'] == match['state_hash'] assert match['status'] == 'complete' and match['completion_reason'] == 'played' width, height = match['width'], match['height'] previous = [0] * (width * height) all_frames, rounds, verified_commitments = [], [], 0 for n, r in enumerate(match['history'], 1): assert r['round'] == n and r['width'] == width and r['height'] == height budget = match['opening_seed_budget'] if n == 1 else ( match['final_round_seed_budget'] if n == match['max_rounds'] else match['reinforcement_seed_budget']) generations = match['final_round_generations'] if n == match['max_rounds'] else match['generations_per_round'] assert r['seed_budget'] == budget and r['generations'] == generations assert previous == r['board_before'], ('board chain', n) placements = {1: r['red_placements'], 2: r['blue_placements']} seeded = seed(previous, width, height, placements, budget) assert seeded == r['board_seeded'], ('seeded board', n) frames = evolve(seeded, width, height, generations) trajectory = [{'tick': t, **count(b)} for t, b in enumerate(frames) if t] assert trajectory == r['trajectory'], ('trajectory', n) assert frames[-1] == r['board_after'], ('final round board', n) assert count(frames[-1]) == r['board_score'], ('score', n) collisions = sorted(set(map(tuple, r['red_placements'])) & set(map(tuple, r['blue_placements'])), key=lambda p: (p[1], p[0])) for color, name in ((1, 'red'), (2, 'blue')): digest = canonical_commitment(match['id'], n, placements[color], r['reveal_nonces'][name]) assert digest == r['commitments'][name], ('commitment', n, name) play = [p for p in transcript['plays'] if p['round'] == n and p['player'] == name] assert len(play) == 1 play = play[0] assert play['placements'] == placements[color] assert play['commitment'] == digest and play['reveal_nonce'] == r['reveal_nonces'][name] assert play['player_name'] == match[name + '_name'] assert play['event_hash'] == r['event_hash'] and play['resolved_at'] == r['resolved_at'] verified_commitments += 1 rounds.append({'round': n, 'before': count(previous), 'seeded': count(seeded), 'after': count(frames[-1]), 'collisions': collisions, 'ticks_verified': generations}) all_frames.append({'round': n, 'before': previous, 'frames': frames, 'red_placements': r['red_placements'], 'blue_placements': r['blue_placements']}) previous = frames[-1] assert len(rounds) == match['max_rounds'] == 7 assert previous == match['board'] and count(previous) == match['population'] assert len(transcript['plays']) == verified_commitments == 14 return { 'checked_utc': datetime.now(timezone.utc).isoformat(), 'match_id': match['id'], 'rules_version': rules['version'], 'rule_cases': rule_checks(), 'rounds': rounds, 'ticks_verified': sum(r['ticks_verified'] for r in rounds), 'commitments_verified': verified_commitments, 'final_population': count(previous), 'transcript_agrees_with_activity': True, 'all_assertions_passed': True, 'limits': ['Reconstructs one completed public match; did not play.', 'Public reveal commitment consistency is not proof of server fairness or player independence.', 'Event and state hash strings agree across endpoints; their hash recipe was not supplied or verified.', 'No inference of intent, general strategic strength, or optimal play.'], 'inputs_sha256': {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in [ROOT/'public-match.json', ROOT/'public-transcript.json', ROOT/'public-rules.json']} }, {'match_id': match['id'], 'width': width, 'height': height, 'red_name': match['red_name'], 'blue_name': match['blue_name'], 'rounds': all_frames} if __name__ == '__main__': report, frames = verify() (ROOT/'verification.json').write_text(json.dumps(report, indent=2)+'\n') (ROOT/'replay-frames.json').write_text(json.dumps(frames, separators=(',', ':'))+'\n') print(json.dumps(report, indent=2))