# Destructible objects (world.destructibles) Readable offline: `gripforge docs world-destructibles`, or the MCP tool `gripforge_docs` (topic `world-destructibles`). ## What it is for `world.destructibles` holds the rules of things that break: structural health, damage filtering by armor and resistance, destruction stages, pieces that detach once, and an optional final explosion. In structure mode one instance also owns a support graph (nodes, breakable links, anchors) and decides which nodes fall when their support is cut. The kit is a driver service, not an automatic system. It computes what should happen and returns it as an effect; your game or engine swaps meshes, moves colliders, spawns debris and loot, runs the explosion query and applies gravity, then confirms the effect. It never moves an entity, fractures a mesh or damages a neighbour by itself. ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "world.destructibles" } gripforge_gamekit_configure { project_id: "gkp_…", id: "world.destructibles", config: { "maxHealth": 100, "armor": 5 } } ``` The installed configuration is the one of the `default` instance. Create named instances with the service: `create(id, config)`, then `request(id, command)`. ## First example A wall with `maxHealth` 100 and `armor` 5. A hit is requested, the engine applies the effect, then confirms it: ```json { "op": "hit", "seq": 1, "amount": 25, "position": [1, 2, 3] } ``` The answer holds the committed state and an action `{ token, effect }` with `effect.damage` 20 (25 minus the armor) and `effect.health` 80. The health is still 100 until the engine sends `{ "op": "resolve", "token": , "success": true }`. A hit of 3, below the armor, does 0 damage and leaves the health at 80. ## Settings and defaults | Setting | Type | Default | Meaning | |---|---|---|---| | `maxHealth` | number | `100` | maximum structural health, in damage points; destruction at zero is final | | `armor` | number | `0` | flat points subtracted from every hit before resistance | | `resistances` | object | `{}` | up to 16 damage kinds mapped to a ratio from 0 to 1; a missing kind has none | | `stages` | number list | `[0.66, 0.33, 0]` | 1 to 8 strictly decreasing health ratios ending at 0; the stage is the number of thresholds reached | | `pieces` | list | `[]` | up to 64 `{ id, detachAt }`: a piece of your engine detaches once when the health ratio reaches `detachAt` | | `explosionRadius` | number | `0` | radius in metres of the final explosion; 0 disables it | | `explosionDamage` | number | `0` | maximum explosion damage requested from the engine on destruction | | `structure` | object | none | support graph, see below; absent: single-object mode | The explosion is requested only when both `explosionRadius` and `explosionDamage` are positive. Unknown fields and values out of range are refused. Built-in presets exist for a crate, an explosive barrel, a wall and a suspended walkway; a `preset` key in the config of `create` takes the matching document of the presets registry, your own keys on top. ## Services, events, variables - `world.destructibles` (service): `create(id, config)`, `request(id, command)`, `remove(id)` and `ids()`. At most 128 named instances; an id has 1 to 128 bytes and cannot be reused while it exists. - Commands of `request`: `hit` (`seq`, `amount` and `position` are required; `kind` defaults to `generic`) and `repair` (`amount`); in structure mode also `hitLink`, `breakLink`, `setAnchor` and `step`; and `resolve`, `preview`, `status`, `config`, `save` and `load`. - A hit's effect is `{ kind: "damage", seq, damage, health, stage, detach, destroyed, explosion }`. `detach` lists the pieces newly detached, `explosion` is `{ position, radius, damage }` on destruction, else null. - `hit` needs a strictly rising `seq` (per instance, or per node and per link in structure mode), assigned by your driver. - `repair` restores health up to the maximum and updates the stage. It never reattaches pieces nor revives a destroyed object. - world.destructibles.created, world.destructibles.removed (events): the instance name (and the preset for `created`). - world.destructibles.changed (event): `{ instance, op, result }` after a decided command. - world.destructibles.refused (event): `{ instance, op, by, reason }` when a tap cancelled the command. ## Confirming effects Every command that changes something returns `{ state, action }`; `state` is what is committed, `action` is `{ token, effect }` or null. The exact order is: 1. Request the command. A `preview` is a prediction and never an action. 2. Check the world is still as expected and apply the whole effect once (mesh, collider, debris, loot, explosion). 3. Send `resolve` with the `token` and `success: true`; if the engine could not apply it, undo what it did and send `success: false`. A dropped hit does not use its `seq`. While an action is pending no other change, `save` or `load` is accepted. A token is consumed by its resolution; a duplicate is refused. Keep a ledger of applied tokens per instance lifetime so a lost reply is not applied twice. A destroyed object refuses `hit` and `repair`; to respawn it, create a new instance. Use the `damage` of the effect for notifications and do not also subtract it through `combat.core` for the same object. ## Structure mode Adding `structure` makes one instance own a whole support graph: 1 to 128 `nodes` (`id`, `anchored`, optional `properties` overriding the root damage settings) and up to 256 `links` (`id`, `a`, `b`, `strength`, a damage capacity from 0.01, default 100). `budgetPerTick` (1 to 64, default 8) caps how many nodes fall per step. The topology is fixed for the instance. ```json { "maxHealth": 100, "structure": { "budgetPerTick": 1, "nodes": [ { "id": "pier", "anchored": true }, { "id": "deck_a" }, { "id": "deck_b" } ], "links": [ { "id": "attachment", "a": "pier", "b": "deck_a", "strength": 50 }, { "id": "beam", "a": "deck_a", "b": "deck_b", "strength": 80 } ] } } ``` A node is supported if a path of live links and live nodes reaches an anchored node. This is connectivity, not a force or stress solver. Commands: `hit` `{ node, seq, amount, position }`, `repair` `{ node, amount }`, `hitLink` `{ id, seq, amount }`, `breakLink` `{ id }`, `setAnchor` `{ node, anchored }` and `step` `{ tick }`. Nodes cut from every anchor are listed in `state.unstable`; the next `step` returns a `collapse` effect with at most `budgetPerTick` fallen nodes and the links to cut. `tick` is a strictly increasing integer of your simulation, not seconds; call `step` once per authoritative tick after the damage effects are resolved. In the example above, breaking `attachment` and stepping at tick 1 drops `deck_a` and breaks `beam`; tick 2 releases `deck_b`. ## Hooks and modding - `world.destructibles.create` `{ instance, preset, config }`: asked before an instance is created. A tap may change `config`; cancelled, the creation is refused. - `world.destructibles.request` `{ instance, op, command }`: asked before every decided command (not `status`, `config`, `save`, `preview`, `load` nor the `step` command). A tap may change `command`; cancelled, the answer is `{ ok: false, cancelled: true, by, reason }` and world.destructibles.refused is emitted. Presets are documents of the presets registry of the kit. ## Engines One Rust core serves the kit host (web and native player), a C library and a WebAssembly module. The wrappers do not move your entities for you: your engine code applies each effect and calls `resolve`. ## When it does not do what you expect - A hit is refused: `seq` did not rise, `position` is missing, an amount is out of range, or the object is destroyed. - The health did not change: the effect was not resolved with `success: true` yet. - No explosion: `explosionRadius` or `explosionDamage` is 0. - A node never falls: it is still linked to an anchor through another path, or `step` was not called with a rising `tick`. - A pending action blocks `save`: resolve it first. ## Limits No geometry fracture, no physics, no gravity and no loot: the engine owns them. The kit adds no tick of its own. A graph cannot be split across instances and its topology cannot change during a lifetime. ## Versions Kit 1.2.0. 1.1.0 added structure mode; 1.2.0 the create and request hooks, the presets registry and the outcome events.