Skip to content

osrlib.crawl.gates

Authored gates: the condition vocabulary, the gate model, and pure evaluation.

A gate is an authored predicate the engine checks when the party attempts something — opening a door, taking a stair. Its two carriers are the requires fields of DoorSpec and TransitionSpec — a gate hangs nowhere else. It is a stateless content object: condition_holds reads live session state at the moment of the attempt and stores nothing, so a key dropped or sold stops opening its door and evaluation never drifts from the truth.

The condition vocabulary is a discriminated union that grows additively:

  • HasItemCondition — some party member's carried inventory holds an item with that catalog id (Inventory.carried_item is the matching rule, equipped slots and all). With consumes=True, the successful command takes one instance from the first holder in marching order.
  • FlagEqualsCondition — a session flag (SetFlag) holds a value.
  • EffectActiveCondition — an active effect of that kind is attached to a party member, so an author can ask for the talisman invoked rather than merely carried.

Gates and locks are orthogonal layers: locked is stateful mechanics with dice and per-character memory, a gate is a stateless authored predicate, and a door carrying both requires both. Evaluation is pure — it draws no dice, costs no game time, and mutates nothing — which is what lets a gate refusal be an ordinary command rejection.

ConditionSpec module-attribute

ConditionSpec = Annotated[
    HasItemCondition | FlagEqualsCondition | EffectActiveCondition, Field(discriminator="condition_type")
]

The condition union, discriminated on condition_type (has_item, flag_equals, effect_active). New kinds join it additively; the discriminator values are wire values and serialize into every document that carries a gate.

EffectActiveCondition

Bases: BaseModel

An active effect of kind is attached to some party member.

Effect kinds are an open, data-driven vocabulary ("fatigue", a custom EffectDefinition.kind), so the field is a free string. Effects attached to a location never count — the test is what the party carries in its bones.

condition_type class-attribute instance-attribute

condition_type: Literal['effect_active'] = 'effect_active'

kind class-attribute instance-attribute

kind: str = Field(min_length=1)

FlagEqualsCondition

Bases: BaseModel

A session flag holds value — the lever that opens the portcullis.

The comparison is flag_values_equal and it is strict: an absent key equals nothing (False included), and a stored True never matches an authored 1.

condition_type class-attribute instance-attribute

condition_type: Literal['flag_equals'] = 'flag_equals'

key class-attribute instance-attribute

key: str = Field(min_length=1)

value instance-attribute

value: str | int | bool

GateSpec

Bases: BaseModel

A condition guarding an attempt, with the authored text for both outcomes.

The gate is the object the narrative hangs on: a refused attempt returns the block's refusal beat in its rejection, and a successful one rides the success beat on the command's event.

condition instance-attribute

condition: ConditionSpec

narrative class-attribute instance-attribute

narrative: NarrativeBlock | None = None

HasItemCondition

Bases: BaseModel

The party carries an item with item_id — equipment or magic item.

Any member's carried inventory satisfies it, equipped slots included: carrying is the whole test. consumes=True makes the item a toll — one instance leaves the first holder in marching order when the gated command succeeds, per success, so a door that swings shut wants another key.

condition_type class-attribute instance-attribute

condition_type: Literal['has_item'] = 'has_item'

item_id class-attribute instance-attribute

item_id: str = Field(min_length=1)

consumes class-attribute instance-attribute

consumes: bool = False

condition_holds

condition_holds(
    condition: ConditionSpec,
    *,
    members: Sequence[Character],
    flags: Mapping[str, str | int | bool],
    ledger: EffectsLedger
) -> bool

Evaluate one condition against live session state.

Pure: no draws, no clock, no mutation, nothing stored. Every walk is over an ordered list — the party in marching order, each inventory in carried order — so the answer is deterministic.

The member domain is every party member, living or dead. The party carries its dead and their packs, so a key that rides a corpse still opens its door.

Parameters:

Name Type Description Default
condition ConditionSpec

The condition to evaluate.

required
members Sequence[Character]

The party, in marching order.

required
flags Mapping[str, str | int | bool]

The session flag store.

required
ledger EffectsLedger

The session's effects ledger.

required

Returns:

Type Description
bool

True when the condition holds right now.

Examples:

from osrlib.core.effects import EffectsLedger
from osrlib.crawl.gates import FlagEqualsCondition, condition_holds

condition = FlagEqualsCondition(key="portcullis", value="raised")
flags = {"portcullis": "raised"}
assert condition_holds(condition, members=[], flags=flags, ledger=EffectsLedger())

first_holder

first_holder(members: Sequence[Character], item_id: str) -> Character | None

The first member in marching order carrying an item with item_id.

The consumption target of a has_item toll, matching exactly what condition_holds tests.

Parameters:

Name Type Description Default
members Sequence[Character]

The party, in marching order.

required
item_id str

The catalog id to look for.

required

Returns:

Type Description
Character | None

The member, or None when nobody carries one.

flag_values_equal

flag_values_equal(stored: str | int | bool, expected: str | int | bool) -> bool

Compare two flag values the one strict way the engine compares them.

Equality plus matching boolness. True == 1 in Python, so a flag somebody set to True must not satisfy an authored 1, and the reverse: the two are different values in an authored document even though Python calls them equal. Every surface that asks "does this flag hold that value" — FlagEqualsCondition on a gate, an authored trigger's flag pattern — asks through here, so a condition and a pattern can never disagree about what equality means.

Parameters:

Name Type Description Default
stored str | int | bool

The value the flag store holds (or the value an event reports written).

required
expected str | int | bool

The value the author wrote.

required

Returns:

Type Description
bool

True when the two are the same value.

Examples:

from osrlib.crawl.gates import flag_values_equal

assert flag_values_equal("open", "open")
assert not flag_values_equal(True, 1)
assert not flag_values_equal(1, True)