Destructibles

Staged destruction of crates, walls and props with armor, resistances, detachable pieces and a final explosion, plus support graphs that collapse; every change is an effect your engine confirms.

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

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

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 #

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 #

<!-- test: kits/world_destructibles/tests/world_destructibles.rs::armor_is_subtracted_from_every_hit_and_a_weak_hit_does_nothing --> A wall with maxHealth 100 and armor 5. A hit is requested, the engine applies the effect, then confirms it:

{ "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": <token>, "success": true }. A hit of 3, below the armor, does 0 damage and leaves the health at 80.

Settings and defaults #

SettingTypeDefaultMeaning
maxHealthnumber100maximum structural health, in damage points; destruction at zero is final
armornumber0flat points subtracted from every hit before resistance
resistancesobject{}up to 16 damage kinds mapped to a ratio from 0 to 1; a missing kind has none
stagesnumber list[0.66, 0.33, 0]1 to 8 strictly decreasing health ratios ending at 0; the stage is the number of thresholds reached
pieceslist[]up to 64 { id, detachAt }: a piece of your engine detaches once when the health ratio reaches detachAt
explosionRadiusnumber0radius in metres of the final explosion; 0 disables it
explosionDamagenumber0maximum explosion damage requested from the engine on destruction
structureobjectnonesupport 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.

{ "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.