# 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 ```json { "id": "world.gravity_zones", "config": { "defaultGravity": [0, 0, 0], "zones": [{ "id": "planet", "mode": "radial", "center": [0, 0, 0], "radius": 30, "strength": 8 }] } } ``` ## First example 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. ```json { "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 | Setting | Type | Default | Meaning | |---|---|---|---| | `defaultGravity` | vector, m/s² | `[0, -9.81, 0]` | acceleration outside every zone; use `[0, 0, 0]` for space | | `zones` | list, at most 256 | `[]` | the gravity fields | A zone: | Key | Default | Meaning | |---|---|---| | `id` | required | unique name (1 to 128 characters); a duplicate is refused | | `mode` | required | `radial`, `directional` or `zero` | | `center` | required | `[x, y, z]` in metres | | `shape` | `sphere` | `sphere` or `box` | | `radius` | `10` | sphere radius, metres | | `halfExtents` | required for a box | positive half sizes, metres | | `rotation` | identity | box orientation as a quaternion `[x, y, z, w]`; it does not rotate a directional gravity | | `direction` | required for `directional` | non-zero world-space vector, normalised by the kit | | `strength` | `9.81` | acceleration in m/s²; a radial field is constant, not inverse-square | | `priority` | `0` | higher wins; on a tie the greater id (UTF-8 order) wins, whatever the insertion order | | `blendDistance` | `0` | width in metres of the inner edge that blends over lower-priority fields; 0 is a hard edge | | `enabled` | `true` | a 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: "", ... })` 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`. ```json { "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.