Dynamics
Continuous-time dynamics primitive for agents on a ContinuousProjection. Owns the per-agent velocity state and the list of Forces contributing to motion; the step method performs the Euler integration step (sum forces → integrate velocity → compute candidate position) without applying it. Applying the new state — including boundary handling — is the caller's responsibility, because boundary policy varies between models (toroidal wrap for boids, walled rejection for pedestrians).
Typical use:
val dynamics = Dynamics<Boid>(space).apply {
addForce(separation(radius = 3.0))
addForce(alignment(radius = 8.0))
addForce(cohesion(radius = 10.0))
maxSpeed = 4.0
minSpeed = 1.0
}
// initial velocity
dynamics.setVelocity(boid, Point2D(...))
// in the agent's process { } body:
while (true) {
val (vNew, pNew) = dynamics.step(boid, dt = 0.05)
dynamics.setVelocity(boid, vNew)
space.moveTo(boid, wrap(pNew))
delay(0.05)
}For the common "no boundary handling needed" case use runDynamics which packages the entire loop into one suspending call.
Mass and speed clamps validate via the Defaults convention (setters throw IllegalArgumentException on invalid values). Forces and velocities aren't validated — Force factories produce bounded values by design.
Parameters
the projection that owns agent positions
per-agent mass function. Default is unit mass for all.
Constructors
Properties
Number of forces currently registered.
Lower bound — velocities below this magnitude are rescaled back up to minSpeed. A body at exactly zero velocity is given a deterministic heading (a pure function of its identity, so runs stay reproducible) and boosted to minSpeed too, so a positive minSpeed truly guarantees the agent never comes to rest (boids). Set to 0 to allow agents to stop. Must be non-negative and <= maxSpeed.
Functions
Set / update the velocity tracked for agent.
Compute one Euler step for every agent in agents without applying any of them — a Jacobi (synchronous) update. Every step reads the current shared state, so the result is independent of the order of agents, unlike calling step and applying per agent in a loop (which lets earlier agents' moves bias later ones — a Gauss-Seidel update that depends on iteration order). Use this when reproducibility / order independence matters, e.g. flocking.
Get the current velocity tracked for agent, or null if not set.