# How Box3D's Soft Step Solver Ensures Stable Stacking Without Iterative Divergence

> Discover how Box3D's Soft Step solver achieves stable stacking and prevents divergence by treating contacts as damped springs, limiting impulses and decaying energy across substeps.

- Repository: [Erin Catto/box3d](https://github.com/erincatto/box3d)
- Tags: internals
- Published: 2026-08-01

---

**Box3D prevents the "exploding Jenga stack" phenomenon by treating contact constraints as damped springs through the `b3Softness` parameters—`biasRate`, `massScale`, and `impulseScale`—which limit corrective impulses and decay accumulated energy across substeps.**

The erincatto/box3d physics engine solves the classic rigid body stacking instability—where tall towers of objects diverge numerically and explode—through a novel **Soft Step contact solver**. Unlike traditional impulse-based methods that inject aggressive corrective velocities when resolving penetration, Box3D's approach blends speculative separation bias with soft penetration constraints. This architecture maintains stable stacking even under high mass ratios and deep contact manifolds.

## The Three Softness Parameters

At the core of the stability mechanism lies the `b3Softness` struct, which packs three coefficients that convert hard constraints into compliant springs:

- **`biasRate`** – Controls how much positional error (penetration) feeds into the velocity correction
- **`massScale`** – Scales the effective mass of the contact, preventing large impulse responses
- **`impulseScale`** – Damps the accumulated impulse from previous iterations, acting as a velocity-proportional damper

During the preparation phase in [`src/contact_solver.c`](https://github.com/erincatto/box3d/blob/main/src/contact_solver.c), the solver assigns softness to each contact constraint. In `b3PrepareContacts_Mesh`, the code selects between global contact softness and a stiffer variant for static bodies:

```c
contactConstraint->softness = (contact->flags & b3_contactStaticFlag) 
    ? context->staticSoftness 
    : context->contactSoftness;

```

*Reference: [`src/contact_solver.c`](https://github.com/erincatto/box3d/blob/main/src/contact_solver.c) lines 161-162*

Static contacts receive `staticSoftness` with a higher `biasRate` and lower `massScale`, ensuring ground bodies remain immovable and eliminating the "pushing-through-floor" failure mode.

## Dual Bias Strategy: Speculative vs. Soft

The solver distinguishes between separating contacts and penetrating contacts, applying different bias strategies to each to prevent divergence.

### Speculative Bias for Separation (s > 0)

When the current separation `s` is positive—meaning the bodies are not penetrating—the solver applies a **speculative bias** proportional to the inverse time step:

```c
velocityBias = s * inv_h;

```

*Reference: [`src/contact_solver.c`](https://github.com/erincatto/box3d/blob/main/src/contact_solver.c) lines 447-450*

This soft bias prevents tunneling and drift without injecting large corrective impulses. By scaling with the time step, it keeps the velocity prediction modest and stable.

### Soft Bias for Penetration (s ≤ 0)

When penetration occurs (`s ≤ 0`) and the solver operates in bias mode (`useBias == true`), the solver computes a **soft bias** using the compliance parameters:

```c
velocityBias = max(softness.massScale * softness.biasRate * s, -contactSpeed);
massScale    = softness.massScale;
impulseScale = softness.impulseScale;

```

*Reference: [`src/contact_solver.c`](https://github.com/erincatto/box3d/blob/main/src/contact_solver.c) lines 452-456*

The bias is explicitly limited by `contactSpeed` and scaled by `massScale`, ensuring the corrective velocity never overshoots. This transforms a hard inequality constraint into a damped spring that naturally converges.

## The Iteration Flow in b3SolveContacts_Mesh

The Soft Step solver follows a disciplined six-phase flow that prevents impulse explosions through progressive damping:

1. **Prepare contacts** – Create `b3ContactConstraint` objects and assign `b3Softness` from the world context. Static contacts receive `staticSoftness` for additional rigidity.

2. **Compute separation** – Evaluate the current distance between anchors, incorporating the pre-computed `baseSeparation`:
   ```c
   // From lines 438-442
   float s = b3Dot(cp->normal, pB - pA) + cp->baseSeparation;
   ```

3. **Select bias strategy** – Choose between speculative bias (`s > 0`) or soft bias (`s ≤ 0`) based on the separation sign.

4. **Solve normal impulse** – Update impulses using a damped formulation that blends relative velocity, bias, and historical impulse decay:
   ```c
   float deltaImpulse = -cp->normalMass * 
       (massScale * vn + velocityBias) - 
       impulseScale * cp->normalImpulse;
   ```

   *Reference: [`src/contact_solver.c`](https://github.com/erincatto/box3d/blob/main/src/contact_solver.c) lines 664-666*

5. **Clamp and apply** – Enforce non-negativity constraints while preserving the damping characteristics:
   ```c
   float newImpulse = max(cp->normalImpulse + deltaImpulse, 0.0f);
   deltaImpulse = newImpulse - cp->normalImpulse;
   // Apply to linear/angular velocities...
   ```

   *Reference: [`src/contact_solver.c`](https://github.com/erincatto/box3d/blob/main/src/contact_solver.c) lines 667-672*

6. **Solve friction** – Compute tangential impulses (central friction, twist friction, and rolling resistance) **after** the normal solve, sharing the same softness framework to prevent tangential destabilization.
   *Reference: [`src/contact_solver.c`](https://github.com/erincatto/box3d/blob/main/src/contact_solver.c) lines 891-911*

## Why This Prevents Iterative Divergence

The Soft Step solver guarantees stability through three mathematical mechanisms that work in concert:

**Damped Spring Behavior** – The combination of `biasRate` (spring stiffness) and `massScale` (effective mass reduction) models each contact as a damped spring. Such systems naturally converge to equilibrium without the oscillations that plague hard constraint solvers.

**Impulse Decay** – The `impulseScale` term reduces the contribution of accumulated impulses each iteration (`deltaImpulse -= impulseScale * cp->normalImpulse`). This acts as an energy dissipation mechanism that prevents the exponential growth of correction terms across substeps.

**Static Contact Stiffening** – By applying `staticSoftness` to ground contacts, the solver ensures immovable bodies truly remain static. This eliminates the feedback loop where dynamic bodies push static bodies, causing the entire stack to drift and diverge.

## Implementation Example

To configure the Soft Step solver in your simulation, adjust the world softness parameters before stepping:

```c
// Configure world softness in src/physics_world.c via b3MakeSoft
b3World* world = b3CreateWorld();
world->contactHertz = 30.0f;        // Frequency for dynamic contacts
world->contactDampingRatio = 0.7f;  // Damping ratio (0 = undamped, 1 = critical)

// Step the simulation - solver automatically applies soft-step logic
float dt = 1.0f / 60.0f;
b3Step(world, dt);

```

*Reference: [`src/physics_world.c`](https://github.com/erincatto/box3d/blob/main/src/physics_world.c) lines 1120-1123*

For specific contacts requiring custom compliance, override the default softness:

```c
b3Contact* contact = b3GetContact(world, contactId);
if (contact) {
    // Stiffer contacts for critical stability points
    contact->softness = b3MakeSoft(60.0f, 0.9f, world->stepContext.h);
}

```

## Summary

- **Box3D's Soft Step solver** treats contacts as damped springs using the `b3Softness` struct (`biasRate`, `massScale`, `impulseScale`) to limit impulse magnitudes.
- **Dual bias strategy** applies speculative bias for separating contacts and soft bias for penetration, preventing aggressive velocity injections.
- **Impulse damping** via `impulseScale` decays accumulated corrections across iterations, eliminating the energy growth that causes divergence.
- **Static stiffening** assigns higher compliance to ground contacts, ensuring immovable anchors remain stable during deep stacking.
- The implementation in [`src/contact_solver.c`](https://github.com/erincatto/box3d/blob/main/src/contact_solver.c) (`b3PrepareContacts_Mesh` and `b3SolveContacts_Mesh`) enables stable Jenga-style stacks without iterative explosions.

## Frequently Asked Questions

### What causes iterative divergence in physics solvers?

Iterative divergence occurs when constraint solvers inject increasingly large corrective impulses to resolve penetration errors, creating a positive feedback loop. In tall stacks, small errors at the bottom compound as the solver propagates corrections upward, eventually producing velocity explosions that numerically "explode" the simulation. Box3D's Soft Step solver prevents this by capping impulse magnitudes through mass scaling and damping historical contributions.

### How does the `b3Softness` struct prevent impulse explosions?

The `b3Softness` struct converts rigid constraints into compliant contacts. By scaling the effective mass (`massScale`) and damping previous impulses (`impulseScale`), it ensures that each iteration adds only a fraction of the corrective force needed. This creates a convergent series where impulses decay geometrically rather than growing exponentially, as implemented in the deltaImpulse calculation at lines 664-666 of [`src/contact_solver.c`](https://github.com/erincatto/box3d/blob/main/src/contact_solver.c).

### What is the difference between speculative bias and soft bias?

**Speculative bias** applies when bodies are separating (`s > 0`), calculating `velocityBias = s * inv_h` to prevent future tunneling without harsh corrections. **Soft bias** applies during penetration (`s ≤ 0`), using `velocityBias = max(softness.massScale * softness.biasRate * s, -contactSpeed)` to limit the correction velocity. The speculative approach keeps separating objects stable, while the soft approach gently pushes penetrating objects apart without overshoot.

### When should I use staticSoftness versus contactSoftness?

Use **`staticSoftness`** for contacts involving immovable bodies (ground, walls) to prevent dynamic objects from pushing through static geometry—this uses higher stiffness and lower mass scaling. Use **`contactSoftness`** for dynamic-dynamic interactions where some compliance prevents jitter. The solver automatically selects the appropriate softness in `b3PrepareContacts_Mesh` based on the `b3_contactStaticFlag`, ensuring optimal stability for each contact type.