Gravity zones

Usage guide of world.gravity_zones: small planets, oriented fields and zero-G volumes; the zones config, priority and blending, the sample and step services, attaching Rapier bodies, the request hook, events and engines.

Source: module-kits:kits/world_gravity_zones/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs world-gravity-zonesMCP tool: gripforge_docs { "topic": "world-gravity-zones" }

Local gravity in your world (world.gravity_zones) #

Small planets, fields with a fixed direction and zero-gravity rooms, with deterministic priorities. Readable offline: gripforge docs world-gravity-zones, or the MCP tool gripforge_docs (topic world-gravity-zones).

What it is for #

The kit answers one question: what is the gravity at this point? A zone is a sphere or an oriented box with a mode (radial toward a centre, directional along a world-space vector, or zero). The kit returns the acceleration, the local "up", and the winning zone. It can also advance a body (velocity and rotation) and, on the Rapier physics port, keep attached dynamic bodies under the local gravity and aligned to the local up. It never moves a collider: the engine keeps collisions and character control. The same Rust core is shipped as a C library (libgfgravity) and WebAssembly for engines that call sample and step themselves.

Turn it on #

{ "id": "world.gravity_zones",
  "config": { "defaultGravity": [0, 0, 0],
              "zones": [{ "id": "planet", "mode": "radial", "center": [0, 0, 0], "radius": 30, "strength": 8 }] } }

First example #

<!-- test: kits/game_engine/host/tests/world_gravity_zones.rs::service_and_save_restore_share_the_library_model --> With the zone above, a point 10 m over the centre falls toward it, a zero-G room can be added at run time, and a save and reload bring it back.

{ "op": "call", "service": "world.gravity_zones", "method": "sample", "args": [[0, 10, 0]] }
{ "op": "call", "service": "world.gravity_zones", "method": "setZone",
  "args": [{ "id": "room", "mode": "zero", "center": [0, 10, 0], "radius": 3, "priority": 5 }] }

The first answer has gravity equal to [0, -8, 0]; after setZone, sample at the same point answers zeroG true and the zone room. After removeZone, the zone is planet again.

Settings and defaults #

SettingTypeDefaultMeaning
defaultGravityvector, m/s²[0, -9.81, 0]acceleration outside every zone; use [0, 0, 0] for space
zoneslist, at most 256[]the gravity fields

A zone:

KeyDefaultMeaning
idrequiredunique name (1 to 128 characters); a duplicate is refused
moderequiredradial, directional or zero
centerrequired[x, y, z] in metres
shapespheresphere or box
radius10sphere radius, metres
halfExtentsrequired for a boxpositive half sizes, metres
rotationidentitybox orientation as a quaternion [x, y, z, w]; it does not rotate a directional gravity
directionrequired for directionalnon-zero world-space vector, normalised by the kit
strength9.81acceleration in m/s²; a radial field is constant, not inverse-square
priority0higher wins; on a tie the greater id (UTF-8 order) wins, whatever the insertion order
blendDistance0width in metres of the inner edge that blends over lower-priority fields; 0 is a hard edge
enabledtruea disabled zone is ignored

An invalid config is reported by the project validation and, at start, logged as an error; the kit then falls back to zero gravity.

Services, events, hooks #

  • world.gravity_zones (service):
    • sample(position) answers gravity, up (a zero vector in zero-G), zone (an id or null) and zeroG.
    • step(body) answers the velocity and rotation after dt. The body holds position, velocity, optional rotation (quaternion) and alignRate; dt must be within 0 to 1 second and alignRate within 0 to 10000 radians per second.
    • config() is a snapshot; configure(config) replaces the fields atomically. configure({ preset: "<id>", ... }) takes the config of a document of world_gravity_zones_presets and applies your own keys over it.
    • setZone(zone) inserts or replaces a zone; removeZone(id) answers whether it existed.
    • attach(entity, options) and detach(entity): Rapier physics only, on an existing dynamic body. options.nativeGravity (default [0, -9.81, 0]) is the gravity the engine already applies, which the kit compensates; options.alignRate (default 6) is how fast the body turns its up toward the local up, in radians per second.
  • gravity.changed (event): an attached body changed its winning zone. Payload: entity, from, to (zone ids or null for the default field), gravity, up.
  • world.gravity_zones.changed (event): a decided method succeeded. Payload: op, result.
  • world.gravity_zones.refused (event): a hook cancelled a decided method. Payload: op, by, reason.
  • world.gravity_zones.request (hook): asked before each decided method (setZone, removeZone, configure, attach, detach) with { op, args }. A tap may change args or cancel; the call then answers { ok: false, cancelled: true, by, reason }.
  • world_gravity_zones_attached (condition): true when the entity is attached. world_gravity_zones_request (verb): runs a decided method from a script, with op and args.
{ "world_gravity_zones_attached": "player" }

Modding #

A mod can change both settings, add documents to world_gravity_zones_presets (named gravity fields) and tap the request hook.

Saves #

The save keeps the zones and the attached bodies with their last winning zone.

Engines #

The rules are the same Rust core everywhere. In the Rust kit host, attach works on the Rapier port only. Other engines call sample and step through libgfgravity and disable their own body gravity before applying the result; they keep their collisions. There is no presenter.

When it does not do what you expect #

  • attach fails with "requires the Rapier physics port": the engine uses another physics; call sample and step yourself.
  • attach fails with "requires an existing dynamic rigid body": the entity has no body yet.
  • A config is refused: a duplicate zone id, more than 256 zones, a zero direction, a missing halfExtents for a box, or an unknown key.
  • Two zones overlap and the wrong one wins: raise the priority of the one you want, or add a blendDistance.
  • A body falls twice as hard: set nativeGravity in attach to what the engine applies.

Limits #

Constant-strength fields, no orbital (inverse-square) gravity. No collision and no locomotion: the engine owns them.

Versions #

Kit 1.1.0 (service contract 1.1.0). The request hook, presets and script verbs since 1.1.0; the Rust core, C library and WebAssembly wrapper since 1.0.0.