Skip to content

Refuse to remove a member who carries an item the adventure needs - #213

Merged
mmacy merged 3 commits into
mainfrom
chunk/quest-item-guard
Oct 31, 2026
Merged

mmacy merged 3 commits into
mainfrom
chunk/quest-item-guard

Conversation

@mmacy

@mmacy mmacy commented Oct 31, 2026

Copy link
Copy Markdown
Owner

RemoveMember without give_to now refuses to remove a member who carries an item the adventure depends on, with the new code party.remove.carries_quest_item. Before, the item left the game with the member, and a quest that needed it could no longer be finished.

The adventure depends on an item when a has_item condition or an item-acquired trigger pattern names it in one of these places:

  • a gate on a door or a transition, which always counts, because the party can come back to it
  • a trigger that can still fire
  • any clause of a quest that isn't completed

The rejection's character and item params name the member and the first such item they carry. Naming a member in give_to moves the item to them, so the removal goes through. A refusal costs no draws, no time, and no change.

The Snoring King found this in a playtest of 2.2.0, when removing a member lost the music box its main quest needs.

https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD

mmacy added 3 commits October 30, 2026 21:36
… tests

In a playtest of a game built on osrlib, the player removed a member who carried the music box the main quest needed, without give_to. The box left the game with the member, and the quest could no longer be finished. osrlib knows which items an adventure depends on, so the session will refuse that removal, and every game gets the check. This commit adds the contract and the tests. The handler doesn't emit the code yet, so the chunk's tests are marked xfail for chunk: quest-item-guard.

The contract:

- RemoveMember without give_to is refused with party.remove.carries_quest_item when the leaving member carries an item the adventure depends on. With give_to, the item moves to that member, so the command isn't refused.
- The adventure depends on an item when its catalog id is named by a HasItemCondition or an ItemAcquiredPattern anywhere in the adventure. The sites are the gate of a door or a transition, a trigger's pattern and conditions, and a quest's activation, each objective's when, and each objective's reveal_when.
- Ids match the way has_item matches them. Mundane items, magic items, and the adventure's own items count, equipped or in the pack. A spent stack doesn't count. The member can be alive, dead, turned to stone, or asleep. Another member who carries the same item doesn't lift the refusal.
- A completed quest protects nothing, and neither does a trigger that has fired and isn't repeatable. A quest that is still open protects the items in all its clauses, even an objective that is already complete. A gate always protects its item.
- The check is the last of the RemoveMember refusals. Its params are the member's id as character and, as item, the catalog id of the first such item in Inventory.all_instances order.
- A refusal costs nothing: no draws, no time, no log entry, and no change.

The RemoveMember docstring lists the code last in its Rejections section and says when to use give_to. The rejection-code docs gate is marked xfail until the handler emits the code. The tests of removals the check must still allow pass today and carry no marker.

Claude-Session: https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD
RemoveMember without give_to now refuses with party.remove.carries_quest_item when the leaving member carries an item the adventure depends on. The handler runs the check last, after every other refusal, before it changes anything or draws anything.

The adventure depends on an item when a HasItemCondition or an ItemAcquiredPattern names it in a door or transition gate, in a trigger that can still fire, or in any clause of a quest that isn't completed. The rejection names the member as character and the first matching item in Inventory.all_instances order as item. A spent stack doesn't count.

The rejection code has a description, the changelog has a bullet, and the LLM referee guide mentions the refusal. The chunk's xfail markers stay for the lead to remove.

Claude-Session: https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD
The tests for party.remove.carries_quest_item pass, so their expected-to-fail markers and the one on the rejection-code docs gate come off.

Claude-Session: https://claude.ai/code/session_01V7ErtnsmhaP7aLwcfmzohD
@mmacy
mmacy merged commit a7d7d2e into main Oct 31, 2026
5 checks passed
@mmacy
mmacy deleted the chunk/quest-item-guard branch October 31, 2026 04:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant