-
-
Notifications
You must be signed in to change notification settings - Fork 0
HOST_INTEGRATION
Gravitas does not own your application loop. A game engine, server, deterministic simulation harness, or unit test creates the host objects and calls Gravitas at deterministic points.
This page is the practical starting point for wiring Gravitas into a host. For runtime ownership details, read Runtime Architecture. For replay and snapshot boundaries, read Serialization And Replay.
- Create or attach one
GravitasWorldContextper simulation. - Add GridForge grid coverage before registering colliders.
- Implement
IMatterAgentto bridge host objects to context andFixedTransform. - Use
SolidBody/LSColliderfor 3D andSolidBody2D/LSCollider2Dfor 2D. - Apply deterministic commands before
context.Simulate(). - Call
context.Simulate()andcontext.LateSimulate()from the authoritative fixed step. - Use
context.Visualize()andcontext.LateVisualize()only for presentation. - Deactivate bodies and colliders before pooling or destroying host wrappers.
flowchart LR
Commands["Ordered commands"] --> Simulate["context.Simulate()"]
Simulate --> Late["context.LateSimulate()"]
Late --> Hash["optional replay hash"]
Late --> Visualize["context.Visualize()"]
Visualize --> Render["host render/update"]
| Need | Use |
|---|---|
| Create an owned world | GravitasWorldContext.CreateOwned(...) |
| Attach a host-owned world | GravitasWorldContext.Attach(world, takeOwnership) |
| Bind a host object | IMatterAgent |
| Register a 3D body | new SolidBody(agent, collider).Initialize(...) |
| Register a 2D body | new SolidBody2D(agent, collider).Initialize(...) |
| Register bodyless geometry | collider.InitializeWithNoBody(agent) |
| Set runtime mode | context.Settings.RuntimeMode |
| Run 3D constraints/ragdolls | context.Constraints3D |
| Run 2D constraints/ragdolls | context.Constraints2D |
| Query 3D, 2D, or mixed geometry |
context.Query3D, context.Query2D, context.QueryMixed
|
| Hash replay state | context.ComputeReplayHash() |
| Reset a session | context.Reset() |
| End a context | context.Dispose() |
Use the LSF lifecycle names as a mental model, not as engine-specific APIs:
| Phase | Host responsibility | Gravitas call |
|---|---|---|
Setup |
Create package defaults, host resources, and worlds. | Create or attach GravitasWorldContext. |
Initialize |
Bind agents, transforms, colliders, bodies, settings, and grids. | Initialize colliders and bodies. |
Execute |
Apply deterministic commands or network input in ordered frame batches. | No direct call. Mutate host-owned state before simulation. |
Simulate |
Advance the authoritative fixed step. |
context.Simulate(). |
LateSimulate |
Finish deterministic end-of-frame work. |
context.LateSimulate(). |
Visualize |
Interpolate or publish presentation state. |
context.Visualize(). |
LateVisualize |
Finish presentation-only work. |
context.LateVisualize(). |
Deactivate |
Pool/despawn agents and release registrations. |
body.Deactivate() or collider.Deactivate(). |
Quit |
Shut down the host process/session. |
context.Dispose() when the context is no longer needed. |
Authoritative simulation state belongs in Simulate and LateSimulate.
Visualize and LateVisualize are for interpolation and presentation; do not
use them to apply gameplay commands or physics corrections.
The host provides IMatterAgent so Gravitas can bind an object to a context and
fixed transform without depending on an engine, ECS, rendering, or a specific
object model.
using FixedMathSharp;
using Gravitas;
internal sealed class HostMatterAgent : IMatterAgent
{
public HostMatterAgent(
GravitasWorldContext context,
FixedTransform transform,
bool isParent = true)
{
Context = context;
Transform = transform;
IsParent = isParent;
}
public GravitasWorldContext Context { get; }
public FixedTransform Transform { get; }
public bool IsParent { get; }
public bool IsInteracting { get; set; }
}IsParent marks whether an agent is intended to be a top-level hierarchy owner.
Hierarchy collision filtering is bound explicitly on colliders:
weaponCollider.SetParent(characterCollider);
leftFootCollider.SetParent(characterCollider);
rightFootCollider.SetParent(characterCollider);SetParent(...) stores a dimension-tagged top-parent collider key, so sibling
filtering does not depend on an engine transform hierarchy. Mixed mode can bind
2D colliders under 3D colliders, or the reverse, without aliasing the separate
collider ID tables. Use ClearParent() when a collider leaves the hierarchy
without being deactivated.
Use CreateOwned(...) when Gravitas should own the GridWorld lifetime:
using Gravitas;
using GravitasWorldContext context = GravitasWorldContext.CreateOwned();Use Attach(...) when the host creates and owns the GridWorld:
using Gravitas;
using GridForge.Grids;
using GridWorld world = new();
using GravitasWorldContext context = GravitasWorldContext.Attach(world);Pass takeOwnership: true to Attach(...) only when disposing the context
should also dispose the supplied world. One active GridWorld can be attached
to only one active GravitasWorldContext.
Colliders partition into existing GridForge voxels. Add grids that cover the simulation area before initializing bodies and colliders:
using FixedMathSharp;
using GridForge.Configuration;
context.World.TryAddGrid(
new GridConfiguration(
new Vector3d(-16, -4, -16),
new Vector3d(16, 8, 16)),
out _);If no voxel exists for a collider's bounds, the collider cannot be distributed into partitions and will not participate in partition-backed collision/query work for that area.
LSCollider and LSCollider2D are public polymorphic runtime types, not
extension points for engine-owned geometry. Gravitas owns their concrete shape
implementations and keeps their base constructors assembly-only so every
registered collider has a supported deterministic geometry contract.
Engine adapters should translate host collider assets into the data-only shape definitions, then create an unbound runtime collider:
ColliderShapeDefinition shape =
ColliderShapeDefinition.Sphere(
Fixed64.Half,
PhysicsMaterial.Default);
LSCollider collider = shape.CreateCollider();
ColliderShapeDefinition2D shape2D =
ColliderShapeDefinition2D.Circle(Fixed64.Half);
LSCollider2D collider2D = shape2D.CreateCollider();The definitions snapshot mesh and polygon inputs and validate dimensions before
creating the matching Gravitas-owned runtime shape. Direct construction of
LSSphereCollider, LSCircleCollider2D, and the other concrete types remains
available when an adapter already knows the exact shape. No engine reference,
reflection, or custom collider subclass is required.
Dynamic 3D matter usually has a host agent, one LSCollider, and one
SolidBody.
using FixedMathSharp;
using Gravitas;
using Gravitas.Colliders;
using Gravitas.Materials;
FixedTransform transform = new(
Vector3d.Zero,
FixedQuaternion.Identity,
Vector3d.One);
HostMatterAgent agent = new(context, transform);
LSSphereCollider collider = new();
collider.Material = new PhysicsMaterial(
staticFriction: Fixed64.One,
dynamicFriction: Fixed64.Half,
restitution: Fixed64.FromFraction(1, 4));
SolidBody body = new(agent, collider)
{
Mass = Fixed64.One
};
body.Initialize(
Vector3d.Zero,
FixedQuaternion.Identity,
BodyMotionType.Dynamic);Initialization binds the body and collider to agent.Context, allocates
context-local body/collider IDs, calculates runtime shape data, and partitions
the collider. FixedTransform and Gravitas body admission both preserve a
normalized orientation invariant. Scaled or extreme finite quaternion inputs
are scale-safely normalized, and a zero quaternion resolves to identity before
runtime shape state is published.
2D scenes use the same host-agent shape, but select the 2D runtime path and create 2D body/collider types:
context.Settings.RuntimeMode = PhysicsRuntimeMode.TwoD;
LSCircleCollider2D collider = new(Fixed64.Half);
SolidBody2D body = new(agent, collider)
{
Mass = Fixed64.One
};
body.Initialize(
agent.Transform.WorldPositionXZ,
motionType: BodyMotionType.Dynamic);The 2D projection uses the LSF X/Z convention: world X maps to 2D X and world Z
maps to 2D Y. World Y remains vertical height or mixed embedding metadata.
Positive planar rotation turns Vector2d.Right toward Vector2d.Forward and is
exposed as WorldRotationXZRadians; its 3D embedding is a negative Y-axis
quaternion rotation. Gravitas canonicalizes authoritative 2D yaw to
[-Fixed64.Pi, Fixed64.Pi), including serialized and multi-turn inputs.
FixedTransform distinguishes authored local components from hierarchy-aware
world views. Host adapters should populate LocalPosition, LocalRotation, and
LocalScale from their engine object, then use the explicit World* members
when synchronizing physics. Gravitas kinematic readback and dynamic publication
use world position/rotation and preserve a 2D host's world Y. A failed parent
inverse is reported rather than silently writing the wrong local pose.
Use InitializeWithNoBody(...) for static or trigger geometry that does not
need body-owned state. IsTrigger is valid only on bodyless colliders; use a
normal body-owned collider for the moving participant that should receive
trigger callbacks:
using Gravitas.Colliders;
LSCuboidCollider floor = new();
floor.InitializeWithNoBody(agent);A body with all translation axes frozen is different from a static body or a bodyless collider. Select the runtime role explicitly:
| Role | Ownership |
|---|---|
BodyMotionType.Dynamic |
Solver controlled. FreezeAxes independently constrains translation and rotation. |
BodyMotionType.Kinematic |
Host controlled. Gravitas samples deterministic host motion and CCD, but applies no solver impulse. |
BodyMotionType.Static |
Immobile and excluded from simulated-body iteration. Use explicit pose setters to reposition it between fixed steps. |
For example, a rotating platform that must not translate is a Dynamic body
with BodyFreezeAxes3D.Position; the 2D equivalent uses
BodyFreezeAxes2D.Position. Freezing every axis does not silently change the
body role. LSCollider.IsStatic and LSCollider2D.IsStatic report only an
explicit Static body or bodyless ownership.
Call SetMotionType(...) to change a registered body's role between fixed-step
transactions. The transition preserves body, collider, pair, and joint
identity, but clears incompatible motion, sleep, CCD, contact warm-start, and
joint-solver state before repartitioning. It rejects calls before
initialization, after deactivation or context reset, during an open fixed-step
transaction, and from simulation callbacks. Ragdoll role changes use their
atomic runtime operation rather than transitioning links individually.
Static bodies do not poll their host transforms. SetPosition(...),
SetRotation(...), and 3D UpdateRotation(...) are the authoritative explicit
repositioning surface; they refresh pure and mixed partition membership
immediately so queries observe the new pose without a simulation step. A pose
change first prepares the complete collider candidate, including exact scale,
canonical geometry, bounds, and mass properties. If that candidate is invalid,
the body pose, host transform, committed collider state, and partitions remain
unchanged.
Bodyless 3D and 2D colliders rebuild from their agent transform during the next
fixed-step partition preparation. If the host mutates a bodyless collider's
transform and needs query results before the next step, call
collider.Simulate() after the mutation to refresh bounds and partition
membership immediately. The bodyless 3D LSCollider.Position and Rotation
setters are the transactional alternative: they validate the tentative world
pose and publish the host transform, canonical geometry, mass properties, and
partitions only after the whole candidate succeeds.
Authored FixedTransform.LocalScale may be signed or zero, but physical
collider dimensions may not. Gravitas requires every consumed authored local
axis throughout the transform ancestry to remain positive: X/Z for 2D colliders
and X/Y/Z for 3D colliders. Gravitas then composes the hierarchy's strict
local-to-world matrices and admits only a representable, nonsingular TRS
decomposition. This rejects canceled reflections and genuine hierarchy shear
instead of approximating either as a rotation plus diagonal scale. Pure 2D also
requires the composed X/Z basis to remain in the simulation plane.
Initialization and runtime rebuild reject invalid scale before changing body
registration, host pose, canonical geometry, mass properties, or partition
state. The host's authored local transforms remain untouched so an adapter or
authoring tool can correct them explicitly.
Owner scale and compound-part scale stay separate through shape preparation. Each final radius, axis length, half-extent, offset, mesh coordinate, or mass property is formed with an exact fused operation and rejected only when that final canonical value is not representable or physically valid. Intermediate Q32.32 saturation is never accepted as a plausible runtime dimension.
Use FixedTransform.TransformPoint(...), InverseTransformPoint(...), and
their explicit X/Z counterparts when converting through the current authored
or presentation snapshot.
Use SolidBody.GetWorldPoint(...) / GetLocalPoint(...), or the matching
SolidBody2D methods, for gameplay conversion through the authoritative
physics pose. These body methods use the collider's last committed owner-scale
snapshot, not a newly read host scale, so render interpolation or a pending
adapter mutation cannot change simulation queries. Their Try* counterparts
return false with a zero output before the first shape commit or when the
final coordinate is outside the Q32.32 domain.
Engine adapters should treat Gravitas as the sole simulation authority. Publish the visual pose to the engine transform, and disable native rigid-body simulation or interpolation that would apply a second motion source or double-interpolate the Gravitas result.
PhysicsMaterial is deterministic collider surface data. Assign it before
simulation when a surface needs explicit static friction, dynamic friction,
restitution, or combine policies:
collider.Material = PhysicsMaterial.Frictionless;Shape definitions and compound parts can also carry materials:
var compound = new LSCompoundCollider(
CompoundColliderPart.Sphere(
Fixed64.Half,
-Vector3d.Right,
PhysicsMaterial.Bouncy),
CompoundColliderPart.Cuboid(
Vector3d.One,
Vector3d.Right,
PhysicsMaterial.Default));Compound parts without an explicit material use the owning compound collider's material when private part colliders are materialized.
IgnoredCollisionLayers is a collider-owned physical filter:
projectileCollider.Layer = new PhysicsLayer(3);
ownerCollider.IgnoredCollisionLayers =
PhysicsLayerMask.FromLayer(projectileCollider.Layer);The rule is symmetric at pair time: if either collider ignores the other
collider's layer, the physical interaction is rejected. This affects discrete
collision pairs, trigger pairs, internal CCD target eligibility, and
grounding/support acceptance. Public query services use the caller's
PhysicsLayerMask instead.
Each context owns its own PhysicsSettings and PhysicsEnvironment.
context.SetFrameRate(60);
PhysicsSettings settings = PhysicsSettings.DefaultSettings();
settings.PoolingEnabled = true;
settings.RestitutionVelocityThreshold = Fixed64.FromFraction(1, 4);
context.ApplySettings(settings);
context.Environment.Gravity = Fixed64.FromFraction(49, 5);Different contexts can run at different frame rates and settings in the same
process. Frame-derived values such as DeltaTime, FrameCount, and TotalTime
are read through the context. Frame rates must stay between 1 and
PhysicsSettings.MaxResolvableFrameRate; ordinary lockstep rates are far below
that ceiling.
Per-body gravity tuning lives on the body. SolidBody.GravityScale multiplies
context gravity for that body; Fixed64.Zero disables environment-gravity
acceleration and grounded weight. SolidBody2D.GravityScale applies the same
policy to that body's planar gravity vector.
context.Constraints3D owns deterministic 3D joints and ragdoll runtimes.
context.Constraints2D owns deterministic 2D joints and ragdoll runtimes.
| Domain | Runtime types | Joint shape | Solved with |
|---|---|---|---|
| 3D |
Joint3D, RagdollRuntime3D
|
Local frames, angular axes, 3D motors/limits | 3D contact islands in LateSimulate()
|
| 2D |
Joint2D, RagdollRuntime2D
|
Planar anchors, scalar angles, scalar motors/limits | 2D contact islands in LateSimulate()
|
3D example:
using Gravitas.Constraints;
Joint3D shoulder = context.Constraints3D.RegisterJoint(new JointDefinition3D(
upperArmBody,
torsoBody,
upperArmLocalFrame,
torsoLocalFrame,
JointType3D.ConeTwist,
JointLimit3D.ConeTwist(maxConeAngle, maxTwistAngle),
JointMotor3D.Disabled,
JointCollisionPolicy.SuppressLinked));2D example:
using Gravitas.Constraints;
Joint2D hinge = context.Constraints2D.RegisterJoint(new JointDefinition2D(
forearmBody2D,
upperArmBody2D,
new JointFrame2D(Vector2d.Right * Fixed64.Half, Fixed64.Zero),
new JointFrame2D(-Vector2d.Right * Fixed64.Half, Fixed64.Zero),
JointType2D.Pin,
JointLimit2D.Angular(-Fixed64.HalfPi, Fixed64.HalfPi),
JointMotor2D.Disabled,
JointCollisionPolicy.SuppressLinked));Enabled joints use PhysicsSettings.DiscreteSolverIterations. Linked sleep/wake
behavior follows the island graph. Linked-collider collision suppression affects
physical collision/CCD pair creation, not public query include-mask semantics.
Ragdolls are authoring conveniences over the same joint model:
RagdollRuntime3D ragdoll = context.Constraints3D.RegisterRagdoll(
new RagdollDefinition3D(
links,
joints,
RagdollSelfCollisionPolicy.SuppressAdjacentLinks));
ragdoll.ActivateDynamic();
ragdoll.DeactivateToKinematic();Animation, IK, pose selection, engine animator hooks, and blending remain host or animation-package responsibilities. A deterministic animation package can compute motor payloads and pass them before the fixed step.
A simple deterministic loop looks like this:
while (running)
{
ApplyOrderedCommandsForFrame();
context.Simulate();
context.LateSimulate();
context.Visualize();
context.LateVisualize();
}Most real hosts call Simulate and LateSimulate from a fixed-rate scheduler
and call visualization phases from the render/update loop.
The high-level order is:
-
context.Simulate()advances the clock, runs simulate-phase services, advances lockstep coroutines, and invokes simulate hooks. -
context.LateSimulate()integrates bodies, processes CCD, refreshes partitions, distributes pairs, solves contacts and joints, refreshes grounding/support, handles mixed contacts when enabled, and invokes late-simulate hooks. -
context.Visualize()updates visual/presentation transforms for enabled services and invokes visualize hooks. -
context.LateVisualize()invokes hooks only.
For the 3D path, the fixed-step order is integrate-then-collide inside
LateSimulate: queued forces affect motion before the discrete collision pass
for that same frame.
For kinematic CCD, hosts must write deterministic target transforms before
context.LateSimulate(). Gravitas captures the body pose at the start of the
late step, reads the host transform as the requested target, and sweeps between
those two poses when continuous collision is enabled. The first static-style
blocker clips the kinematic pose and writes the clipped transform back to the
host binding.
2D, 3D, and mixed queries are explicit context services:
using Gravitas.Queries;
using Gravitas.Support;
using SwiftCollections;
PhysicsLayerMask layerMask = PhysicsLayerMask.FromLayer(0);
bool rayHitFound = context.Query3D.Raycast(
origin,
direction,
maxDistance,
out Physics3DHit rayHit,
layerMask);
SwiftList<Physics2DHit> planarHits = new();
int planarHitCount = context.Query2D.RaycastAll(
start2D,
end2D,
layerMask,
planarHits);
SwiftList<PhysicsMixedHit> mixedHits = new();
int mixedHitCount = context.QueryMixed.SweepSphereAgainst2DAll(
origin,
origin + direction * maxDistance,
radius,
layerMask,
mixedHits,
excludedCollider: null);All-hit APIs use caller-owned buffers. Batch APIs use typed request spans,
caller-owned output spans or shared hit lists, and PhysicsQueryHitRange
buffers.
Ground checks use context.Settings.GroundCheckLayerMask; hosts should set this
explicitly for their layer model. SolidBody.GroundingMode and
SolidBody2D.GroundingMode can stay automatic or switch to manual host-owned
support through UseManualGrounding(...), SetManualGrounding(...),
ClearManualGrounding(), and UseAutomaticGrounding(...).
Read Query Services for the full query surface.
For deterministic runs, apply ordered commands before context.Simulate().
Given the same initial context, settings, world state, command order, and frame
count, Gravitas should replay to the same authoritative body, collider, clock,
and contact state.
using Chronicler;
context.Simulate();
context.LateSimulate();
ChronicleHash hash = context.ComputeReplayHash();
SendFrameHashToLockstepPeer(context.FrameCount, hash);ComputeReplayHash() hashes context settings, physical environment values,
clock state, body state, collider shape/filter state, retained
continuation-affecting pair/contact state, and active CCD handoff state. It does
not hash host object identity, delegates, diagnostics buffers, debug draw
commands, query scratch buffers, or visualization interpolation caches.
The returned ChronicleHash is a deterministic conformance signal, not a
cryptographic hash and not a compatibility promise across package versions.
Deactivate runtime objects before pooling or destroying their host wrappers:
body.Deactivate();
floor.Deactivate();SolidBody.Deactivate() deactivates the collider, removes the body from the
physics service, and clears the dynamic ID. LSCollider.Deactivate() clears
partition membership, removes collision-pair references, clears explicit parent
binding, returns active pairs to the pool when enabled, and releases the
collider ID.
Use context.Reset() for a reusable session context. Reset detaches Gravitas
partition payloads from GridForge voxels and clears context-local runtime state
while preserving the world and its grids. Use context.Dispose() when the
context is finished.
- Do not mutate authoritative physics state from visualization phases.
- Do not read wall-clock time from runtime simulation logic.
- Keep command ordering deterministic before
Simulate(). - Add grid coverage before initializing colliders.
- Keep public query buffers caller-owned in hot paths.
- Use explicit runtime modes:
ThreeD,TwoD,Both, orMixed. - Use
Mixedonly when cross-dimensional contacts are intended. - Treat Chronicler as populate-existing-shell infrastructure, not an object factory.
| Area | Source |
|---|---|
| Context and lifecycle |
src/Gravitas/Runtime/GravitasWorldContext.cs, src/Gravitas/Runtime/GravitasClock.cs
|
| Host boundary | src/Gravitas/Core/IMatterAgent.cs |
| 3D body/service |
src/Gravitas/Core/3D/SolidBody.cs, src/Gravitas/Core/3D/GravitasPhysicsService.cs
|
| 2D body/service |
src/Gravitas/Core/2D/SolidBody2D.cs, src/Gravitas/Core/2D/GravitasPhysics2DService.cs
|
| Settings |
src/Gravitas/Settings/PhysicsSettings.cs, src/Gravitas/Settings/PhysicsRuntimeMode.cs
|
| Constraints |
src/Gravitas/Constraints/3D, src/Gravitas/Constraints/2D
|
| Query APIs | src/Gravitas/Queries |
| Replay hash | src/Gravitas/Determinism/GravitasReplayHashService.cs |