osrlib.crawl.interpreter
The interpreter: the listener that plays an adventure's authored triggers and quests.
Interpreter is an ordinary listener the
game registers on its session
(GameSession.register_listener).
It watches the events of every accepted command, matches them against the adventure's
TriggerSpecs and
QuestSpecs, and acts the only way anything outside
the engine may act: by executing ordinary referee commands, each stamped with the
trigger or quest it acted for.
That discipline is what keeps an authored game replayable. The interpreter emits no events of its own and remembers nothing between commands, so a replay — which runs with no listeners at all — rebuilds the same world by re-executing the same log. Every effect a trigger or a quest has is a command in that log, and every one of those commands says whose idea it was.
Interpreter
Interpreter(session: GameSession)
Plays an adventure's authored triggers and quests by issuing referee commands.
Register one, once, on a session that has already been built:
Registering twice fires everything twice — the same rule every listener follows —
and a session restored from a save needs the registration again, because listeners
are code and a save carries data. Nothing migrates: the interpreter's slot in
listener_state is empty and stays empty forever.
What it does with a command's events. It walks them in the order they
happened and, per event, the adventure's triggers in document order and then its
quests in document order — one rule, and the only order there is. A trigger
matches when its pattern fits the event, its fired-state allows it (once-only
unless repeatable), and every one of its conditions holds against session state
right now. A match fires immediately, before the walk moves on, so a later
trigger's conditions see what an earlier firing has already changed.
What a firing issues, all of it stamped source="trigger:{id}":
MarkTriggerFired, carrying thefiredbeat. The mark goes in first, which is what makes once-only safe against a trigger whose own consequences would match it again.- The consequences, in authored order, with
@partyand@firstexpanded to the living members they name — so the log records concrete character ids and replays exactly. AddJournalEntrywhen the trigger's narrative carries a journal form, last, so the beat is stamped with the clock the consequences left behind.
What a quest walk issues, all of it stamped source="quest:{id}". A quest
clause (TriggerClause) is matched exactly
the way a trigger is — the same patterns, the same live conditions — and the walk
goes:
- An inactive quest whose activation clause matches gets
ActivateQuest, and the walk carries on into the objectives of the quest it just activated: the same event that starts a quest can finish something in it. - An active quest's objectives walk in authored order. A hidden, unrevealed,
incomplete objective whose
reveal_whenmatches getsRevealObjective; an incomplete objective whosewhenmatches getsCompleteObjective. An objective that completes without ever being revealed needs no reveal — completing shows it. - The moment a completion lands, the quest's completion rule is checked against
live state (
allorany), and a satisfied rule getsCompleteQuestfollowed by the rewards in authored order, selectors expanded exactly as a trigger's consequences are. On a quest that concludes the adventure the session is invictorybefore the first reward is issued, which is why a reward that would resume play there drops with a note.
Everything is evaluated as the walk goes: a flag an earlier firing wrote satisfies a later clause's condition in the same batch, and a quest completed earlier in the walk is completed for everything after it.
Where the interpreter's discipline stops and the referee's ruling begins. The
completion rule is checked only after a completion the interpreter itself issued,
and no pattern matches the quest events, so a referee who completes the last
objective by hand completes the quest by hand too. For the same reason a
hand-driven CompleteQuest grants no
rewards: rewards are this listener reading the quest, and what a replay re-executes
is the reward commands themselves.
When something does not work out, the run continues and the log says why. A
rejected consequence or reward is dropped on its own — a spawn that meets an open
encounter, a grant to a character who is not there — and a
RecordNote records the trigger or quest, the
slot's position and type, and the rejection. If a wipe mid-cascade ends the
session, the remaining commands land or drop by the ordinary rules of a terminal
mode. And a cascade is bounded: what a firing or a quest advancement issues is one
deeper than the event that caused it, matching stops below depth five, and every
advancement the bound suppresses is recorded as a note instead of being issued —
no state moves, so a once-only trigger cut short here is still fireable later, and
a suppressed quest advancement waits for its clause to match again. Clauses are
edge-triggered on both surfaces: the suppressed edge is gone.
What it never does. It returns no events, because everything it causes is
already logged by the commands it executed, and it keeps no memory between
commands. Read what a trigger or a quest did from the command log, the journal,
session.fired_triggers, and session.quests, all of which a replay rebuilds.
Bind the interpreter to the session it watches and issues commands through.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
GameSession
|
The session; its adventure's triggers and quests are read once here, being frozen content. |
required |
key
class-attribute
instance-attribute
The listener key; its state entry exists because registration creates one, and is the empty dict for the life of the session.
handle
Match one command's events and act on what they crossed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
events
|
Sequence[Event]
|
The command's accumulated events, in the order they happened. |
required |
state
|
dict
|
The listener's state slot, always the empty dict. |
required |
Returns:
| Type | Description |
|---|---|
list[Event]
|
No events and the empty state — everything the interpreter does is a |
dict
|
command it executed, and it remembers nothing. |