# Dungeon runs for parties (world.instances) A coordinator that owns dungeon runs: who was admitted, which boss is next, which attempt is live, who already claimed a reward. Readable offline: `gripforge docs world-instances`, or the MCP tool `gripforge_docs` (topic `world-instances`). ## What it is for The authoritative game opens a run for a party with a fixed roster and an ordered list of bosses. The kit then decides who may enter, which boss may start, which results are valid (an old attempt is refused) and who may claim a reward, exactly once. It does not create scenes or servers, spawn the bosses, or give the loot: the game owns the scene, the boss health and the inventory, and calls the kit with trusted data. Never pass a roster or a result straight from a client. ## Turn it on ```json { "id": "world.instances", "config": { "maxInstances": 64 } } ``` It requires `game.engine`. `completeDefeated` also needs `combat.core` (it reads `combat.health`). ## First example Open a run, admit one member, start the first boss, complete it, then claim the reward twice. ```json { "op": "open", "instance": "crypt", "party": "p", "partyGeneration": 1, "dungeon": "crypt", "members": ["ann", "bob"], "bosses": ["warden", "king"] } { "op": "enter", "instance": "crypt", "actor": "ann" } { "op": "start", "instance": "crypt", "boss": "warden" } { "op": "complete", "instance": "crypt", "boss": "warden", "attempt": 1 } { "op": "claim", "instance": "crypt", "boss": "warden", "actor": "ann" } ``` Each command goes through `request("default", command)`. The first claim answers `granted` true with a `receipt`; the second answers `granted` false with the same `receipt`. A claim by someone outside the roster, or before the boss is completed, is an error. ## Settings and defaults | Setting | Type | Default | Meaning | |---|---|---|---| | `maxInstances` | integer | `64` | dungeon runs per coordinator (1 to 256); when it is reached, `open` is refused | ## Service `world.instances`: - `request(id, command)`: runs a command on coordinator `id` (`default` exists from the start). Commands: - `open { instance, party, partyGeneration, dungeon, members, bosses }`: members and bosses are non-empty and unique (at most 40 and 32). A second binding of the same party and dungeon is refused. - `enter { instance, actor }` and `leave { instance, actor }`: only roster members; leaving an empty instance cancels the active encounter. - `start { instance, boss }`: the next uncompleted boss, with at least one actor present; read the incremented `attempt` in the state. - `wipe` and `complete { instance, boss, attempt }`: the attempt must be the live one, a late result is refused. - `claim { instance, boss, actor }`: answers `{ granted, receipt }`, the receipt being `[instance, generation, boss, actor]`. - `reset { instance }`: only an empty, uncompleted run (a run with a completed boss is reward-locked). - Reads: `status`, `config`, `save`, `load`, `preview`. - `create(id, config)` makes another coordinator (at most 128), `remove(id)` disposes one, `ids()` lists them. - `completeDefeated(instance, boss, sceneEntity, attempt)`: completes the exact attempt on `default` only if the entity's `combat.health` is dead; otherwise the error is "boss is still alive or absent". The game supplies the entity. A claim is an eligibility decision, not an inventory change. Save the receipt, the awarded items and the run together before you acknowledge the reward, and do not give the reward both on the decision and on an event. ## Events and hooks - world.instances.created (event): `instance`, `preset`. world.instances.removed: `instance`. - world.instances.changed (event): a command that changes the state succeeded. Payload: `instance`, `op`, `result`. - world.instances.refused (event): a hook cancelled a command. Payload: `instance`, `op`, `by`, `reason`. - `world.instances.create` (hook): `{ instance, preset, config }`, before a coordinator is created; a tap may change `config` or cancel (the call then fails with "creation refused"). - `world.instances.request` (hook): `{ instance, op, command }`, before any command that changes the state (not the reads); a tap may change `command` or cancel (the call answers `{ ok: false, cancelled: true, by, reason }`). ## Modding A mod can change `maxInstances`, tap both hooks, and add `world_instances_presets` documents that `create(id, { preset })` merges under your own keys. A script can run a command with the verb `world_instances_request` and test a coordinator with the condition `world_instances_exists`. ## Saves The save keeps every coordinator, runs, completed bosses, attempts and claims. A load checks membership, uniqueness, boss order, attempts and claims before it commits; a bad save is refused and logged. Loading spawns no scene and no server: resynchronise your scene objects and connections afterwards. ## Engines The rules are one Rust core, used by the Rust kit host and the web. There is no presenter. ## When it does not do what you expect - "not in bound roster": the actor is not in the `members` of the `open`. - "party already bound to dungeon": the same party and dungeon were opened before; use a new generation. - "encounter unavailable": nobody is present, or the boss is not the next uncompleted one. - "stale encounter result": the `attempt` is not the live one. - "occupied or reward-locked instance": `reset` on a run that has someone in it or a completed boss. ## Limits The roster is fixed once opened. No scheduled reset or automatic clean-up, and no distributed transaction across characters or servers. The game owns scenes, servers, boss health and loot. ## Versions Kit 1.0.0 (first Rust dungeon-party delivery).