diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c746b5..1f062e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ## [Unreleased] +## [1.5.0] - 2026-08-07 + ### Added - Authored quests, the state they advance, and the ending they reach. A `QuestSpec` (`osrlib.crawl.quests`) is adventure data like a trigger: an optional activation clause, `ObjectiveSpec`s each with a completion clause of their own, `rewards` from the same `ConsequenceCommand` surface under the same `@party`/`@first` selectors, a completion rule of `"all"` or `"any"`, and an optional `concludes_adventure` marker. Every "did this happen?" is a `TriggerClause` — a `TriggerPattern` plus the conditions gates use, matched by the same matcher and the same live evaluation triggers get, so the two surfaces cannot drift; a consuming condition is rejected at parse, because a quest observes and does not take. `Adventure.quests` carries them in document order, with an `Adventure.quest(id)` accessor, and `validate_adventure` walks every clause and reward through the same reference and party-selector checks a trigger gets. Quest state is engine-owned: `GameSession.quests` maps each quest id to a `QuestState` (`inactive` → `active` → `completed`) holding an `ObjectiveState` (`revealed`, `complete`) per objective, seeded at construction from the adventure — activation-less quests active from round 0, objectives visible unless authored `hidden` — so `new`, `load_game`, and replay all begin from the same block. Four referee commands are its only writers: `ActivateQuest`, `RevealObjective`, `CompleteObjective` (which reveals as it completes), and `CompleteQuest`, each emitting a player-visible event carrying the authored beat — `QuestActivatedEvent`, `ObjectiveRevealedEvent`, `ObjectiveCompletedEvent`, `QuestCompletedEvent`, and, when a concluding quest finishes outside a terminal mode, `AdventureCompletedEvent` as the session clears any open encounter or battle and enters `victory`. Unlike `MarkTriggerFired`'s open trigger id, their ids are a closed domain resolved against the adventure's own specs (`session.command.unknown_quest`, `session.command.unknown_objective`), and a command contradicting the state it finds rejects with `session.command.quest_state`; `CompleteQuest` deliberately checks only that the quest is active, because ruling a quest done is the referee's call. Each beat appends its display text to the journal as itself, with no `JournalEntryAddedEvent` behind it — the lifecycle event *is* that beat's event. The `Interpreter` plays all of it: per event, the adventure's triggers in document order and then its quests, issuing every lifecycle command, reward, and note stamped `source="quest:{id}"`, checking the completion rule the moment a completion it issued lands, and dropping a reward that cannot land — a spawn in `victory`, say — with a note rather than a raise. The block persists under a new `quests` key with no `schema_version` bump: a save written without it keeps the constructor's seed, and an adventure that authors no quests plays exactly as before. @@ -108,7 +110,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - The documentation site: quickstart, guides, front-end walk-throughs, and a full reference for every command, event, rejection code, message code, RNG stream, and content id. - The typed surface: complete type hints under `py.typed`, checked in CI. -[Unreleased]: https://github.com/mmacy/osrlib-python/compare/v1.4.0...HEAD +[Unreleased]: https://github.com/mmacy/osrlib-python/compare/v1.5.0...HEAD +[1.5.0]: https://github.com/mmacy/osrlib-python/compare/v1.4.0...v1.5.0 [1.4.0]: https://github.com/mmacy/osrlib-python/compare/v1.3.0...v1.4.0 [1.3.0]: https://github.com/mmacy/osrlib-python/compare/v1.2.1...v1.3.0 [1.2.1]: https://github.com/mmacy/osrlib-python/compare/v1.2.0...v1.2.1 diff --git a/pyproject.toml b/pyproject.toml index f783455..8eee73a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "osrlib" -version = "1.4.0" +version = "1.5.0" description = "B/X (1981 Basic/Expert) tabletop RPG rules engine for turn-based dungeon crawlers" readme = "README.md" # The wheel ships Open Game Content (osrlib/data/*.json and its LICENSE-OGL.md diff --git a/uv.lock b/uv.lock index 4a8aa3e..4876922 100644 --- a/uv.lock +++ b/uv.lock @@ -487,7 +487,7 @@ wheels = [ [[package]] name = "osrlib" -version = "1.4.0" +version = "1.5.0" source = { editable = "." } dependencies = [ { name = "pydantic" },