# 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 ```text 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 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: ```json { "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 | Setting | Type | Default | Meaning | |---|---|---|---| | `entry` | frame | position `[0, 0, 0]` | Entry opening. | | `exit` | frame | position `[0, 0, 10]` | Destination opening. | | `cooldown` | number | `0.2` | Seconds before the same traveller can cross again after a commit, 0 to 60. | | `exitOffset` | number | `0.02` | Small outward offset at the exit, metres, 0 to 10. | | `enabled` | boolean | `true` | Initial 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.