Queries, events and introspection

Queries ask the world questions, events report what the last step did, and the counters and the debug draw show what a world holds. None of them changes the world: a world read between two snapshots snapshots to the same bytes, and queries are safe at any point between steps.

Queries

m3Pos3 from = {0.0, 10.0, 0.0};
m3Vec3 down = {0.0f, -20.0f, 0.0f}; // the ray runs to y = -10
m3CastHit hit;
bool found = m3World_CastRayClosest(world, from, down, m3DefaultQueryFilter(), &hit);
m3Pos3 ground = from;
if (found)
{
    ground = hit.point; // where the ray met the floor
}

Every query takes an m3QueryFilter, which works like a shape's filter (collision filters); m3DefaultQueryFilter sees everything. Disabled bodies are invisible to every query.

A call that fills an array takes the array's capacity right after it, writes at most that many entries, and returns the total, which may be more; a null array with a capacity of 0 measures the total first. A null array with a positive capacity, or a negative capacity, is refused. A refused call returns 0 and zeroes the array over its whole capacity, so no entry from an earlier call reads as an answer; a successful one, an empty answer included, writes only its own entries. List calls only read, so none is refused for a busy world.

Events

Each step rebuilds the event streams. They stay valid until the next step, a restore or the world's destruction, and their order is fixed: the same step always reports the same events in the same order.

Events observe; they are not state. A restore clears every stream.

The pre-solve veto

m3World_SetPreSolveCallback installs a function the step calls for each contact about to be solved whose shapes enabled pre-solve events (enablePreSolveEvents or m3Shape_EnablePreSolveEvents). Returning false disables that contact for that step only: a one-way platform lets a body through from below this way. The callback is called from one thread, in a fixed pair order, with the deepest point and the normal from shape A to shape B.

Each step's vetoes are recorded by the journal, so a replay applies them without the callback, and lands on the recorded bits. Rollback is different: resimulating from a snapshot calls the callback again. The callback must therefore be a pure function of its arguments, and of host state that is the same on every run. One that reads a clock, a random source or changing host state breaks the host's own rollback, not the engine. Installing the callback is host wiring, and neither the journal nor a snapshot holds it.

Debug draw

m3World_Draw draws the world through the callbacks of an m3DebugDraw: segments, points and triangles, each with a colour. Its switches choose the layers: shape wireframes, solid shapes, contact points with their normals scaled by impulse, joints, the broad phase's bounds, a grey tint for sleeping bodies, island colours, each body's principal inertia axes, and the broad-phase tree's inner boxes. Start from a zeroed m3DebugDraw, so a layer a later release adds stays off. A draw changes nothing in the world.

Introspection

Reading any of these changes nothing, and a test holds a world that polls them every step to the bits of a twin that never looks.

Every refusal returns its result and changes nothing. A refusal also counts in m3Counters.misuseCount when a correct program would never make it, so a host can check the counter once a frame and catch bugs in release builds. The engine has no log; the counter and the results are how it reports.

Which refusals count misuse, in one place: