Portals

Detect a body crossing a rectangular portal, get the transformed pose and velocity at the exit, and commit or cancel the crossing: settings, commands, hooks, saves.

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

Also read offline — CLI: grip-forge docs world-portalsMCP tool: gripforge_docs { "topic": "world-portals" }

Portals that carry a body from one opening to another (world.portals) #

What it is for #

world.portals models a pair of rectangular openings, an entry and an exit. When a traveller crosses the entry plane front to back, the kit returns a plan: where the traveller appears at the exit, its rotation, and its linear and angular velocity turned to match. Your game applies the plan, then confirms or cancels it. The kit does not render the portal view, stream scenes, test collisions or move the body: the engine owns all of that.

Turn it on #

gripforge_gamekit_install   { project_id: "gkp_…", id: "world.portals" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "world.portals",
                              config: { "entry": { "position": [0, 0, 0] }, "exit": { "position": [0, 0, 10] } } }

The kit has no requirement. One instance tracks one traveller through one pair; the default instance is created from the installed settings, and create(id, config) adds more (at most 128).

First example #

<!-- test: kits/world_portals/tests/world_portals.rs::crossing_the_entry_plans_a_pose_at_the_exit --> The entry at the origin faces +Z and the exit is 10 m further along +Z. A body moving from z = 1 to z = -1 crosses the entry:

{ "op": "prepare", "previous": [0, 0, 1], "position": [0, 0, -1], "velocity": [0, 0, -4] }

The answer is a pending plan with token: 1, fraction: 0.5, a position 1 m out of the exit plus the exitOffset (z = 11.02) and a velocity turned half a turn (z = 4). status then reports one pending crossing. Apply the plan to the body, then send { "op": "commit", "token": 1 }.

Settings and defaults #

SettingTypeDefaultMeaning
entryframeposition [0, 0, 0]Entry opening.
exitframeposition [0, 0, 10]Destination opening.
cooldownnumber0.2Seconds before the same traveller can cross again after a commit, 0 to 60.
exitOffsetnumber0.02Small outward offset at the exit, metres, 0 to 10.
enabledbooleantrueInitial state for new crossings.

A frame has position (required, metres), rotation (quaternion xyzw, normalized; local +Z is the front), halfWidth (default 1) and halfHeight (default 2), in metres. The configuration is immutable after creation and is validated: a negative cooldown, a missing position, a zero halfWidth or an unknown field refuses the instance.

Services and commands #

Service world.portals: create(id, config), request(id, command), remove(id) (drops the instance and any pending plan), ids(). There is no automatic tick: your controller sends commands.

  • advance {dt}: moves the cooldown forward; call it once per simulation substep, with dt up to 0.1 s.
  • prepare {previous, position, velocity, rotation?, angularVelocity?, radius?, from?}: previous and position are the body centre before and after the proposed move. from is entry (default) or exit. It answers null when the portal is disabled, cooling down, crossed the wrong way, missed the opening, or the body (with its radius) is too wide. Otherwise it answers a plan with token, position, rotation, velocity, angularVelocity, rotationDelta and fraction.
  • commit {token}: call it after the engine applied the whole plan; it starts the cooldown. A stale or reused token is refused.
  • cancel {token}: call it if the destination is blocked; no cooldown starts.
  • plan: shows the pending plan. enabled {enabled}: toggles new crossings.
  • status, config, save, load {save}, preview {command}: read, persist or predict without changing the instance.

While a plan is pending, only status, config, plan, commit and cancel are accepted.

Events, hooks and modding #

  • world.portals.created, world.portals.removed, world.portals.changed, world.portals.refused (events): an instance was created or removed, a decided command ran, or a hook cancelled one.
  • world.portals.create (hook): asked before an instance is created; a mod may change its config or refuse it.
  • world.portals.request (hook): asked before a decided command applies; a mod may change the command or cancel it.

Reads and fixed-step commands such as advance skip the request hook. A config may name a preset from the presets registry.

Saves #

save records the configuration and the model state, never a pending plan, and is refused while a crossing is pending, so commit or cancel first. Tokens are never reused within a live instance, even after a load.

When it does not do what you expect #

  • prepare answers null: the body did not cross front to back, missed the opening, is disabled, cooling down, or too large.
  • A command is refused with "pending": finish the crossing with commit or cancel first.
  • A body appears inside a wall: the kit does not test destination clearance; do it before commit and cancel if blocked.
  • The camera or physics cache jumps: apply position, rotation and velocities together, and fix the camera yourself.

Limits #

No portal rendering or render-to-texture, no scene streaming, no camera continuity, hierarchy or collision handling: these belong to the engine. The kit transforms existing velocities without scaling or integrating them.

Versions #

Kit 1.1.0 adds the hooks, presets, events and script verbs. 1.0.0 introduced the portal core and saves.