osrlib.core.events
The event base class, the emission contract, and the first kernel events.
Every rules resolution emits typed events, and this module locks the rules all of them obey:
- Events carry structured fields and a message code — dotted snake_case namespaced by
subsystem (
combat.attack.hit,exploration.torch.expired) — never baked English prose. The default English message formatter ships outside the event models (inosrlib.messages), so front ends can localize and LLM narrators get facts rather than canned text. - Events carry a visibility level, because B/X hides some rolls by design: monster hit points and morale rolls are the referee's. Front ends filter on it; an LLM referee sees everything.
- Consumers must tolerate unknown event types and unknown fields: within a
schema_version, the event schema grows additively only. The base class enforcesextra="ignore"andfrozen=Trueon every subclass at class-definition time, so no subclass can silently break that guarantee withextra="forbid"or a mutable config.
The serialized type discriminator is declared here:
every kernel event class declares a single-valued event_type: Literal[...] wire
field (snake_case, schema-stable, additive-only). Pydantic discriminates the
KernelEvent union on it, giving native tagged-union
JSON Schema for API consumers and mechanical "ignore unknown event types" — see
parse_event. Message codes stay free to be
outcome-bearing: one event class may emit several codes from its declared closed set
(combat.attack.hit / combat.attack.missed on the attack event), so formatters and
narration key off codes while consumers discriminate on event_type.
KERNEL_EVENT_CLASSES
module-attribute
KERNEL_EVENT_CLASSES: tuple[type[Event], ...] = (
InitiativeRolledEvent,
AttackRolledEvent,
DamageDealtEvent,
DamageAbsorbedEvent,
SavingThrowRolledEvent,
MoraleCheckedEvent,
ReactionRolledEvent,
ConditionGainedEvent,
ConditionRemovedEvent,
EffectAttachedEvent,
EffectTickedEvent,
EffectExpiredEvent,
EffectReleasedEvent,
HealingAppliedEvent,
DeathEvent,
EquipmentDestroyedEvent,
LevelDrainedEvent,
MonsterRevivedEvent,
HitPointsReportedEvent,
TargetsSelectedEvent,
SpellsMemorizedEvent,
SpellCastEvent,
SpellDisruptedEvent,
SpellForgottenEvent,
SpellBookUpdatedEvent,
UndeadTurnedEvent,
MagicDispelledEvent,
)
Every kernel event class, in declaration order — the discriminated union's members.
KernelEvent
module-attribute
KernelEvent = Annotated[
InitiativeRolledEvent
| AttackRolledEvent
| DamageDealtEvent
| DamageAbsorbedEvent
| SavingThrowRolledEvent
| MoraleCheckedEvent
| ReactionRolledEvent
| ConditionGainedEvent
| ConditionRemovedEvent
| EffectAttachedEvent
| EffectTickedEvent
| EffectExpiredEvent
| EffectReleasedEvent
| HealingAppliedEvent
| DeathEvent
| EquipmentDestroyedEvent
| LevelDrainedEvent
| MonsterRevivedEvent
| HitPointsReportedEvent
| TargetsSelectedEvent
| SpellsMemorizedEvent
| SpellCastEvent
| SpellDisruptedEvent
| SpellForgottenEvent
| SpellBookUpdatedEvent
| UndeadTurnedEvent
| MagicDispelledEvent,
Field(discriminator="event_type"),
]
Any kernel event, discriminated by event_type.
AttackRolledEvent
Bases: Event
An attack roll resolved: the die, the modifiers, and what it needed.
Emitted by attack_roll. code is
combat.attack.hit or combat.attack.missed for a rolled attack, or
combat.attack.auto_hit for a helpless target (no roll required); roll,
total, and required are None in the auto-hit case. natural carries 1 or
20 when the natural roll overrode the modified total.
ConditionGainedEvent
Bases: Event
A creature gained a condition.
Emitted by grant_condition — directly, or
through EffectsLedger.attach
attaching an effect that carries one.
allowed_codes
class-attribute
event_type
class-attribute
instance-attribute
event_type: Literal['condition_gained'] = 'condition_gained'
ConditionRemovedEvent
Bases: Event
A creature lost a condition.
Emitted by remove_condition — directly,
or through EffectsLedger.release
or expiry removing the effect that granted it.
allowed_codes
class-attribute
event_type
class-attribute
instance-attribute
event_type: Literal['condition_removed'] = 'condition_removed'
DamageAbsorbedEvent
Bases: Event
A hit absorbed by an immunity gate: no damage was rolled.
Emitted in place of
DamageDealtEvent when a
harmed_only_by or energy defense excludes the source.
event_type
class-attribute
instance-attribute
event_type: Literal['damage_absorbed'] = 'damage_absorbed'
DamageDealtEvent
Bases: Event
Damage applied to a creature.
Emitted by deal_damage, from a weapon attack, a
spell, or an effect such as splash damage. Carries the amount only — never the
target's remaining hit points: monster HP is hidden by design, and the
referee-visibility
HitPointsReportedEvent carries it.
DeathEvent
Bases: Event
A creature reduced to 0 hit points or less is killed.
Emitted by kill. code is combat.death.died for an
ordinary death, or combat.death.permanent when a regenerating creature's
non-regenerable damage ledger reaches max hit points, ending any chance of
revival (the troll).
allowed_codes
class-attribute
EffectAttachedEvent
Bases: Event
An effect attached to a creature, item, or location (referee bookkeeping).
Emitted by EffectsLedger.attach.
allowed_codes
class-attribute
event_type
class-attribute
instance-attribute
event_type: Literal['effect_attached'] = 'effect_attached'
EffectExpiredEvent
Bases: Event
An effect's duration ran out (referee bookkeeping).
Emitted by EffectsLedger.advance.
event_type
class-attribute
instance-attribute
event_type: Literal['effect_expired'] = 'effect_expired'
EffectReleasedEvent
Bases: Event
An effect explicitly released before expiry (referee bookkeeping).
Emitted by EffectsLedger.release — a
dispel magic or a charm's passed re-save, for example.
allowed_codes
class-attribute
event_type
class-attribute
instance-attribute
event_type: Literal['effect_released'] = 'effect_released'
EffectTickedEvent
Bases: Event
An effect's periodic tick resolved (referee bookkeeping).
Emitted by EffectsLedger.advance
(regeneration, a charm's re-save) or
pop_mirror_image popping one figment.
event_type
class-attribute
instance-attribute
event_type: Literal['effect_ticked'] = 'effect_ticked'
EquipmentDestroyedEvent
Bases: Event
A victim's equipment destroyed by a destructive death (dragon breath).
Emitted by destroy_equipment.
item_names are the destroyed items; saved_items (additive) are the instance
ids of magic items that passed the magic_item_death_save roll — the crawl
lands them in a drop pile at the victim's cell.
allowed_codes
class-attribute
event_type
class-attribute
instance-attribute
event_type: Literal['equipment_destroyed'] = 'equipment_destroyed'
Event
Bases: BaseModel
Base class for all osrlib events.
Events are frozen: they are records of what happened, appended to the session log, never mutated. Subclasses add structured fields only — entity IDs, roll results, quantities — and must never bake in English prose.
code is the event's message code: two or more dot-separated segments, each
matching [a-z][a-z0-9_]*, namespaced by subsystem (combat.attack.hit). A
subclass declaring an allowed_codes class attribute pins its outcome-bearing
code set: instances must carry one of them.
HealingAppliedEvent
Bases: Event
Healing applied — or blocked (mummy rot renders magical healing ineffective).
Emitted by apply_healing or a regeneration
tick. code is combat.healing.applied normally, or combat.healing.blocked
when a condition blocks it.
allowed_codes
class-attribute
event_type
class-attribute
instance-attribute
event_type: Literal['healing_applied'] = 'healing_applied'
HitPointsReportedEvent
Bases: Event
A creature's hit point state — referee visibility: monster HP is hidden by design.
Emitted alongside damage, healing, death, energy drain, and regeneration — any change to a creature's current or maximum hit points — so the referee's view stays in sync.
allowed_codes
class-attribute
event_type
class-attribute
instance-attribute
event_type: Literal['hit_points_reported'] = 'hit_points_reported'
InitiativeRoll
InitiativeRolledEvent
Bases: Event
Initiative rolled for a round: every roll (re-rolls included) and the acting order.
Emitted once per round by
roll_initiative. mode is "side" when
one roll sets the order for an entire side, or "individual" when each
combatant rolls separately.
allowed_codes
class-attribute
event_type
class-attribute
instance-attribute
event_type: Literal['initiative_rolled'] = 'initiative_rolled'
LevelDrainedEvent
Bases: Event
Energy drain resolved: levels lost, with the terminal case as its own code.
Emitted by drain_monster_hd or
drain_levels. code is
combat.drain.drained for levels lost, or combat.drain.slain for the terminal
case — the victim is already at level 1 (or 1 Hit Die or fewer) with nowhere
left to drain, so the drain kills it outright.
spawn_consequence is the structured-but-manual field carrying the SRD's spawn
prose ("becomes a wight in 1d4 days, under the control of the wight that killed
them") — the kernel kills, the game narrates.
allowed_codes
class-attribute
event_type
class-attribute
instance-attribute
event_type: Literal['level_drained'] = 'level_drained'
MagicDispelledEvent
Bases: Event
A dispel magic resolved: which effects were released and which survived.
Emitted by cast_spell when a dispel
magic-kind spell resolves.
event_type
class-attribute
instance-attribute
event_type: Literal['magic_dispelled'] = 'magic_dispelled'
MonsterRevivedEvent
Bases: Event
A regenerating monster returned from death (the troll's 2d6-round revival).
Emitted by a regeneration tick inside
EffectsLedger.advance.
allowed_codes
class-attribute
event_type
class-attribute
instance-attribute
event_type: Literal['monster_revived'] = 'monster_revived'
MoraleCheckedEvent
Bases: Event
A morale check: referee visibility — players learn the outcome from behavior.
Emitted by check_morale. code is
combat.morale.held or combat.morale.broke for a rolled check, or
combat.morale.exempt when the score is 2 (never fights) or 12 (never checks)
and no roll is made.
PreparedSpell
ReactionRolledEvent
Bases: Event
A monster reaction roll: referee visibility — players learn reactions from behavior.
Emitted by roll_reaction.
allowed_codes
class-attribute
event_type
class-attribute
instance-attribute
event_type: Literal['reaction_rolled'] = 'reaction_rolled'
SavingThrowRolledEvent
Bases: Event
A saving throw resolved; roll and required are None for auto-save defenses.
Emitted by saving_throw. code is
combat.save.passed or combat.save.failed for a rolled save, or
combat.save.auto when a defense auto-passes it.
SpellBookUpdatedEvent
Bases: Event
A spell added to an arcane caster's spell book.
Emitted by add_spell_to_book.
event_type
class-attribute
instance-attribute
event_type: Literal['spell_book_updated'] = 'spell_book_updated'
SpellCastEvent
Bases: Event
A spell cast: the memorized copy was consumed.
Emitted by cast_spell or
cast_from_scroll. code is
magic.cast.cast for an ordinary cast, or magic.cast.no_effect when every
target was ineligible or unaffected — the copy is still spent, since rejections
are free and would leak hidden state about which targets were eligible.
manual=True marks modes the kernel doesn't execute — the game narrates the
effect from the spell's prose. Resolution consequences ride the existing event
types (saves, damage, conditions, effects, healing, deaths), exactly as breath
weapons do.
allowed_codes
class-attribute
SpellDisruptedEvent
Bases: Event
A declared casting disrupted: the copy is lost as if it had been cast.
Emitted by disrupt_casting.
event_type
class-attribute
instance-attribute
event_type: Literal['spell_disrupted'] = 'spell_disrupted'
SpellForgottenEvent
Bases: Event
A memorized copy forgotten because level drain shrank the caster's slots.
Emitted by
forget_excess_memorized.
event_type
class-attribute
instance-attribute
event_type: Literal['spell_forgotten'] = 'spell_forgotten'
SpellsMemorizedEvent
Bases: Event
A caster's daily preparation resolved: the full prepared list.
Emitted by memorize_spells. One event per
preparation — memorization is a full replacement, so the list is the caster's
complete new memory.
allowed_codes
class-attribute
event_type
class-attribute
instance-attribute
event_type: Literal['spells_memorized'] = 'spells_memorized'
TargetsSelectedEvent
Bases: Event
The targeting model's resolution: which candidates an effect selected.
Emitted by select_targets.
allowed_codes
class-attribute
event_type
class-attribute
instance-attribute
event_type: Literal['targets_selected'] = 'targets_selected'
TurningTypeOutcome
Bases: BaseModel
One undead type's turning verdict: its column, the cell, and the threshold.
UndeadTurnedEvent
Bases: Event
A turning attempt resolved — player visibility: the player rolls turning dice.
Emitted by turn_undead.
Carries the 2d6 turn roll, the 2d6 HD pool when one was rolled (some type
succeeded), the per-type verdicts, and the affected monsters. Per-monster
consequences ride ConditionGainedEvent/DeathEvent. The code is failed when
no type succeeded, destroyed when any affected monster was destroyed, and
turned otherwise.
Visibility
Bases: StrEnum
Who may see an event.
The wire values are "player" and "referee" — lowercase, serialized into every
event; changing them is a schema_version bump.
parse_event
Parse one serialized kernel event, skipping unknown event types.
The mechanical half of "consumers must ignore unknown event types": an
event_type this library doesn't know returns None instead of raising, so a
newer producer's log replays under an older consumer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Mapping[str, object]
|
A mapping previously produced by an event's |
required |
Returns:
| Type | Description |
|---|---|
Event | None
|
The event, or |
Raises:
| Type | Description |
|---|---|
ContentValidationError
|
If the event type is known but the payload is malformed. |