Skip to content

osrlib.crawl.encounter

The encounter procedure: surprise, distance, reaction, parley, evasion, pursuit.

An encounter — wandering, keyed on area entry, or referee-spawned through SpawnMonsters — opens with surprise, distance, and reaction, then runs in round beats: each encounter command is one round, and the monsters act per their stance after it. Battle opens through osrlib.crawl.battle when a stance or the party demands it.

The stance map resolves bands the OSE SRD leaves to a human referee: 2- attacks now; 3–5 hostile — the monsters attack at the end of the next encounter round unless the party has begun evading or improved the stance by parley; 6–8 uncertain — hold, posture, re-roll next round at +0; 9–11 indifferent — the party may pass, parley, or withdraw freely; 12+ friendly. Only the attacks/hostile stances pursue an evading party, as a documented adaptation (see the adaptations register) — RAW leaves pursuit itself to the referee too, keyed here to low reactions.

HANDLERS module-attribute

HANDLERS = {
    Parley: _handle_parley,
    Evade: _handle_evade,
    EngageBattle: _handle_engage_battle,
    Wait: _handle_wait,
    TurnUndead: _handle_turn_undead,
}

PURSUIT_ROUND_CAP module-attribute

PURSUIT_ROUND_CAP = 30

The round at which a running pursuit gives up: the terminal escape valve.

With the party no faster than its pursuers, the gap never grows, so a pursuit that reaches this round ends in exhaustion rather than running forever.

EncounterGroup

Bases: BaseModel

One monster group in an encounter: its members and range-track distance.

member_treasure and group_treasure are the carried bundles generated at spawn (individual P–T per monster, group U–V per group): slain and surrendered members' bundles drop as loot at battle end; routed ones flee with theirs.

id instance-attribute

id: str

label instance-attribute

label: str

monster_ids class-attribute instance-attribute

monster_ids: list[str] = Field(min_length=1)

distance_feet class-attribute instance-attribute

distance_feet: int = Field(ge=0)

fleeing class-attribute instance-attribute

fleeing: bool = False

fled class-attribute instance-attribute

fled: bool = False

surrendered class-attribute instance-attribute

surrendered: bool = False

member_treasure class-attribute instance-attribute

member_treasure: dict[str, TreasureBundle] = {}

group_treasure class-attribute instance-attribute

group_treasure: TreasureBundle | None = None

EncounterState

Bases: BaseModel

The serialized encounter: groups, stance, surprise, and the pursuit.

kind instance-attribute

kind: str

area_ref class-attribute instance-attribute

area_ref: str | None = None

groups class-attribute instance-attribute

groups: list[EncounterGroup] = Field(min_length=1)

stance class-attribute instance-attribute

stance: str | None = None

round class-attribute instance-attribute

round: int = 0

started_round instance-attribute

started_round: int

party_surprised class-attribute instance-attribute

party_surprised: bool = False

monsters_surprised class-attribute instance-attribute

monsters_surprised: bool = False

monsters_skip_rounds class-attribute instance-attribute

monsters_skip_rounds: int = 0

hostile_deadline class-attribute instance-attribute

hostile_deadline: int | None = None

evading class-attribute instance-attribute

evading: bool = False

pursuit class-attribute instance-attribute

pursuit: PursuitState | None = None

PursuitState

Bases: BaseModel

A running pursuit: the abstract gap, updated round by round.

round class-attribute instance-attribute

round: int = 0

gap_feet class-attribute instance-attribute

gap_feet: int = Field(ge=0)

end_encounter

end_encounter(session, outcome: str) -> list[Event]

Conclude the encounter: defeats, effect release, the minimum-turn clock owe.

Defeated, routed, and surrendered monsters post MonsterDefeatedEvents to the ledger; turned effects on routed undead release; remaining effects on the encounter's monsters release too — the fiction moves on (a dead troll's pending revival is game narration after the battle). The clock advances to max(next turn boundary, encounter start + one turn), with the wandering cadence still suspended: when the encounter started mid-turn, the minimum-one-turn clause dominates, so the conclusion may itself land mid-turn — the boundary clause only guarantees the boundary is reached.

start_encounter

start_encounter(
    session,
    *,
    groups: list[tuple[str, list]],
    kind: str,
    area_ref: str | None = None,
    distance_feet: int | None = None,
    monsters_roll_surprise: bool = True,
    monsters_aware: bool = False,
    party_aware: bool = False,
    pinned_stance: ReactionResult | None = None
) -> list[Event]

Open an encounter: surprise, distance, reaction, and the first consequences.

Wandering monsters never roll for surprise (they come "moving in the direction of the party"); a keyed area's aware flag, a failed door forcing, and the lit-party rule each skip the monsters' roll instead; a successful listen marks the party aware. The party is surprised on 1–2 — 1–3 when unlit and not every living member has infravision, as a documented adaptation (see the adaptations register; the blind-party adaptation).

Parameters:

Name Type Description Default
session GameSession

The running session.

required
groups list[tuple[str, list]]

(label, instances) pairs, instances already in the registry.

required
kind str

"wandering", "keyed", or "spawned".

required
area_ref str | None

The keyed area's state reference, when keyed.

None
distance_feet int | None

A fixed distance; None rolls 2d6 × 10 on the encounter stream.

None
monsters_roll_surprise bool

False when the monsters can never be surprised.

True
monsters_aware bool

True when the monsters expect intruders.

False
party_aware bool

True when the party heard the room.

False
pinned_stance ReactionResult | None

A keyed stance that skips the reaction roll.

None

Returns:

Type Description
list[Event]

The encounter-opening events.