## GREEN Phase: State-Space Formulation ### Introduction: What Is State Space? **State space** is the mathematical representation of all possible configurations a system can be in. For games, this means formalizing: - **State vector** `x`: Complete description of the game at an instant - **Transition function** `f`: How state evolves over time - **State space** `X`: Set of all possible state vectors - **Trajectory**: Path through state space as game evolves **Why Formalize State?** 1. **Debugging**: "Which states lead to the bug?" 2. **Testing**: "Are all states reachable and recoverable?" 3. **Balance**: "Is the state space fair? Any deadlocks?" 4. **Optimization**: "Which path through state space is fastest?" 5. **Documentation**: "What IS the complete state of this system?" **Game Example - Tic-Tac-Toe**: ```python # State vector: 9 cells, each can be {Empty, X, O} # State space size: 3^9 = 19,683 states # But many invalid (11 X's and 0 O's is impossible) # Valid states: ~5,478 (considering turn order) class TicTacToe: def __init__(self): # State vector: 9 integers self.board = [0, 0, 0, 0, 0, 0, 0, 0, 0] # 0=Empty, 1=X, 2=O self.current_player = 1 # 1=X, 2=O def state_vector(self): # Complete state representation return (tuple(self.board), self.current_player) def transition(self, action): # Action: cell index to mark new_state = self.state_vector() # ... apply action return new_state ``` **Key Insight**: If you can't write down the complete state vector, you don't fully understand your system. ### 1. State Vectors: Defining Complete Game State A **state vector** is a mathematical representation of everything needed to simulate the game forward in time. #### What Goes in a State Vector? **Continuous Variables**: - Position: `(x, y, z)` - Velocity: `(vx, vy, vz)` - Rotation: `(roll, pitch, yaw)` or quaternion - Resources: `health`, `ammo`, `mana` **Discrete Variables**: - Flags: `is_grounded`, `is_invulnerable` - Enums: `current_animation_state` - Counts: `combo_count`, `jump_count` **Example - Fighting Game Character**: ```python class FighterState: def __init__(self): # Continuous self.position = np.array([0.0, 0.0]) # (x, y) self.velocity = np.array([0.0, 0.0]) self.health = 100.0 # Discrete self.state = State.IDLE # Enum self.frame_in_state = 0 self.facing_right = True self.hitstun_remaining = 0 self.meter = 0 # Super meter # Inputs (part of state for frame-perfect analysis) self.input_buffer = [] # Last 10 frames of inputs def to_vector(self): # Complete state as numpy array (for math operations) continuous = np.array([ self.position[0], self.position[1], self.velocity[0], self.velocity[1], self.health, float(self.meter) ]) # Discrete encoded as integers discrete = np.array([ self.state.value, self.frame_in_state, 1 if self.facing_right else 0, self.hitstun_remaining ]) return np.concatenate([continuous, discrete]) def from_vector(self, vec): # Reconstruct state from vector self.position = vec[0:2] self.velocity = vec[2:4] self.health = vec[4] self.meter = int(vec[5]) self.state = State(int(vec[6])) # ... etc ``` **Why This Matters**: - Can save/load complete state - Can hash state for duplicate detection - Can measure "distance" between states - Can visualize state in phase space #### Partial vs. Complete State **Partial State** (DANGEROUS): ```cpp // Only tracks some variables struct PlayerState { Vector3 position; float health; // Missing: velocity, animation state, input buffer, status effects }; // Problem: Can't fully restore simulation from this // Loading this state will have undefined velocity, wrong animation ``` **Complete State** (SAFE): ```cpp struct PlayerState { // Kinematics Vector3 position; Vector3 velocity; Quaternion rotation; Vector3 angular_velocity; // Resources float health; float stamina; int ammo; // Status AnimationState anim_state; int frame_in_animation; std::vector active_effects; // Input std::deque input_buffer; // Last N frames // Flags bool is_grounded; bool is_invulnerable; int jump_count; float coyote_time_remaining; }; // Can fully reconstruct simulation from this ``` **Test for Completeness**: ```python def test_state_completeness(): # Save state state1 = game.save_state() # Simulate forward 100 frames for _ in range(100): game.update() state2 = game.save_state() # Restore state1 game.load_state(state1) # Simulate forward 100 frames again for _ in range(100): game.update() state3 = game.save_state() # State2 and state3 MUST be identical (deterministic) assert state2 == state3, "State vector incomplete or non-deterministic!" ``` #### Example - RTS Tech Tree State ```python class TechTreeState: def __init__(self): # Complete state of research system self.researched = set() # Set of tech IDs self.in_progress = {} # {tech_id: progress_percent} self.available_resources = { 'minerals': 1000, 'gas': 500, 'exotic_matter': 10 } self.research_slots = 3 # How many concurrent researches def to_vector(self): # Encode as fixed-size vector for analysis # (Assume 50 possible techs, numbered 0-49) researched_vec = np.zeros(50) for tech_id in self.researched: researched_vec[tech_id] = 1.0 progress_vec = np.zeros(50) for tech_id, progress in self.in_progress.items(): progress_vec[tech_id] = progress / 100.0 resource_vec = np.array([ self.available_resources['minerals'], self.available_resources['gas'], self.available_resources['exotic_matter'], float(self.research_slots) ]) return np.concatenate([researched_vec, progress_vec, resource_vec]) def state_hash(self): # For duplicate detection in graph search return hash(( frozenset(self.researched), frozenset(self.in_progress.items()), tuple(self.available_resources.values()) )) ``` ### 2. State Transitions: How State Evolves A **state transition** is a function that maps current state to next state. **Types of Transitions**: 1. **Discrete-time**: State updates at fixed intervals (turn-based, ticks) 2. **Continuous-time**: State evolves continuously (physics, real-time) 3. **Event-driven**: State changes on specific events (triggers, collisions) #### Discrete State Transitions **Example - Puzzle Game**: ```python class SokobanState: def __init__(self, player_pos, box_positions, walls, goals): self.player = player_pos self.boxes = frozenset(box_positions) # Immutable for hashing self.walls = frozenset(walls) self.goals = frozenset(goals) def transition(self, action): """ Discrete transition function. action: 'UP', 'DOWN', 'LEFT', 'RIGHT' Returns: new_state, is_valid """ dx, dy = { 'UP': (0, -1), 'DOWN': (0, 1), 'LEFT': (-1, 0), 'RIGHT': (1, 0) }[action] new_player = (self.player[0] + dx, self.player[1] + dy) # Check collision with wall if new_player in self.walls: return self, False # Invalid move # Check collision with box if new_player in self.boxes: # Try to push box new_box_pos = (new_player[0] + dx, new_player[1] + dy) # Can't push into wall or another box if new_box_pos in self.walls or new_box_pos in self.boxes: return self, False # Valid push new_boxes = set(self.boxes) new_boxes.remove(new_player) new_boxes.add(new_box_pos) return SokobanState(new_player, new_boxes, self.walls, self.goals), True # Valid move without pushing return SokobanState(new_player, self.boxes, self.walls, self.goals), True def is_goal_state(self): # Check if all boxes on goals return self.boxes == self.goals def get_successors(self): # All valid next states successors = [] for action in ['UP', 'DOWN', 'LEFT', 'RIGHT']: new_state, valid = self.transition(action) if valid and new_state != self: successors.append((action, new_state)) return successors ``` **State Transition Graph**: ```python def build_state_graph(initial_state): """Build complete graph of reachable states.""" visited = set() queue = [initial_state] edges = [] # (state1, action, state2) while queue: state = queue.pop(0) state_hash = hash(state) if state_hash in visited: continue visited.add(state_hash) # Explore successors for action, next_state in state.get_successors(): edges.append((state, action, next_state)) if hash(next_state) not in visited: queue.append(next_state) return visited, edges # Analyze puzzle initial = SokobanState(...) states, edges = build_state_graph(initial) print(f"Puzzle has {len(states)} reachable states") print(f"State space fully explored: {len(edges)} transitions") # Check if goal is reachable goal_reachable = any(s.is_goal_state() for s in states) print(f"Puzzle solvable: {goal_reachable}") ``` #### Continuous State Transitions **Example - Racing Game Physics**: ```python class VehicleState: def __init__(self): # State vector: [x, y, vx, vy, heading, angular_vel] self.x = 0.0 self.y = 0.0 self.vx = 0.0 self.vy = 0.0 self.heading = 0.0 # radians self.angular_vel = 0.0 def state_vector(self): return np.array([self.x, self.y, self.vx, self.vy, self.heading, self.angular_vel]) def state_derivative(self, controls): """ Continuous transition: dx/dt = f(x, u) controls: (throttle, steering) """ throttle, steering = controls # Physics parameters mass = 1000.0 drag = 0.3 engine_force = 5000.0 steering_rate = 2.0 # Forces in local frame forward_force = throttle * engine_force drag_force = drag * (self.vx**2 + self.vy**2) # Convert to world frame cos_h = np.cos(self.heading) sin_h = np.sin(self.heading) fx = forward_force * cos_h - drag_force * self.vx fy = forward_force * sin_h - drag_force * self.vy # Derivatives dx_dt = self.vx dy_dt = self.vy dvx_dt = fx / mass dvy_dt = fy / mass dheading_dt = self.angular_vel dangular_vel_dt = steering * steering_rate return np.array([dx_dt, dy_dt, dvx_dt, dvy_dt, dheading_dt, dangular_vel_dt]) def integrate(self, controls, dt): """Update state using semi-implicit Euler.""" derivative = self.state_derivative(controls) # Update velocities first self.vx += derivative[2] * dt self.vy += derivative[3] * dt self.angular_vel += derivative[5] * dt # Then positions (using updated velocities) self.x += self.vx * dt self.y += self.vy * dt self.heading += self.angular_vel * dt ``` **Simulating Trajectory**: ```python def simulate_trajectory(initial_state, control_sequence, dt=0.016): """ Simulate vehicle through state space. Returns trajectory: list of state vectors. """ state = initial_state trajectory = [state.state_vector()] for controls in control_sequence: state.integrate(controls, dt) trajectory.append(state.state_vector()) return np.array(trajectory) # Example: Full throttle, no steering for 5 seconds controls = [(1.0, 0.0)] * 300 # 5 sec at 60 FPS trajectory = simulate_trajectory(VehicleState(), controls) # Analyze trajectory print(f"Final position: ({trajectory[-1][0]:.1f}, {trajectory[-1][1]:.1f})") print(f"Final velocity: {np.linalg.norm(trajectory[-1][2:4]):.1f} m/s") ``` #### Event-Driven Transitions **Example - Fighting Game State Machine**: ```python class FighterStateMachine: class State(Enum): IDLE = 0 WALKING = 1 JUMPING = 2 ATTACKING = 3 HITSTUN = 4 BLOCKING = 5 def __init__(self): self.current_state = self.State.IDLE self.frame_in_state = 0 # Transition table: (current_state, event) -> (next_state, action) self.transitions = { (self.State.IDLE, 'MOVE'): (self.State.WALKING, self.start_walk), (self.State.IDLE, 'JUMP'): (self.State.JUMPING, self.start_jump), (self.State.IDLE, 'ATTACK'): (self.State.ATTACKING, self.start_attack), (self.State.IDLE, 'HIT'): (self.State.HITSTUN, self.take_hit), (self.State.WALKING, 'STOP'): (self.State.IDLE, None), (self.State.WALKING, 'JUMP'): (self.State.JUMPING, self.start_jump), (self.State.WALKING, 'HIT'): (self.State.HITSTUN, self.take_hit), (self.State.JUMPING, 'LAND'): (self.State.IDLE, None), (self.State.JUMPING, 'HIT'): (self.State.HITSTUN, self.take_hit), (self.State.ATTACKING, 'COMPLETE'): (self.State.IDLE, None), (self.State.ATTACKING, 'HIT'): (self.State.HITSTUN, self.take_hit), (self.State.HITSTUN, 'RECOVER'): (self.State.IDLE, None), (self.State.BLOCKING, 'RELEASE'): (self.State.IDLE, None), } def handle_event(self, event): """Event-driven state transition.""" key = (self.current_state, event) if key in self.transitions: next_state, action = self.transitions[key] # Execute transition action if action: action() # Change state self.current_state = next_state self.frame_in_state = 0 return True # Event not valid for current state return False def update(self): """Frame update - may trigger automatic transitions.""" self.frame_in_state += 1 # Automatic transitions based on frame count if self.current_state == self.State.ATTACKING: if self.frame_in_state >= 30: # Attack lasts 30 frames self.handle_event('COMPLETE') if self.current_state == self.State.HITSTUN: if self.frame_in_state >= self.hitstun_duration: self.handle_event('RECOVER') # Action callbacks def start_walk(self): pass def start_jump(self): self.velocity_y = 10.0 def start_attack(self): pass def take_hit(self): self.hitstun_duration = 20 ``` **Visualizing State Machine**: ```python def generate_state_diagram(state_machine): """Generate Graphviz diagram of state transitions.""" import graphviz dot = graphviz.Digraph(comment='Fighter State Machine') # Add nodes for state in FighterStateMachine.State: dot.node(state.name, state.name) # Add edges for (from_state, event), (to_state, action) in state_machine.transitions.items(): label = event dot.edge(from_state.name, to_state.name, label=label) return dot # Visualize fsm = FighterStateMachine() diagram = generate_state_diagram(fsm) diagram.render('fighter_state_machine', view=True) ``` ### 3. Phase Space: Visualizing Dynamics **Phase space** is a coordinate system where each axis represents one state variable. A point in phase space represents a complete state. A trajectory is a path through phase space. #### 2D Phase Space Example **Platformer Jump Analysis**: ```python import matplotlib.pyplot as plt class JumpPhysics: def __init__(self): self.position_y = 0.0 self.velocity_y = 0.0 self.gravity = -20.0 self.jump_velocity = 10.0 def simulate_jump(self, duration=2.0, dt=0.016): """Simulate jump and record phase space trajectory.""" trajectory = [] # Jump! self.velocity_y = self.jump_velocity t = 0 while t < duration: # Record state trajectory.append((self.position_y, self.velocity_y)) # Integrate self.velocity_y += self.gravity * dt self.position_y += self.velocity_y * dt # Ground collision if self.position_y < 0: self.position_y = 0 self.velocity_y = 0 t += dt return np.array(trajectory) # Simulate jump = JumpPhysics() trajectory = jump.simulate_jump() # Plot phase space plt.figure(figsize=(10, 6)) plt.plot(trajectory[:, 0], trajectory[:, 1], 'b-', linewidth=2) plt.xlabel('Position Y (m)', fontsize=12) plt.ylabel('Velocity Y (m/s)', fontsize=12) plt.title('Jump Trajectory in Phase Space', fontsize=14) plt.grid(True, alpha=0.3) plt.axhline(y=0, color='r', linestyle='--', label='Zero velocity') plt.axvline(x=0, color='g', linestyle='--', label='Ground level') plt.legend() # Annotate key points plt.plot(trajectory[0, 0], trajectory[0, 1], 'go', markersize=10, label='Jump start') max_height_idx = np.argmax(trajectory[:, 0]) plt.plot(trajectory[max_height_idx, 0], trajectory[max_height_idx, 1], 'ro', markersize=10, label='Apex (vy=0)') plt.savefig('jump_phase_space.png', dpi=150) plt.show() ``` **What Phase Space Shows**: - **Closed loop**: Periodic motion (oscillation) - **Spiral inward**: Damped motion (approaches equilibrium) - **Spiral outward**: Unstable motion (energy increases) - **Straight line**: Motion in one dimension #### Attractors and Equilibria **Example - Damped Pendulum**: ```python class Pendulum: def __init__(self, theta=0.5, omega=0.0): self.theta = theta # Angle (radians) self.omega = omega # Angular velocity self.length = 1.0 self.gravity = 9.8 self.damping = 0.1 def derivatives(self): """State derivatives: d/dt [theta, omega]""" dtheta_dt = self.omega domega_dt = -(self.gravity / self.length) * np.sin(self.theta) \ - self.damping * self.omega return np.array([dtheta_dt, domega_dt]) def simulate(self, duration=10.0, dt=0.01): trajectory = [] t = 0 while t < duration: trajectory.append([self.theta, self.omega]) # RK4 integration (better than Euler for visualization) k1 = self.derivatives() theta_temp = self.theta + 0.5 * dt * k1[0] omega_temp = self.omega + 0.5 * dt * k1[1] self.theta, self.omega = theta_temp, omega_temp k2 = self.derivatives() # ... (full RK4) # Simpler: Euler deriv = self.derivatives() self.omega += deriv[1] * dt self.theta += self.omega * dt t += dt return np.array(trajectory) # Simulate multiple initial conditions plt.figure(figsize=(10, 8)) for theta0 in np.linspace(-3, 3, 10): pend = Pendulum(theta=theta0, omega=0) traj = pend.simulate(duration=20.0) plt.plot(traj[:, 0], traj[:, 1], alpha=0.6) plt.xlabel('Angle θ (rad)', fontsize=12) plt.ylabel('Angular Velocity ω (rad/s)', fontsize=12) plt.title('Damped Pendulum Phase Space\n(All trajectories spiral to origin)', fontsize=14) plt.grid(True, alpha=0.3) plt.plot(0, 0, 'ro', markersize=15, label='Attractor (equilibrium)') plt.legend() plt.savefig('pendulum_phase_space.png', dpi=150) ``` **Attractor** = state that system evolves toward - **(0, 0)** for damped pendulum: all motion eventually stops **Game Application - Combat System**: ```python # Health regeneration system class CombatState: def __init__(self, health=50, regen_rate=0): self.health = health self.regen_rate = regen_rate self.max_health = 100 def derivatives(self): # Health naturally regenerates toward max dhealth_dt = 0.5 * (self.max_health - self.health) # Exponential regen dregen_dt = -0.1 * self.regen_rate # Regen rate decays return np.array([dhealth_dt, dregen_dt]) # Simulate... # Attractor: (health=100, regen_rate=0) # Player always heals toward full health if not taking damage ``` #### Multi-Dimensional Phase Space For systems with >2 state variables, visualize projections: ```python # RTS resource system: [minerals, gas, supply] class ResourceState: def __init__(self, minerals=100, gas=0, supply=10): self.minerals = minerals self.gas = gas self.supply = supply def simulate_step(self, dt): # Workers gather resources workers = min(self.supply / 2, 10) self.minerals += workers * 0.7 * dt self.gas += workers * 0.3 * dt # Supply depot construction if self.minerals > 100: self.minerals -= 100 self.supply += 8 # 3D phase space from mpl_toolkits.mplot3d import Axes3D fig = plt.figure(figsize=(12, 9)) ax = fig.add_subplot(111, projection='3d') # Simulate trajectory state = ResourceState() trajectory = [] for _ in range(1000): trajectory.append([state.minerals, state.gas, state.supply]) state.simulate_step(0.1) trajectory = np.array(trajectory) ax.plot(trajectory[:, 0], trajectory[:, 1], trajectory[:, 2], 'b-', linewidth=2, alpha=0.7) ax.set_xlabel('Minerals') ax.set_ylabel('Gas') ax.set_zlabel('Supply') ax.set_title('RTS Resource Phase Space') plt.savefig('rts_phase_space_3d.png', dpi=150) ``` ### 4. Reachability Analysis **Reachability**: Can state B be reached from state A through valid transitions? Critical for: - Puzzle solvability - Speedrun routing - Tech tree validation - Tutorial design #### Graph-Based Reachability **Example - Tech Tree**: ```python class TechTree: def __init__(self): # Tech dependencies: tech -> list of prerequisites self.prerequisites = { 'ARCHERY': [], 'MINING': [], 'BRONZE_WORKING': ['MINING'], 'IRON_WORKING': ['BRONZE_WORKING'], 'MACHINERY': ['IRON_WORKING', 'ENGINEERING'], 'ENGINEERING': ['MATHEMATICS'], 'MATHEMATICS': [], 'GUNPOWDER': ['CHEMISTRY', 'MACHINERY'], 'CHEMISTRY': ['MATHEMATICS'], } # Tech costs self.costs = { 'ARCHERY': {'science': 50}, 'MINING': {'science': 30}, 'BRONZE_WORKING': {'science': 80, 'copper': 10}, 'IRON_WORKING': {'science': 120, 'iron': 15}, # ... etc } def can_research(self, tech, researched_techs, available_resources): """Check if tech is immediately researchable.""" # Prerequisites met? prereqs = self.prerequisites.get(tech, []) if not all(p in researched_techs for p in prereqs): return False # Resources available? cost = self.costs.get(tech, {}) for resource, amount in cost.items(): if available_resources.get(resource, 0) < amount: return False return True def reachable_techs(self, initial_researched, resources): """Find all techs reachable from current state.""" reachable = set(initial_researched) queue = list(initial_researched) # BFS through tech tree while queue: current = queue.pop(0) # Find techs unlocked by current for tech, prereqs in self.prerequisites.items(): if tech in reachable: continue # All prerequisites researched? if all(p in reachable for p in prereqs): # Resource check (simplified - assumes infinite resources) reachable.add(tech) queue.append(tech) return reachable def is_reachable(self, target_tech, initial_state): """Check if target tech is reachable from initial state.""" reachable = self.reachable_techs(initial_state, {}) return target_tech in reachable def shortest_path(self, target_tech, initial_researched): """Find shortest research path to target tech.""" queue = [(initial_researched, [])] visited = {frozenset(initial_researched)} while queue: researched, path = queue.pop(0) # Check if target reached if target_tech in researched: return path # Explore next techs to research for tech in self.prerequisites.keys(): if tech in researched: continue if self.can_research(tech, researched, {}): new_researched = researched | {tech} state_hash = frozenset(new_researched) if state_hash not in visited: visited.add(state_hash) queue.append((new_researched, path + [tech])) return None # Not reachable # Usage tree = TechTree() # Check reachability print("GUNPOWDER reachable from start:", tree.is_reachable('GUNPOWDER', set())) # Find research path path = tree.shortest_path('GUNPOWDER', set()) print(f"Shortest path to GUNPOWDER: {' → '.join(path)}") # Output: MATHEMATICS → CHEMISTRY → MINING → BRONZE_WORKING → # IRON_WORKING → ENGINEERING → MACHINERY → GUNPOWDER ``` #### Resource-Constrained Reachability **Example - Puzzle With Limited Moves**: ```python class ResourceConstrainedPuzzle: def __init__(self, initial_state, goal_state, max_moves): self.initial = initial_state self.goal = goal_state self.max_moves = max_moves def is_reachable(self): """BFS with move limit.""" queue = [(self.initial, 0)] # (state, moves_used) visited = {hash(self.initial)} while queue: state, moves = queue.pop(0) if state == self.goal: return True, moves if moves >= self.max_moves: continue # Move limit reached # Explore successors for action, next_state in state.get_successors(): state_hash = hash(next_state) if state_hash not in visited: visited.add(state_hash) queue.append((next_state, moves + 1)) return False, None def find_par_time(self): """Find minimum moves needed (for speedrun 'par' time).""" reachable, moves = self.is_reachable() if reachable: return moves return float('inf') # Example: Puzzle must be solved in ≤20 moves puzzle = ResourceConstrainedPuzzle(initial, goal, max_moves=20) solvable, optimal_moves = puzzle.is_reachable() if solvable: print(f"Puzzle solvable in {optimal_moves} moves (par: 20)") else: print("Puzzle IMPOSSIBLE with 20 move limit!") ``` #### Probabilistic Reachability **Example - Roguelike Item Spawns**: ```python class RoguelikeState: def __init__(self, player_stats, inventory, floor): self.stats = player_stats self.inventory = inventory self.floor = floor def get_successors_probabilistic(self): """Returns (next_state, probability) pairs.""" successors = [] # Room 1: 60% weapon, 40% armor if floor == 1: s1 = RoguelikeState(self.stats, self.inventory + ['weapon'], 2) s2 = RoguelikeState(self.stats, self.inventory + ['armor'], 2) successors.append((s1, 0.6)) successors.append((s2, 0.4)) # ... etc return successors def probabilistic_reachability(initial_state, goal_predicate, max_depth=10): """Calculate probability of reaching goal state.""" # State -> probability of being in that state state_probs = {hash(initial_state): 1.0} for depth in range(max_depth): new_state_probs = {} for state_hash, prob in state_probs.items(): state = # ... reconstruct state from hash # Check if goal reached if goal_predicate(state): return prob # Return probability # Propagate probability to successors for next_state, transition_prob in state.get_successors_probabilistic(): next_hash = hash(next_state) new_state_probs[next_hash] = new_state_probs.get(next_hash, 0) + \ prob * transition_prob state_probs = new_state_probs return 0.0 # Goal not reached within max_depth # Usage initial = RoguelikeState(stats={'hp': 100}, inventory=[], floor=1) goal = lambda s: 'legendary_sword' in s.inventory prob = probabilistic_reachability(initial, goal, max_depth=20) print(f"Probability of finding legendary sword: {prob*100:.1f}%") ``` ### 5. Controllability: Can Player Reach Desired States? **Controllability**: Given a desired target state, can the player reach it through available actions? Different from reachability: - **Reachability**: "Is it possible?" (binary) - **Controllability**: "Can the player do it?" (considers input constraints) #### Example - Fighting Game Combo System ```python class ComboAnalyzer: def __init__(self): # Define moves and their properties self.moves = { 'LP': {'startup': 3, 'active': 2, 'recovery': 6, 'hitstun': 12, 'damage': 10}, 'MP': {'startup': 5, 'active': 3, 'recovery': 8, 'hitstun': 15, 'damage': 20}, 'HP': {'startup': 8, 'active': 4, 'recovery': 12, 'hitstun': 20, 'damage': 40}, 'LK': {'startup': 4, 'active': 2, 'recovery': 7, 'hitstun': 10, 'damage': 15}, } def can_combo(self, move1, move2): """Check if move2 can combo after move1 connects.""" # Total frames for move1 total_frames_1 = self.moves[move1]['startup'] + \ self.moves[move1]['active'] + \ self.moves[move1]['recovery'] hitstun = self.moves[move1]['hitstun'] # Time until attacker recovers attacker_recovery = self.moves[move1]['active'] + \ self.moves[move1]['recovery'] # Time until defender recovers defender_recovery = hitstun # For combo to work: attacker must recover before defender # AND have time to execute move2 startup_2 = self.moves[move2]['startup'] # Frame advantage advantage = defender_recovery - attacker_recovery # Can we land move2 before defender recovers? return advantage >= startup_2 def find_combos(self, max_length=4): """Find all valid combo sequences.""" move_list = list(self.moves.keys()) combos = [] def search(sequence): if len(sequence) >= max_length: return if len(sequence) == 0: # Start with any move for move in move_list: search([move]) else: # Try to extend combo last_move = sequence[-1] for next_move in move_list: if self.can_combo(last_move, next_move): new_sequence = sequence + [next_move] combos.append(new_sequence) search(new_sequence) search([]) return combos def optimal_combo(self): """Find highest damage combo.""" all_combos = self.find_combos(max_length=5) best_combo = None best_damage = 0 for combo in all_combos: damage = sum(self.moves[move]['damage'] for move in combo) if damage > best_damage: best_damage = damage best_combo = combo return best_combo, best_damage # Analyze combos analyzer = ComboAnalyzer() combos = analyzer.find_combos(max_length=3) print(f"Found {len(combos)} valid combos") for combo in combos[:10]: damage = sum(analyzer.moves[m]['damage'] for m in combo) print(f" {' → '.join(combo)}: {damage} damage") optimal, damage = analyzer.optimal_combo() print(f"\nOptimal combo: {' → '.join(optimal)} ({damage} damage)") ``` #### State Controllability Matrix For linear systems, controllability can be analyzed mathematically: ```python import numpy as np class LinearSystemControllability: """ Analyze controllability of linear system: x(t+1) = A*x(t) + B*u(t) where x = state, u = control input """ def __init__(self, A, B): self.A = A # State transition matrix self.B = B # Control input matrix self.n = A.shape[0] # State dimension def controllability_matrix(self): """Compute controllability matrix [B, AB, A^2B, ..., A^(n-1)B].""" C = self.B AB = self.A @ self.B for i in range(1, self.n): C = np.hstack([C, AB]) AB = self.A @ AB return C def is_controllable(self): """System is controllable if C has full rank.""" C = self.controllability_matrix() rank = np.linalg.matrix_rank(C) return rank == self.n def min_time_to_state(self, x0, x_target, max_steps=100): """Find minimum time to reach target state (if controllable).""" # This is simplified - real implementation would use optimal control if not self.is_controllable(): return None # Not reachable # Placeholder: would use LQR or similar return max_steps # Conservative estimate # Example: 2D vehicle (position + velocity) # State: [x, vx] # Control: acceleration A = np.array([[1, 1], # x += vx (discrete time) [0, 0.95]]) # vx *= 0.95 (drag) B = np.array([[0], [1]]) # vx += acceleration system = LinearSystemControllability(A, B) print(f"System controllable: {system.is_controllable()}") # True - can reach any state through acceleration control ``` #### Practical Controllability Testing **Example - Speedrun Route Validation**: ```python class SpeedrunRoute: def __init__(self, level_data): self.level = level_data def validate_sequence(self, checkpoint_sequence): """ Check if player can actually execute the planned route. Considers input constraints (human limitations). """ issues = [] for i in range(len(checkpoint_sequence) - 1): current = checkpoint_sequence[i] next_cp = checkpoint_sequence[i + 1] # Check distance distance = self.level.distance(current, next_cp) time_available = next_cp['time'] - current['time'] # Can player physically cover this distance? max_speed = 10.0 # units/second min_time_needed = distance / max_speed if min_time_needed > time_available: issues.append({ 'segment': f"{current['name']} → {next_cp['name']}", 'problem': 'Speed required exceeds max player speed', 'required_speed': distance / time_available, 'max_speed': max_speed }) # Check if required inputs are humanly possible required_inputs = self.level.inputs_needed(current, next_cp) if self.is_tas_only(required_inputs): issues.append({ 'segment': f"{current['name']} → {next_cp['name']}", 'problem': 'Requires frame-perfect inputs (TAS only)', 'inputs': required_inputs }) return issues def is_tas_only(self, input_sequence): """Check if input sequence requires TAS (tool-assisted speedrun).""" # Frame-perfect window = TAS only for i in range(len(input_sequence) - 1): frame_gap = input_sequence[i+1]['frame'] - input_sequence[i]['frame'] if frame_gap <= 2: # 2-frame window = frame-perfect return True return False # Validate route route = SpeedrunRoute(level_data) checkpoints = [ {'name': 'Start', 'time': 0.0, 'pos': (0, 0)}, {'name': 'Skip 1', 'time': 2.5, 'pos': (30, 10)}, {'name': 'Boss', 'time': 45.0, 'pos': (200, 50)} ] issues = route.validate_sequence(checkpoints) if issues: print("Route validation FAILED:") for issue in issues: print(f" {issue['segment']}: {issue['problem']}") else: print("Route is humanly achievable!") ``` ### 6. State Machines: Finite State Automata State machines are the most common application of state-space concepts in games. #### Formal Definition **Finite State Machine (FSM)**: - `S`: Set of states - `s0`: Initial state - `Σ`: Set of input symbols (events) - `δ`: Transition function `δ: S × Σ → S` - `F`: Set of accepting (final) states (optional) #### Implementation Pattern ```cpp // C++ implementation template class StateMachine { private: State current_state; std::unordered_map, State> transitions; std::unordered_map, std::function> actions; public: StateMachine(State initial) : current_state(initial) {} void add_transition(State from, Event event, State to, std::function action = nullptr) { transitions[{from, event}] = to; if (action) { actions[{from, event}] = action; } } bool handle_event(Event event) { auto key = std::make_pair(current_state, event); if (transitions.find(key) != transitions.end()) { // Execute transition action if (actions.find(key) != actions.end()) { actions[key](); } // Change state current_state = transitions[key]; return true; } return false; // Invalid transition } State get_state() const { return current_state; } }; // Usage for enemy AI enum class AIState { PATROL, CHASE, ATTACK, FLEE }; enum class AIEvent { SEE_PLAYER, LOSE_PLAYER, IN_RANGE, OUT_RANGE, LOW_HEALTH }; StateMachine ai(AIState::PATROL); ai.add_transition(AIState::PATROL, AIEvent::SEE_PLAYER, AIState::CHASE, []() { play_sound("alert"); }); ai.add_transition(AIState::CHASE, AIEvent::IN_RANGE, AIState::ATTACK); ai.add_transition(AIState::CHASE, AIEvent::LOSE_PLAYER, AIState::PATROL); ai.add_transition(AIState::ATTACK, AIEvent::OUT_RANGE, AIState::CHASE); ai.add_transition(AIState::ATTACK, AIEvent::LOW_HEALTH, AIState::FLEE); // In game loop if (can_see_player()) { ai.handle_event(AIEvent::SEE_PLAYER); } ``` #### Hierarchical State Machines **Example - Character Controller**: ```python class HierarchicalStateMachine: """State machine with nested sub-states.""" class State: def __init__(self, name, parent=None): self.name = name self.parent = parent self.substates = {} self.current_substate = None def add_substate(self, state): self.substates[state.name] = state if self.current_substate is None: self.current_substate = state def get_full_state(self): """Return hierarchical state path.""" if self.current_substate: return [self.name] + self.current_substate.get_full_state() return [self.name] def __init__(self): # Build hierarchy self.root = self.State('Root') # Top-level states grounded = self.State('Grounded', parent=self.root) airborne = self.State('Airborne', parent=self.root) # Grounded substates idle = self.State('Idle', parent=grounded) walking = self.State('Walking', parent=grounded) running = self.State('Running', parent=grounded) grounded.add_substate(idle) grounded.add_substate(walking) grounded.add_substate(running) # Airborne substates jumping = self.State('Jumping', parent=airborne) falling = self.State('Falling', parent=airborne) airborne.add_substate(jumping) airborne.add_substate(falling) self.root.add_substate(grounded) self.root.add_substate(airborne) self.current = self.root # Check state hsm = HierarchicalStateMachine() state_path = hsm.current.get_full_state() print(' → '.join(state_path)) # Root → Grounded → Idle # Transition logic can check at any level if hsm.current.parent.name == 'Grounded': # Any grounded state pass ``` ### 7. Implementation Patterns #### Pattern 1: Immutable State for Debugging ```python from dataclasses import dataclass, replace from typing import Tuple @dataclass(frozen=True) # Immutable class GameState: player_pos: Tuple[float, float] player_health: float enemies: Tuple[Tuple[float, float], ...] # Immutable tuple frame: int def update(self, dt): """Return NEW state (don't modify self).""" new_pos = (self.player_pos[0] + dt, self.player_pos[1]) return replace(self, player_pos=new_pos, frame=self.frame + 1) # Benefits: # - Can keep state history for replay # - Thread-safe # - Easy to diff states for debugging history = [] state = GameState(player_pos=(0, 0), player_health=100, enemies=(), frame=0) for _ in range(100): state = state.update(0.016) history.append(state) # Debug: what was state at frame 50? print(history[50]) ``` #### Pattern 2: State Snapshot/Restore ```cpp // C++ save/load complete state class GameObject { public: struct Snapshot { glm::vec3 position; glm::vec3 velocity; glm::quat rotation; float health; AnimationState anim_state; int anim_frame; // ... complete state // Serialization std::vector serialize() const { std::vector data; // Pack all members into byte array // ... return data; } static Snapshot deserialize(const std::vector& data) { Snapshot snap; // Unpack from byte array // ... return snap; } }; Snapshot save_snapshot() const { return Snapshot{position, velocity, rotation, health, anim_state, anim_frame}; } void restore_snapshot(const Snapshot& snap) { position = snap.position; velocity = snap.velocity; rotation = snap.rotation; health = snap.health; anim_state = snap.anim_state; anim_frame = snap.anim_frame; // ... } }; // Rollback netcode std::deque state_history; void on_frame() { // Save state state_history.push_back(obj.save_snapshot()); // Keep last 60 frames if (state_history.size() > 60) { state_history.pop_front(); } } void rollback_to_frame(int frame) { int history_index = frame - (current_frame - state_history.size()); obj.restore_snapshot(state_history[history_index]); // Re-simulate forward for (int f = frame; f < current_frame; ++f) { obj.update(); } } ``` #### Pattern 3: State Hash for Determinism Verification ```python import hashlib import struct class DeterministicState: def __init__(self): self.position = [0.0, 0.0, 0.0] self.velocity = [0.0, 0.0, 0.0] self.health = 100.0 def state_hash(self): """Compute deterministic hash of state.""" # Pack all floats into bytes (deterministic format) data = struct.pack('7f', self.position[0], self.position[1], self.position[2], self.velocity[0], self.velocity[1], self.velocity[2], self.health) return hashlib.sha256(data).hexdigest() # Multiplayer determinism check server_state = DeterministicState() client_state = DeterministicState() # Both simulate for _ in range(100): server_state.update() client_state.update() # Compare if server_state.state_hash() == client_state.state_hash(): print("✓ Client and server in sync") else: print("✗ DESYNC DETECTED") print(f" Server: {server_state.state_hash()}") print(f" Client: {client_state.state_hash()}") ``` #### Pattern 4: State Space Search for AI ```python def state_space_search(initial_state, goal_predicate, max_depth=10): """ Generic state space search. Used for AI planning, puzzle solving, etc. """ # Priority queue: (cost, state, path) import heapq frontier = [(0, initial_state, [])] visited = {hash(initial_state)} while frontier: cost, state, path = heapq.heappop(frontier) # Goal check if goal_predicate(state): return path, cost # Depth limit if len(path) >= max_depth: continue # Expand successors for action, next_state, action_cost in state.get_successors_with_cost(): state_hash = hash(next_state) if state_hash not in visited: visited.add(state_hash) new_cost = cost + action_cost new_path = path + [action] heapq.heappush(frontier, (new_cost, next_state, new_path)) return None, float('inf') # No path found # Example: NPC pathfinding through game state space class NPCState: def __init__(self, position, has_key=False): self.position = position self.has_key = has_key def get_successors_with_cost(self): # Return (action, next_state, cost) successors = [] # Movement actions for direction in ['north', 'south', 'east', 'west']: new_pos = self.move(direction) if self.is_valid(new_pos): cost = 1.0 # Base movement cost successors.append((direction, NPCState(new_pos, self.has_key), cost)) # Interact with environment if self.can_pickup_key(): successors.append(('pickup_key', NPCState(self.position, has_key=True), 2.0)) # Picking up costs time return successors def __hash__(self): return hash((self.position, self.has_key)) # Find path initial = NPCState(position=(0, 0), has_key=False) goal = lambda s: s.position == (10, 10) and s.has_key path, cost = state_space_search(initial, goal, max_depth=30) if path: print(f"Found path: {' → '.join(path)} (cost: {cost})") ``` ### 8. Decision Framework: When to Use State-Space Analysis #### Use State-Space Analysis When: 1. **System has discrete states with complex transitions** - Example: Fighting game combos, AI behaviors - Tool: State machine, transition graph 2. **Need to verify reachability/solvability** - Example: Puzzle games, tech trees - Tool: Graph search (BFS/DFS) 3. **System has hidden deadlocks or impossible states** - Example: Tutorial soft-locks, resource starvation - Tool: Reachability analysis, state space enumeration 4. **Debugging state-dependent bugs** - Example: "Only crashes when X and Y both true" - Tool: State vector logging, state space replay 5. **Optimizing paths through state space** - Example: Speedrun routing, AI planning - Tool: A*, Dijkstra, dynamic programming 6. **Verifying determinism (multiplayer)** - Example: Rollback netcode, replay systems - Tool: State hashing, snapshot comparison 7. **Analyzing system dynamics** - Example: Economy balance, health regeneration - Tool: Phase space plots, equilibrium analysis #### DON'T Use State-Space Analysis When: 1. **State space is infinite or continuous without structure** - Example: Pure analog physics simulation - Alternative: Numerical ODE integration 2. **System is purely reactive (no memory)** - Example: Stateless particle effects - Alternative: Direct computation 3. **Emergent behavior is more important than formal guarantees** - Example: Flock of birds (individual states don't matter) - Alternative: Agent-based modeling 4. **Time/complexity budget is tight** - State-space analysis can be expensive - Alternative: Heuristics, playtesting ### 9. Testing Checklist #### State Vector Completeness - [ ] State vector includes ALL variables affecting simulation - [ ] State save/load produces identical behavior - [ ] State hash is deterministic across platforms - [ ] State vector has no platform-dependent types (double vs float) #### State Transition Validation - [ ] All state transitions are explicitly defined - [ ] No undefined transitions (what happens if event X in state Y?) - [ ] Transitions are deterministic (same input → same output) - [ ] Transition function tested on boundary cases #### Reachability - [ ] All "required" states are reachable from initial state - [ ] No deadlock states (states with no outgoing transitions) - [ ] Goal states reachable within resource constraints - [ ] Tested with automated graph search, not just manual play #### Controllability - [ ] Player can reach intended states within input constraints - [ ] No frame-perfect inputs required for normal gameplay - [ ] Tutorial states form connected path (no soft-locks) - [ ] Tested with realistic input timing #### State Machine Correctness - [ ] State machine has explicit initial state - [ ] All states have transitions for all possible events (or explicit "ignore") - [ ] No unreachable states in state machine - [ ] State machine tested with event sequences, not just individual events #### Performance - [ ] State space size is tractable (< 10^6 states if enumerating) - [ ] State transitions execute in bounded time - [ ] State hash computation is fast (< 1ms) - [ ] State save/load is fast enough for target use case #### Debugging Support - [ ] State can be serialized to human-readable format - [ ] State history can be recorded for replay - [ ] State diff tool exists for comparing states - [ ] Visualization exists for key state variables ## REFACTOR Phase: Pressure Tests ### Pressure Test 1: Fighting Game Frame Data Analysis **Scenario**: Implement combo analyzer for a 2D fighter with 20 moves per character. **Requirements**: 1. Build state-space representation of character states 2. Analyze all possible combo sequences (up to 5 hits) 3. Detect infinite combos (loops in state graph) 4. Find optimal combos (max damage for given meter) 5. Verify all states are escapable (no true infinites) **Expected Deliverables**: ```python # State representation class FighterFrame: state: Enum # IDLE, ATTACKING, HITSTUN, BLOCKSTUN, etc. frame: int hitstun: int position: tuple meter: int # Analysis functions def find_all_combos(max_length=5) -> List[Combo] def detect_infinites() -> List[ComboLoop] def optimal_combo(meter_budget=100) -> Combo def verify_escapability() -> Dict[State, bool] # Visualization def plot_state_transition_graph() def plot_combo_tree() ``` **Success Criteria**: - Finds all valid combos (match ground truth from manual testing) - Correctly identifies infinite combo if one exists - Optimal combo matches known best combo - No false positives for infinites - Visualization clearly shows state structure **Common Pitfalls**: - Forgetting juggle state (opponent in air) - Not modeling meter gain/consumption - Ignoring position (corner combos different) - Missing state: attacker recovery vs defender hitstun ### Pressure Test 2: RTS Tech Tree Validation **Scenario**: Strategy game with 60 technologies, resource constraints, and building requirements. **Requirements**: 1. Verify all end-game techs are reachable 2. Find shortest path to key techs 3. Detect resource deadlocks (can't afford required path) 4. Generate "tech tree visualization" 5. Validate no circular dependencies **State Vector**: ```python @dataclass class TechTreeState: researched: Set[str] resources: Dict[str, int] # minerals, gas, exotic_matter buildings: Set[str] # Lab, Forge, Observatory time_elapsed: float ``` **Analysis**: ```python def verify_reachability(target_tech: str) -> bool def shortest_research_path(target: str) -> List[str] def find_deadlocks() -> List[DeadlockScenario] def optimal_build_order(goals: List[str]) -> BuildOrder def detect_circular_deps() -> List[CircularDependency] ``` **Success Criteria**: - All 60 techs reachable from start (or intentionally unreachable documented) - Shortest paths match speedrun community knowledge - Finds planted deadlock (e.g., tech requires more exotic matter than exists) - Build order beats naive order by >10% - Circular dependency detector catches planted cycle **Test Deadlock**: - Tech A requires 100 Exotic Matter - Tech B requires 100 Exotic Matter - Tech C requires both A and B - Only 150 Exotic Matter in game - Deadlock: Can't get C regardless of order ### Pressure Test 3: Puzzle Game Solvability **Scenario**: Sokoban-style puzzle with 10 boxes, 10 goals, 50x50 grid. **Requirements**: 1. Determine if puzzle is solvable 2. Find solution (if solvable) 3. Compute minimum moves (par time) 4. Identify "dead states" (positions where puzzle becomes unsolvable) 5. Generate hint system **State Space**: ```python @dataclass(frozen=True) class PuzzleState: player: Tuple[int, int] boxes: FrozenSet[Tuple[int, int]] def __hash__(self): return hash((self.player, self.boxes)) def is_dead_state(self) -> bool: # Box in corner = dead # Box against wall not aligned with goal = dead pass ``` **Analysis**: ```python def is_solvable() -> bool def solve() -> List[Action] def minimum_moves() -> int def dead_state_detection() -> Set[PuzzleState] def generate_hint(current_state) -> Action ``` **Success Criteria**: - Solves solvable 10x10 puzzle in < 10 seconds - Correctly identifies unsolvable puzzle - Solution is optimal (or near-optimal, within 10%) - Dead state detection catches obvious cases (box in corner) - Hint system makes progress toward solution **Planted Issues**: - Puzzle with box pushed into corner (unsolvable) - Puzzle with 20-move solution (find it) - State space size: ~10^6 (tractable with pruning) ### Pressure Test 4: Speedrun Route Optimization **Scenario**: Platformer with 10 checkpoints, multiple paths, time/resource constraints. **Requirements**: 1. Find fastest route through checkpoints 2. Account for resource collection (must grab key for door) 3. Validate route is humanly achievable (no TAS-only tricks) 4. Generate input sequence 5. Compare to known world record route **State Space**: ```python @dataclass class SpeedrunState: checkpoint: int time: float resources: Set[str] # keys, powerups player_state: PlayerState # health, velocity, etc. ``` **Analysis**: ```python def optimal_route() -> List[Checkpoint] def validate_humanly_possible(route) -> bool def generate_input_sequence(route) -> List[Input] def compare_to_wr(route) -> Comparison def find_skips(current_route) -> List[AlternatePath] ``` **Success Criteria**: - Route time within 5% of world record (or better!) - No frame-perfect inputs required - All resource dependencies satisfied (has key when reaching door) - Input sequence executes successfully in game - Discovers known skip (if one exists in level) **Test Scenario**: - 10 checkpoints - Normal route: A→B→C→D→E (60 seconds) - Skip: A→D (requires high jump, saves 20 sec) - Optimizer should find skip ### Pressure Test 5: Character State Machine Debugging **Scenario**: Third-person action game character has bug: "sometimes get stuck in crouch animation". **Requirements**: 1. Build complete state machine from code 2. Visualize state graph 3. Find unreachable states 4. Find states with no exit transitions 5. Identify bug: missing transition **Given**: ```cpp enum State { IDLE, WALKING, RUNNING, JUMPING, CROUCHING, ROLLING }; // Transitions scattered across multiple files void handle_crouch_input() { if (state == IDLE || state == WALKING) { state = CROUCHING; } } void handle_jump_input() { if (state == IDLE || state == WALKING || state == RUNNING) { state = JUMPING; } // BUG: No check for CROUCHING state! } void update() { if (state == CROUCHING && !crouch_button_held) { // BUG: Missing transition back to IDLE! // Player stuck in CROUCHING forever } } ``` **Analysis Tasks**: ```python def extract_state_machine_from_code() -> StateMachine def visualize_state_graph() -> Graph def find_stuck_states() -> List[State] def find_missing_transitions() -> List[MissingTransition] def suggest_fix() -> List[Fix] ``` **Success Criteria**: - Extracts complete state machine (6 states) - Visualization shows all transitions - Identifies CROUCHING as stuck state - Finds missing transition: CROUCHING → IDLE on button release - Suggests fix: "Add transition CROUCHING→IDLE when !crouch_button_held" ### Pressure Test 6: System Dynamics Visualization **Scenario**: City builder game with population, food, and happiness. Playtesters report "city always dies after 10 minutes". **Requirements**: 1. Model city state space (population, food, happiness) 2. Simulate dynamics (how variables evolve) 3. Plot phase space trajectory 4. Identify attractors/equilibria 5. Find unstable regions (death spirals) **State Space**: ```python class CityState: population: float food: float happiness: float def derivatives(self): # Population growth depends on food and happiness birth_rate = 0.01 * self.happiness death_rate = 0.02 if self.food < self.population else 0.005 dpop_dt = (birth_rate - death_rate) * self.population # Food production depends on population (workers) production = 0.5 * self.population consumption = 0.7 * self.population dfood_dt = production - consumption # Happiness depends on food availability food_ratio = self.food / max(self.population, 1) dhappiness_dt = (food_ratio - 1.0) * 0.1 return [dpop_dt, dfood_dt, dhappiness_dt] ``` **Analysis**: ```python def simulate_city(initial_state, duration=600) -> Trajectory def plot_phase_space_2d(var1, var2) def find_equilibria() -> List[EquilibriumPoint] def stability_analysis(equilibrium) -> StabilityType def identify_death_spiral_regions() -> List[Region] ``` **Success Criteria**: - Simulation reproduces "city dies" behavior - Phase space plot shows trajectory spiraling to (0, 0, 0) - Identifies unstable equilibrium or no stable equilibrium - Finds threshold: if food < 0.7*population, death spiral begins - Suggests fix: "Increase food production rate or decrease consumption" **Expected Finding**: - Consumption > production for all population > 0 - No stable equilibrium exists - System always evolves toward population=0 (death) - Fix: Balance production/consumption ratio ## Summary State-space modeling provides a **formal, mathematical framework** for understanding game systems: **Core Concepts**: 1. **State Vector**: Complete description of system at instant 2. **State Transitions**: Functions mapping state → next state 3. **Phase Space**: Geometric representation of state dynamics 4. **Reachability**: "Can we get from A to B?" 5. **Controllability**: "Can the player get from A to B?" **When to Use**: - Debugging state-dependent bugs - Verifying puzzle solvability - Analyzing fighting game combos - Optimizing speedrun routes - Validating tech trees - Implementing state machines **Key Benefits**: - **Verification**: Prove properties (all states reachable, no deadlocks) - **Optimization**: Find optimal paths through state space - **Debugging**: Understand what states lead to bugs - **Documentation**: Formalize "what is the state of this system?" **Practical Patterns**: - Immutable states for debugging - State snapshot/restore for rollback - State hashing for determinism checks - State-space search for AI/planning **Remember**: If you can't write down the complete state vector, you don't fully understand your system. State-space formalism forces clarity and reveals hidden assumptions. ## Further Reading **Books**: - *Introduction to the Theory of Computation* by Michael Sipser (state machines) - *Nonlinear Dynamics and Chaos* by Steven Strogatz (phase space, attractors) - *Artificial Intelligence: A Modern Approach* by Russell & Norvig (state-space search) **Papers**: - "Formal Methods for Game Design" (VerifyThis competition) - "State Space Search for Game AI" (AI Game Programming Wisdom) **Game-Specific**: - Fighting game frame data sites (analyze real state machines) - Speedrun wikis (state-space optimization in practice) - Puzzle game solvers (reachability analysis) ## Glossary - **State Vector**: Complete mathematical description of system state - **State Space**: Set of all possible states - **Trajectory**: Path through state space over time - **Phase Space**: Coordinate system where axes are state variables - **Attractor**: State toward which system evolves - **Equilibrium**: State where system doesn't change - **Reachability**: Whether state B can be reached from state A - **Controllability**: Whether player can steer system to desired state - **Transition Function**: Maps (current state, input) → next state - **FSM**: Finite State Machine - discrete states with transition rules - **Deterministic**: Same input always produces same output - **Deadlock**: State with no outgoing transitions - **Dead State**: State from which goal is unreachable