This article presents a compact simulation architecture for
This article presents a compact simulation architecture for
Simulation is the core runtime loop behind responsive, time-driven behavior. This article builds a complete ,
The result is a clean foundation for physics, animation, particles, camera motion, AI steering, and any feature that depends on consistent simulation updates.
The simulation subsystem is organized around a small set of responsibilities: time control, world management, force . application, state integration, and output for rendering.
┌───────────────────────────────┐
│ simulation_system │
│ - manages timestep │
│ - runs integrator │
│ - updates world │
└──────────────┬────────────────┘
│
┌────────┴─────────┐
│ simulation_world │
│ - bodies │
│ - global forces │
└────────┬─────────┘
│
┌──────────┴───────────┐
│ rigid_body │
│ - state │
│ - mass │
│ - accumulated Forces │
└──────────┬───────────┘
│
┌────────┴─────────┐
│ simulation_state │
│ pos, vel, acc │
└──────────────────┘
The
This pattern keeps simulation state stable while allowing rendering to run at its own pace.
Real time is continuous; simulation time is discrete. A fixed timestep converts variable frame timing into predictable simulation steps.
A
frameTime → accumulator → simulate(dt) → render
Here is the accumulator in action:
accumulator = 0
while (true) {
frameTime = getFrameTime()
accumulator += frameTime
while (accumulator >= dt) {
simulate(dt)
accumulator -= dt
}
render()
}
With this approach, physics can advance at a fixed rate, such as 60 Hz, while rendering remains independent. For off-screen calculations, there is no need to wait for real time to pass: the system can simply advance to the next timestep as quickly as needed. This is how simulation solvers typically operate.
Each simulated object is represented by a
rigid_body ├── simulation_state │ ├── position │ ├── velocity │ └── acceleration └── m_mass └── m_accumulatedForce
Keeping the body small makes the simulation easier to reason about, test, and extend.
During each step, the world gathers all active forces before computing acceleration:
f_total = f_gravity + f_drag + f_spring + ...
Acceleration then follows directly from Newton’s second law:
a = f_total / m_mass
In a typical simulation pipeline, the flow can be summarized as follows:
Separating force accumulation from acceleration keeps the design extensible. New force generators—such as wind, constraints, springs, or impulses—can be added without changing the integration pipeline.
Integration advances each body from its current state to the next timestep. This tutorial compares two common approaches.
v += a * dt;
x += v * dt;
The same Euler step can also be written in equation form:
x(t+dt) = x(t) + v(t)*dt v(t+dt) = v(t) + a(t)*dt
RK4 improves stability by sampling the derivative four times within a single timestep:
k1 = f(state);
k2 = f(state + k1*dt/2);
k3 = f(state + k2*dt/2);
k4 = f(state + k3*dt);
state += (k1 + 2k2 + 2k3 + k4) * dt/6;
Visually, those four samples trace the timestep from the initial state through two midpoint estimates and a final endpoint estimate:
By combining multiple derivative samples, RK4 typically produces smoother and more accurate motion than Euler, particularly when acceleration changes quickly.
The
simulation_world ├── m_bodies[] ├── Gravity via m_forces ├── accumulateForces() └── computeAccelerations()
In practice, the world prepares every body for integration by applying shared forces and computing the accelerations required for the next step.
The simulation_system orchestrates the subsystem by connecting time management, the simulation world, and the selected integrator.
Simulation_system ├── keeps track of time (fixed or variable) ├── simulation_world ├── integrator (Euler or RK4) └── step_simulation
A typical step follows this order:
frameTime = determine the elapsed time, fixed time step or dynamic time step
// reset forces and accelerations
world.clearForces()
world.accumulateForces()
world.computeAccelerations()
// integrate over time to find the next state
For every body in the world:
integrator.integrate(body.state, frameTime)
The structure is intentionally conventional: clear forces, apply forces, compute acceleration, and integrate each body forward.
Assembled end to end, the subsystem forms a simple pipeline:
┌──────────────────────────────────────────────┐
│ simulation_system │
│ │
│ frameTime = clock.frameDeltaTime() │
│ simulate(frameTime) │
└───────────────────────┬──────────────────────┘
│
simulate(dt)
│
┌───────────────────┴───────────────────┐
│ simulation_world │
│ clearForces() │
│ accumulateForces() │
│ computeAccelerations() │
└───────────────────┬───────────────────┘
│
integrate(dt)
│
┌───────────────────┴───────────────────┐
│ Integrator │
│ Euler or RK4 │
└───────────────────┬───────────────────┘
│
update state
│
┌───────────────────┴───────────────────┐
│ rigid_body │
│ position, velocity, acceleration │
└───────────────────────────────────────┘
This pipeline can support a broad range of engine features:
This architecture matters because it shows how the main parts of a simulation engine fit together:
Together, these ideas provide the foundation for the next stages of the BeforeTheMesh simulation arc.
A well-designed simulation subsystem separates time management, force calculation, world state, and integration into clear responsibilities. That separation keeps the engine predictable, testable, and extensible as new behaviors are added. With fixed timestep control, modular bodies, a dedicated world, and interchangeable integrators, the framework now has a stable base for more advanced systems such as collision detection, constraints, and real-time visualization.
Future work can expand this foundation to include: