Claude Code Plugins

Community-maintained marketplace

Feedback

physics-simulation-patterns

@tachyon-beep/skillpacks
1
0

Rigid body dynamics, soft bodies, cloth, fluids, deterministic physics

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 physics-simulation-patterns
description Rigid body dynamics, soft bodies, cloth, fluids, deterministic physics

Physics Simulation Patterns

Description

Master rigid body dynamics, soft body simulation, cloth, fluids, and vehicle physics for real-time game engines. Apply fixed timestep integration, continuous collision detection, and deterministic simulation patterns to avoid physics explosions, tunneling, and multiplayer desyncs.

When to Use This Skill

Use this skill when implementing or debugging:

  • Vehicle physics (cars, boats, aircraft)
  • Character physics (ragdolls, dynamic movement)
  • Destructible environments (debris, particles)
  • Cloth and soft body simulation
  • Fluid dynamics for games
  • Multiplayer physics synchronization
  • Any real-time physics simulation requiring stability and determinism

Do NOT use this skill for:

  • Basic kinematic movement (simple position/velocity updates)
  • Pure animation systems without physics
  • UI animations or tweens
  • Turn-based games without real-time physics

Quick Start (Time-Constrained Implementation)

If you need working physics quickly (< 4 hours), follow this priority order:

CRITICAL (Never Skip):

  1. Fixed timestep: Use engine's fixed update (Unity: FixedUpdate(), Unreal: auto-handled)
  2. Use engine built-ins: Unity WheelCollider, Unreal WheeledVehicleMovementComponent
  3. Enable CCD: For objects faster than 20 m/s (prevents tunneling through walls)
  4. Semi-implicit integration: Engine default (don't change it)

IMPORTANT (Strongly Recommended): 5. Lower center of mass for vehicles (improves stability) 6. Test at different frame rates (30 FPS and 144 FPS should behave identically) 7. Add velocity clamping for safety (prevent physics explosions)

CAN DEFER (Optimize Later):

  • Custom tire friction models (use engine defaults first)
  • Advanced aerodynamics and downforce
  • Detailed damage and deformation systems
  • Performance optimizations (if meeting target frame rate)

Example - Unity Vehicle in 30 Minutes:

// 1. Add WheelColliders to wheel positions
// 2. Configure in FixedUpdate():
void FixedUpdate() {  // ← Fixed timestep automatically
    wheelFL.motorTorque = Input.GetAxis("Vertical") * 1500f;
    wheelFR.motorTorque = Input.GetAxis("Vertical") * 1500f;
    wheelFL.steerAngle = Input.GetAxis("Horizontal") * 30f;
    wheelFR.steerAngle = Input.GetAxis("Horizontal") * 30f;
}

// 3. Enable CCD on Rigidbody:
GetComponent<Rigidbody>().collisionDetectionMode = CollisionDetectionMode.Continuous;

// 4. Lower center of mass:
GetComponent<Rigidbody>().centerOfMass = new Vector3(0, -0.5f, 0);

This gives you functional vehicle physics. Refine later based on feel and performance.


Core Concepts

1. Physics Integration Methods

Physics integration updates object positions based on forces. The method choice determines stability, accuracy, and performance.

Euler Integration (Explicit/Forward Euler):

# Simple but UNSTABLE for most game physics
velocity += acceleration * dt
position += velocity * dt

# Problem: Energy accumulation
# At high speeds or large dt, objects gain energy and "explode"

Semi-Implicit Euler (Symplectic Euler):

# Better stability - use for most game physics
velocity += acceleration * dt
position += velocity * dt  # Uses UPDATED velocity

# Advantage: Conserves energy better, stable for games
# This is what Unity/Unreal use internally

Verlet Integration:

# Position-based, good for constraints
new_position = 2 * position - previous_position + acceleration * dt * dt
previous_position = position
position = new_position

# Advantage: Stable, easy constraints, no explicit velocity
# Used in: Cloth simulation, rope physics, soft bodies

Runge-Kutta (RK4):

# High accuracy but expensive
# 4 function evaluations per timestep
k1 = f(t, y)
k2 = f(t + dt/2, y + dt/2 * k1)
k3 = f(t + dt/2, y + dt/2 * k2)
k4 = f(t + dt, y + dt * k3)
y_next = y + dt/6 * (k1 + 2*k2 + 2*k3 + k4)

# Use ONLY when: Accuracy critical, performance not (orbital mechanics, space sims)
# Most games: Semi-implicit Euler is better trade-off

Decision Framework:

  • Rigid bodies (vehicles, debris): Semi-Implicit Euler + Fixed Timestep
  • Cloth/rope/chains: Verlet Integration + Position-based constraints
  • Orbital mechanics: RK4 or higher-order methods
  • Never use: Explicit Euler (unstable)

2. Fixed Timestep vs Variable Timestep

The Problem: Variable timestep (using raw frame delta time) causes:

  • Frame-rate dependent physics
  • Instability at low frame rates
  • Non-determinism (different results on different machines)
  • Multiplayer desyncs

The Solution: Fixed Timestep with Accumulator:

# ALWAYS use this pattern for game physics
FIXED_TIMESTEP = 1.0 / 60.0  # 60 Hz physics (16.67ms)
accumulator = 0.0

def game_loop():
    global accumulator
    frame_time = get_delta_time()

    # Clamp to prevent spiral of death
    if frame_time > 0.25:
        frame_time = 0.25  # Max 250ms (4 FPS minimum)

    accumulator += frame_time

    # Run physics in fixed steps
    while accumulator >= FIXED_TIMESTEP:
        physics_update(FIXED_TIMESTEP)  # Always same dt
        accumulator -= FIXED_TIMESTEP

    # Interpolate rendering between physics states
    alpha = accumulator / FIXED_TIMESTEP
    render_interpolated(alpha)

def render_interpolated(alpha):
    # Smooth visuals between physics steps
    interpolated_pos = previous_pos + (current_pos - previous_pos) * alpha
    draw_at_position(interpolated_pos)

Why This Works:

  • Physics always runs at 60 Hz (consistent)
  • Fast machines: Multiple render frames per physics step
  • Slow machines: Multiple physics steps per render frame
  • Deterministic: Same inputs produce same outputs
  • Interpolation: Smooth visuals even at low frame rates

Common Mistake - Variable Timestep Scaling:

# ❌ WRONG - Still frame-rate dependent
dt = get_delta_time()
velocity += acceleration * dt * 60  # "Scale to 60 FPS"

# Problem: Integration errors still vary with dt
# Physics behaves differently at different frame rates

Unity Example:

// Unity provides this via FixedUpdate()
void FixedUpdate() {
    // Always runs at Time.fixedDeltaTime (default 0.02 = 50 Hz)
    // Use for all physics operations
    rb.AddForce(force);
}

void Update() {
    // Variable timestep - use for input, rendering
    // NEVER use for physics calculations
}

3. Continuous Collision Detection (CCD)

The Problem: Tunneling:

Frame 1: [Bullet]    |Wall|
Frame 2:            |Wall|    [Bullet]
         ^ Bullet passed through wall between frames!

At high velocities, discrete collision checks miss collisions. For a 200 mph car (89 m/s) at 60 FPS, the car moves 1.48 meters per frame - can easily phase through walls.

Solution 1: Conservative Advancement:

def conservative_advancement(start_pos, end_pos, obstacles):
    """Move in small steps until collision"""
    current_pos = start_pos
    direction = (end_pos - start_pos).normalized()
    remaining_distance = (end_pos - start_pos).length()

    while remaining_distance > 0:
        # Find distance to nearest obstacle
        safe_distance = min_distance_to_obstacles(current_pos, obstacles)

        # Move by safe distance (slightly less for safety margin)
        step = min(safe_distance * 0.9, remaining_distance)
        current_pos += direction * step
        remaining_distance -= step

        if safe_distance < EPSILON:
            return current_pos, True  # Collision detected

    return current_pos, False

Solution 2: Swept Collision Detection:

def swept_sphere_vs_plane(sphere_center, sphere_radius, velocity, plane):
    """Check collision over movement path"""
    # Ray from sphere center along velocity
    ray_start = sphere_center - plane.normal * sphere_radius
    ray_direction = velocity.normalized()
    ray_length = velocity.length()

    # Intersect ray with plane
    t = ray_plane_intersection(ray_start, ray_direction, plane)

    if 0 <= t <= ray_length:
        # Collision occurs during this frame
        collision_time = t / ray_length  # 0 to 1
        collision_point = ray_start + ray_direction * t
        return True, collision_time, collision_point

    return False, None, None

Solution 3: Speculative Contacts (Modern Engines):

def speculative_contacts(body, dt):
    """Predict and prevent tunneling before it happens"""
    # Expand collision shape based on velocity
    velocity_magnitude = body.velocity.length()
    expansion = velocity_magnitude * dt

    # Use expanded AABB for collision detection
    expanded_bounds = body.bounds.expand(expansion)

    # Check collisions with expanded bounds
    contacts = check_collisions(expanded_bounds)

    # Apply contact constraints to prevent penetration
    for contact in contacts:
        apply_contact_constraint(body, contact, dt)

When to Use CCD:

  • Always: Bullets, projectiles, fast vehicles (>50 m/s)
  • Usually: Player characters (falling at terminal velocity)
  • Sometimes: Debris, particles (if important)
  • Never: Static objects, slow-moving props

Unity Example:

Rigidbody rb = GetComponent<Rigidbody>();

// For very fast objects (bullets)
rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;

// For fast objects that hit static geometry (vehicles)
rb.collisionDetectionMode = CollisionDetectionMode.Continuous;

// Default (fast but can tunnel)
rb.collisionDetectionMode = CollisionDetectionMode.Discrete;

4. Deterministic Physics

Why Determinism Matters:

  • Multiplayer: Clients must compute same physics results
  • Replays: Must reproduce exact gameplay
  • Rollback netcode: Must rewind and re-simulate
  • Testing: Must be reproducible for bug fixing

Sources of Non-Determinism:

  1. Floating-Point Non-Determinism:
# ❌ Can produce different results on different CPUs
a = 0.1 + 0.2  # Might be 0.30000000000000004 or slightly different

# ✅ Use fixed-point math for critical calculations
FIXED_POINT_SCALE = 1000
a = (1 * FIXED_POINT_SCALE + 2 * FIXED_POINT_SCALE) // 10  # Exact
  1. Iteration Order Non-Determinism:
# ❌ Dictionary/set iteration order undefined
for obj in physics_objects:  # If physics_objects is a dict/set
    obj.update()

# ✅ Sort objects by unique ID for consistent order
sorted_objects = sorted(physics_objects, key=lambda obj: obj.id)
for obj in sorted_objects:
    obj.update()
  1. Multi-Threading Non-Determinism:
# ❌ Parallel physics updates can happen in any order
parallel_for(objects, lambda obj: obj.update())

# ✅ Either: Single-threaded physics updates
for obj in objects:
    obj.update()

# ✅ Or: Deterministic parallel scheduling (island-based)
islands = partition_into_islands(objects)  # No dependencies between islands
for island in islands:
    parallel_for(island, lambda obj: obj.update())  # Order within island fixed
  1. Random Number Non-Determinism:
# ❌ Different seed every run
debris_velocity = random.uniform(-10, 10)

# ✅ Seeded RNG, advance deterministically
rng = Random(seed=12345)
debris_velocity = rng.uniform(-10, 10)

Deterministic Physics Checklist:

  • Fixed timestep (never variable)
  • Sorted iteration order (by ID or consistent key)
  • Single-threaded physics or deterministic parallel
  • Seeded random number generators
  • Avoid floating-point math in critical paths (or use fixed-point)
  • Consistent compiler flags (same floating-point mode)
  • No OS/hardware dependencies (timer resolution, etc.)

Unreal Engine Example:

// Enable deterministic physics in Unreal
// Project Settings > Physics > Simulation
bEnableEnhancedDeterminism = true;

// Use fixed timestep
FixedDeltaTime = 0.0333f;  // 30 Hz for determinism

// Disable async physics
bTickPhysicsAsync = false;

Decision Frameworks

Framework 1: Full Physics vs Kinematic Control

Use Full Physics Simulation When:

  • Realistic force-based interactions required (collisions, explosions)
  • Unpredictable outcomes are desirable (debris, ragdolls)
  • Complex constraint solving needed (vehicles, joints)
  • Object interactions with environment are core gameplay

Use Kinematic Control When:

  • Precise, predictable movement required (elevators, cutscenes)
  • Performance is critical (hundreds of objects)
  • Simple animations are sufficient
  • No force-based interactions needed

Hybrid Approach (Common):

class Vehicle:
    def __init__(self):
        self.mode = "kinematic"  # Start kinematic
        self.rigidbody = None

    def transition_to_physics(self):
        """Switch to physics when player takes control"""
        self.mode = "physics"
        self.rigidbody = create_rigidbody(self)
        self.rigidbody.velocity = self.kinematic_velocity

    def transition_to_kinematic(self):
        """Switch to kinematic for cutscenes"""
        self.kinematic_velocity = self.rigidbody.velocity
        destroy_rigidbody(self.rigidbody)
        self.mode = "kinematic"

    def update(self, dt):
        if self.mode == "physics":
            # Full physics simulation
            self.apply_forces(dt)
        else:
            # Direct position control
            self.position += self.kinematic_velocity * dt

Examples:

  • Racing game car: Physics (forces, suspension, tire grip)
  • Racing game camera: Kinematic (smooth following, no physics)
  • Destructible building: Physics after destruction trigger
  • Elevator: Kinematic (predictable timing)
  • Ragdoll: Kinematic (alive) → Physics (dead)

Framework 2: Sub-Stepping Decision

Sub-stepping: Running multiple small physics steps per frame for stability.

def physics_update(dt):
    SUB_STEPS = 4
    sub_dt = dt / SUB_STEPS

    for _ in range(SUB_STEPS):
        # Smaller timesteps = more stable
        integrate_forces(sub_dt)
        solve_constraints(sub_dt)
        integrate_velocities(sub_dt)

Use Sub-Stepping When:

  • Complex constraints (vehicle suspension, rope physics)
  • Stiff springs (high spring constants)
  • High-speed collisions requiring accuracy
  • Soft body or cloth simulation

Skip Sub-Stepping When:

  • Simple rigid bodies with no constraints
  • Performance is critical
  • Objects are slow-moving
  • Using very small base timestep already (>120 Hz)

Practical Guidelines:

  • Most rigid bodies: 1 step (60 Hz fixed timestep sufficient)
  • Vehicles with suspension: 2-4 steps
  • Cloth simulation: 4-10 steps
  • Rope/chain physics: 8-16 steps

Unity Example:

// Project Settings > Time > Fixed Timestep
Time.fixedDeltaTime = 0.0166f;  // 60 Hz base

// Physics Settings > Solver Iteration Count
Physics.defaultSolverIterations = 6;        // Velocity
Physics.defaultSolverVelocityIterations = 1;  // Position

// For complex vehicle physics, increase iterations
Rigidbody rb = GetComponent<Rigidbody>();
rb.solverIterations = 12;
rb.solverVelocityIterations = 2;

Framework 3: Real Physics vs Faked Physics

Real Physics (Forces, constraints, integration):

# Particle with gravity
particle.force += Vector3(0, -9.8 * particle.mass, 0)
particle.velocity += (particle.force / particle.mass) * dt
particle.position += particle.velocity * dt

Faked Physics (Direct manipulation):

# Fake gravity for particles (much faster)
particle.position.y -= 9.8 * dt * dt / 2
particle.velocity.y -= 9.8 * dt

# Simple ballistic trajectory (no integration needed)
t = elapsed_time
particle.position = start_position + velocity * t + 0.5 * gravity * t * t

Use Real Physics When:

  • Interactions with other physics objects (collisions, joints)
  • Forces are complex or change dynamically
  • Constraints must be solved (rope, springs)
  • Accuracy is critical (gameplay-affecting)

Use Faked Physics When:

  • Visual effects only (sparks, dust, blood splatter)
  • No interactions with other objects
  • Performance is critical (thousands of particles)
  • Simple, predictable motion (arcs, bounces)

Performance Comparison:

  • Real physics: ~100-1000 objects at 60 FPS
  • Faked physics: ~10,000-100,000 particles at 60 FPS

Example - Explosion Debris:

# GOOD: Nearby debris (10-20 pieces) - real physics
for debris in nearby_debris:
    debris.rigidbody.add_force(explosion_force)
    debris.rigidbody.add_torque(random_spin)

# GOOD: Distant particles (1000s) - faked physics
for particle in distant_particles:
    particle.velocity = (particle.position - explosion_center).normalized() * speed
    particle.lifetime = 2.0
    # Simple ballistic arc, no collision detection

Framework 4: When to Use Specific Integration Methods

Scenario Integration Method Reason
Rigid body vehicles Semi-Implicit Euler Stable, fast, energy-conserving
Cloth/fabric Verlet + PBD Position-based constraints, stable
Rope/chains Verlet + PBD Easy to constrain, no velocity needed
Space/orbital sim RK4 or higher High accuracy for long-term stability
Soft bodies Position-Based Dynamics Unconditionally stable
Ragdolls Semi-Implicit Euler Fast, good enough for visual quality
Particle effects Explicit Euler Fast, accuracy doesn't matter
Fluids (SPH) Leapfrog or Verlet Symplectic, energy-conserving

Framework 5: Multiplayer Physics Architecture

Choose your multiplayer architecture based on whether physics is deterministic:

Deterministic Physics + Low Latency RequiredRollback Netcode:

  • Examples: Fighting games (Street Fighter, Guilty Gear), competitive platformers
  • Re-simulates past frames when late inputs arrive
  • Requires: Bit-perfect determinism, fast physics (must re-run multiple frames)
  • Advantages: Feels instant (no input delay), handles variable latency well
  • Disadvantages: Complex to implement, requires deterministic physics

Deterministic Physics + Turn-Based or Slow PaceLock-Step:

  • Examples: RTS games (StarCraft), turn-based strategy
  • All clients wait for all inputs before advancing
  • Requires: Determinism, same simulation order on all clients
  • Advantages: Simple, guaranteed sync, minimal bandwidth
  • Disadvantages: Input lag = highest player latency

Non-Deterministic PhysicsServer-Authoritative:

  • Examples: Most MMOs, open-world games, battle royale
  • Server runs authoritative physics, sends snapshots to clients
  • Clients predict locally, reconcile with server updates
  • Advantages: No determinism required, easier to implement, cheat-resistant
  • Disadvantages: Bandwidth intensive, visible corrections/rubber-banding

Decision Table:

Requirement Architecture Implementation Time
Physics already deterministic + competitive Rollback 2-4 weeks
Physics already deterministic + slow-paced Lock-step 1-2 weeks
Physics NOT deterministic Server-authoritative 1-3 weeks
Need it working in < 1 week Server-authoritative Fastest
Converting non-deterministic to deterministic Refactor first 1-2 weeks + architecture time

Time-Constrained Multiplayer (< 1 week):

# Server-authoritative is fastest to implement
class Server:
    def update(self):
        # Server runs authoritative physics
        self.physics_update(FIXED_TIMESTEP)

        # Send snapshots to clients (10-30 Hz)
        if time.time() - self.last_snapshot > SNAPSHOT_INTERVAL:
            self.broadcast_snapshot(self.get_physics_state())

class Client:
    def update(self):
        # Client predicts locally
        self.physics_update(FIXED_TIMESTEP)

        # When server snapshot arrives, reconcile
        if snapshot_received:
            # Hard snap or interpolate to server state
            self.reconcile_with_server(snapshot)

Converting to Deterministic Physics (for rollback/lock-step later):

  1. Implement fixed timestep (if not already)
  2. Sort all object iteration by ID
  3. Seed all RNG deterministically
  4. Use fixed-point math for critical calculations (or ensure same FP mode)
  5. Make physics single-threaded (or island-based deterministic parallel)
  6. Test on different machines for bit-perfect results

Critical: Don't attempt rollback netcode with non-deterministic physics. Either refactor for determinism OR use server-authoritative.


Implementation Patterns

Pattern 1: Fixed Timestep Game Loop

Complete implementation with interpolation:

class GameEngine:
    def __init__(self):
        self.PHYSICS_TIMESTEP = 1.0 / 60.0  # 16.67ms
        self.MAX_FRAME_TIME = 0.25  # Don't spiral if below 4 FPS
        self.accumulator = 0.0

        self.current_state = PhysicsState()
        self.previous_state = PhysicsState()

    def run(self):
        last_time = time.time()

        while self.running:
            current_time = time.time()
            frame_time = current_time - last_time
            last_time = current_time

            # Cap frame time to prevent spiral of death
            if frame_time > self.MAX_FRAME_TIME:
                frame_time = self.MAX_FRAME_TIME

            self.accumulator += frame_time

            # Physics updates (fixed timestep)
            while self.accumulator >= self.PHYSICS_TIMESTEP:
                # Store previous state for interpolation
                self.previous_state.copy_from(self.current_state)

                # Physics update
                self.physics_update(self.PHYSICS_TIMESTEP)

                self.accumulator -= self.PHYSICS_TIMESTEP

            # Interpolation factor
            alpha = self.accumulator / self.PHYSICS_TIMESTEP

            # Render with interpolation
            self.render(alpha)

    def physics_update(self, dt):
        """Fixed timestep physics"""
        # Apply forces
        for obj in self.physics_objects:
            obj.apply_forces(dt)

        # Integrate
        for obj in self.physics_objects:
            obj.velocity += (obj.force / obj.mass) * dt
            obj.position += obj.velocity * dt
            obj.force = Vector3.ZERO

        # Collision detection and response
        self.resolve_collisions()

    def render(self, alpha):
        """Interpolate between physics states for smooth rendering"""
        for obj in self.physics_objects:
            # Interpolate position
            interpolated_pos = lerp(
                obj.previous_position,
                obj.current_position,
                alpha
            )

            # Interpolate rotation (use slerp for quaternions)
            interpolated_rot = slerp(
                obj.previous_rotation,
                obj.current_rotation,
                alpha
            )

            obj.render_at(interpolated_pos, interpolated_rot)

Key Points:

  1. Physics always runs at fixed interval (deterministic)
  2. Rendering interpolates between states (smooth)
  3. Handles both fast and slow frame rates gracefully
  4. Prevents spiral of death with max frame time cap

Pattern 2: Vehicle Physics with Suspension

Realistic vehicle simulation using ray-cast suspension:

class VehicleController:
    def __init__(self):
        self.mass = 1500  # kg
        self.wheel_positions = [
            Vector3(-1, 0, 1.5),   # Front-left
            Vector3(1, 0, 1.5),    # Front-right
            Vector3(-1, 0, -1.5),  # Rear-left
            Vector3(1, 0, -1.5),   # Rear-right
        ]

        # Suspension parameters
        self.suspension_length = 0.4  # meters
        self.suspension_stiffness = 50000  # N/m
        self.suspension_damping = 4500  # N·s/m

        # Tire parameters
        self.tire_grip = 2.0
        self.tire_friction_curve = TireFrictionCurve()

    def physics_update(self, dt):
        """Vehicle physics with proper suspension"""
        total_suspension_force = Vector3.ZERO

        # 1. Ray-cast suspension for each wheel
        for i, wheel_pos in enumerate(self.wheel_positions):
            world_wheel_pos = self.transform.transform_point(wheel_pos)
            ray_direction = -self.transform.up

            hit, hit_distance, hit_normal = raycast(
                world_wheel_pos,
                ray_direction,
                self.suspension_length
            )

            if hit:
                # Calculate suspension compression
                compression = self.suspension_length - hit_distance

                # Spring force: F = -kx
                spring_force = self.suspension_stiffness * compression

                # Damper force: F = -cv (relative to ground)
                wheel_velocity = self.get_point_velocity(wheel_pos)
                suspension_velocity = Vector3.dot(wheel_velocity, hit_normal)
                damper_force = self.suspension_damping * suspension_velocity

                # Total suspension force
                total_force = spring_force - damper_force
                suspension_force = hit_normal * total_force

                # Apply force at wheel position
                self.add_force_at_position(suspension_force, world_wheel_pos)

                # 2. Tire forces (grip and friction)
                self.apply_tire_forces(i, world_wheel_pos, hit_normal, dt)

        # 3. Aerodynamic drag
        drag_force = -self.velocity * self.velocity.length() * 0.4
        self.add_force(drag_force)

        # 4. Engine force
        if self.throttle > 0:
            engine_force = self.transform.forward * self.engine_power * self.throttle
            self.add_force(engine_force)

    def apply_tire_forces(self, wheel_index, wheel_position, ground_normal, dt):
        """Calculate longitudinal and lateral tire forces"""
        # Get wheel velocity
        wheel_velocity = self.get_point_velocity(wheel_position)

        # Project velocity onto ground plane
        ground_velocity = wheel_velocity - ground_normal * Vector3.dot(wheel_velocity, ground_normal)

        # Forward and lateral directions
        forward = self.transform.forward
        lateral = self.transform.right

        # Slip calculations
        forward_speed = Vector3.dot(ground_velocity, forward)
        lateral_speed = Vector3.dot(ground_velocity, lateral)

        # Tire friction curve (slip ratio → force)
        forward_force = self.tire_friction_curve.evaluate(forward_speed) * self.tire_grip
        lateral_force = self.tire_friction_curve.evaluate(lateral_speed) * self.tire_grip

        # Apply tire forces
        tire_force = forward * forward_force + lateral * lateral_force
        self.add_force_at_position(tire_force, wheel_position)

    def get_point_velocity(self, local_point):
        """Get velocity of a point on the vehicle (includes rotation)"""
        world_point = self.transform.transform_point(local_point)
        offset = world_point - self.center_of_mass
        return self.velocity + Vector3.cross(self.angular_velocity, offset)

class TireFrictionCurve:
    """Pacejka tire model (simplified)"""
    def evaluate(self, slip):
        # Peak grip at ~15% slip
        # https://en.wikipedia.org/wiki/Hans_B._Pacejka
        B = 10  # Stiffness factor
        C = 1.9  # Shape factor
        D = 1.0  # Peak value
        E = 0.97  # Curvature factor

        return D * math.sin(C * math.atan(B * slip - E * (B * slip - math.atan(B * slip))))

Why This Works:

  • Ray-cast suspension: Handles varying terrain
  • Spring-damper model: Realistic suspension behavior
  • Tire friction curve: Realistic grip/slip behavior
  • Force application at wheel positions: Proper torque for steering

Pattern 3: Continuous Collision Detection for Projectiles

Swept sphere collision for bullets and fast projectiles:

class Projectile:
    def __init__(self, position, velocity, radius):
        self.position = position
        self.velocity = velocity
        self.radius = radius
        self.active = True

    def update_with_ccd(self, dt, world):
        """Update with continuous collision detection"""
        if not self.active:
            return

        start_position = self.position
        end_position = self.position + self.velocity * dt

        # Swept collision detection
        collision, hit_time, hit_point, hit_normal = self.swept_sphere_cast(
            start_position,
            end_position,
            self.radius,
            world
        )

        if collision:
            # Move to collision point
            self.position = start_position + self.velocity * (dt * hit_time)

            # Handle collision (bounce, damage, destroy, etc.)
            self.on_collision(hit_point, hit_normal)
        else:
            # No collision, move full distance
            self.position = end_position

    def swept_sphere_cast(self, start, end, radius, world):
        """Sweep sphere along path, detect first collision"""
        direction = (end - start).normalized()
        distance = (end - start).length()

        # Ray-cast with radius offset
        # Check multiple rays (center + offset in perpendicular directions)
        earliest_hit = None
        earliest_time = float('inf')

        # Center ray
        hit, t, point, normal = world.raycast(start, direction, distance)
        if hit and t < earliest_time:
            earliest_time = t
            earliest_hit = (point, normal)

        # Offset rays (perpendicular to velocity)
        perp1, perp2 = get_perpendicular_vectors(direction)
        for angle in [0, 90, 180, 270]:
            rad = math.radians(angle)
            offset = (perp1 * math.cos(rad) + perp2 * math.sin(rad)) * radius

            hit, t, point, normal = world.raycast(
                start + offset,
                direction,
                distance
            )

            if hit and t < earliest_time:
                earliest_time = t
                earliest_hit = (point, normal)

        if earliest_hit:
            point, normal = earliest_hit
            return True, earliest_time / distance, point, normal

        return False, None, None, None

    def on_collision(self, hit_point, hit_normal):
        """Handle collision response"""
        # Example: Destroy projectile and spawn impact effect
        self.active = False
        spawn_impact_effect(hit_point, hit_normal)

        # Or bounce:
        # self.velocity = reflect(self.velocity, hit_normal) * 0.8

Key Features:

  • Swept collision: Never tunnels through thin objects
  • Multiple ray-casts: Handles sphere shape accurately
  • Early-out: Stops at first collision
  • Configurable response: Destroy, bounce, penetrate, etc.

Pattern 4: Deterministic Physics for Multiplayer

Lock-step multiplayer with deterministic physics:

class DeterministicPhysicsEngine:
    def __init__(self, seed):
        # Fixed-point math for determinism
        self.FIXED_POINT_SCALE = 1000

        # Seeded RNG
        self.rng = Random(seed)

        # Sorted objects for consistent iteration
        self.physics_objects = []  # Sorted by ID

        # Fixed timestep
        self.TIMESTEP = 1.0 / 60.0

    def add_object(self, obj):
        """Add object and maintain sorted order"""
        self.physics_objects.append(obj)
        self.physics_objects.sort(key=lambda o: o.id)

    def physics_step(self, dt):
        """Deterministic physics update"""
        assert dt == self.TIMESTEP, "Must use fixed timestep!"

        # Phase 1: Apply forces (sorted order)
        for obj in self.physics_objects:  # Consistent order
            obj.apply_forces(dt)

        # Phase 2: Integrate (sorted order)
        for obj in self.physics_objects:
            self.integrate_object(obj, dt)

        # Phase 3: Collision detection (sorted pairs)
        self.detect_and_resolve_collisions()

    def integrate_object(self, obj, dt):
        """Fixed-point integration for determinism"""
        # Convert to fixed-point
        vel_x = int(obj.velocity.x * self.FIXED_POINT_SCALE)
        vel_y = int(obj.velocity.y * self.FIXED_POINT_SCALE)
        vel_z = int(obj.velocity.z * self.FIXED_POINT_SCALE)

        acc_x = int(obj.acceleration.x * self.FIXED_POINT_SCALE)
        acc_y = int(obj.acceleration.y * self.FIXED_POINT_SCALE)
        acc_z = int(obj.acceleration.z * self.FIXED_POINT_SCALE)

        dt_fixed = int(dt * self.FIXED_POINT_SCALE)

        # Semi-implicit Euler (fixed-point)
        vel_x += (acc_x * dt_fixed) // self.FIXED_POINT_SCALE
        vel_y += (acc_y * dt_fixed) // self.FIXED_POINT_SCALE
        vel_z += (acc_z * dt_fixed) // self.FIXED_POINT_SCALE

        pos_x = int(obj.position.x * self.FIXED_POINT_SCALE)
        pos_y = int(obj.position.y * self.FIXED_POINT_SCALE)
        pos_z = int(obj.position.z * self.FIXED_POINT_SCALE)

        pos_x += (vel_x * dt_fixed) // self.FIXED_POINT_SCALE
        pos_y += (vel_y * dt_fixed) // self.FIXED_POINT_SCALE
        pos_z += (vel_z * dt_fixed) // self.FIXED_POINT_SCALE

        # Convert back to float
        obj.velocity.x = vel_x / self.FIXED_POINT_SCALE
        obj.velocity.y = vel_y / self.FIXED_POINT_SCALE
        obj.velocity.z = vel_z / self.FIXED_POINT_SCALE

        obj.position.x = pos_x / self.FIXED_POINT_SCALE
        obj.position.y = pos_y / self.FIXED_POINT_SCALE
        obj.position.z = pos_z / self.FIXED_POINT_SCALE

    def detect_and_resolve_collisions(self):
        """Deterministic collision detection"""
        # Sort pairs for consistent order
        pairs = []
        for i in range(len(self.physics_objects)):
            for j in range(i + 1, len(self.physics_objects)):
                obj_a = self.physics_objects[i]
                obj_b = self.physics_objects[j]

                # Always put lower ID first
                if obj_a.id < obj_b.id:
                    pairs.append((obj_a, obj_b))
                else:
                    pairs.append((obj_b, obj_a))

        # Process collisions in sorted order
        for obj_a, obj_b in pairs:
            if self.check_collision(obj_a, obj_b):
                self.resolve_collision(obj_a, obj_b)

    def get_random_value(self):
        """Deterministic random numbers"""
        return self.rng.random()

# Usage in multiplayer game:
def multiplayer_game_loop():
    # All clients use same seed
    engine = DeterministicPhysicsEngine(seed=game_session_id)

    while running:
        # Receive inputs from all players
        player_inputs = receive_inputs_from_all_clients()

        # Sort inputs by player ID for determinism
        player_inputs.sort(key=lambda inp: inp.player_id)

        # Apply inputs in order
        for input in player_inputs:
            apply_player_input(input)

        # Run physics (identical on all clients)
        engine.physics_step(engine.TIMESTEP)

        # Render (can differ per client)
        render()

Determinism Guarantees:

  • Fixed-point math: No floating-point non-determinism
  • Sorted iteration: Consistent operation order
  • Seeded RNG: Reproducible randomness
  • Fixed timestep: Same dt every frame
  • Single-threaded: No parallel non-determinism

Pattern 5: Position-Based Dynamics for Cloth

Unconditionally stable cloth simulation:

class ClothSimulation:
    def __init__(self, width, height, particle_spacing):
        self.particles = []
        self.constraints = []

        # Create particle grid
        for y in range(height):
            for x in range(width):
                pos = Vector3(x * particle_spacing, y * particle_spacing, 0)
                particle = ClothParticle(pos, mass=0.1)
                self.particles.append(particle)

        # Create distance constraints (structural)
        for y in range(height):
            for x in range(width):
                idx = y * width + x

                # Horizontal constraint
                if x < width - 1:
                    self.constraints.append(
                        DistanceConstraint(
                            self.particles[idx],
                            self.particles[idx + 1],
                            particle_spacing
                        )
                    )

                # Vertical constraint
                if y < height - 1:
                    self.constraints.append(
                        DistanceConstraint(
                            self.particles[idx],
                            self.particles[idx + width],
                            particle_spacing
                        )
                    )

        # Add shear constraints (diagonals) for stiffness
        for y in range(height - 1):
            for x in range(width - 1):
                idx = y * width + x

                # Diagonal constraints
                self.constraints.append(
                    DistanceConstraint(
                        self.particles[idx],
                        self.particles[idx + width + 1],
                        particle_spacing * math.sqrt(2)
                    )
                )

                self.constraints.append(
                    DistanceConstraint(
                        self.particles[idx + 1],
                        self.particles[idx + width],
                        particle_spacing * math.sqrt(2)
                    )
                )

    def simulate(self, dt, iterations=10):
        """Position-Based Dynamics simulation"""
        # 1. Apply external forces (gravity, wind)
        for particle in self.particles:
            if not particle.fixed:
                particle.velocity += Vector3(0, -9.8, 0) * dt  # Gravity
                particle.velocity += self.wind_force * dt

        # 2. Predict positions (Verlet integration)
        for particle in self.particles:
            if not particle.fixed:
                particle.predicted_position = particle.position + particle.velocity * dt

        # 3. Solve constraints (multiple iterations for stability)
        for _ in range(iterations):
            for constraint in self.constraints:
                constraint.solve()

            # Collision constraints
            for particle in self.particles:
                if not particle.fixed:
                    self.resolve_collisions(particle)

        # 4. Update velocities and positions
        for particle in self.particles:
            if not particle.fixed:
                particle.velocity = (particle.predicted_position - particle.position) / dt
                particle.position = particle.predicted_position

                # Velocity damping (air resistance)
                particle.velocity *= 0.99

    def resolve_collisions(self, particle):
        """Keep particles above ground plane"""
        if particle.predicted_position.y < 0:
            particle.predicted_position.y = 0
            # Friction
            particle.velocity *= 0.8

class ClothParticle:
    def __init__(self, position, mass):
        self.position = position
        self.predicted_position = position
        self.velocity = Vector3.ZERO
        self.mass = mass
        self.fixed = False  # Pinned particles don't move

class DistanceConstraint:
    def __init__(self, particle_a, particle_b, rest_length):
        self.particle_a = particle_a
        self.particle_b = particle_b
        self.rest_length = rest_length

    def solve(self):
        """Enforce distance constraint"""
        if self.particle_a.fixed and self.particle_b.fixed:
            return

        delta = self.particle_b.predicted_position - self.particle_a.predicted_position
        current_length = delta.length()

        if current_length == 0:
            return

        # Correction to restore rest length
        correction = delta * (1.0 - self.rest_length / current_length) * 0.5

        # Apply correction (weighted by mass)
        if not self.particle_a.fixed:
            self.particle_a.predicted_position += correction

        if not self.particle_b.fixed:
            self.particle_b.predicted_position -= correction

Why Position-Based Dynamics:

  • Unconditionally stable: Cannot explode, even with large timesteps
  • Fast: Simple position corrections, no matrix solves
  • Intuitive: Easy to add constraints (distance, bending, collision)
  • Controllable: Iterations directly control stiffness

Used in: Unity's cloth simulation, many game engines


Common Pitfalls

Pitfall 1: Variable Timestep Physics (The Cardinal Sin)

The Mistake:

# ❌ NEVER DO THIS
def update(self, dt):
    self.velocity += self.acceleration * dt
    self.position += self.velocity * dt

Why It Fails:

  • Physics behaves differently at different frame rates
  • Integration errors accumulate differently
  • Non-deterministic (same inputs ≠ same outputs)
  • Multiplayer desyncs guaranteed

Real-World Example: Game ships, works fine on dev machines (high FPS). Players with low-end hardware (30 FPS) report:

  • Cars are "floaty" and hard to control
  • Objects fall through floors
  • Multiplayer is "laggy" (desyncs)

The Fix:

# ✅ ALWAYS use fixed timestep
FIXED_TIMESTEP = 1.0 / 60.0
accumulator = 0.0

def game_loop():
    global accumulator
    dt = get_frame_time()
    accumulator += dt

    while accumulator >= FIXED_TIMESTEP:
        physics_update(FIXED_TIMESTEP)  # Fixed dt
        accumulator -= FIXED_TIMESTEP

Detection:

  • If dt appears in physics calculations, you're at risk
  • Test at different frame rates (30 FPS vs 144 FPS) - should behave identically

Pitfall 2: Physics Explosions (Energy Accumulation)

The Mistake:

# ❌ Explicit Euler - unstable!
velocity += acceleration * dt
position += velocity * dt  # Uses OLD velocity

Why It Fails: At high speeds or large dt, explicit Euler adds energy to the system. Objects accelerate indefinitely, leading to "physics explosions".

Symptoms:

  • Objects suddenly fly off at high speed
  • Ragdolls "explode" into the sky
  • Vehicles flip and spin uncontrollably
  • Happens more at low frame rates

Real-World Example: Racing game vehicle hits wall at 150 mph. Instead of stopping, it bounces off at 300 mph and flies into orbit.

The Fix:

# ✅ Semi-implicit Euler - stable
velocity += acceleration * dt
position += velocity * dt  # Uses NEW velocity

# Energy is conserved (or slightly dissipated)

Why This Works: Semi-implicit Euler is symplectic - it conserves energy rather than adding it. Objects slow down naturally instead of speeding up.

Additional Safety:

# Velocity clamping for safety
MAX_VELOCITY = 100.0
if velocity.length() > MAX_VELOCITY:
    velocity = velocity.normalized() * MAX_VELOCITY

Pitfall 3: Tunneling (Missing CCD)

The Mistake:

# ❌ Discrete collision detection only
def check_collision(bullet):
    if bullet.position inside wall:
        bullet.on_collision()

Why It Fails: Fast objects move multiple body-lengths per frame, skipping over thin obstacles.

The Math:

  • Bullet speed: 1000 m/s
  • Frame rate: 60 FPS
  • Distance per frame: 16.67 meters
  • Wall thickness: 0.2 meters
  • Result: Bullet is on one side of wall in frame N, other side in frame N+1, never "inside"

Real-World Example: FPS game, players shoot through walls, hitting players on the other side. Especially bad with high ping or low frame rates.

The Fix:

# ✅ Swept collision detection
def update_with_ccd(bullet, dt):
    start_pos = bullet.position
    end_pos = bullet.position + bullet.velocity * dt

    hit, hit_point, hit_time = swept_raycast(start_pos, end_pos, world)

    if hit:
        bullet.position = lerp(start_pos, end_pos, hit_time)
        bullet.on_collision(hit_point)
    else:
        bullet.position = end_pos

When CCD is Critical:

  • Projectiles (bullets, arrows)
  • Fast vehicles (>50 m/s)
  • Falling objects (terminal velocity ~53 m/s)
  • Player characters (lunging attacks, dashes)

Unity Setting:

rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;

Pitfall 4: Non-Deterministic Multiplayer Physics

The Mistake:

# ❌ Many sources of non-determinism
def update_physics():
    for obj in physics_objects:  # Undefined iteration order
        obj.update(Time.deltaTime)  # Variable dt

        if random.random() < 0.1:  # Non-seeded RNG
            obj.apply_impulse()

Why It Fails:

  • Dictionary/set iteration order is non-deterministic
  • Variable timestep differs on different machines
  • Random numbers differ without seeding
  • Floating-point math can differ across CPUs

Symptoms:

  • Multiplayer clients "desync" (see different physics states)
  • Replays don't match original gameplay
  • Rollback netcode fails (re-simulation produces different results)
  • Heisenbugs (disappear when you try to debug them)

Real-World Example: Fighting game with rollback netcode. Players see different positions for characters, hits don't register, game is "laggy" despite good ping. Problem: physics is non-deterministic.

The Fix:

# ✅ Deterministic physics
class DeterministicEngine:
    def __init__(self, seed):
        self.FIXED_TIMESTEP = 1.0 / 60.0  # Fixed dt
        self.rng = Random(seed)  # Seeded RNG
        self.objects = []  # List (ordered), not dict/set

    def update_physics(self):
        # Sort for consistent order
        sorted_objects = sorted(self.objects, key=lambda o: o.id)

        for obj in sorted_objects:
            obj.update(self.FIXED_TIMESTEP)  # Fixed dt

            if self.rng.random() < 0.1:  # Seeded RNG
                obj.apply_impulse()

Determinism Checklist:

  • Fixed timestep (never variable)
  • Sorted iteration order
  • Seeded RNG
  • Single-threaded physics (or deterministic parallel)
  • Consistent floating-point mode
  • Same code/compiler on all clients

Pitfall 5: Missing Sub-Stepping for Constraints

The Mistake:

# ❌ Single physics step with stiff constraints
def update(self, dt):
    self.solve_constraints()  # Only once
    self.integrate(dt)

Why It Fails: Stiff constraints (vehicle suspension, rope physics) need multiple iterations to converge. Single iteration causes jittering and instability.

Symptoms:

  • Vehicle suspension jitters and bounces
  • Ropes stretch unrealistically
  • Joints don't stay connected
  • Soft bodies "explode" or collapse

Real-World Example: Racing game with suspension springs. At 60 FPS with single iteration, cars bounce violently. Suspension never settles.

The Fix:

# ✅ Sub-stepping for constraint stability
def update(self, dt):
    SUB_STEPS = 4
    sub_dt = dt / SUB_STEPS

    for _ in range(SUB_STEPS):
        self.solve_constraints()
        self.integrate(sub_dt)

Guidelines:

  • Simple rigid bodies: 1 step (no sub-stepping needed)
  • Vehicles with suspension: 2-4 steps
  • Rope/chain physics: 8-16 steps
  • Cloth simulation: 4-10 steps

Unity Example:

// Increase solver iterations for complex constraints
Rigidbody rb = GetComponent<Rigidbody>();
rb.solverIterations = 12;  // Default is 6
rb.solverVelocityIterations = 2;  // Default is 1

Pitfall 6: Ignoring Center of Mass

The Mistake:

# ❌ Apply force at object origin
def apply_force(self, force):
    self.acceleration = force / self.mass
    # No torque calculation!

Why It Fails: Forces not applied at center of mass create torque. Ignoring this makes objects rotate incorrectly or not at all.

Real-World Example: Car accelerates, but nose doesn't dip down. Car turns, but body doesn't lean. Feels unrealistic.

The Fix:

# ✅ Calculate torque from force application point
def add_force_at_position(self, force, world_position):
    # Linear force
    self.acceleration += force / self.mass

    # Angular force (torque)
    offset = world_position - self.center_of_mass
    torque = Vector3.cross(offset, force)
    self.angular_acceleration += torque / self.moment_of_inertia

Correct Center of Mass:

# Calculate center of mass for compound object
def calculate_center_of_mass(self):
    total_mass = 0
    weighted_position = Vector3.ZERO

    for part in self.parts:
        total_mass += part.mass
        weighted_position += part.position * part.mass

    self.center_of_mass = weighted_position / total_mass

Unity Tip:

// Set center of mass for vehicles
Rigidbody rb = GetComponent<Rigidbody>();
rb.centerOfMass = new Vector3(0, -0.5f, 0);  // Lower = more stable

Pitfall 7: Wrong Collision Response

The Mistake:

# ❌ Incorrect collision impulse
def resolve_collision(a, b):
    a.velocity = -a.velocity  # Just reverse direction
    b.velocity = -b.velocity

Why It Fails:

  • Doesn't conserve momentum
  • Doesn't account for relative masses
  • Ignores coefficient of restitution (bounciness)
  • No friction

Real-World Example: Small box hits large truck. Box bounces off at same speed, truck also bounces. Physics looks wrong (momentum not conserved).

The Fix:

# ✅ Physically correct impulse-based collision
def resolve_collision(a, b, collision_normal, restitution=0.5):
    # Relative velocity
    relative_velocity = a.velocity - b.velocity
    velocity_along_normal = Vector3.dot(relative_velocity, collision_normal)

    # Don't resolve if separating
    if velocity_along_normal > 0:
        return

    # Calculate impulse magnitude
    # Formula: j = -(1 + e) * v_rel · n / (1/m_a + 1/m_b)
    impulse_magnitude = -(1 + restitution) * velocity_along_normal
    impulse_magnitude /= (1 / a.mass + 1 / b.mass)

    # Apply impulse
    impulse = collision_normal * impulse_magnitude
    a.velocity += impulse / a.mass
    b.velocity -= impulse / b.mass

With Friction:

def resolve_collision_with_friction(a, b, collision_normal, restitution=0.5, friction=0.3):
    # Normal impulse (from above)
    # ... normal impulse calculation ...

    # Tangential (friction) impulse
    relative_velocity = a.velocity - b.velocity
    tangent = relative_velocity - collision_normal * Vector3.dot(relative_velocity, collision_normal)

    if tangent.length() > 0:
        tangent = tangent.normalized()

        # Friction impulse (capped by friction coefficient)
        friction_magnitude = -Vector3.dot(relative_velocity, tangent)
        friction_magnitude /= (1 / a.mass + 1 / b.mass)
        friction_magnitude = clamp(friction_magnitude, -friction * impulse_magnitude, friction * impulse_magnitude)

        friction_impulse = tangent * friction_magnitude
        a.velocity += friction_impulse / a.mass
        b.velocity -= friction_impulse / b.mass

Real-World Examples

Example 1: Unity Vehicle Physics

Unity's WheelCollider (simplified conceptual implementation):

public class UnityVehiclePhysics : MonoBehaviour
{
    public WheelCollider frontLeft, frontRight, rearLeft, rearRight;
    public float motorTorque = 1500f;
    public float brakeTorque = 3000f;
    public float maxSteerAngle = 30f;

    private Rigidbody rb;

    void Start()
    {
        rb = GetComponent<Rigidbody>();

        // Lower center of mass for stability
        rb.centerOfMass = new Vector3(0, -0.5f, 0);

        // Continuous collision for high-speed stability
        rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
    }

    void FixedUpdate()  // ← Fixed timestep (50 Hz default)
    {
        // Input
        float motor = Input.GetAxis("Vertical") * motorTorque;
        float steering = Input.GetAxis("Horizontal") * maxSteerAngle;
        float brake = Input.GetKey(KeyCode.Space) ? brakeTorque : 0;

        // Apply to wheels
        frontLeft.steerAngle = steering;
        frontRight.steerAngle = steering;

        rearLeft.motorTorque = motor;
        rearRight.motorTorque = motor;

        frontLeft.brakeTorque = brake;
        frontRight.brakeTorque = brake;
        rearLeft.brakeTorque = brake;
        rearRight.brakeTorque = brake;
    }
}

// WheelCollider does internally:
// - Ray-cast suspension (spring-damper model)
// - Tire friction curve (Pacejka model)
// - Sub-stepping for stability
// - Force application at contact point

Key Patterns:

  1. Fixed timestep via FixedUpdate() - deterministic physics
  2. Continuous collision detection - prevents tunneling at high speeds
  3. Lowered center of mass - improves vehicle stability
  4. WheelCollider handles complex tire physics automatically

Example 2: Unreal Engine Chaos Physics

Vehicle physics in Unreal (Blueprint/C++):

// Unreal's vehicle physics (simplified concepts)
class AVehiclePawn : public APawn
{
public:
    void SetupVehicle()
    {
        // Create physics vehicle
        VehicleMovement = CreateDefaultSubobject<UWheeledVehicleMovementComponent>(TEXT("VehicleMovement"));

        // Configure wheels
        VehicleMovement->WheelSetups.SetNum(4);

        // Front wheels (steering)
        VehicleMovement->WheelSetups[0].WheelClass = UFrontWheel::StaticClass();
        VehicleMovement->WheelSetups[1].WheelClass = UFrontWheel::StaticClass();

        // Rear wheels (drive)
        VehicleMovement->WheelSetups[2].WheelClass = URearWheel::StaticClass();
        VehicleMovement->WheelSetups[3].WheelClass = URearWheel::StaticClass();

        // Engine setup
        VehicleMovement->MaxEngineRPM = 6000.0f;
        VehicleMovement->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(0.0f, 400.0f);
        VehicleMovement->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(3000.0f, 500.0f);
        VehicleMovement->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(6000.0f, 400.0f);

        // Transmission
        VehicleMovement->TransmissionSetup.bUseGearAutoBox = true;
        VehicleMovement->TransmissionSetup.GearSwitchTime = 0.5f;
    }

    void Tick(float DeltaTime) override
    {
        // Physics runs in substepped fixed timestep
        // Unreal handles this automatically via Chaos physics
    }
};

// Enable deterministic physics in Unreal
// Edit > Project Settings > Physics
// - Substepping: Enabled
// - Max Substep Delta Time: 0.0166 (60 Hz)
// - Max Substeps: 6

Key Patterns:

  1. Sub-stepping for constraint stability (suspension, gears)
  2. Torque curves for realistic engine behavior
  3. Automatic gear shifting (state machine)
  4. Wheel classes with tire friction models

Example 3: Rocket League Physics

Vehicle physics with aerodynamics (conceptual):

class RocketLeagueVehicle:
    def __init__(self):
        self.rigidbody = Rigidbody(mass=180)  # kg
        self.boost_force = 30000  # Newtons
        self.jump_impulse = 5000

        # Aerial control
        self.air_control_torque = 400
        self.air_damping = 0.3

        # Ground physics
        self.wheel_friction = 3.0
        self.drift_friction = 1.5

    def fixed_update(self, dt):  # Fixed 120 Hz
        """Physics update at 120 Hz for responsiveness"""

        if self.is_grounded():
            self.apply_ground_physics(dt)
        else:
            self.apply_aerial_physics(dt)

        # Boost
        if self.boost_active and self.boost_fuel > 0:
            boost_direction = self.transform.forward
            self.rigidbody.add_force(boost_direction * self.boost_force)
            self.boost_fuel -= 30 * dt  # 30 boost per second

    def apply_ground_physics(self, dt):
        """Wheel-based ground control"""
        # Steering input
        steer_angle = self.input.steering * 30  # degrees

        # Apply wheel forces
        for wheel in self.wheels:
            # Forward force from throttle
            forward_force = self.transform.forward * self.input.throttle * 1500

            # Lateral friction (with drift reduction)
            friction_multiplier = self.drift_friction if self.input.drift else self.wheel_friction
            lateral_force = self.calculate_tire_force(wheel, friction_multiplier)

            self.rigidbody.add_force_at_position(
                forward_force + lateral_force,
                wheel.world_position
            )

    def apply_aerial_physics(self, dt):
        """Aerial control via orientation torque"""
        # Air roll, pitch, yaw
        torque = Vector3(
            self.input.pitch * self.air_control_torque,
            self.input.yaw * self.air_control_torque,
            self.input.roll * self.air_control_torque
        )

        self.rigidbody.add_torque(torque)

        # Air damping (reduces rotation speed in air)
        self.rigidbody.angular_velocity *= (1.0 - self.air_damping * dt)

        # Gravity
        self.rigidbody.add_force(Vector3(0, -9.8 * self.rigidbody.mass, 0))

    def jump(self):
        """Dodge/jump mechanics"""
        if self.can_jump:
            self.rigidbody.add_impulse(Vector3.UP * self.jump_impulse)
            self.can_jump = False

            # Dodge: Add impulse + angular momentum
            if self.input.direction.length() > 0:
                dodge_direction = self.input.direction.normalized()
                self.rigidbody.add_impulse(dodge_direction * self.jump_impulse * 0.8)

                # Flip car
                axis = Vector3.cross(Vector3.UP, dodge_direction)
                self.rigidbody.add_angular_impulse(axis * 10)

# Fixed timestep game loop
PHYSICS_RATE = 120  # Hz (higher for competitive gameplay)
FIXED_DT = 1.0 / PHYSICS_RATE

def game_loop():
    accumulator = 0.0

    while running:
        frame_time = get_delta_time()
        accumulator += frame_time

        while accumulator >= FIXED_DT:
            vehicle.fixed_update(FIXED_DT)  # 120 Hz physics
            accumulator -= FIXED_DT

        render(accumulator / FIXED_DT)  # Interpolate

Key Patterns:

  1. High-frequency fixed timestep (120 Hz) for competitive responsiveness
  2. State-based physics (grounded vs aerial)
  3. Drift mechanics via friction modulation
  4. Aerial control via direct torque application
  5. Boost as continuous force (not impulse)

Example 4: Half-Life 2 Gravity Gun

Constraint-based object manipulation:

// Valve's physics manipulation (conceptual)
class CGravityGun
{
public:
    void HoldObject(CPhysicsObject* pObject)
    {
        // Create spring constraint to hold object
        m_pHeldObject = pObject;

        // Target position: In front of player
        Vector targetPos = GetPlayer()->GetEyePosition() + GetPlayer()->GetForward() * m_flHoldDistance;

        // Create spring-damper constraint
        m_pConstraint = CreateSpringConstraint(
            pObject,
            targetPos,
            stiffness: 1000.0f,   // Strong spring
            damping: 50.0f        // Moderate damping
        );

        // Reduce object's angular velocity for stability
        pObject->SetAngularDamping(0.8f);
    }

    void UpdateHeldObject(float dt)
    {
        if (!m_pHeldObject) return;

        // Update target position
        Vector targetPos = GetPlayer()->GetEyePosition() + GetPlayer()->GetForward() * m_flHoldDistance;

        // Calculate spring force
        Vector offset = targetPos - m_pHeldObject->GetPosition();
        Vector force = offset * m_flStiffness;

        // Calculate damping force
        Vector velocity = m_pHeldObject->GetVelocity();
        Vector dampingForce = -velocity * m_flDamping;

        // Apply forces
        m_pHeldObject->AddForce(force + dampingForce);

        // Rotate object to face player (optional)
        Quaternion targetRotation = LookRotation(GetPlayer()->GetForward());
        Quaternion currentRotation = m_pHeldObject->GetRotation();
        Quaternion deltaRotation = ShortestRotation(currentRotation, targetRotation);

        Vector torque = deltaRotation.ToTorque() * 100.0f;
        m_pHeldObject->AddTorque(torque);
    }

    void LaunchObject()
    {
        if (!m_pHeldObject) return;

        // Punt object forward
        Vector launchVelocity = GetPlayer()->GetForward() * m_flLaunchSpeed;
        m_pHeldObject->SetVelocity(launchVelocity);

        // Restore normal damping
        m_pHeldObject->SetAngularDamping(0.05f);

        // Destroy constraint
        DestroyConstraint(m_pConstraint);
        m_pHeldObject = nullptr;
    }
};

Key Patterns:

  1. Spring-damper constraint for smooth following
  2. Angular damping to reduce oscillation
  3. Constraint-based (not direct position setting)
  4. Smooth transition from constraint to free physics

Example 5: Angry Birds Destruction

Large-scale destructible physics:

// Unity-based destruction (Angry Birds style)
public class DestructibleStructure : MonoBehaviour
{
    public float health = 100f;
    public GameObject[] debrisPrefabs;

    private Rigidbody rb;
    private bool isDestroyed = false;

    void Start()
    {
        rb = GetComponent<Rigidbody>();

        // Static until hit (optimization)
        rb.isKinematic = true;
    }

    void OnCollisionEnter(Collision collision)
    {
        // Calculate damage from impact
        float impactForce = collision.impulse.magnitude;
        float damage = impactForce / rb.mass;

        health -= damage;

        if (health <= 0 && !isDestroyed)
        {
            Destroy();
        }
        else if (rb.isKinematic)
        {
            // Transition to physics when hit
            rb.isKinematic = false;
        }
    }

    void Destroy()
    {
        isDestroyed = true;

        // Spawn debris pieces
        for (int i = 0; i < debrisPrefabs.Length; i++)
        {
            Vector3 spawnPos = transform.position + Random.insideUnitSphere * 0.5f;
            GameObject debris = Instantiate(debrisPrefabs[i], spawnPos, Random.rotation);

            Rigidbody debrisRb = debris.GetComponent<Rigidbody>();

            // Inherit velocity
            debrisRb.velocity = rb.velocity;

            // Add explosion impulse
            Vector3 explosionDir = (spawnPos - transform.position).normalized;
            debrisRb.AddForce(explosionDir * 500f, ForceMode.Impulse);

            // Random spin
            debrisRb.AddTorque(Random.insideUnitSphere * 10f, ForceMode.Impulse);

            // Auto-despawn after 5 seconds
            Destroy(debris, 5f);
        }

        // Destroy original object
        Destroy(gameObject);
    }
}

// Bird projectile
public class Bird : MonoBehaviour
{
    private Rigidbody rb;

    void Start()
    {
        rb = GetComponent<Rigidbody>();

        // Continuous collision (fast-moving)
        rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;

        // High mass for destruction
        rb.mass = 10f;
    }

    void FixedUpdate()
    {
        // Apply drag for realistic arc
        rb.velocity *= 0.99f;

        // Rotate to face direction of travel
        if (rb.velocity.magnitude > 0.1f)
        {
            transform.rotation = Quaternion.LookRotation(rb.velocity);
        }
    }
}

Key Patterns:

  1. Kinematic → Dynamic transition (optimization)
  2. Impulse-based damage calculation
  3. Debris spawning with inherited velocity
  4. CCD for fast-moving projectiles
  5. Auto-despawn for performance

Cross-References

Use This Skill WITH:

  • performance-optimization-patterns: Physics is expensive; profile and optimize
  • netcode-patterns: Deterministic physics for multiplayer synchronization
  • state-machines: Managing physics states (grounded, aerial, ragdoll)
  • pooling-patterns: Reusing physics objects (debris, particles)

Use This Skill AFTER:

  • game-architecture-fundamentals: Understand fixed update loops
  • 3d-math-essentials: Vector math, quaternions, coordinate spaces
  • collision-detection-patterns: Broad-phase, narrow-phase algorithms

Related Skills:

  • character-controller-patterns: Kinematic character control vs physics-based
  • animation-blending: Transitioning between animation and ragdoll
  • vfx-patterns: Faking physics for visual effects

Performance Optimization

When physics becomes a bottleneck (< 60 FPS with target object count), apply these optimizations:

1. Spatial Partitioning (Broad-Phase Collision)

Problem: Checking every object pair is O(n²) - with 100 objects, that's 4,950 checks per frame.

Solution - Uniform Grid:

class SpatialGrid:
    def __init__(self, cell_size):
        self.cell_size = cell_size
        self.grid = {}  # Dict[Tuple[int, int], List[Object]]

    def insert(self, obj):
        """Insert object into grid cells it overlaps"""
        cells = self.get_cells_for_bounds(obj.bounds)
        for cell in cells:
            if cell not in self.grid:
                self.grid[cell] = []
            self.grid[cell].append(obj)

    def get_potential_collisions(self, obj):
        """Get only nearby objects (same or adjacent cells)"""
        cells = self.get_cells_for_bounds(obj.bounds)
        nearby = set()

        for cell in cells:
            if cell in self.grid:
                nearby.update(self.grid[cell])

        return nearby

    def clear(self):
        """Clear grid each frame before re-inserting"""
        self.grid.clear()

# Usage:
def physics_update(dt):
    grid = SpatialGrid(cell_size=10.0)

    # Insert all objects
    for obj in physics_objects:
        grid.insert(obj)

    # Collision detection (only check nearby)
    for obj in physics_objects:
        nearby = grid.get_potential_collisions(obj)
        for other in nearby:
            if obj.id < other.id:  # Avoid duplicate checks
                check_collision(obj, other)

Performance: O(n) instead of O(n²). With 100 objects, ~400 checks vs 4,950.

2. Physics Islands and Sleeping Objects

Concept: Objects at rest don't need simulation until disturbed.

class SleepingObject:
    def __init__(self):
        self.is_sleeping = False
        self.sleep_timer = 0.0
        self.sleep_threshold_time = 0.5  # seconds
        self.sleep_velocity_threshold = 0.01  # m/s

    def update(self, dt):
        if self.is_sleeping:
            return  # Skip physics simulation

        # Check if object should sleep
        if self.velocity.length() < self.sleep_velocity_threshold:
            self.sleep_timer += dt
            if self.sleep_timer > self.sleep_threshold_time:
                self.is_sleeping = True
        else:
            self.sleep_timer = 0.0

        # Normal physics update
        self.integrate(dt)

    def wake_up(self):
        """Called when object is hit or disturbed"""
        self.is_sleeping = False
        self.sleep_timer = 0.0

# Propagate wake-up through connected objects
def on_collision(obj_a, obj_b):
    if obj_a.is_sleeping:
        obj_a.wake_up()
    if obj_b.is_sleeping:
        obj_b.wake_up()

Performance: Large static environments (buildings, props) don't consume CPU when untouched.

3. Level of Detail (LOD) for Physics

Concept: Simplify physics for distant objects.

class PhysicsLOD:
    def __init__(self, camera):
        self.camera = camera

        # Distance thresholds
        self.FULL_PHYSICS_DISTANCE = 50.0      # < 50m: Full physics
        self.SIMPLIFIED_PHYSICS_DISTANCE = 200.0  # 50-200m: Reduced fidelity
        # > 200m: Kinematic or disabled

    def get_lod_level(self, obj):
        distance = (obj.position - self.camera.position).length()

        if distance < self.FULL_PHYSICS_DISTANCE:
            return "FULL"
        elif distance < self.SIMPLIFIED_PHYSICS_DISTANCE:
            return "SIMPLIFIED"
        else:
            return "KINEMATIC"

    def update_physics(self, obj, dt):
        lod = self.get_lod_level(obj)

        if lod == "FULL":
            # Full physics: All features, high solver iterations
            obj.solver_iterations = 12
            obj.enable_ccd = True
            obj.update_full_physics(dt)

        elif lod == "SIMPLIFIED":
            # Simplified: Reduced iterations, no CCD
            obj.solver_iterations = 4
            obj.enable_ccd = False
            obj.update_full_physics(dt)

        else:  # KINEMATIC
            # No physics simulation, just update position
            obj.update_kinematic(dt)

Performance: Can handle 3-10x more objects by reducing fidelity for distant objects.

4. Solver Iteration Tuning

Trade-off: More iterations = more stable constraints, but slower.

// Unity example - tune per object
Rigidbody rb = GetComponent<Rigidbody>();

// Simple objects (debris, props)
rb.solverIterations = 4;           // Velocity constraints
rb.solverVelocityIterations = 1;   // Position constraints

// Complex objects (vehicles with suspension)
rb.solverIterations = 12;
rb.solverVelocityIterations = 2;

// Critical objects (player character)
rb.solverIterations = 20;
rb.solverVelocityIterations = 4;

Guideline: Start low (4/1), increase only if jittering or instability observed.

5. Collision Layer Matrix

Concept: Many object pairs never need collision checks.

// Unity: Edit > Project Settings > Physics > Layer Collision Matrix
// Example layer setup:
// - Layer 8: PlayerBullets (collide with Enemies, Environment)
// - Layer 9: EnemyBullets (collide with Player, Environment)
// - Layer 10: Environment (collide with everything)
// - Layer 11: Debris (collide only with Environment)

// Bullets don't collide with each other (huge savings)
Physics.IgnoreLayerCollision(8, 8);  // PlayerBullets vs PlayerBullets
Physics.IgnoreLayerCollision(9, 9);  // EnemyBullets vs EnemyBullets
Physics.IgnoreLayerCollision(8, 9);  // PlayerBullets vs EnemyBullets

// Debris doesn't collide with bullets (performance)
Physics.IgnoreLayerCollision(11, 8);
Physics.IgnoreLayerCollision(11, 9);

Performance: Can reduce collision checks by 50-90% depending on game.

Performance Optimization Checklist

  • Spatial partitioning implemented (grid, octree, or sort-and-sweep)
  • Sleeping/waking system for static objects
  • Physics LOD based on distance to camera
  • Solver iterations tuned per object type (not all need 12)
  • Collision layers configured (ignore unnecessary pairs)
  • Profiled to identify actual bottleneck (don't guess)

Debugging Guide

Debugging Physics Explosions

Symptoms: Objects suddenly fly off at extreme speeds, spin wildly, or "explode" apart.

Diagnosis Checklist:

  1. Check integration method:

    # ❌ BAD: Explicit Euler
    velocity += acceleration * dt
    position += velocity * dt  # Uses OLD velocity
    
    # ✅ GOOD: Semi-implicit Euler
    velocity += acceleration * dt
    position += velocity * dt  # Uses NEW velocity
    
  2. Add velocity clamping:

    MAX_VELOCITY = 100.0  # m/s (adjust for your game)
    if velocity.length() > MAX_VELOCITY:
        velocity = velocity.normalized() * MAX_VELOCITY
    
  3. Check for NaN/Infinity:

    def safe_normalize(vector):
        length = vector.length()
        if length < 0.0001 or math.isnan(length) or math.isinf(length):
            return Vector3(0, 1, 0)  # Default direction
        return vector / length
    
  4. Verify timestep size:

    assert dt <= 0.033, f"Timestep too large: {dt}s (should be ≤ 0.033)"
    

Debugging Tunneling

Symptoms: Fast objects pass through walls, bullets hit players behind walls.

Diagnosis Checklist:

  1. Calculate required CCD threshold:

    # Rule: Enable CCD if object moves > half its size per frame
    velocity_per_frame = velocity * dt
    object_size = collider.radius * 2
    
    if velocity_per_frame > object_size * 0.5:
        print(f"⚠️  CCD required! Moving {velocity_per_frame}m, size {object_size}m")
        enable_ccd()
    
  2. Verify CCD is enabled:

    // Unity
    Debug.Log($"CCD Mode: {rigidbody.collisionDetectionMode}");
    // Should be Continuous or ContinuousDynamic
    
  3. Check collider thickness:

    # Walls should be at least 2x the distance fast objects travel per frame
    min_wall_thickness = max_object_speed * FIXED_TIMESTEP * 2
    print(f"Minimum wall thickness: {min_wall_thickness}m")
    

Debugging Multiplayer Desyncs

Symptoms: Clients see different physics states, objects in different positions.

Diagnosis Process:

  1. Enable determinism logging:

    def log_physics_state(frame):
        # Log complete physics state each frame
        state_hash = hash_physics_state(physics_objects)
        print(f"Frame {frame}: Hash {state_hash}")
    
        # Log first object details for debugging
        obj = physics_objects[0]
        print(f"  Obj[0]: pos={obj.position}, vel={obj.velocity}")
    
  2. Compare logs from both clients:

    # If hashes differ, find first divergence frame
    diff client1.log client2.log
    
  3. Check iteration order:

    # ❌ BAD: Undefined order
    for obj in physics_objects:  # If dict/set
        obj.update()
    
    # ✅ GOOD: Sorted order
    for obj in sorted(physics_objects, key=lambda o: o.id):
        obj.update()
    
  4. Verify RNG synchronization:

    # Both clients must use same seed
    rng = Random(seed=game_session_id)
    
    # Log RNG calls
    value = rng.random()
    print(f"RNG: {value}")  # Should match on both clients
    
  5. Test on different machines:

    # Different CPUs can produce different floating-point results
    # Use fixed-point math if needed:
    def to_fixed(value, scale=1000):
        return int(value * scale)
    
    def from_fixed(fixed_value, scale=1000):
        return fixed_value / scale
    

Debugging Jittery Constraints

Symptoms: Vehicle suspension bounces, ropes vibrate, joints don't settle.

Diagnosis:

  1. Increase sub-steps:

    # Current: 1 step
    physics_update(dt)
    
    # Fix: 4 sub-steps
    for _ in range(4):
        physics_update(dt / 4)
    
  2. Increase solver iterations (Unity):

    rigidbody.solverIterations = 12;  // Try 8, 12, 16
    
  3. Check spring stiffness (might be too high):

    # Too stiff: stiffness = 100000
    # Better: stiffness = 50000 (or lower)
    suspension_stiffness = 50000
    

Common Debugging Commands

# Enable physics visualization
debug_draw_colliders = True
debug_draw_forces = True
debug_draw_velocities = True

# Frame-by-frame physics
pause_physics = True
step_one_frame = False

if step_one_frame or not pause_physics:
    physics_update(dt)
    step_one_frame = False

# Slow motion
time_scale = 0.1  # 10x slower
physics_update(dt * time_scale)

Testing Checklist

Fixed Timestep Verification

  • Physics runs at consistent rate regardless of frame rate
  • Test at 30 FPS, 60 FPS, 144 FPS - identical behavior
  • Rendering interpolates smoothly between physics states
  • No spiral of death at low frame rates (frame time clamped)

Stability Testing

  • No physics explosions at high speeds
  • Objects settle into rest (don't vibrate forever)
  • Stacked objects are stable (no jittering)
  • Constraints converge (suspension, ropes, chains)

Collision Detection

  • Fast objects don't tunnel through thin walls
  • CCD enabled for projectiles and fast vehicles
  • Collision response conserves momentum
  • Friction behaves realistically

Determinism Testing (Multiplayer)

  • Same inputs produce same outputs (bit-identical)
  • Fixed timestep (never variable)
  • Sorted iteration order (by ID or consistent key)
  • Seeded random number generator
  • Single-threaded physics or deterministic parallel
  • Tested on different machines (same results)

Performance Testing

  • Meets target frame rate with max object count
  • Spatial partitioning for collision broad-phase
  • Physics islands for sleeping objects
  • LOD for distant physics objects

Edge Cases

  • Handles zero/infinite/NaN values gracefully
  • Extreme velocities clamped or handled
  • Extreme forces don't cause explosions
  • Division by zero checks (mass, distance, etc.)

Integration Testing

  • Physics integrates with game state (damage, score, etc.)
  • Transitions between physics states work (kinematic ↔ dynamic)
  • Save/load preserves physics state
  • Replay system reproduces physics accurately

Summary

Physics simulation for games requires balancing realism, performance, and stability. The core principles are:

  1. Always use fixed timestep - Determinism and stability
  2. Choose integration method carefully - Semi-implicit Euler for most cases
  3. Use CCD for fast objects - Prevent tunneling
  4. Design for determinism - Critical for multiplayer
  5. Sub-step complex constraints - Vehicles, cloth, ropes
  6. Fake physics when possible - Visual effects don't need real physics
  7. Test under pressure - High speeds, low frame rates, edge cases

Master these patterns and avoid the common pitfalls, and your physics systems will be stable, performant, and feel great to play.