Skip to content

A save-or-die trap kills a character who made the saving throw, when on_save is half #34

Description

@mmacy

What happens

TrapEffect lets a trap set both kills=True and save.on_save="half". When it does, a character who passes the saving throw is killed anyway.

Why

_resolve_trap (src/osrlib/crawl/exploration.py:780-798) branches on the save result:

if save.passed:
    if effect.save.on_save == "negates":
        negated = True
    else:
        halved = True
if negated:
    continue
if effect.kills:
    events.extend(kill(victim))
    continue

Only negates short-circuits the victim. A passing half save sets halved — which is meaningful only for the damage roll further down — and then falls straight through to effect.kills. halved is never consulted again on that path, so the save has no effect at all.

The result is a trap that reads as "save vs. death or die, half on a successful save" and behaves as "everyone the trap catches dies."

Where the fix belongs

TrapEffect already carries a model validator holding two cross-field implications (dungeon.py:262-273: a volley needs damage dice, a duration needs a condition), so this is the same class of rule and the same place for it. Two options:

  1. Reject the combination. Add kills=True with save.on_save="half" to _condition_duration_needs_a_condition's sibling rules — half of a kill isn't a thing B/X expresses, so the model can refuse to represent it. This is the option consistent with how the other two implications are handled, and it fails loudly at construction rather than at the table.
  2. Make a passed save survive. Treat any passed save as negating a kill, since a save-or-die save is by definition the one that saves you.

I'd take (1): it keeps the engine branch honest instead of adding a special case, and any document already carrying the combination is a document whose author didn't mean it.

Either way kills deserves a line in the TrapEffect docstring saying what a save is expected to do with it — the current text ("kills marks save-or-die forms (poison gas)") doesn't say the save interaction exists.

How it surfaced

Documenting osr-editor's trap builder, which offers both controls freely and will author the combination without complaint. The editor's guide now warns about it in prose (mmacy/osr-editor#33), which is a workaround, not a fix. If the model starts rejecting it, the editor's builder should follow by pinning on_save when save-or-die is ticked — happy to send that PR here or there once you've picked a direction.

Found against osrlib 1.3.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions