An objective says what it is called: display names end the raw-id transcript - #66
Merged
Conversation
…nscript ObjectiveSpec gains name, the display label a quest log shows beside its checkbox (#65) — optional and defaulting empty, so every existing schema-3 document loads unchanged; empty means unauthored, and every surface that shows a label falls back to the objective's id. ObjectiveView.name carries the resolved label, which makes the view's own "what it is called" docstring true at last. ObjectiveRevealedEvent and ObjectiveCompletedEvent carry name and quest_name — the symmetry QuestActivatedEvent already shipped — AdventureCompletedEvent carries the concluding quest's name, and the default templates print the names in place of the raw ids, falling back to the ids so an event logged before the fields existed still formats in the old wording. The event fields default empty only for that older artifact's sake; the engine always fills them at emission. validate_adventure needs no new rule: the quest walk resolves references, and a display name references nothing. Goldens: phase15_quest.json and phase5_milestone.json regenerate because the objective and adventure-completed events now carry the name fields and the formatter renders them — the transcript reads "Quest The Votive Idol: objective Recover the idol is done." where it read raw ids. No command, draw, or state block changed; the diffs are exactly the added fields and the renamed lines. The examples and fixtures author objective names so those transcripts read as authored content, and the TUI chapter's captured output is re-captured from the same milestone run. Claude-Session: https://claude.ai/code/session_01GSYzXCjpPnVDLTC2rpmhTZ
mmacy
commented
Aug 8, 2026
mmacy
left a comment
Owner
Author
There was a problem hiding this comment.
Review — pass
Read the full diff and spot-checked the load-bearing claims at the head commit:
- The fallback design is coherent end to end.
ObjectiveSpec.nameoptional-empty is the right call over #65's "presumably required":QuestSpec.namewas required from birth in 1.5.0, butObjectiveSpecpredates the field under schema 3, and the additive rule decides it. Every label surface degrades to the id, and the three places that promise a label (ObjectiveView.name, the two objective events, the templates) each keep the promise — the view viaobjective.name or objective.idat construction, the events resolved at emission, the templates viaor-fallback so a pre-field artifact prints exactly the old wording. Verified the emission sites insession.pyfill from the owning quest's spec (quest_name=spec.name) andAdventureCompletedEvent.namefrom a field that is required non-empty, so a freshly emitted event never carries an empty label. - The symmetry claim checks out:
QuestActivatedEventships a requiredname, so the objective events carryingquest_namefollows the established shape rather than inventing one. - The leak pin is real:
_quest_viewsskips unrevealed objectives beforeObjectiveViewis ever built, andtest_the_view_carries_no_quest_wiringnow asserts the hidden objective's display name is absent from the serialized view — the name is exactly as secret as its id. - No validation rule is correct, not an omission: there is no reference to resolve and no invalid state — empty means unauthored by design. Documented on the field.
- Formatter totality holds:
format_messagestill never raises, and the executed old-artifact tests (model_validatewithout the fields) prove the additive-parse claim rather than asserting it.
One note for the record: the PR body says the golden diffs are exactly the added fields and renamed lines, but phase5_milestone.json also catches up party_document.engine_version 1.4.0 → 1.5.0 — expected regeneration fallout, harmless.
CI green on both platforms, docs job green, mergeable. Merging squash; this unblocks osr-web phase 2 (mmacy/osr-web#44).
This was referenced Aug 8, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #65.
An objective finally has a player-facing display name everywhere its id used to stand alone: on the spec, in the player view, on the lifecycle events, and in the default formatter's lines.
The fallback design
ObjectiveSpec.nameis optional, defaulting empty — not required likeQuestSpec.name. The additive rule decided this: quests shipped in 1.5.0 withnamerequired from birth, butObjectiveSpecalready exists in the wild under schema 3, and a required field would brick every document written before it. Empty means unauthored, and the fallback is documented on the field: every surface that shows a label degrades to the objective's id.ObjectiveView.nameis always populated — the authored name, or the id when the document authors none — so the view's docstring promise ("what it is called") is true and a quest-log renderer never ships an empty caption.ObjectiveRevealedEvent/ObjectiveCompletedEventcarrynameandquest_name, both resolved at emission (a wire client holds no document to resolve them from).quest_namefollows the issue's symmetry argument:QuestActivatedEventalready ships the quest's name, so the objective events do too rather than making hosts resolve it.AdventureCompletedEventgainsnamefor the same reason — its template leaked the raw quest id the same way. All three new event fields default""purely because events are serialized artifacts under the additive-only rule: a log written before the fields existed still parses. The engine always fills them.Quest {quest_name or quest_id}: objective {name or objective_id} is done.— a freshly emitted event prints names, an old artifact prints exactly the pre-change wording, andformat_messagestays total.validate_adventuregains no rule: quest-name validation is parse-time only (min_length=1), and an objective's name is deliberately allowed to be absent — there is no reference to resolve and no invalid state to catch.Tests and goldens
test_quests.py: spec default and pre-name-document parse, resolved names on the events, the id fallback on events and view, the new template wordings, old-artifact formatting fallback, and the leak pin extended — a hidden objective's display name is exactly as secret as its id.phase15_quest.jsonandphase5_milestone.jsonregenerated with their own generators: the diffs are exactly the added event fields and the renamed transcript lines — no command, draw, or state block moved. The other goldens came out byte-identical.examples/tui_crawler/content.py,tests/crawl_fixtures.py) now name their objectives ("Recover the idol", "Bring it home", "Speak the rite", …), the TUI status line prints names instead of slugs, and the TUI chapter's captured transcript is re-captured from the real milestone run.docs/spec.mdrecords the field in the quests section with its fallback, in the section already covered by the additive-within-schema_versionsentence; the views guide and CHANGELOG move with it.Full
uv run pytest(2246 passed),uv run ruff format --check, anduv run ruff checkare green.https://claude.ai/code/session_01GSYzXCjpPnVDLTC2rpmhTZ