12 KiB
NEO-112 — Implementation plan
Story reference
| Field | Value |
|---|---|
| Key | NEO-112 |
| Title | E7M1-01: Prototype QuestDef catalog + schemas + CI |
| Linear | https://linear.app/neon-sprawl/issue/NEO-112/e7m1-01-prototype-questdef-catalog-schemas-ci |
| Module | E7.M1 — QuestStateMachine · Epic 7 Slice 1 · backlog E7M1-01 |
| Branch | NEO-112-e7m1-prototype-questdef-catalog-schemas-ci |
| Precursor | E3.M1 gather Ready · E3.M2 craft Ready · E5.M3 encounter Ready (frozen item/recipe/encounter ids) |
| Pattern | NEO-100 — row JSON Schema + catalog envelope + scripts/validate_content.py gate |
| Blocks | NEO-113 — server quest catalog load (fail-fast) |
Kickoff clarifications
No clarifications needed. Scope, frozen quest ids, step/objective graph, prerequisite rules, objective kinds, and cross-ref targets are fully specified in E7M1 kickoff decisions, E7.M1 freeze table, and E7M1-01 backlog entry. Catalog envelope and CI constant naming follow NEO-100 / NEO-87 precedent without ambiguity.
| Topic | Question | Agent recommendation | Answer |
|---|---|---|---|
| — | — | — | No blocking decisions (see above). |
Goal, scope, and out-of-scope
Goal: Lock content shape and CI validation for four frozen QuestDef rows before server load (NEO-113+).
In scope (from Linear + E7M1-01):
content/schemas/quest-def.schema.json,content/schemas/quest-step-def.schema.json,content/schemas/quest-objective-def.schema.json.content/quests/prototype_quests.json— four rows matching the module doc freeze table.scripts/validate_content.py— schema validation, duplicate ids, four-quest allowlist, cross-refitemId/recipeId/encounterIdto frozen catalogs, acyclicprerequisiteQuestIds, objectivekindenum, chain terminal stepinventory_has_itemcontract_handoff_token×1 gate.- Designer note / CI rules in E7_M1 (CI enforcement line already present) + CT.M1 PR gate paragraph +
content/README.mdquest row. documentation_and_implementation_alignment.mdE7.M1 row → note NEO-112 catalog when complete.
Out of scope (from Linear):
- Server loader, runtime engine, HTTP, Godot (NEO-113+).
IQuestDefinitionRegistry, player quest state, objective hooks (E7M1-03+).- Client counterpart: none — server-only content + CI; player-visible work starts at NEO-122 / NEO-123.
Acceptance criteria checklist
- PR gate validates quest JSON against schema.
- Exactly four prototype quest ids; duplicate
idfails CI. - Every objective reference resolves to a frozen catalog id.
- Stable id list documented in module doc freeze box (already present; verify after ship).
Implementation reconciliation (shipped)
- Schemas:
quest-objective-def.schema.json,quest-step-def.schema.json,quest-def.schema.json. - Catalog:
prototype_quests.json— four frozen quest rows per E7.M1 freeze table. - CI:
PROTOTYPE_E7M1_QUEST_IDS, prerequisite acyclic gate, objective cross-ref gates, chain terminal token gate invalidate_content.py; duplicate quest/step/objective id rejection. - Docs: CT.M1 quest paragraph,
content/README.mdquests row + E7M1 paragraph, alignment register E7.M1 row, E7M1-01 backlog checkboxes.
Technical approach
-
quest-objective-def.schema.json: Draft 2020-12 object;additionalProperties: false. Required:id,kind.kindenum:gather_item,craft_recipe,encounter_complete,inventory_has_item. UseoneOfbranches keyed bykind:gather_item: requireditemId,quantity(integer ≥ 1).craft_recipe: requiredrecipeId,quantity(integer ≥ 1).encounter_complete: requiredencounterId(no quantity).inventory_has_item: requireditemId,quantity(integer ≥ 1).- Id patterns:
^[a-z][a-z0-9_]*$(same as other catalogs).
-
quest-step-def.schema.json: Required:id,displayName,objectives(non-empty array of$refquest-objective-def). Reject duplicate objectiveidwithin a step in CI (post-schema). -
quest-def.schema.json: Required:id,displayName,prerequisiteQuestIds(array of strings; may be empty),steps(non-empty array of$refquest-step-def). Reject duplicate stepidwithin a quest in CI (post-schema). -
Catalog file
content/quests/prototype_quests.json:Envelope:
schemaVersion: 1, top-levelquestsarray. Four rows per freeze table:iddisplayNameprerequisiteQuestIdsSteps prototype_quest_gather_introIntro: Salvage Run []1× gather_itemscrap_metal_bulk×3prototype_quest_refine_introIntro: Refine Stock [prototype_quest_gather_intro]1× craft_reciperefine_scrap_standard×1prototype_quest_combat_introIntro: Clear the Pocket []1× encounter_completeprototype_combat_pocketprototype_quest_operator_chainOperator Chain all three intros 4 steps: gather ×5 → refine → make_field_stim_mk0 → inventory_has_itemtoken ×1Step/objective
idvalues are stable content keys (e.g.gather_intro_step_salvage,chain_obj_token); chosen for readability and future HTTP projection (E7M1-04). -
validate_content.pyconstants (keep in sync with E7.M1 module doc + future NEO-113 loader):Constant Value PROTOTYPE_E7M1_QUEST_IDS{ prototype_quest_gather_intro, prototype_quest_refine_intro, prototype_quest_combat_intro, prototype_quest_operator_chain }PROTOTYPE_E7M1_CHAIN_QUEST_IDprototype_quest_operator_chainPROTOTYPE_E7M1_CHAIN_TERMINAL_ITEM_IDcontract_handoff_token -
validate_content.pyvalidators:- Add schema paths + dir:
QUEST_DEF_SCHEMA,QUEST_STEP_DEF_SCHEMA,QUEST_OBJECTIVE_DEF_SCHEMA,QUESTS_DIR. - Register
$refbetween quest / step / objective schemas in the Draft 2020-12 registry (same pattern as recipe + recipe-io-row). _validate_quest_catalogs: envelopeschemaVersion: 1, top-levelquestsarray; validate each row; trackseen_ids; reject duplicate questidacross files; buildid_to_rowmap._prototype_e7m1_quest_gate: ids must equalPROTOTYPE_E7M1_QUEST_IDS._prototype_e7m1_quest_prerequisite_gate: everyprerequisiteQuestIdsentry must exist inseen_ids; graph must be acyclic (DFS / Kahn)._prototype_e7m1_quest_cross_ref_gate: per objective —itemId∈PROTOTYPE_SLICE1_ITEM_IDS;recipeId∈PROTOTYPE_SLICE3_RECIPE_IDS;encounterId∈PROTOTYPE_E5M3_ENCOUNTER_IDS(only when present for kind)._prototype_e7m1_chain_terminal_gate: operator chain last step's sole objective isinventory_has_itemwithitemIdcontract_handoff_token,quantity1.- Run quest validation after item, recipe, and encounter catalogs (cross-ref dependencies).
- Extend module docstring, startup schema file checks, success summary with quest file/id counts.
- Add schema paths + dir:
-
Docs:
E7_M1_QuestStateMachine.md— verify CI enforcement line under freeze table matches shipped gates.CT_M1_ContentValidationPipeline.md— quest catalog PR gate paragraph.content/README.md— addquests/table row + E7M1 prototype paragraph (four quest ids, cross-ref rules).documentation_and_implementation_alignment.md— E7.M1 row notes NEO-112 catalog when complete.E7M1-prototype-backlog.md— E7M1-01 checkboxes when implementation completes.
-
No server/C#/client changes in this story.
Files to add
| Path | Purpose |
|---|---|
content/schemas/quest-objective-def.schema.json |
JSON Schema for a single quest objective row (discriminated by kind). |
content/schemas/quest-step-def.schema.json |
JSON Schema for a quest step (objectives array). |
content/schemas/quest-def.schema.json |
JSON Schema for a single QuestDef row (steps + prerequisites). |
content/quests/prototype_quests.json |
Prototype four-quest catalog (schemaVersion + quests array). |
docs/plans/NEO-112-implementation-plan.md |
This plan. |
Files to modify
| Path | Rationale |
|---|---|
scripts/validate_content.py |
Load/validate quest catalogs; duplicate id; E7M1 four-id gate; item/recipe/encounter cross-refs; acyclic prerequisites; chain terminal token gate; success summary. |
docs/decomposition/modules/CT_M1_ContentValidationPipeline.md |
PR gate paragraph for quest catalog validation. |
content/README.md |
quests/ path row + E7M1 prototype CI paragraph. |
docs/decomposition/modules/documentation_and_implementation_alignment.md |
E7.M1 / NEO-112 status after catalog lands. |
Tests
| Item | Coverage |
|---|---|
| CI / PR gate | .github/workflows/pr-gate.yml runs python scripts/validate_content.py — extended script is the primary regression signal. |
| Manual negative cases | From repo root after implementation: (1) duplicate quest id → non-zero exit; (2) remove frozen quest id → gate error; (3) unknown itemId / recipeId / encounterId in objective → cross-ref error; (4) cyclic prerequisiteQuestIds → gate error; (5) unknown prerequisite id → gate error; (6) operator chain terminal step not inventory_has_item token ×1 → gate error; (7) invalid kind or missing required field per kind → schema error. |
| Manual happy path | pip install -r scripts/requirements-content.txt && python3 scripts/validate_content.py — reports quest catalog + existing catalogs OK. |
No dedicated pytest module in-repo today (same as NEO-100 / NEO-87).
Open questions / risks
| Question / risk | Agent recommendation | Status |
|---|---|---|
Objective oneOf schema complexity vs flat optional fields |
oneOf per kind — mirrors encounter completionCriteria; catches wrong-field-at-wrong-kind at schema time. |
adopted |
| Combat intro has no gather prerequisite (parallel onboarding path) | Ship as freeze table specifies — chain quest requires all three intros; refine only requires gather intro. | adopted |
| Step/objective ids not in freeze table | Stable descriptive ids in catalog JSON — required for E7M1-04 HTTP projection; not part of four-quest id gate. | adopted |
content/README.md has no quest paragraph yet |
Add paragraph on ship — unlike NEO-100 (pre-landed), quests row is new. | adopted |