osrlib.crawl.battle
The range-track battle state machine and the pluggable monster action policy.
start_battle opens battle — from the
encounter procedure's own stance handling, a pursuit collapsing to melee range, or
the party's own choice through the
EngageBattle command — and each round after
that runs through
ResolveBattleRound, which carries one
BattleDeclaration per living, able
party member. A round wraps the kernel's combat functions in the OSE SRD's
sequence: declaration, initiative, then per side — monster morale, movement,
missiles, magic, melee — with slow-weapon actors last. The combat space is the
abstract per-group range track (the Bard's Tale convention), as a documented
adaptation — see the adaptations register: each monster group sits at a distance
from the party, closing at encounter rate and meleeing at 5'; party ranks derive
from marching order under the formation_width_limit flag (width 3 inside a keyed
area, 2 in a corridor).
The machine detects spell disruption (a declared caster is successfully attacked,
or fails a save, after initiative resolves against them but before their action),
auto-invokes morale, consumes marker conditions and battle-bound spell effects, and
resolves area footprints deterministically: an area's capacity in creatures is
ceil(span / 10) × width, filled in stable spawn order, cones reach-limited, with
the engaged party front rank appended under aoe_friendly_fire.
Monster and NPC-party sides act through a pluggable
ActionPolicy, substitutable per encounter
side; the shipped ScriptedPolicy and
NpcPartyPolicy draw only from the
monster_action stream, so swapping in a custom policy never shifts attack or
damage draws. Initiative still resolves in side blocks — the sides order by their
best individual total, since the SRD's phase sequence runs per side, not per
combatant.
NPC_PARTY_MORALE
module-attribute
NPC adventurer groups check morale at ML 9, the Veteran stat block's printed score. osrlib anchors on the OSE SRD's own low-level-adventurer monster rather than an invented number, as a documented adaptation (see the adaptations register).
ActionPolicy
Bases: Protocol
The pluggable monster brain — substituted per encounter side by games.
Policies draw only from the monster_action stream, so a policy change never
shifts attack or damage draws. A policy may return any actions the kernel
validates; the default never casts (monster casting tags are manual data).
choose
choose(session, group, stream) -> list[MonsterAction]
Choose this round's actions for one group's living, able monsters.
BattleState
MonsterAction
NpcPartyPolicy
The default ActionPolicy for NPC adventurer sides.
The OSE SRD gives no tactics of its own for an opposing party of adventurers, so osrlib supplies one, as a documented adaptation (see the adaptations register).
Per living member each round, in priority order: (1) a caster holding a
memorized cure spell heals the group's most-wounded member below half hp
(lowest hp ratio, ties by id); (1½) a member below half hp with no cure left
in the group drinks a carried healing potion — the one item-use the default
policy performs; (2) a caster holding a memorized wired offensive spell casts
it — highest spell level first, ties by spell id — at the party (areas through
the footprint rule, single-target picks uniform from the reachable rank);
(3) a member with a missile weapon and a gap beyond 5' shoots; (4) otherwise
close and melee, the monster default. Policy draws come only from the
monster_action stream; NPC casts post declarations and are disruptable
exactly like party casts (RAW's trigger doesn't care which side declares).
choose
choose(session, group, stream) -> list[MonsterAction]
Choose this round's actions (see ActionPolicy).
ScriptedPolicy
The default ActionPolicy: scripted breath, then close-and-melee.
Monsters with a scripted pattern in their data follow it: an uses_per_day
breath weapon opens with breath, then breath or melee with equal chance while
daily uses remain (the dragons, per the OSE SRD); a per_round_chance_in_six
gate rolls each round (the hellhound). Otherwise a group beyond 5' closes; at
5' it melees, each monster picking its target uniformly from the reachable
party rank. Monster missile routines carry no structured range data, so osrlib
treats them the same as melee — close, then attack at 5' — as a documented
adaptation (see the adaptations register). Groups never cast.
choose
choose(session, group, stream) -> list[MonsterAction]
Choose actions for the group (see ActionPolicy).
start_battle
start_battle(session, *, party_free_round: bool = False, monsters_free_round: bool = False) -> list[Event]
Open battle from the current encounter: the range track takes over.
ML 2 groups rout when battle starts (RAW). A surprise advantage becomes a free
round: the monsters' free round resolves immediately (machine-run, the party
cannot act); the party's free round holds the monsters through the first
ResolveBattleRound.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
GameSession
|
The running session. |
required |
party_free_round
|
bool
|
The monsters were surprised. |
False
|
monsters_free_round
|
bool
|
The party was surprised with a hostile/attacking stance. |
False
|
Returns:
| Type | Description |
|---|---|
list[Event]
|
The battle-opening events. |