Skip to content

The idol comes home: quests, victory, and the authored example - #61

Merged
mmacy merged 9 commits into
mainfrom
phase-15-impl
Aug 7, 2026
Merged

The idol comes home: quests, victory, and the authored example#61
mmacy merged 9 commits into
mainfrom
phase-15-impl

Conversation

@mmacy

@mmacy mmacy commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Implements the phase 15 plan (#60): the quest spec, the four lifecycle commands and five player-visible events, engine-owned quest state with construction seeding and persistence, adventure completion into victory, PlayerView.quests, LevelSpec.guidance, the quest validation walk, the interpreter's quest processing — and the example crawlers author their fetch quest as adventure data, register the library Interpreter, and delete the hand-rolled listener.

The milestone, twice over

  • The phase golden (phase15_quest.json) runs the barrow errand as authored data — trigger-then-activation on the threshold crossing, the bundled idol matched by catalog id, a hidden objective revealed by an area entry and completed by a game-issued flag, the concluding completion into victory, rewards landing after the transition with the authored spawn dropping and noting — and replays byte-equal with no listeners registered, load(save) equal to replay(seed, commands).
  • The example's fetch quest runs end to end through the real terminal loop (--seed 21), pays on delivery, journals every beat, and closes the transcript in [victory] deterministically.

Notable decisions

  • TriggerSpec keeps its flat wire shape. The clause model (TriggerClause) is quests-only; rehoming when/conditions inside every serialized trigger would be a schema bump bought for symmetry.
  • Quest ids are a closed domain — the deliberate opposite of MarkTriggerFired's open one, cross-referenced both ways: the view must render what the log only references.
  • The beat mapping (plan fact 3): quest blocks speak offer/completion, objective blocks offer/progress; a quest beat journals its display text verbatim and emits no JournalEntryAddedEvent — the lifecycle event is the beat's event. narrative.py's carrier table and the spec's journal paragraph both say so now.
  • Victory is entered in exactly one place (_handle_complete_quest), sticky from both terminal modes, rewards issued by the interpreter after the transition so a resume-play reward drops with a note.
  • Validation shares one body with triggers: _validate_trigger split into _validate_clause/_validate_consequence, trigger error strings byte-identical, quests walking the same helpers.
  • The XP retune (600 → 1200): the idol's 2,200 gp left the valuation delta (mundane gear is worth zero XP by RAW) and the 200 gp reward now lands in town after the last award; the retuned amount restores every member's final XP byte-identically, verified against the old golden.
  • Two unpinned scope additions, disclosed: the TUI gained a give verb (after sell all the seller carries 1,600 coins of weight and the party cannot move — the two-trip script physically requires spreading the purse), and a pre-existing engine defect surfaced by the widened fuzzer is fixed in its own commit (GiveItems/DropItems naming one magic instance twice raised out of execute instead of rejecting).

phase5_milestone.json is the only pre-existing golden that changed — regenerated for the example's redesign, explained in its commit. No SCHEMA_VERSION bump; pre-phase documents and saves load unchanged; docs/adaptations.md untouched (no SRD text is interpreted this phase). The spec gains exactly the four sentence-level amendments the plan argued.

Review provenance

Implemented stage-by-stage with senior review of every diff before commit, then rubber-ducked by a fresh reviewer against the spec, the plan, and AGENTS.md, with active verification (full gate, golden idempotency and regeneration, XP byte-comparison against the old golden, byte-identical trigger validation messages, the single victory entrance, layering/determinism/schema sweeps, greenfield anti-pattern hunt). Round 1: one blocking finding — the TUI walkthrough transcript was invented rather than excerpted; it is now two byte-exact excerpts of the live seed-21 run with the surprising bits taught instead of hidden — plus a dead parameter, a docs fragment without a runnable twin, and a wrong README seed, all fixed. Round 2 verified the fixes, caught one regression (a neighbouring fragment falsified by the new quest opening the journal), fixed and re-verified. Final verdict: SOLID, with the engine code needing no changes across the entire review.

Closing deliverable: the osr-forge no-quest-surface issue is filed and linked in a comment below.

https://claude.ai/code/session_01NQ83sqNnXVeXEd55NK1xR7

mmacy added 9 commits August 6, 2026 22:27
Work items 1-5 of the phase 15 plan: TriggerClause/ObjectiveSpec/QuestSpec
in the new crawl/quests.py; the four lifecycle commands with their closed
id domain and three rejection codes; the five player-visible quest events;
QuestState/ObjectiveState seeded at session construction (activation-less
quests active from round 0); the handlers with the beat mapping, the
victory transition, and terminal stickiness; and the quests block in
persistence with no schema bump.

Claude-Session: https://claude.ai/code/session_01NQ83sqNnXVeXEd55NK1xR7
Pre-existing defect the widened command fuzz surfaced: GiveItems naming
one magic instance id twice raised ValueError from _apply_give, and the
DropItems twin half-ran — both breaking the schema-valid-commands-reject-
never-throw contract. The shared validation pre-phase now rejects the
second naming with exploration.item.not_carried: the whole instance
leaves on the first naming, so the second has nothing behind it.

Claude-Session: https://claude.ai/code/session_01NQ83sqNnXVeXEd55NK1xR7
…hows them

Work items 7 and 8: validate_adventure walks quests through the same two
bodies triggers use — _validate_trigger's body split into _validate_clause
and _validate_consequence, its error lines byte-identical — so the two
authoring surfaces cannot drift; quest ids unique across the adventure,
every clause and reward reference resolved. PlayerView.quests ships
active quests and revealed objectives only, offer and speaker as flat
strings, and the leak pins extend to the quest wiring: no clauses, no
rewards, no hidden objectives, no inactive quests, no narrative-block
field ever a key in the projection.

Claude-Session: https://claude.ai/code/session_01NQ83sqNnXVeXEd55NK1xR7
…n that wins

Work item 6 and the phase golden: per event, triggers in document order
then quests in document order — activation, reveals, completions, the
completion rule checked the moment a completion the walk itself issued
lands, then CompleteQuest and the rewards in authored order. Clause
matching reuses _matches and condition_holds verbatim; every issued
command carries source="quest:{id}" via the shared _Owner value that
keeps the stamp and the note label from ever drifting apart; suppressed
advancements past the depth bound note instead of issuing, edge gone.
The phase15_quest golden runs the barrow errand to victory with the
spawn reward dropping in the ended session, and replays byte-equal with
no listeners registered.

Claude-Session: https://claude.ai/code/session_01NQ83sqNnXVeXEd55NK1xR7
…ter plays it

Work items 9 and 10: LevelSpec.guidance, the per-level ambient steering
slot the engine never reads. The example's Jade Idol becomes a bundled
GearTemplate placed in the shrine cache by id, so taking it reports the
catalog id the authored quest matches; the fetch quest lands as
Adventure.quests with pay-on-delivery rewards, and quest.py is deleted —
both front ends register the library Interpreter instead, on create and
restore alike. The milestone script makes two trips, because the
homecoming with the idol ends the adventure on the spot: town business
on the first return, the errand on the second, victory closing the
transcript. The authored XP retunes 600 -> 1200 to cover the 2,400 gp
that left the valuation delta (the mundane idol and the town-paid
reward), leaving every member's final XP identical to the old run. The
TUI grows a give verb — the sale coin outweighs a marching party, and
spreading the purse is the engine's own answer.

Claude-Session: https://claude.ai/code/session_01NQ83sqNnXVeXEd55NK1xR7
The golden scripts the reworked example, so the run it snapshots is new:
two session.xp.adventure_award events instead of one (the two-trip
script banks the delve award on the first return and the shrine's 50 gp
on the second), extra travel and town turns on the clock, the quest
block and victory mode in the final state, and the interpreter's
provably empty listener slot where the deleted listener's state used to
sit. The post_award checkpoint is now pinned explicitly to the final
return and a first_return checkpoint joins it — named captures replacing
the old last-town-wins accident. Every member's final XP is byte-
identical to the previous golden: the retuned authored award covers
exactly what the redesign removed from the valuation delta.

Claude-Session: https://claude.ai/code/session_01NQ83sqNnXVeXEd55NK1xR7
…the guides

Work item 11's remainder. Four sentence-level spec amendments — the
journal's display-text rule, the from-start activation consequence, the
validation clause rewritten to the executed seam, and the player view's
speaker attribution. narrative.py's carrier table rewritten to the
shipped mapping: quest blocks speak offer and completion, objective
blocks offer and progress, the quest layer journals the display text it
showed and leaves the journal field to carriers whose display beat the
players never see. The authoring guide gains the quest section and the
guidance slot, the commands guide the four lifecycle commands and their
closed id domain, the views guide PlayerView.quests and the journal-
growth event surface, and the changelog the phase's three entries.

Claude-Session: https://claude.ai/code/session_01NQ83sqNnXVeXEd55NK1xR7
The TUI walkthrough's fetch-quest transcript is now two byte-exact
excerpts of the real seed-21 run, with the split named in prose and the
two teachable surprises — the cache spreading the idol to the thief, and
the completion beat riding both closing events — taught instead of
hidden. _validate_consequence drops the magic parameter nothing read.
The views guide's complete example authors a two-objective quest and
registers the interpreter, so the quest projection and the no-journal-
event pin run under the docs-examples harness and the no-run fragment
has its runnable twin. The example README's scripted run uses seed 21,
the seed whose transcript does what the paragraph says.

Claude-Session: https://claude.ai/code/session_01NQ83sqNnXVeXEd55NK1xR7
… twin

The quest added to the views guide's runnable example opens the journal
with its offer beat, and the revision updated the runnable block's
assertion to the last-entry form but left the narrated fragment above it
on the old whole-journal equality — the fragment its own page disproves.
Mirror the [-1] form, which is also the honest teaching now that a quest
opens the journal.

Claude-Session: https://claude.ai/code/session_01NQ83sqNnXVeXEd55NK1xR7
@mmacy

mmacy commented Aug 7, 2026

Copy link
Copy Markdown
Owner Author

Closing deliverable filed: mmacy/osr-forge#39 — the overrides schema has no quest surface; quest authoring stays native-project-only until osr-forge grows one against this settled schema.

@mmacy
mmacy merged commit ad22ecb into main Aug 7, 2026
5 checks passed
@mmacy
mmacy deleted the phase-15-impl branch August 7, 2026 07:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant