Claude Code Plugins

Community-maintained marketplace

Feedback

debugging-simulation-chaos

@tachyon-beep/skillpacks
1
0

Deterministic simulation, replay systems, debugging chaos synchronization

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name debugging-simulation-chaos
description Deterministic simulation, replay systems, debugging chaos synchronization

Debugging Simulation Chaos

Description

Master deterministic simulation, replay systems, debug visualization, chaos identification, and systematic debugging methodologies for game simulations. Build deterministic multiplayer simulations where clients stay perfectly in sync. Implement replay recording, debug visualization tools, and rigorous testing strategies. Understand butterfly effects, floating-point non-determinism, and order-dependence. Apply scientific method to debugging: reproduce, isolate, hypothesize, test, verify.

When to Use This Skill

Use this skill when debugging or implementing:

  • Multiplayer game simulations (physics, ecosystems, AI) that must stay in sync
  • Replay systems (save game state and inputs for playback)
  • Non-reproducible bugs in simulations (works sometimes, fails other times)
  • Desync issues (clients see different simulation states)
  • "Works on my machine" problems (platform-specific non-determinism)
  • Chaotic systems (small input changes cause massive output differences)
  • Emergent bugs (simulation stable for hours, then suddenly collapses)

Do NOT use this skill for:

  • Simple single-player games with no replay requirements (determinism not critical)
  • Purely cosmetic/visual bugs (animation glitches, rendering issues)
  • Network latency/packet loss problems (this is about simulation determinism, not networking)
  • Intentionally random gameplay (roguelikes where variance is desired)

Quick Start (Emergency Desync Debugging)

If you have a critical multiplayer desync bug and need to debug NOW (< 4 hours):

CRITICAL (Do This First):

  1. Build minimal replay system - Record inputs, replay identical sequence
  2. Add state checksums - Hash simulation state every frame, compare between clients
  3. Identify divergence point - Binary search to find first frame where states differ
  4. Check common culprits:
    • Unseeded Math.random() → Use seeded RNG
    • Map/Set iteration → Sort keys before iterating
    • Floating point accumulation → Use fixed-point or integer math for critical values
    • Date.now() / timing → Use simulation ticks, not wall-clock time

IMPORTANT (Within First Hour): 5. Add debug visualization (draw entity positions, show state values) 6. Write unit test that reproduces bug (given same inputs → same outputs) 7. Binary search the problem (comment out subsystems until desync disappears) 8. Add assertions (check invariants every frame, fail fast on divergence)

CAN DEFER (After Fix):

  • Full replay UI with scrubbing/playback controls
  • Property-based testing for all invariants
  • Comprehensive debug draw system
  • Performance profiling tools

Example - Emergency Desync Fix in 2 Hours:

import hashlib
import json
import random

# 1. DETERMINISTIC RNG (seed per simulation)
class SeededRandom:
    def __init__(self, seed):
        self.rng = random.Random(seed)

    def random(self):
        return self.rng.random()

    def randint(self, a, b):
        return self.rng.randint(a, b)

# 2. STATE CHECKSUM (detect divergence immediately)
def checksum_state(simulation):
    """Hash simulation state to detect desyncs"""
    # Sort all dictionaries/sets to ensure consistent order
    state = {
        'deer': sorted([(d.id, d.x, d.y, d.energy) for d in simulation.deer]),
        'wolves': sorted([(w.id, w.x, w.y, w.energy) for w in simulation.wolves]),
        'grass': simulation.grass,
        'tick': simulation.tick
    }
    state_json = json.dumps(state, sort_keys=True)
    return hashlib.sha256(state_json.encode()).hexdigest()

# 3. REPLAY SYSTEM (reproduce bug)
class ReplaySystem:
    def __init__(self):
        self.recorded_inputs = []
        self.is_recording = True
        self.is_replaying = False
        self.replay_index = 0

    def record_input(self, input_event):
        """Record input during normal gameplay"""
        if self.is_recording:
            self.recorded_inputs.append({
                'tick': input_event['tick'],
                'action': input_event['action'],
                'data': input_event['data']
            })

    def start_replay(self):
        """Start replaying recorded inputs"""
        self.is_replaying = True
        self.is_recording = False
        self.replay_index = 0

    def get_next_input(self, current_tick):
        """Get next input event if available"""
        if not self.is_replaying or self.replay_index >= len(self.recorded_inputs):
            return None

        next_input = self.recorded_inputs[self.replay_index]
        if next_input['tick'] == current_tick:
            self.replay_index += 1
            return next_input
        return None

    def save_replay(self, filename):
        """Save replay to file"""
        with open(filename, 'w') as f:
            json.dump(self.recorded_inputs, f)

    def load_replay(self, filename):
        """Load replay from file"""
        with open(filename, 'r') as f:
            self.recorded_inputs = json.load(f)

# 4. DETERMINISTIC SIMULATION
class DeterministicEcosystem:
    def __init__(self, seed):
        self.rng = SeededRandom(seed)
        self.tick = 0
        self.deer = []
        self.wolves = []
        self.grass = 1000.0
        self.replay = ReplaySystem()

        # Initialize with deterministic entity IDs
        for i in range(10):
            self.deer.append(Deer(id=i, x=i*10.0, y=0.0, energy=100.0))

    def step(self, dt=1.0):
        """One simulation step - MUST be deterministic"""
        self.tick += 1

        # Process in deterministic order (sorted by ID)
        self.deer.sort(key=lambda d: d.id)
        self.wolves.sort(key=lambda w: w.id)

        # Update grass (no randomness)
        self.grass += 5.0 * dt
        self.grass = min(1000.0, self.grass)

        # Update deer (deterministic)
        for deer in self.deer:
            # Use seeded RNG for any randomness
            if self.rng.random() < 0.1:
                deer.reproduce()

        # Checksum validation (detect desyncs immediately)
        checksum = checksum_state(self)
        print(f"Tick {self.tick}: Checksum = {checksum[:8]}")

        return checksum

# 5. UNIT TEST (reproducibility)
def test_determinism():
    """Verify simulation is deterministic"""
    # Run simulation twice with same seed
    sim1 = DeterministicEcosystem(seed=42)
    sim2 = DeterministicEcosystem(seed=42)

    for _ in range(100):
        checksum1 = sim1.step()
        checksum2 = sim2.step()

        assert checksum1 == checksum2, f"DESYNC at tick {sim1.tick}!"

    print("✓ Determinism test passed - 100 ticks identical")

# Run test
test_determinism()

This gives you:

  • ✅ Deterministic RNG (seeded per simulation)
  • ✅ State checksums (detect desyncs immediately)
  • ✅ Replay system (reproduce bugs)
  • ✅ Deterministic ordering (sort before iterating)
  • ✅ Unit test (verify determinism)

Core Concepts

1. Determinism in Simulations

What: A deterministic simulation produces the exact same outputs given the same inputs and initial state. No variance, no "close enough" - bit-for-bit identical.

Why Critical:

  • Multiplayer sync: Clients run same simulation, stay in sync
  • Replay systems: Record inputs, replay produces identical results
  • Testing: Bugs reproducible, not random "sometimes happens"
  • Debugging: Can bisect to find exact tick where bug occurs

Sources of Non-Determinism (ALL must be eliminated):

1.1 Random Number Generation

# ❌ NON-DETERMINISTIC (uses system entropy)
import random
x = random.random()  # Different on each client!

# ✅ DETERMINISTIC (seeded RNG)
class SeededRandom:
    def __init__(self, seed):
        self.rng = random.Random(seed)

    def random(self):
        return self.rng.random()

# Usage
rng = SeededRandom(seed=12345)  # Same seed on all clients
x = rng.random()  # Identical on all clients

Key Insight: Global random.seed() is NOT enough - need separate RNG instance per simulation (multiple simulations might run in same process).

1.2 Map/Set Iteration Order

# ❌ NON-DETERMINISTIC (iteration order undefined)
entities = {'wolf_1': wolf1, 'deer_2': deer2, 'wolf_3': wolf3}
for entity_id, entity in entities.items():
    entity.update()  # Order varies between runs!

# ✅ DETERMINISTIC (sort before iterating)
for entity_id in sorted(entities.keys()):
    entities[entity_id].update()  # Always same order

Languages affected: Python, JavaScript, Java (HashMap), C++ (unordered_map)

1.3 Floating Point Accumulation

# ❌ NON-DETERMINISTIC (accumulation errors differ)
position = 0.0
for _ in range(1000):
    position += 0.1  # Accumulates rounding errors differently on platforms

# ✅ DETERMINISTIC (fixed-point or integer math)
position_int = 0  # Store as integer (units = 0.01)
for _ in range(1000):
    position_int += 10  # Add 0.1 as integer
position = position_int / 100.0  # Convert back

Example: x86 vs ARM might compute 0.1 + 0.2 with different rounding. After 10,000 operations, divergence is massive.

1.4 Timing Dependencies

# ❌ NON-DETERMINISTIC (wall-clock time varies)
import time
current_time = time.time()
if current_time % 10 < 5:
    spawn_enemy()

# ✅ DETERMINISTIC (simulation ticks)
self.tick += 1
if self.tick % 100 == 0:
    spawn_enemy()

Never use: Date.now(), Time.time(), performance.now(), clock_gettime()

1.5 Multithreading/Async

# ❌ NON-DETERMINISTIC (thread scheduling varies)
import threading

def update_entity(entity):
    entity.position += entity.velocity

threads = [threading.Thread(target=update_entity, args=(e,)) for e in entities]
for t in threads:
    t.start()
for t in threads:
    t.join()

# ✅ DETERMINISTIC (single-threaded or deterministic parallel)
for entity in entities:
    entity.position += entity.velocity

Rule: Simulation must be single-threaded OR use deterministic parallel algorithms (map/reduce with associative operations).

1.6 Platform-Specific Behavior

# ❌ NON-DETERMINISTIC (math functions differ by platform)
import math
angle = math.sin(1.5)  # x86 vs ARM might differ in last bit

# ✅ DETERMINISTIC (use consistent math library)
# Use fixed-point, or test on all platforms
# Or use deterministic math library (e.g., fixed-point trigonometry)

Affected: sin, cos, sqrt (hardware implementations vary)

2. Replay Systems

What: Record all inputs to a simulation, then replay them to reproduce exact behavior.

Architecture:

Normal Gameplay:
  User Input → Simulation → Game State → Render

Replay:
  Recorded Inputs → Simulation → Game State → Render
                   (identical to original)

Key Components:

2.1 Input Recording

class ReplayRecorder:
    def __init__(self):
        self.events = []

    def record(self, tick, event_type, event_data):
        """Record an input event"""
        self.events.append({
            'tick': tick,
            'type': event_type,
            'data': event_data
        })

    def save(self, filename):
        """Save replay to file"""
        import json
        with open(filename, 'w') as f:
            json.dump({
                'version': 1,
                'seed': self.initial_seed,
                'events': self.events
            }, f)

# Usage
recorder = ReplayRecorder()
recorder.record(tick=0, event_type='spawn_deer', data={'x': 10, 'y': 20})
recorder.record(tick=15, event_type='player_hunt', data={'target_id': 5})
recorder.save('bug_reproduction.replay')

2.2 Replay Playback

class ReplayPlayer:
    def __init__(self, replay_data):
        self.events = replay_data['events']
        self.seed = replay_data['seed']
        self.event_index = 0

    def get_events_for_tick(self, tick):
        """Get all events that should occur this tick"""
        events_this_tick = []
        while self.event_index < len(self.events):
            event = self.events[self.event_index]
            if event['tick'] == tick:
                events_this_tick.append(event)
                self.event_index += 1
            elif event['tick'] > tick:
                break
            else:
                self.event_index += 1
        return events_this_tick

# Usage
import json
with open('bug_reproduction.replay', 'r') as f:
    replay_data = json.load(f)

player = ReplayPlayer(replay_data)
sim = Simulation(seed=replay_data['seed'])

for tick in range(1000):
    events = player.get_events_for_tick(tick)
    for event in events:
        sim.apply_event(event)
    sim.step()

2.3 State Snapshots

class SnapshotSystem:
    def __init__(self):
        self.snapshots = {}

    def save_snapshot(self, tick, simulation):
        """Save complete simulation state"""
        import copy
        self.snapshots[tick] = copy.deepcopy(simulation.get_state())

    def load_snapshot(self, tick):
        """Restore simulation state"""
        return self.snapshots.get(tick)

# Usage - save every 100 ticks
snapshot_system = SnapshotSystem()
if sim.tick % 100 == 0:
    snapshot_system.save_snapshot(sim.tick, sim)

# Can jump to any saved tick for debugging
sim.set_state(snapshot_system.load_snapshot(500))

Real-World Example: Rocket League saves input replay (not video) - only 100KB for 5-minute match.

3. Debug Visualization

What: Visual tools to inspect simulation state in real-time. Makes invisible bugs visible.

Key Visualizations:

3.1 Debug Draw System

class DebugDraw:
    def __init__(self):
        self.shapes = []

    def draw_circle(self, x, y, radius, color, label=None):
        """Draw debug circle"""
        self.shapes.append({
            'type': 'circle',
            'x': x, 'y': y,
            'radius': radius,
            'color': color,
            'label': label
        })

    def draw_line(self, x1, y1, x2, y2, color):
        """Draw debug line"""
        self.shapes.append({
            'type': 'line',
            'x1': x1, 'y1': y1,
            'x2': x2, 'y2': y2,
            'color': color
        })

    def draw_text(self, x, y, text, color):
        """Draw debug text"""
        self.shapes.append({
            'type': 'text',
            'x': x, 'y': y,
            'text': text,
            'color': color
        })

    def clear(self):
        """Clear all debug shapes"""
        self.shapes = []

# Usage in simulation
debug = DebugDraw()

for deer in simulation.deer:
    # Draw deer position
    debug.draw_circle(deer.x, deer.y, radius=2, color='green')

    # Draw deer state
    debug.draw_text(deer.x, deer.y + 3, f"E:{deer.energy:.0f}", color='white')

    # Draw deer seeking food
    if deer.target_grass:
        debug.draw_line(deer.x, deer.y, deer.target_grass.x, deer.target_grass.y, color='yellow')

# Render debug shapes on screen
render_debug(debug.shapes)

3.2 Population Graphs

import matplotlib.pyplot as plt

class PopulationGraph:
    def __init__(self):
        self.history = {
            'ticks': [],
            'deer': [],
            'wolves': [],
            'grass': []
        }

    def record(self, tick, deer_count, wolf_count, grass_amount):
        """Record population snapshot"""
        self.history['ticks'].append(tick)
        self.history['deer'].append(deer_count)
        self.history['wolves'].append(wolf_count)
        self.history['grass'].append(grass_amount)

    def plot(self):
        """Plot population over time"""
        plt.figure(figsize=(12, 6))
        plt.plot(self.history['ticks'], self.history['deer'], label='Deer', color='green')
        plt.plot(self.history['ticks'], self.history['wolves'], label='Wolves', color='red')
        plt.plot(self.history['ticks'], self.history['grass'], label='Grass', color='brown', alpha=0.5)
        plt.xlabel('Tick')
        plt.ylabel('Population')
        plt.title('Ecosystem Population Over Time')
        plt.legend()
        plt.grid(True)
        plt.show()

# Usage
graph = PopulationGraph()
for tick in range(1000):
    simulation.step()
    graph.record(tick, len(simulation.deer), len(simulation.wolves), simulation.grass)

# Visualize
graph.plot()

What to Look For:

  • Divergence: Two clients' graphs should be identical
  • Oscillations: Natural or chaotic?
  • Extinction events: Sudden drops to zero
  • Runaway growth: Exponential explosion

3.3 State Comparison View

class StateComparator:
    def compare_states(self, state_a, state_b):
        """Find differences between two simulation states"""
        differences = []

        # Compare entity counts
        if len(state_a['deer']) != len(state_b['deer']):
            differences.append({
                'type': 'count_mismatch',
                'entity': 'deer',
                'count_a': len(state_a['deer']),
                'count_b': len(state_b['deer'])
            })

        # Compare entity states
        for entity_id in state_a['deer']:
            if entity_id not in state_b['deer']:
                differences.append({
                    'type': 'missing_entity',
                    'entity_id': entity_id,
                    'present_in': 'A'
                })
                continue

            deer_a = state_a['deer'][entity_id]
            deer_b = state_b['deer'][entity_id]

            # Check position
            if deer_a['x'] != deer_b['x'] or deer_a['y'] != deer_b['y']:
                differences.append({
                    'type': 'position_mismatch',
                    'entity_id': entity_id,
                    'position_a': (deer_a['x'], deer_a['y']),
                    'position_b': (deer_b['x'], deer_b['y']),
                    'distance': math.sqrt((deer_a['x'] - deer_b['x'])**2 +
                                         (deer_a['y'] - deer_b['y'])**2)
                })

            # Check energy
            if deer_a['energy'] != deer_b['energy']:
                differences.append({
                    'type': 'energy_mismatch',
                    'entity_id': entity_id,
                    'energy_a': deer_a['energy'],
                    'energy_b': deer_b['energy'],
                    'delta': deer_a['energy'] - deer_b['energy']
                })

        return differences

# Usage
comparator = StateComparator()
differences = comparator.compare_states(client1_state, client2_state)

for diff in differences:
    if diff['type'] == 'count_mismatch':
        print(f"⚠️ {diff['entity']} count: {diff['count_a']} vs {diff['count_b']}")
    elif diff['type'] == 'position_mismatch':
        print(f"⚠️ Entity {diff['entity_id']} position off by {diff['distance']:.2f}")

4. Chaos Theory and Butterfly Effects

What: Chaotic systems are sensitive to initial conditions. Tiny differences (0.0001) compound into massive divergence.

Example - Butterfly Effect:

def simulate_population(initial_deer):
    deer = initial_deer
    for tick in range(1000):
        deer = deer * 1.1 - deer * deer / 500  # Logistic growth
    return deer

# Tiny difference in initial condition
result1 = simulate_population(100.0000)
result2 = simulate_population(100.0001)

print(f"Result 1: {result1:.2f}")
print(f"Result 2: {result2:.2f}")
print(f"Difference: {abs(result1 - result2):.2f}")

# Output:
# Result 1: 450.23
# Result 2: 478.91
# Difference: 28.68  (Tiny 0.0001 input → massive 28.68 output!)

Why This Matters:

  • Floating point errors accumulate
  • "Close enough" is NOT enough
  • Must be bit-for-bit identical
  • Small bugs cause massive desyncs over time

Feedback Loops Amplify Errors:

Tick 0: Deer = 100 vs 100.0001 (0.0001% error)
↓
Tick 10: Deer = 110 vs 110.001 (0.001% error) - error grew 10x
↓
Tick 100: Deer = 250 vs 252 (0.8% error) - error grew 800x
↓
Tick 1000: Deer = 450 vs 478 (6% error) - error grew 6000x

Identifying Chaotic Systems:

  • Oscillations grow over time (not dampen)
  • Small parameter changes cause wildly different outcomes
  • Sensitive to floating point precision
  • Feedback loops (output becomes input)

Testing for Chaos:

def test_sensitivity():
    """Test if system is sensitive to initial conditions"""
    base_result = simulate(initial_value=100.0)

    # Perturb initial condition by 0.01%
    perturbed_result = simulate(initial_value=100.01)

    error = abs(base_result - perturbed_result)
    error_percent = error / base_result * 100

    if error_percent > 1.0:
        print(f"⚠️ CHAOTIC SYSTEM: 0.01% input → {error_percent:.1f}% output")
        print("Requires exact determinism, no tolerance for error")
    else:
        print(f"✓ Stable system: 0.01% input → {error_percent:.3f}% output")

Decision Frameworks

Framework 1: When Does Determinism Matter?

Question: Do I need a fully deterministic simulation?

Decision Tree:

Q: Is this multiplayer with client-side prediction?
├─ YES → DETERMINISM REQUIRED
│  └─ Clients must stay in sync
│
└─ NO → Q: Do you need replay functionality?
   ├─ YES → DETERMINISM REQUIRED
   │  └─ Replays must reproduce exactly
   │
   └─ NO → Q: Are bugs reproducible?
      ├─ NO → DETERMINISM HELPFUL
      │  └─ Makes debugging much easier
      │
      └─ YES → Determinism optional
         └─ Can tolerate some randomness

Examples:

Game Type Determinism Why
Multiplayer RTS (StarCraft) REQUIRED Lockstep networking, clients must sync
Fighting game (Street Fighter) REQUIRED Rollback netcode requires determinism
Single-player with replays (Rocket League) REQUIRED Replays must be exact
Competitive esports (any) REQUIRED Replays for analysis, bug reproduction
Single-player action (Assassin's Creed) Optional Random enemy spawns OK
Roguelike (Hades) Optional Randomness is feature

Cost of Determinism:

  • Can't use platform math libraries (must use fixed-point or cross-platform lib)
  • Can't use multithreading easily (need deterministic parallel algorithms)
  • More testing required (verify on all platforms)
  • Development time: +20-30%

Benefit of Determinism:

  • Multiplayer: Minimal bandwidth (send inputs, not state)
  • Replays: 100KB instead of 1GB video
  • Debugging: Reproducible bugs, not "random" failures
  • Testing: Unit tests always pass/fail consistently

Framework 2: Replay System Complexity Level

Question: How sophisticated should my replay system be?

Level 1: Minimal (Emergency Bug Reproduction)

# Just record inputs, no UI
class MinimalReplay:
    def __init__(self):
        self.inputs = []

    def record(self, tick, action, data):
        self.inputs.append((tick, action, data))

    def save(self, filename):
        import json
        with open(filename, 'w') as f:
            json.dump(self.inputs, f)

Time to implement: 2-4 hours Use when: Need to reproduce a bug ASAP

Level 2: Basic (Playback + Validation)

# Add playback, checksums, validation
class BasicReplay:
    def record_with_checksum(self, tick, action, data, state_hash):
        self.inputs.append({
            'tick': tick,
            'action': action,
            'data': data,
            'checksum': state_hash  # Verify replay matches
        })

Time to implement: 1-2 days Use when: Need replays for testing, regression testing

Level 3: Advanced (UI + Scrubbing + Debugging)

# Add UI, frame-by-frame, jump to tick
class AdvancedReplay:
    def __init__(self):
        self.snapshots = {}  # Save state every N ticks
        self.current_tick = 0

    def jump_to_tick(self, target_tick):
        # Find nearest snapshot before target
        snapshot_tick = max([t for t in self.snapshots.keys() if t <= target_tick])
        self.restore_snapshot(snapshot_tick)

        # Simulate forward to exact tick
        while self.current_tick < target_tick:
            self.step()

    def step_forward(self):
        """Advance one tick"""
        self.current_tick += 1
        self.simulate_tick()

    def step_backward(self):
        """Go back one tick (restore from snapshot + simulate)"""
        self.jump_to_tick(self.current_tick - 1)

Time to implement: 1-2 weeks Use when: Complex debugging needs, esports replays, player-facing replays

Decision:

  • Critical bug, time-constrained → Level 1 (minimal)
  • Ongoing development, need testing → Level 2 (basic)
  • Shipped game, player replays → Level 3 (advanced)

Framework 3: Debug Visualization Strategy

Question: What should I visualize for this bug?

Debugging Workflow:

1. Understand symptoms
   ├─ Desync? → Visualize divergence points
   ├─ Crash/instability? → Visualize state over time
   ├─ Wrong behavior? → Visualize entity decisions
   └─ Performance? → Visualize performance metrics

2. Choose visualizations
   ├─ Spatial bugs → Debug draw (positions, paths, ranges)
   ├─ Temporal bugs → Graphs (populations, resources over time)
   ├─ Logic bugs → State inspector (entity internal state)
   └─ Comparison → Side-by-side diff (two clients)

3. Iterate
   └─ Add more detail to narrow down problem

Visualization Types:

Bug Type Visualization Example
Desync State checksums, comparison table "Tick 500: Client A has 50 deer, Client B has 52 deer"
Population collapse Population graph Graph shows deer plummet from 100 to 0 at tick 345
Pathfinding Debug draw lines Show entity path, target destination, obstacles
Energy/resources Bar charts over entities Show energy bars above each deer
Collision Debug circles Show collision radius, overlap detection
Chaos/butterfly Sensitivity graph "0.01% input change → 15% output change"

Implementation Priority:

  1. State checksums (detect problem exists) - 1 hour
  2. Population graphs (see trends over time) - 2 hours
  3. Debug draw (see spatial relationships) - 4 hours
  4. State inspector (examine individual entities) - 4 hours
  5. Comparison diff (find exact differences) - 8 hours

Implementation Patterns

Pattern 1: Complete Deterministic Simulation (Production-Ready)

import hashlib
import json
import random
import copy

class SeededRandom:
    """Deterministic RNG - same seed → same sequence"""
    def __init__(self, seed):
        self.rng = random.Random(seed)

    def random(self):
        return self.rng.random()

    def randint(self, a, b):
        return self.rng.randint(a, b)

    def choice(self, sequence):
        return self.rng.choice(sequence)

class Entity:
    """Base entity with deterministic ID"""
    _next_id = 0

    def __init__(self, x, y, energy):
        self.id = Entity._next_id
        Entity._next_id += 1
        self.x = x
        self.y = y
        self.energy = energy
        self.alive = True

    def to_dict(self):
        """Serialize for checksum"""
        return {
            'id': self.id,
            'x': self.x,
            'y': self.y,
            'energy': self.energy,
            'alive': self.alive
        }

class Deer(Entity):
    def __init__(self, x, y):
        super().__init__(x, y, energy=100.0)
        self.reproduction_cooldown = 0.0

    def update(self, dt, rng, grass_amount):
        """Deterministic update"""
        # Deterministic energy consumption
        self.energy -= 3.0 * dt
        self.reproduction_cooldown = max(0, self.reproduction_cooldown - dt)

        # Deterministic eating
        if grass_amount > 10:
            eat_amount = min(20.0, grass_amount)
            self.energy = min(100.0, self.energy + eat_amount * 0.5)
            grass_consumed = eat_amount
        else:
            grass_consumed = 0

        # Deterministic reproduction
        can_reproduce = (
            self.energy > 80 and
            self.reproduction_cooldown == 0 and
            rng.random() < 0.1  # Seeded randomness
        )

        if can_reproduce:
            self.energy -= 30.0
            self.reproduction_cooldown = 20.0
            return Deer(self.x + rng.random() * 4 - 2, self.y + rng.random() * 4 - 2), grass_consumed

        # Death from starvation
        if self.energy <= 0:
            self.alive = False

        return None, grass_consumed

class DeterministicSimulation:
    def __init__(self, seed):
        """Initialize with seed for determinism"""
        self.seed = seed
        self.rng = SeededRandom(seed)
        self.tick = 0
        self.grass = 1000.0
        self.deer = []
        self.wolves = []

        # Deterministic initialization
        for i in range(10):
            self.deer.append(Deer(x=float(i * 10), y=0.0))

        # State tracking
        self.checksum_history = []

    def step(self, dt=1.0):
        """One simulation step - MUST be deterministic"""
        self.tick += 1

        # 1. Grass growth (deterministic)
        self.grass += 5.0 * dt
        self.grass = min(1000.0, self.grass)

        # 2. Update entities in deterministic order (sorted by ID)
        self.deer.sort(key=lambda d: d.id)

        new_deer = []
        total_grass_consumed = 0.0

        for deer in self.deer[:]:  # Copy list to avoid mutation during iteration
            if not deer.alive:
                continue

            baby, grass_consumed = deer.update(dt, self.rng, self.grass)
            total_grass_consumed += grass_consumed

            if baby:
                new_deer.append(baby)

        # Apply grass consumption
        self.grass -= total_grass_consumed
        self.grass = max(0.0, self.grass)

        # Add babies
        self.deer.extend(new_deer)

        # Remove dead deer
        self.deer = [d for d in self.deer if d.alive]

        # 3. Compute checksum
        checksum = self.compute_checksum()
        self.checksum_history.append((self.tick, checksum))

        return checksum

    def compute_checksum(self):
        """Hash simulation state for desync detection"""
        state = {
            'tick': self.tick,
            'grass': self.grass,
            'deer': sorted([d.to_dict() for d in self.deer], key=lambda x: x['id'])
        }
        state_json = json.dumps(state, sort_keys=True)
        return hashlib.sha256(state_json.encode()).hexdigest()

    def get_state(self):
        """Get complete simulation state (for snapshots)"""
        return {
            'seed': self.seed,
            'tick': self.tick,
            'grass': self.grass,
            'deer': [copy.deepcopy(d.to_dict()) for d in self.deer],
            'rng_state': self.rng.rng.getstate()
        }

    def set_state(self, state):
        """Restore simulation state (for replay/debugging)"""
        self.seed = state['seed']
        self.tick = state['tick']
        self.grass = state['grass']

        # Restore entities
        Entity._next_id = max([d['id'] for d in state['deer']] + [0]) + 1
        self.deer = []
        for deer_data in state['deer']:
            deer = Deer(deer_data['x'], deer_data['y'])
            deer.id = deer_data['id']
            deer.energy = deer_data['energy']
            deer.alive = deer_data['alive']
            self.deer.append(deer)

        # Restore RNG state
        self.rng.rng.setstate(state['rng_state'])

# TESTING DETERMINISM
def test_determinism():
    """Verify simulation is deterministic"""
    print("Testing determinism...")

    # Run two simulations with same seed
    sim1 = DeterministicSimulation(seed=42)
    sim2 = DeterministicSimulation(seed=42)

    for i in range(100):
        checksum1 = sim1.step()
        checksum2 = sim2.step()

        if checksum1 != checksum2:
            print(f"❌ DESYNC at tick {sim1.tick}!")
            print(f"   Sim1 checksum: {checksum1[:16]}")
            print(f"   Sim2 checksum: {checksum2[:16]}")
            return False

    print(f"✅ Determinism verified - 100 ticks identical")
    return True

# Run test
test_determinism()

Key Features:

  • ✅ Seeded RNG (same seed → same random values)
  • ✅ Deterministic entity IDs
  • ✅ Sorted iteration (consistent order)
  • ✅ Fixed-point friendly (uses float but can be replaced)
  • ✅ State checksums (detect desyncs)
  • ✅ State save/load (snapshots for replay)
  • ✅ Unit test (verify determinism)

Pattern 2: Replay System with Validation

class ReplaySystem:
    def __init__(self):
        self.events = []
        self.snapshots = {}
        self.is_recording = True
        self.is_replaying = False
        self.replay_index = 0
        self.initial_state = None

    def start_recording(self, simulation):
        """Start recording from current state"""
        self.is_recording = True
        self.is_replaying = False
        self.events = []
        self.initial_state = simulation.get_state()

    def record_event(self, tick, event_type, event_data, state_checksum=None):
        """Record an event with optional checksum"""
        if not self.is_recording:
            return

        self.events.append({
            'tick': tick,
            'type': event_type,
            'data': event_data,
            'checksum': state_checksum
        })

    def save_snapshot(self, tick, simulation):
        """Save state snapshot for fast seeking"""
        self.snapshots[tick] = simulation.get_state()

    def start_replay(self):
        """Start replaying recorded events"""
        self.is_recording = False
        self.is_replaying = True
        self.replay_index = 0

    def get_events_for_tick(self, tick):
        """Get all events for this tick"""
        events = []
        while self.replay_index < len(self.events):
            event = self.events[self.replay_index]
            if event['tick'] == tick:
                events.append(event)
                self.replay_index += 1
            elif event['tick'] > tick:
                break
            else:
                self.replay_index += 1
        return events

    def validate_replay(self, simulation):
        """Verify replay matches original"""
        print("Validating replay...")

        # Restore initial state
        simulation.set_state(self.initial_state)

        # Replay all events
        for tick in range(max([e['tick'] for e in self.events]) + 1):
            events = self.get_events_for_tick(tick)
            for event in events:
                # Apply event
                if event['type'] == 'player_hunt':
                    simulation.hunt_deer(event['data']['deer_id'])

            simulation.step()

            # Validate checksum
            current_checksum = simulation.compute_checksum()
            expected_checksum = next((e['checksum'] for e in self.events if e['tick'] == tick and e['checksum']), None)

            if expected_checksum and current_checksum != expected_checksum:
                print(f"❌ Replay diverged at tick {tick}")
                print(f"   Expected: {expected_checksum[:16]}")
                print(f"   Got:      {current_checksum[:16]}")
                return False

        print("✅ Replay validated successfully")
        return True

    def save(self, filename):
        """Save replay to file"""
        with open(filename, 'w') as f:
            json.dump({
                'version': 1,
                'initial_state': self.initial_state,
                'events': self.events,
                'snapshots': self.snapshots
            }, f, indent=2)

    def load(self, filename):
        """Load replay from file"""
        with open(filename, 'r') as f:
            data = json.load(f)
            self.initial_state = data['initial_state']
            self.events = data['events']
            self.snapshots = data.get('snapshots', {})

# USAGE EXAMPLE
replay = ReplaySystem()
sim = DeterministicSimulation(seed=12345)

# Start recording
replay.start_recording(sim)

# Simulate gameplay
for tick in range(100):
    sim.step()
    checksum = sim.compute_checksum()
    replay.record_event(tick, 'tick', {}, state_checksum=checksum)

    # Save snapshot every 10 ticks
    if tick % 10 == 0:
        replay.save_snapshot(tick, sim)

# Save replay
replay.save('test_replay.json')

# Validate replay
replay.start_replay()
sim2 = DeterministicSimulation(seed=12345)
replay.validate_replay(sim2)

Pattern 3: Debug Visualization Suite

class DebugVisualization:
    def __init__(self):
        self.enabled = True
        self.draw_commands = []
        self.graphs = {}

    # Debug Draw
    def draw_circle(self, x, y, radius, color, label=None):
        """Draw debug circle"""
        if not self.enabled:
            return
        self.draw_commands.append({
            'type': 'circle',
            'x': x, 'y': y,
            'radius': radius,
            'color': color,
            'label': label
        })

    def draw_line(self, x1, y1, x2, y2, color, width=1):
        """Draw debug line"""
        if not self.enabled:
            return
        self.draw_commands.append({
            'type': 'line',
            'start': (x1, y1),
            'end': (x2, y2),
            'color': color,
            'width': width
        })

    def draw_text(self, x, y, text, color='white', size=12):
        """Draw debug text"""
        if not self.enabled:
            return
        self.draw_commands.append({
            'type': 'text',
            'x': x, 'y': y,
            'text': str(text),
            'color': color,
            'size': size
        })

    # Graphing
    def init_graph(self, graph_name, series_names):
        """Initialize a time-series graph"""
        self.graphs[graph_name] = {
            'series': {name: [] for name in series_names},
            'ticks': []
        }

    def record_values(self, graph_name, tick, **values):
        """Record values for graph"""
        if graph_name not in self.graphs:
            return

        graph = self.graphs[graph_name]
        graph['ticks'].append(tick)

        for series_name, value in values.items():
            if series_name in graph['series']:
                graph['series'][series_name].append(value)

    def plot_graph(self, graph_name):
        """Plot graph using matplotlib"""
        import matplotlib.pyplot as plt

        if graph_name not in self.graphs:
            return

        graph = self.graphs[graph_name]

        plt.figure(figsize=(12, 6))
        for series_name, values in graph['series'].items():
            plt.plot(graph['ticks'], values, label=series_name)

        plt.xlabel('Tick')
        plt.ylabel('Value')
        plt.title(f'{graph_name} Over Time')
        plt.legend()
        plt.grid(True)
        plt.show()

    # State Inspection
    def inspect_entity(self, entity, x, y):
        """Show entity state on screen"""
        self.draw_circle(entity.x, entity.y, radius=2, color='yellow')

        state_text = f"ID: {entity.id}\n"
        state_text += f"Energy: {entity.energy:.1f}\n"
        state_text += f"Pos: ({entity.x:.1f}, {entity.y:.1f})"

        self.draw_text(x, y, state_text, color='white', size=10)

    # Comparison
    def compare_states(self, state_a, state_b, label_a="Client A", label_b="Client B"):
        """Compare two simulation states"""
        print(f"\n=== State Comparison: {label_a} vs {label_b} ===")

        # Compare deer counts
        deer_a = len(state_a['deer'])
        deer_b = len(state_b['deer'])

        if deer_a != deer_b:
            print(f"❌ Deer count: {deer_a} vs {deer_b} (diff: {abs(deer_a - deer_b)})")
        else:
            print(f"✅ Deer count: {deer_a} (identical)")

        # Compare grass
        grass_a = state_a['grass']
        grass_b = state_b['grass']
        grass_diff = abs(grass_a - grass_b)

        if grass_diff > 0.01:
            print(f"❌ Grass: {grass_a:.2f} vs {grass_b:.2f} (diff: {grass_diff:.2f})")
        else:
            print(f"✅ Grass: {grass_a:.2f} (identical)")

        # Compare individual deer
        deer_ids_a = set(d['id'] for d in state_a['deer'])
        deer_ids_b = set(d['id'] for d in state_b['deer'])

        missing_in_b = deer_ids_a - deer_ids_b
        missing_in_a = deer_ids_b - deer_ids_a

        if missing_in_b:
            print(f"❌ Deer only in {label_a}: {missing_in_b}")
        if missing_in_a:
            print(f"❌ Deer only in {label_b}: {missing_in_a}")

        # Compare common deer
        common_ids = deer_ids_a & deer_ids_b
        for deer_id in common_ids:
            deer_a = next(d for d in state_a['deer'] if d['id'] == deer_id)
            deer_b = next(d for d in state_b['deer'] if d['id'] == deer_id)

            energy_diff = abs(deer_a['energy'] - deer_b['energy'])
            if energy_diff > 0.01:
                print(f"❌ Deer {deer_id} energy: {deer_a['energy']:.2f} vs {deer_b['energy']:.2f}")

    def clear(self):
        """Clear debug draw commands"""
        self.draw_commands = []

# USAGE IN SIMULATION
debug = DebugVisualization()

# Initialize graphs
debug.init_graph('population', ['deer', 'wolves', 'grass'])

# During simulation
for tick in range(1000):
    sim.step()

    # Record for graph
    debug.record_values('population', tick,
                       deer=len(sim.deer),
                       wolves=len(sim.wolves),
                       grass=sim.grass)

    # Debug draw
    for deer in sim.deer:
        debug.draw_circle(deer.x, deer.y, radius=2, color='green')
        debug.draw_text(deer.x, deer.y + 3, f"E:{deer.energy:.0f}", color='white')

    # Clear for next frame
    debug.clear()

# Plot graph
debug.plot_graph('population')

# Compare states
state1 = sim1.get_state()
state2 = sim2.get_state()
debug.compare_states(state1, state2, "Client 1", "Client 2")

Pattern 4: Assertion Framework (Fail Fast)

class SimulationAssertions:
    """Runtime checks to catch bugs immediately"""

    @staticmethod
    def assert_positive(value, name):
        """Assert value is positive"""
        assert value >= 0, f"{name} must be >= 0, got {value}"

    @staticmethod
    def assert_in_range(value, min_val, max_val, name):
        """Assert value is in range"""
        assert min_val <= value <= max_val, f"{name} must be in [{min_val}, {max_val}], got {value}"

    @staticmethod
    def assert_energy_valid(entity):
        """Assert entity energy is valid"""
        assert 0 <= entity.energy <= 100, f"Entity {entity.id} energy invalid: {entity.energy}"

    @staticmethod
    def assert_population_bounded(simulation, max_deer, max_wolves):
        """Assert populations don't explode"""
        deer_count = len(simulation.deer)
        wolf_count = len(simulation.wolves)

        assert deer_count <= max_deer, f"Deer explosion: {deer_count} > {max_deer}"
        assert wolf_count <= max_wolves, f"Wolf explosion: {wolf_count} > {max_wolves}"

    @staticmethod
    def assert_no_duplicates(entities):
        """Assert no duplicate entity IDs"""
        ids = [e.id for e in entities]
        assert len(ids) == len(set(ids)), f"Duplicate entity IDs found: {ids}"

    @staticmethod
    def assert_checksum_matches(simulation, expected_checksum):
        """Assert state checksum matches expected"""
        actual = simulation.compute_checksum()
        assert actual == expected_checksum, f"Checksum mismatch!\n  Expected: {expected_checksum[:16]}\n  Got: {actual[:16]}"

# USE IN SIMULATION
class AssertingSimulation(DeterministicSimulation):
    def step(self, dt=1.0):
        """Step with assertions"""

        # Pre-conditions
        SimulationAssertions.assert_positive(self.grass, "grass")
        SimulationAssertions.assert_no_duplicates(self.deer)

        # Run simulation
        result = super().step(dt)

        # Post-conditions
        SimulationAssertions.assert_population_bounded(self, max_deer=1000, max_wolves=200)

        for deer in self.deer:
            SimulationAssertions.assert_energy_valid(deer)

        return result

# TESTING WITH ASSERTIONS
sim = AssertingSimulation(seed=42)
for tick in range(1000):
    sim.step()  # Fails immediately if any assertion violated

Pattern 5: Property-Based Testing

# Requires: pip install hypothesis
from hypothesis import given, strategies as st
import hypothesis

class PropertyTests:
    """Property-based tests for simulation invariants"""

    @staticmethod
    @given(seed=st.integers(min_value=0, max_value=1000000))
    def test_determinism_property(seed):
        """Property: Same seed always produces same result"""
        sim1 = DeterministicSimulation(seed=seed)
        sim2 = DeterministicSimulation(seed=seed)

        for _ in range(10):
            checksum1 = sim1.step()
            checksum2 = sim2.step()
            assert checksum1 == checksum2, "Determinism violated"

    @staticmethod
    @given(seed=st.integers(min_value=0, max_value=1000000))
    def test_energy_conservation_property(seed):
        """Property: Total energy never increases without input"""
        sim = DeterministicSimulation(seed=seed)

        for _ in range(100):
            energy_before = sum(d.energy for d in sim.deer) + sim.grass
            sim.step()
            energy_after = sum(d.energy for d in sim.deer) + sim.grass

            # Energy can only decrease (metabolism) or stay same (eating just moves it)
            assert energy_after <= energy_before + 10, "Energy created from nothing!"

    @staticmethod
    @given(seed=st.integers(min_value=0, max_value=1000000))
    def test_population_bounded_property(seed):
        """Property: Populations stay within reasonable bounds"""
        sim = DeterministicSimulation(seed=seed)

        for _ in range(100):
            sim.step()
            assert len(sim.deer) <= 1000, "Deer population explosion"
            assert len(sim.deer) >= 0, "Negative deer population"

    @staticmethod
    def test_replay_reproducibility_property():
        """Property: Replay always produces identical result"""
        sim = DeterministicSimulation(seed=123)
        replay = ReplaySystem()
        replay.start_recording(sim)

        # Run simulation
        for tick in range(50):
            sim.step()
            replay.record_event(tick, 'tick', {}, state_checksum=sim.compute_checksum())

        # Replay should be identical
        assert replay.validate_replay(DeterministicSimulation(seed=123))

# RUN PROPERTY TESTS
if __name__ == '__main__':
    PropertyTests.test_determinism_property()
    PropertyTests.test_energy_conservation_property()
    PropertyTests.test_population_bounded_property()
    PropertyTests.test_replay_reproducibility_property()
    print("✅ All property tests passed")

Common Pitfalls

Pitfall 1: Unseeded Random Number Generation

The Mistake:

# ❌ Uses system entropy - different on each client
import random
if random.random() < 0.5:
    spawn_deer()

Why This Fails:

  • Each client generates different random numbers
  • Multiplayer: Instant desync
  • Testing: Bug not reproducible (different each run)

Real Example: Multiplayer ecosystem - Client A rolls 0.48 (spawns deer), Client B rolls 0.52 (doesn't spawn). Now Client A has 51 deer, Client B has 50. Error compounds over time.

The Fix:

# ✅ Seeded RNG - same seed produces same sequence
class SeededRandom:
    def __init__(self, seed):
        self.rng = random.Random(seed)

    def random(self):
        return self.rng.random()

# All clients use same seed
rng = SeededRandom(seed=12345)
if rng.random() < 0.5:
    spawn_deer()

Pitfall 2: Map/Dictionary Iteration Order

The Mistake:

# ❌ Iteration order is undefined (Python < 3.7, JavaScript, C++)
entities = {'deer_1': deer1, 'wolf_2': wolf2, 'deer_3': deer3}
for entity_id, entity in entities.items():
    entity.update()  # ORDER VARIES!

Why This Fails:

  • Iteration order affects which entity updates first
  • If entity1 eats all food before entity2 can, order matters
  • Multiplayer: Clients iterate in different orders → desync

Real Example: Deer A and Deer B both targeting same grass patch. On Client 1, Deer A updates first (eats grass). On Client 2, Deer B updates first (eats grass). Now Deer A and Deer B have different energy on each client.

The Fix:

# ✅ Sort keys before iterating
for entity_id in sorted(entities.keys()):
    entities[entity_id].update()

Pitfall 3: Floating Point Accumulation

The Mistake:

# ❌ Accumulating floating point creates divergence
position = 0.0
for _ in range(10000):
    position += 0.1  # Rounding errors compound!

Why This Fails:

  • 0.1 cannot be represented exactly in binary floating point
  • Error: ~0.0000000000000001 per addition
  • After 10,000 additions: ~0.001 total error
  • Multiplayer: Different CPU architectures round differently

Real Example: After 1 hour of gameplay (360,000 frames at 60 FPS):

  • Client A (x86): deer position = 1234.567
  • Client B (ARM): deer position = 1234.571
  • 0.004 difference → deer on different tiles → different grass eaten → DESYNC

The Fix:

# ✅ Use fixed-point or integer math for critical values
position_int = 0  # Store as integer (units = 0.001)
for _ in range(10000):
    position_int += 100  # 0.1 * 1000 = 100
position = position_int / 1000.0  # Convert to float for display

Or use integer positions:

# Position in millimeters instead of meters
x_mm = 5000  # 5 meters
y_mm = 3000  # 3 meters

# No floating point errors
x_mm += velocity_mm_per_tick

Pitfall 4: Using Wall-Clock Time

The Mistake:

# ❌ Uses real-world time - varies between clients
import time
current_time = time.time()
if current_time % 60 < 30:
    spawn_enemy()

Why This Fails:

  • Clients start at different real-world times
  • Network latency causes time skew
  • Frame rate differences mean different number of checks

Real Example:

  • Client A checks at 14:30:25 → time % 60 = 25 → spawns enemy
  • Client B checks at 14:30:26 (1 second lag) → time % 60 = 26 → spawns enemy
  • Now clients have enemies at different positions

The Fix:

# ✅ Use simulation ticks (deterministic)
self.tick += 1
if self.tick % 600 == 0:  # Every 600 ticks
    spawn_enemy()

Pitfall 5: No Replay System (Can't Reproduce Bugs)

The Mistake:

# ❌ No recording - bug happens once, can't debug
def run_simulation():
    for tick in range(10000):
        simulate_tick()
    # Bug happened at tick 7345, but you don't know that
    # And you can't reproduce it

Why This Fails:

  • Can't reproduce bug to debug
  • Can't verify fix actually works
  • Can't create regression tests
  • Can't analyze what happened

Real Example: "Deer population goes to zero after 30 minutes" - without replay, you don't know:

  • Which tick did first deer die?
  • What was energy level before death?
  • Was there grass available?
  • What were other deer doing?

The Fix:

# ✅ Record inputs for replay
replay = ReplaySystem()
replay.start_recording(sim)

for tick in range(10000):
    checksum = simulate_tick()
    replay.record_event(tick, 'tick', {}, checksum=checksum)

# Save when bug occurs
if bug_detected():
    replay.save('bug_7345.replay')
    # Now you can replay and debug at exactly tick 7345

Pitfall 6: No Debug Visualization (Debugging Blind)

The Mistake:

# ❌ No visualization - can't see what's happening
simulation.step()
# Is deer moving toward grass or away?
# Is wolf actually chasing deer?
# Why did population spike?
# NO IDEA - just looking at numbers

Why This Fails:

  • Can't see spatial relationships
  • Can't see trends over time
  • Can't spot anomalies visually
  • Waste hours guessing

Real Example: "Deer population oscillates weirdly" - without graph, you don't see:

  • Oscillation period getting shorter (approaching chaos)
  • Oscillation amplitude growing (instability)
  • Sudden spike at tick 500 (bug trigger)

The Fix:

# ✅ Visualize everything
debug = DebugVisualization()

# Draw entities
for deer in simulation.deer:
    debug.draw_circle(deer.x, deer.y, radius=2, color='green')
    debug.draw_text(deer.x, deer.y + 3, f"E:{deer.energy:.0f}")

# Graph populations
debug.record_values('population', tick, deer=len(simulation.deer))
debug.plot_graph('population')  # See trends immediately

Pitfall 7: No Assertions (Silent Failures)

The Mistake:

# ❌ Bug happens, simulation keeps running with corrupted state
deer.energy = -50  # INVALID but no error
simulation.deer.append(existing_deer)  # DUPLICATE but no error
# Corrupted state → weird behavior 1000 ticks later → impossible to debug

Why This Fails:

  • Bug symptoms appear far from root cause
  • Corrupted state compounds over time
  • Hard to trace back to original error

Real Example: Tick 100: Deer energy goes negative (bug) Tick 500: Negative energy deer reproduces (shouldn't happen) Tick 1000: Population explosion from immortal deer (visible symptom) Without assertion, you debug tick 1000, never find root cause at tick 100.

The Fix:

# ✅ Fail fast with assertions
deer.energy -= consumption
assert deer.energy >= 0, f"Deer {deer.id} energy negative: {deer.energy}"
# Fails immediately at tick 100, shows exact problem

Pitfall 8: Testing Only Happy Path (Missing Edge Cases)

The Mistake:

# ❌ Only test normal conditions
def test_ecosystem():
    sim = Simulation()
    sim.step()  # With 10 deer, 2 wolves
    assert len(sim.deer) > 0  # Passes!

# But never test:
# - What if 0 deer?
# - What if 1000 deer?
# - What if 0 grass?
# - What if deer.energy = 0.0001?

Why This Fails:

  • Edge cases trigger bugs
  • Integer overflow at extreme values
  • Division by zero when populations hit zero
  • Floating point precision at very small values

The Fix:

# ✅ Test edge cases
def test_edge_cases():
    # Zero population
    sim = Simulation()
    sim.deer = []
    sim.step()  # Should not crash

    # Huge population
    sim.deer = [Deer() for _ in range(10000)]
    sim.step()  # Should not explode

    # Extreme values
    deer = Deer()
    deer.energy = 0.0001  # Nearly dead
    deer.update(dt=1.0)  # Should handle gracefully

Pitfall 9: Not Understanding Butterfly Effects

The Mistake:

# ❌ Thinks "close enough" is fine
if abs(deer_count_a - deer_count_b) < 5:
    print("Practically the same!")  # WRONG!

# Small difference compounds exponentially

Why This Fails:

  • Chaotic systems amplify small differences
  • 0.1% error at tick 0 → 10% error at tick 1000
  • Feedback loops compound errors
  • "Close" is not deterministic

Real Example: Tick 0: 100 deer vs 100.01 deer (0.01% difference) Tick 100: 150 deer vs 151 deer (0.7% difference - grew 70x) Tick 500: 300 deer vs 325 deer (8% difference - grew 800x) Tick 1000: 450 deer vs 520 deer (15% difference - grew 1500x)

The Fix:

# ✅ Require exact match
assert deer_count_a == deer_count_b, "Must be EXACTLY equal"
# Or checksums
assert checksum_a == checksum_b, "States must be bit-for-bit identical"

Real-World Examples

Example 1: Rocket League - Replay System

Architecture: Deterministic physics + input recording

How It Works:

  • Game records: Player inputs, initial ball/car positions, RNG seed
  • File size: ~100 KB for 5-minute match (tiny!)
  • Replay: Re-runs physics simulation with recorded inputs
  • Result: Bit-for-bit identical to original match

Technical Details:

# Conceptual Rocket League replay
class RocketLeagueReplay:
    def __init__(self):
        self.initial_state = {
            'ball_pos': (0, 0, 100),
            'ball_vel': (0, 0, 0),
            'cars': [
                {'pos': (-2000, 0, 17), 'vel': (0, 0, 0)},
                {'pos': (2000, 0, 17), 'vel': (0, 0, 0)}
            ],
            'rng_seed': 42
        }
        self.inputs = []

    def record_input(self, tick, player_id, throttle, steer, boost):
        """Record player input"""
        self.inputs.append({
            'tick': tick,
            'player': player_id,
            'throttle': throttle,
            'steer': steer,
            'boost': boost
        })

    def replay(self):
        """Replay match from inputs"""
        physics = DeterministicPhysics(seed=self.initial_state['rng_seed'])
        physics.set_initial_state(self.initial_state)

        for tick in range(18000):  # 5 min @ 60fps
            # Apply player inputs
            for input_event in self.get_inputs_for_tick(tick):
                physics.apply_input(input_event)

            # Step physics (deterministic)
            physics.step(dt=1/60.0)

        return physics.get_state()

Key Lessons:

  • Deterministic physics engine required
  • Replays compress 1000x (100 KB vs 100 MB video)
  • Can analyze pro plays in extreme detail
  • Can debug "phantom hits" by inspecting exact frame

Determinism Requirements:

  • Fixed timestep (1/60 second)
  • Seeded RNG for ball bounce variance
  • Fixed-point math for position/velocity
  • Sorted player update order

Example 2: StarCraft II - Deterministic RTS

Challenge: 300+ units, complex AI, must stay in sync across clients.

Architecture: Lockstep networking + deterministic simulation

How It Works:

Tick 0:
  Client A: Send "Move unit 5 to (100, 200)"
  Client B: Send "Attack unit 12 with unit 8"

Tick 1:
  Both clients receive both commands
  Both clients execute in SAME ORDER (sorted by player ID)
  Both clients simulate identically

Result: Clients stay in sync, only send tiny command messages

Determinism Techniques:

# SC2-style deterministic update
class SC2Simulation:
    def __init__(self, seed):
        self.rng = SeededRandom(seed)
        self.units = {}
        self.tick = 0

    def step(self, commands):
        """
        Execute one tick
        commands: List of player commands (already sorted)
        """
        self.tick += 1

        # Execute commands in deterministic order
        for command in sorted(commands, key=lambda c: (c.player_id, c.unit_id)):
            self.execute_command(command)

        # Update all units in deterministic order (sorted by unit ID)
        for unit_id in sorted(self.units.keys()):
            self.units[unit_id].update(self.rng)

        # Collision detection (deterministic order)
        self.detect_collisions()

        return self.compute_checksum()

    def execute_command(self, command):
        """Execute player command deterministically"""
        unit = self.units[command.unit_id]

        if command.type == 'move':
            unit.target = command.position
        elif command.type == 'attack':
            unit.target_unit = command.target_unit_id

    def compute_checksum(self):
        """Hash game state to detect desyncs"""
        state = {
            'tick': self.tick,
            'units': sorted([u.to_dict() for u in self.units.values()],
                          key=lambda u: u['id'])
        }
        import hashlib, json
        return hashlib.sha256(json.dumps(state, sort_keys=True).encode()).hexdigest()

Desync Detection:

# Clients send checksums periodically
if self.tick % 60 == 0:  # Every 1 second
    checksum = self.compute_checksum()
    send_to_server({'type': 'checksum', 'tick': self.tick, 'checksum': checksum})

# Server compares
if client_a_checksum != client_b_checksum:
    print(f"DESYNC DETECTED at tick {tick}!")
    # Pause game, log replay, investigate

Key Lessons:

  • Sort EVERYTHING (commands, unit updates, collisions)
  • Seeded RNG for random events (critical hits)
  • Checksum validation every second
  • Pause game immediately on desync (don't let it compound)

Example 3: Age of Empires II - Floating Point Determinism

Problem: Different CPUs (x86 vs ARM) computed math differently → desyncs.

Solution: Fixed-point math for all positions and calculations.

Before (Floating Point):

// ❌ Non-deterministic across platforms
float unit_x = 100.0f;
unit_x += velocity * deltaTime;  // Different rounding on x86 vs ARM!

After (Fixed-Point):

// ✅ Deterministic - integers are always same
typedef int32_t fixed_t;  // 16.16 fixed-point
#define FIXED_SHIFT 16
#define FIXED_ONE (1 << FIXED_SHIFT)

fixed_t unit_x = 100 * FIXED_ONE;  // 100.0 in fixed-point
unit_x += velocity * deltaTime / FIXED_ONE;  // Integer math, always identical

Python Example:

class FixedPoint:
    """16.16 fixed-point number (16 bits integer, 16 bits fraction)"""
    SHIFT = 16
    ONE = 1 << SHIFT

    def __init__(self, value=0):
        if isinstance(value, float):
            self.raw = int(value * self.ONE)
        else:
            self.raw = value

    def to_float(self):
        return self.raw / self.ONE

    def __add__(self, other):
        result = FixedPoint()
        result.raw = self.raw + other.raw
        return result

    def __mul__(self, other):
        result = FixedPoint()
        # Multiply then shift to avoid overflow
        result.raw = (self.raw * other.raw) >> self.SHIFT
        return result

# Usage
x = FixedPoint(100.5)  # 100.5 in fixed-point
y = FixedPoint(2.25)
z = x + y  # 102.75
print(z.to_float())  # 102.75 - deterministic on all platforms!

Key Lessons:

  • Fixed-point eliminates platform differences
  • Slight precision loss acceptable for determinism
  • Use for: positions, velocities, health values
  • Keep floats for: rendering, UI, non-critical values

Example 4: Factorio - Deterministic Factory Simulation

Challenge: 1000s of entities (assemblers, belts, inserters) must stay in sync.

Architecture: Entity component system + deterministic update order

Determinism Strategy:

class FactorioSimulation:
    def __init__(self, seed):
        self.rng = SeededRandom(seed)
        self.entities = {}  # entity_id → entity
        self.tick = 0

    def step(self):
        """One tick - update all entities deterministically"""
        self.tick += 1

        # Update in deterministic order (sorted by entity ID)
        for entity_id in sorted(self.entities.keys()):
            entity = self.entities[entity_id]
            entity.update(self)

        # Process entity interactions (deterministic order)
        self.process_item_transfers()
        self.process_crafting()
        self.process_power_network()

    def process_item_transfers(self):
        """Move items between entities deterministically"""
        # Get all transfer requests
        transfers = []
        for entity_id in sorted(self.entities.keys()):
            entity = self.entities[entity_id]
            if hasattr(entity, 'get_transfer_requests'):
                transfers.extend(entity.get_transfer_requests())

        # Sort transfers deterministically
        transfers.sort(key=lambda t: (t.source_id, t.dest_id, t.item_type))

        # Execute transfers in order
        for transfer in transfers:
            self.execute_transfer(transfer)

Item on Belt Determinism:

class TransportBelt:
    def __init__(self, id):
        self.id = id
        self.items = []  # List of items on belt

    def update(self, simulation):
        """Move items along belt"""
        # Sort items by position for deterministic processing
        self.items.sort(key=lambda item: item.position)

        # Move each item
        for item in self.items:
            item.position += 0.03125  # Fixed speed (1/32 per tick)

            # If at end, try to transfer
            if item.position >= 1.0:
                self.try_transfer_item(item, simulation)

Key Lessons:

  • Sort entities before update
  • Sort items on belts before processing
  • Fixed speed (no floating point accumulation)
  • Deterministic collision resolution (sort by ID)

Example 5: Overwatch - Replay Debugging

Use Case: Debug "favor the shooter" issues where client sees hit but server disagrees.

Architecture: Server records authoritative replay + client replays

Debugging Process:

class OverwatchDebugger:
    def debug_hit_registration(self, bug_report):
        """Debug why shot didn't register"""

        # 1. Load server replay
        server_replay = load_replay(bug_report.match_id)
        server_replay.jump_to_tick(bug_report.tick)

        # 2. Load client replay (if available)
        client_replay = load_replay(bug_report.client_replay_id)
        client_replay.jump_to_tick(bug_report.tick)

        # 3. Compare states
        server_state = server_replay.get_state()
        client_state = client_replay.get_state()

        # 4. Visualize differences
        debug = DebugVisualization()

        # Draw server's view of player positions
        debug.draw_circle(server_state.shooter.x, server_state.shooter.y,
                         radius=1, color='blue', label='Server')
        debug.draw_circle(server_state.target.x, server_state.target.y,
                         radius=1, color='red', label='Server Target')

        # Draw client's view
        debug.draw_circle(client_state.shooter.x, client_state.shooter.y,
                         radius=1, color='cyan', label='Client')
        debug.draw_circle(client_state.target.x, client_state.target.y,
                         radius=1, color='orange', label='Client Target')

        # Draw raycast
        debug.draw_line(server_state.shooter.x, server_state.shooter.y,
                       server_state.raycast_hit.x, server_state.raycast_hit.y,
                       color='yellow')

        # 5. Identify discrepancy
        distance = math.sqrt((server_state.target.x - client_state.target.x)**2 +
                            (server_state.target.y - client_state.target.y)**2)

        print(f"Target position difference: {distance:.3f} meters")
        print(f"Network latency: {bug_report.ping}ms")
        print(f"Conclusion: Client prediction error due to {bug_report.ping}ms lag")

Key Lessons:

  • Both client and server record replays
  • Can replay side-by-side to compare
  • Visualize discrepancies (where did each think target was?)
  • Identify root cause (lag, prediction error, actual bug)

Cross-References

Use This Skill WITH:

  • ecosystem-simulation: Ecosystem desyncs, population divergence
  • physics-simulation-patterns: Physics determinism, floating point errors
  • ai-and-agent-simulation: Deterministic AI, sorted agent updates
  • crowd-simulation: Deterministic crowd movement, collision resolution
  • economic-simulation-patterns: Deterministic economy calculations

Use This Skill BEFORE:

  • multiplayer-implementation: Must ensure determinism first
  • replay-systems: Determinism required for replays
  • competitive-esports: Tournament integrity requires bug-free determinism

Broader Context:

  • systematic-debugging (superpowers): General debugging methodology
  • test-driven-development (superpowers): Write tests for determinism
  • root-cause-tracing (superpowers): Trace desync to root cause

Testing Checklist

Determinism Validation

  • Run simulation twice with same seed → identical checksums
  • Test on different platforms (x86, ARM, Windows, Linux, Mac)
  • Verify RNG is seeded per simulation (not global)
  • Check all dict/map iteration is sorted
  • Validate no Date.now() or wall-clock time usage
  • Test multithreading is deterministic (or disabled)
  • Run 10,000 ticks without divergence

Replay System

  • Record inputs and initial state
  • Replay produces identical result to original
  • Checksum validation detects any divergence
  • Can save and load replay files
  • Can jump to any tick using snapshots
  • Replay file size reasonable (< 1MB for 10 min gameplay)

Debug Visualization

  • Debug draw shows entity positions
  • Population graphs show trends over time
  • Can compare two simulation states side-by-side
  • State inspector shows entity internal values
  • Frame-by-frame stepping works
  • Visual diff highlights discrepancies

Chaos/Butterfly Effect

  • Test sensitivity to initial conditions (0.01% perturbation)
  • Identify feedback loops that amplify errors
  • Verify "close enough" is not used (must be exact)
  • Test stability under parameter changes
  • Check oscillations dampen (not grow exponentially)

Edge Cases

  • Zero population (ecosystem empty)
  • Extreme populations (10,000+ entities)
  • Zero resources (grass depleted)
  • Boundary conditions (map edges, value limits)
  • Rapid events (1000 entities spawned at once)
  • Long runtime (10+ hours without issues)

Property-Based Testing

  • Determinism property (same seed → same result)
  • Energy conservation (total energy <= initial)
  • Population bounds (never negative, never explode)
  • Replay reproducibility (replay always matches)
  • State invariants (no duplicate IDs, valid ranges)

Multiplayer Sync

  • Clients exchange checksums periodically
  • Desync detected within 1 second
  • Pause game on desync detection
  • Log replay for debugging
  • Can identify which subsystem diverged

Summary

Debugging simulation chaos requires determinism, replay systems, debug visualization, and systematic methodology.

Core Principles:

  1. Determinism is non-negotiable - Same inputs → Same outputs, always
  2. Replay everything - Can't debug what you can't reproduce
  3. Visualize everything - Make invisible bugs visible
  4. Test systematically - Property-based tests, edge cases, chaos tests
  5. Fail fast - Assertions catch bugs at root cause, not symptoms
  6. Understand chaos - Butterfly effects mean 0.0001% error → 10% divergence

Most Common Failures:

  • ❌ Unseeded RNG → desync
  • ❌ Unordered map iteration → desync
  • ❌ Floating point accumulation → desync
  • ❌ Wall-clock time → desync
  • ❌ No replay system → can't reproduce bugs
  • ❌ No visualization → debugging blind
  • ❌ "Close enough" tolerance → chaotic systems compound errors

Success Pattern:

# 1. Deterministic simulation
sim = DeterministicSimulation(seed=42)

# 2. Record replay
replay = ReplaySystem()
replay.start_recording(sim)

# 3. Add assertions
assert sim.tick >= 0
assert len(sim.deer) <= MAX_DEER

# 4. Visualize
debug = DebugVisualization()
debug.plot_graph('population')

# 5. Test determinism
assert sim1.checksum() == sim2.checksum()

Master these patterns, avoid the pitfalls, and your simulations will be deterministic, debuggable, and rock-solid reliable.