Skip to content

osrlib.core.effects

Named conditions and the effect lifecycle engine.

This module ships in two layers. The condition layer — Condition and ActiveCondition — is pure vocabulary: creatures carry a tuple of active conditions so a serialized creature is honest on its own. The engine layer — EffectsLedger — owns durations, periodic ticks, expiry, and stacking, and is the single writer of creature conditions: combat reads conditions locally, and only the engine's helpers (plus the kernel's death routine, for dead) mutate them.

At each round boundary, expirations resolve before ticks, and simultaneous effects resolve in attachment order, tie-broken by effect id. While a target is petrified, its other attached effects suspend — no ticks, durations frozen — so a poisoned, petrified adventurer is a problem for after stone to flesh.

Effect-internal randomness (revival delays, onset dice, duration dice) draws from the EFFECTS_STREAM stream, so battle-resolution draws never shift effect draws and vice versa.

Part of the core kernel. Start with EffectsLedger.attach to apply a spell or ability's effect to a creature, item, or location, and with grant_condition / remove_condition for condition changes outside the ledger's timed lifecycle, as the kernel's death routine does for dead. The target/registry parameters below are duck-typed per the combatant convention (see osrlib.core.combat).

EFFECTS_STREAM module-attribute

EFFECTS_STREAM = 'effects'

Stream key convention for effect-internal draws: durations, onsets, revivals.

MODIFIER_KINDS module-attribute

MODIFIER_KINDS = frozenset(
    {
        "attack_bonus",
        "damage_bonus",
        "morale_bonus",
        "save_bonus",
        "ac_bonus",
        "ac_set",
        "ac_set_vs_missile",
        "attack_penalty_of_attackers",
        "damage_reduction_per_die",
        "damage_multiplier",
        "melee_damage_multiplier",
        "missile_immunity_nonmagical",
        "strength_set",
        "weapon_damage_dice_bonus",
        "counts_as_magical",
        "magical_healing_half",
    }
)

The closed vocabulary of stat-modifier kinds combat consults.

ac_bonus, strength_set, and the damage multipliers serve the magic items: ac_bonus improves AC by its value (descending down, ascending up), ac_set sets it outright, strength_set replaces the STR score combat modifiers derive from (Gauntlets of Ogre Power's 18, the Ring of Weakness's 3), and the multipliers double weapon damage (giant strength) or melee damage only (growth) after the roll.

ActiveCondition

Bases: BaseModel

A condition a creature currently has, with the effect that owns it.

effect_id is None only for conditions no ledger effect owns: dead, written by the kernel's death routine — death is a kernel outcome, not a timed effect.

condition instance-attribute

condition: Condition

effect_id class-attribute instance-attribute

effect_id: str | None = None

ActiveEffect

Bases: BaseModel

A live effect on a creature, item, or location.

target_ref is an entity id or a location string (a burning oil pool attaches to a location, a stationary silence to a cell). expires_round is the absolute round the effect expires on (None for indefinite and permanent effects); petrification suspension pushes it forward. state is the effect's own bookkeeping (revival round, counted rest days). caster_level records the casting caster's level on spell-attached effects — dispel magic's survival roll compares against it.

effect_id instance-attribute

effect_id: str

definition instance-attribute

definition: EffectDefinition

target_ref instance-attribute

target_ref: str

attached_round class-attribute instance-attribute

attached_round: int = Field(ge=0)

expires_round class-attribute instance-attribute

expires_round: int | None = None

caster_level class-attribute instance-attribute

caster_level: int | None = None

state class-attribute instance-attribute

state: dict[str, int] = {}

ActiveModifier

Bases: ModifierSpec

A live stat modifier on a creature, with the effect that owns it.

Creatures carry a stat_modifiers tuple so a serialized creature is honest on its own, mirroring conditions. Only the effects engine writes it (attach grants, expiry and release remove) — the single-writer rule extends; combat reads it locally through the modifier_* helpers below.

effect_id instance-attribute

effect_id: str

Condition

Bases: StrEnum

The named conditions.

The wire values are lowercase — they serialize into creatures and saves; changing them is a schema_version bump. Combat hooks exist for the subset the kernel consumes (paralysed, asleep, blind, averted_eyes, petrified, poisoned, diseased, dead; the silenced/feebleminded/weakened casting gates, the weakened attack gate, and the entangled movement predicate); the rest are additive-safe vocabulary — afraid, turned, confused, and invisible are marker states consumed by the battle machine and by games.

PARALYSED class-attribute instance-attribute

PARALYSED = 'paralysed'

ASLEEP class-attribute instance-attribute

ASLEEP = 'asleep'

BLIND class-attribute instance-attribute

BLIND = 'blind'

CHARMED class-attribute instance-attribute

CHARMED = 'charmed'

PETRIFIED class-attribute instance-attribute

PETRIFIED = 'petrified'

DISEASED class-attribute instance-attribute

DISEASED = 'diseased'

EXHAUSTED class-attribute instance-attribute

EXHAUSTED = 'exhausted'

LYCANTHROPY_INCUBATION class-attribute instance-attribute

LYCANTHROPY_INCUBATION = 'lycanthropy_incubation'

AVERTED_EYES class-attribute instance-attribute

AVERTED_EYES = 'averted_eyes'

POISONED class-attribute instance-attribute

POISONED = 'poisoned'

DEAD class-attribute instance-attribute

DEAD = 'dead'

SILENCED class-attribute instance-attribute

SILENCED = 'silenced'

ENTANGLED class-attribute instance-attribute

ENTANGLED = 'entangled'

AFRAID class-attribute instance-attribute

AFRAID = 'afraid'

FEEBLEMINDED class-attribute instance-attribute

FEEBLEMINDED = 'feebleminded'

INVISIBLE class-attribute instance-attribute

INVISIBLE = 'invisible'

TURNED class-attribute instance-attribute

TURNED = 'turned'

CONFUSED class-attribute instance-attribute

CONFUSED = 'confused'

WEAKENED class-attribute instance-attribute

WEAKENED = 'weakened'

EffectDefinition

Bases: BaseModel

A frozen effect blueprint: duration, ticks, stacking, expiry, and condition.

Durations are duration_amount (fixed) or duration_dice (rolled at attach from the effects stream) counts of duration_unit; both None means indefinite (until released) and permanent=True marks effects only magic removes (petrification — stone is not dead). tick names a periodic behavior the ledger executes every tick_interval_rounds; expiry names an outcome resolved when the duration runs out (death for delayed poison, splash_damage for the douse's second application). condition is granted at attach and removed at expiry or release; modifiers are granted and removed the same way. dispellable=True marks spell-attached effects dispel magic can end: every effect cast_spell attaches is dispellable, including permanent ones (permanent=True means "no duration expiry", not "undispellable"), while monster-inflicted effects stay non-dispellable.

kind class-attribute instance-attribute

kind: str = Field(min_length=1)

duration_unit class-attribute instance-attribute

duration_unit: TimeUnit | None = None

duration_amount class-attribute instance-attribute

duration_amount: int | None = None

duration_dice class-attribute instance-attribute

duration_dice: str | None = None

permanent class-attribute instance-attribute

permanent: bool = False

tick class-attribute instance-attribute

tick: str | None = None

tick_interval_rounds class-attribute instance-attribute

tick_interval_rounds: int = Field(default=1, ge=1)

stacking class-attribute instance-attribute

stacking: Literal['stack', 'refresh', 'ignore'] = 'stack'

expiry class-attribute instance-attribute

expiry: str | None = None

condition class-attribute instance-attribute

condition: Condition | None = None

modifiers class-attribute instance-attribute

modifiers: tuple[ModifierSpec, ...] = ()

dispellable class-attribute instance-attribute

dispellable: bool = False

params class-attribute instance-attribute

params: dict[str, int | str | bool | tuple[int | str, ...]] = {}

EffectsLedger

Bases: BaseModel

The serializable effect engine: attach, release, and clock-driven advance.

effects class-attribute instance-attribute

effects: list[ActiveEffect] = []

active_on

active_on(target_ref: str, kind: str | None = None) -> list[ActiveEffect]

Return the live effects on a target, optionally filtered by kind.

Parameters:

Name Type Description Default
target_ref str

The entity id or location string.

required
kind str | None

An effect kind to filter by.

None

Returns:

Type Description
list[ActiveEffect]

The matching effects, in attachment order.

attach

attach(
    definition: EffectDefinition,
    target_ref: str,
    *,
    clock: GameClock,
    allocator: Any,
    registry: Mapping[str, Any] | None = None,
    stream: RngStream | None = None,
    caster_level: int | None = None
) -> tuple[ActiveEffect | None, list[Event]]

Attach an effect, resolving stacking, duration dice, conditions, and modifiers.

Parameters:

Name Type Description Default
definition EffectDefinition

The effect blueprint.

required
target_ref str

The entity id or location string to attach to.

required
clock GameClock

The game clock (the attach round anchors the duration).

required
allocator Any

The IdAllocator granting effect ids.

required
registry Mapping[str, Any] | None

Live combatants by entity id — a Character or MonsterInstance per id — for condition and modifier grants; a location ref simply isn't a key.

None
stream RngStream | None

The effects stream; required when the definition rolls duration dice.

None
caster_level int | None

The casting caster's level, recorded on spell-attached effects for dispel magic's survival roll.

None

Returns:

Type Description
ActiveEffect | None

The attached effect and its events — or (None, []) when stacking says

list[Event]

ignore and the kind is already present, or the target is immune to the

tuple[ActiveEffect | None, list[Event]]

effect's condition.

release

release(effect_id: str, registry: Mapping[str, Any] | None = None) -> list[Event]

Release an effect before expiry, removing its condition.

Parameters:

Name Type Description Default
effect_id str

The effect to release.

required
registry Mapping[str, Any] | None

Live combatants by entity id — a Character or MonsterInstance per id — for condition removal.

None

Returns:

Type Description
list[Event]

The released and condition-removed events.

Raises:

Type Description
ValueError

If no live effect has that id.

advance

advance(
    clock: GameClock,
    n: int,
    unit: TimeUnit,
    registry: Mapping[str, Any],
    *,
    stream: RngStream,
    allocator: Any | None = None
) -> list[Event]

Advance the clock and resolve every round boundary in the span.

The canonical tick order, locked by test: at each boundary, expirations resolve before ticks; simultaneous effects resolve in attachment order, tie-broken by effect id. Suspended effects (target petrified by another effect) neither tick nor age — their expiry pushes forward one round per suspended round.

Parameters:

Name Type Description Default
clock GameClock

The game clock; advanced in place.

required
n int

How many units to advance.

required
unit TimeUnit

The unit to advance in.

required
registry Mapping[str, Any]

Live combatants by entity id — a Character or MonsterInstance per id. A GameSession holds one across play; a plain dict works too.

required
stream RngStream

The effects stream for effect-internal draws.

required
allocator Any | None

The IdAllocator, needed only by behaviors that attach follow-on effects.

None

Returns:

Type Description
list[Event]

Every event the advance produced, in resolution order.

ModifierSpec

Bases: BaseModel

One stat modifier an effect grants while active.

value is the signed adjustment (bless's +1, protection from evil's −1 to attackers, shield's AC-set values); dice carries dice-valued bonuses (striking's +1d6 weapon damage). Scopes: element restricts save bonuses and per-die reductions to one element (resist fire), versus_other_alignment restricts save bonuses to attacks from creatures of another alignment (protection from evil), save_categories restricts save bonuses to named categories (the Displacer Cloak's petrification/rods/spells/staves/wands list), and melee_only restricts an attacker penalty to melee attacks (the cloak's −2 leaves missiles unaffected, RAW). from_item marks item-sourced modifiers (potion effects, ward scrolls): they are exempt from the cumulative largest-bonus cap — RAW's carve-out covers magic items generally, not just worn ones.

kind instance-attribute

kind: str

value class-attribute instance-attribute

value: int = 0

dice class-attribute instance-attribute

dice: str | None = None

element class-attribute instance-attribute

element: str | None = None

versus_other_alignment class-attribute instance-attribute

versus_other_alignment: bool = False

save_categories class-attribute instance-attribute

save_categories: tuple[str, ...] = ()

melee_only class-attribute instance-attribute

melee_only: bool = False

from_item class-attribute instance-attribute

from_item: bool = False

grant_condition

grant_condition(target: Any, condition: Condition, effect_id: str | None) -> list[Event]

Grant a condition to a creature — the single-writer mutation point.

A condition the creature is immune to (its defenses' condition_immunities) is not granted and nothing is emitted; duplicate grants from the same effect are no-ops.

Parameters:

Name Type Description Default
target Any

The creature to grant the condition to: a Character or a MonsterInstance; its conditions tuple is replaced.

required
condition Condition

The condition to grant.

required
effect_id str | None

The owning effect, or None for dead.

required

Returns:

Type Description
list[Event]

The condition-gained event, or nothing when immune or duplicate.

has_condition

has_condition(target: Any, condition: Condition) -> bool

Return whether a creature currently has condition.

Parameters:

Name Type Description Default
target Any

The creature to check: a Character or a MonsterInstance — any object carrying a conditions tuple works.

required
condition Condition

The condition to look for.

required

Returns:

Type Description
bool

True when any active condition matches.

has_modifier

has_modifier(target: Any, kind: str) -> bool

Return whether a creature carries any modifier of kind (the flag kinds).

Parameters:

Name Type Description Default
target Any

The creature to read modifiers from: a Character or a MonsterInstance.

required
kind str

The modifier kind to look for.

required

Returns:

Type Description
bool

True when any active modifier matches.

kill

kill(target: Any, *, permanent: bool = False) -> list[Event]

Kill a creature: hit points to 0, the dead condition, and the death event.

"A character or monster reduced to 0 hit points or less is killed." Idempotent — a creature already dead emits nothing.

Parameters:

Name Type Description Default
target Any

The creature to kill: a Character or a MonsterInstance.

required
permanent bool

True for a regenerating creature's permanent death (the troll's non-regenerable ledger reaching max HP).

False

Returns:

Type Description
list[Event]

The death, condition, and referee hit-point events.

modifier_dice

modifier_dice(target: Any, kind: str) -> str | None

Return the dice of the first matching dice-valued modifier (striking's +1d6).

First-only is the cumulative rule for dice bonuses: two strikings don't combine.

Parameters:

Name Type Description Default
target Any

The creature to read modifiers from: a Character or a MonsterInstance.

required
kind str

The modifier kind to look for.

required

Returns:

Type Description
str | None

The dice expression, or None when no matching modifier is active.

modifier_total

modifier_total(
    target: Any,
    kind: str,
    *,
    element: str | None = None,
    versus_differs: bool = False,
    save_category: str | None = None,
    melee: bool = False
) -> int

Return a creature's cumulative modifier for one statistic.

The cumulative-effects rule, from the OSE SRD ("Multiple spells affecting the same game statistic do not combine"): only the single largest bonus and the single largest penalty apply — a bless and a blight offset; two blesses don't stack. Spell modifiers combine freely with non-spell modifiers (the RAW carve-out for magic items — item-sourced modifiers ride equipped-item queries and item-kind effects, both outside this cap; see osrlib.core.combat).

Parameters:

Name Type Description Default
target Any

The creature to total the modifier for: a Character or a MonsterInstance.

required
kind str

The modifier kind to total.

required
element str | None

The damage or save element in play, if any.

None
versus_differs bool

True when the source creature's alignment differs.

False
save_category str | None

The saving throw category in play, if any.

None
melee bool

True when the attack in play is melee.

False

Returns:

Type Description
int

The signed cumulative modifier.

modifier_values

modifier_values(
    target: Any,
    kind: str,
    *,
    element: str | None = None,
    versus_differs: bool = False,
    save_category: str | None = None,
    melee: bool = False
) -> list[int]

Return the matching modifier values on a creature, scope-filtered.

Element-scoped modifiers match only their element; alignment-scoped modifiers match only when the caller attests the source's alignment differs (versus_differs); category-scoped save bonuses match only their categories; melee-only modifiers match only when the caller attests a melee attack.

Parameters:

Name Type Description Default
target Any

The creature to read modifiers from: a Character or a MonsterInstance.

required
kind str

The modifier kind to look for.

required
element str | None

The damage or save element in play, if any.

None
versus_differs bool

True when the source creature's alignment differs from the target's.

False
save_category str | None

The saving throw category in play, if any.

None
melee bool

True when the attack in play is melee.

False

Returns:

Type Description
list[int]

The matching values, in attachment order.

regeneration_definition

regeneration_definition(params: Mapping[str, Any]) -> EffectDefinition

Build a regeneration effect from a monster's regeneration ability params.

Parameters:

Name Type Description Default
params Mapping[str, Any]

The compiled tag params — per_round, delay_rounds, blocked_by, revive, while_alive.

required

Returns:

Type Description
EffectDefinition

An indefinite per-round regeneration effect definition.

remove_condition

remove_condition(target: Any, condition: Condition, effect_id: str | None) -> list[Event]

Remove the condition owned by effect_id from a creature.

Parameters:

Name Type Description Default
target Any

The creature to remove the condition from: a Character or a MonsterInstance; its conditions tuple is replaced.

required
condition Condition

The condition to remove.

required
effect_id str | None

The owning effect (None for dead).

required

Returns:

Type Description
list[Event]

The condition-removed event, or nothing when the creature didn't have it.