11 KiB
NEO-124 — Implementation plan
Story reference
| Field | Value |
|---|---|
| Key | NEO-124 |
| Title | E7M2-01: QuestRewardBundle schema + quest catalog extension + CI |
| Linear | https://linear.app/neon-sprawl/issue/NEO-124/e7m2-01-questrewardbundle-schema-quest-catalog-extension-ci |
| Module | E7.M2 — RewardAndUnlockRouter · Epic 7 Slice 2 · backlog E7M2-01 |
| Branch | NEO-124-e7m2-questrewardbundle-schema-quest-catalog-extension-ci |
| Precursor | E7.M1 Ready (NEO-112–NEO-123) · NEO-43 MissionRewardSkillXpGrant prep · frozen item + skill catalogs |
| Pattern | NEO-112 — extend row JSON Schema + catalog + scripts/validate_content.py gate |
| Blocks | NEO-125 (server catalog load), NEO-126 (delivery store) |
| Client counterpart | none — infrastructure-only (E7M2-prototype-backlog) |
Kickoff clarifications
| Topic | Question | Agent recommendation | Answer |
|---|---|---|---|
| Bundle array keys | item_grants / skill_xp_grants (backlog prose) vs camelCase? |
itemGrants + skillXpGrants — matches repo JSON (fixedGrants, prerequisiteQuestIds, completionRewardBundle). |
Adopted — camelCase |
Skill XP sourceKind in content |
Explicit sourceKind: mission_reward per row vs omit? |
Omit — v1 Slice 2 is mission_reward only; CI checks target skill allowedXpSourceKinds includes mission_reward; apply path hardcodes via MissionRewardSkillXpGrant (NEO-43). |
Adopted — omit from content |
Additional defaults (no kickoff question — settled by backlog / precedent):
- Item grant rows:
$refexistingreward-grant-row.schema.json(itemId+quantity) — same shape as E5.M3fixedGrants. - Bundle required on all four quests: CI gate enforces presence + exact freeze-table contents (not optional omission on prototype ids).
- Empty
itemGrants: omit property when no item rows (XP-only quests); schema allows optionalitemGrantswithminItems: 0when present. - At least one grant: bundle must contain ≥1 item or skill XP row (
skillXpGrantsnon-empty for all four frozen quests per freeze table).
Goal, scope, and out-of-scope
Goal: Lock completionRewardBundle content shape and CI validation on the four frozen E7.M1 quests before server load (NEO-125+).
In scope (from Linear + E7M2-01):
content/schemas/quest-reward-bundle.schema.json,content/schemas/quest-skill-xp-grant.schema.json.- Extend
quest-def.schema.jsonwith optionalcompletionRewardBundle($refbundle schema). - Update
prototype_quests.json— bundles per freeze table (below). scripts/validate_content.py: bundle schema validation via extended quest validator registry; item id cross-refs; skill id cross-refs;mission_rewardallowlist alignment (skill row must listmission_rewardinallowedXpSourceKinds); E7M2 prototype bundle freeze gate.- Designer note in E7_M2 (camelCase array keys + CI line) +
content/README.mdE7 Slice 2 paragraph + CT.M1 quest-bundle PR gate paragraph.
Out of scope (from Linear):
- Server loader, delivery store, router, HTTP, Godot (NEO-125+).
sourceKindfield in content rows; unlock/flag grant kinds.
Acceptance criteria checklist
- PR gate validates quest JSON including optional completion bundles.
- All four prototype quests include
completionRewardBundlematching freeze table. - Invalid item/skill refs fail CI.
Technical approach
1. quest-skill-xp-grant.schema.json
Draft 2020-12 object; additionalProperties: false. Required: skillId (pattern ^[a-z][a-z0-9_]*$), amount (integer ≥ 1). No sourceKind — apply path uses MissionRewardSkillXpGrant (NEO-43).
2. quest-reward-bundle.schema.json
Draft 2020-12 object; additionalProperties: false. Optional: itemGrants (array of $ref reward-grant-row), skillXpGrants (array of $ref quest-skill-xp-grant). At least one array must be present with ≥1 row (enforce in schema via anyOf / minItems or post-schema CI helper). Omit itemGrants when empty (XP-only quests).
3. Extend quest-def.schema.json
Add optional property completionRewardBundle → $ref quest-reward-bundle. Remaining required fields unchanged.
4. Extend _quest_def_validator() registry
Register quest-reward-bundle.schema.json and quest-skill-xp-grant.schema.json (and existing reward-grant-row.schema.json) in the Draft 2020-12 $ref registry alongside step/objective schemas.
5. Catalog — content/quests/prototype_quests.json
Add completionRewardBundle to each of the four rows:
| Quest id | itemGrants |
skillXpGrants |
|---|---|---|
prototype_quest_gather_intro |
(omit) | salvage 25 |
prototype_quest_refine_intro |
(omit) | refine 25 |
prototype_quest_combat_intro |
(omit) | salvage 25 |
prototype_quest_operator_chain |
survey_drone_kit ×1 |
salvage 50 |
Example (gather intro):
"completionRewardBundle": {
"skillXpGrants": [{ "skillId": "salvage", "amount": 25 }]
}
Example (operator chain):
"completionRewardBundle": {
"itemGrants": [{ "itemId": "survey_drone_kit", "quantity": 1 }],
"skillXpGrants": [{ "skillId": "salvage", "amount": 50 }]
}
6. validate_content.py constants + gates
| Constant | Purpose |
|---|---|
PROTOTYPE_E7M2_COMPLETION_BUNDLES |
Map quest id → normalized { "itemGrants": [...], "skillXpGrants": [...] } matching freeze table (keep in sync with E7.M2 module doc + future NEO-125 loader) |
PROTOTYPE_E7M2_MISSION_REWARD_SOURCE_KIND |
"mission_reward" — used in cross-ref gate only |
New validators (run after quest schema pass, alongside existing E7M1 gates):
_prototype_e7m2_completion_bundle_presence_gate: every id inPROTOTYPE_E7M1_QUEST_IDSmust havecompletionRewardBundleobject._prototype_e7m2_completion_bundle_freeze_gate: bundle contents must exactly matchPROTOTYPE_E7M2_COMPLETION_BUNDLES(sorted grant rows for stable compare)._prototype_e7m2_completion_bundle_cross_ref_gate: eachitemGrants[].itemId∈ frozen item ids; eachskillXpGrants[].skillId∈ frozen skill ids; each target skill’sallowedXpSourceKindsmust includemission_reward(load skill rows from validated skill catalog or reuse existing skill id map if available inmain()scope).
Extend module docstring (quest catalogs bullet) and startup schema file existence checks for new schema paths.
7. Docs
E7_M2_RewardAndUnlockRouter.md— update freeze prose toitemGrants/skillXpGrants(camelCase); add CI enforcement line under prototype freeze.content/README.md— Prototype E7 Slice 2 — quest completion bundles (NEO-124) paragraph: four quests require bundles; cross-ref rules; link E7M2 backlog + this plan.CT_M1_ContentValidationPipeline.md— extend quest catalog bullet with completion bundle validation.E7M2-prototype-backlog.md— E7M2-01 checkboxes when implementation completes.documentation_and_implementation_alignment.md— E7.M2 row note NEO-124 catalog in progress / landed.
8. No server/C#/client changes in this story.
Files to add
| Path | Purpose |
|---|---|
content/schemas/quest-skill-xp-grant.schema.json |
Single skill XP row on a quest completion bundle (skillId, amount). |
content/schemas/quest-reward-bundle.schema.json |
Composite bundle (itemGrants, skillXpGrants) attached to QuestDef. |
docs/plans/NEO-124-implementation-plan.md |
This plan. |
Files to modify
| Path | Rationale |
|---|---|
content/schemas/quest-def.schema.json |
Add optional completionRewardBundle property referencing bundle schema. |
content/quests/prototype_quests.json |
Add completionRewardBundle to all four frozen quest rows per freeze table. |
scripts/validate_content.py |
New schema paths; extended quest $ref registry; E7M2 bundle presence/freeze/cross-ref gates; docstring + schema existence checks. |
docs/decomposition/modules/E7_M2_RewardAndUnlockRouter.md |
camelCase grant keys + CI enforcement note under prototype freeze. |
content/README.md |
E7 Slice 2 quest completion bundle paragraph (NEO-124). |
docs/decomposition/modules/CT_M1_ContentValidationPipeline.md |
PR gate paragraph for quest completion bundles. |
docs/plans/E7M2-prototype-backlog.md |
Mark E7M2-01 AC complete when shipped. |
docs/decomposition/modules/documentation_and_implementation_alignment.md |
E7.M2 alignment row — NEO-124 catalog landed. |
Tests
| Target | Coverage |
|---|---|
| No new automated test files | Content-only slice; regression surface is scripts/validate_content.py invoked by PR gate (NEO-112 / NEO-100 precedent). |
| Manual verification | pip install -r scripts/requirements-content.txt && python3 scripts/validate_content.py — must pass with four bundles. Spot-check failures: typo itemId, unknown skillId, skill without mission_reward in allowedXpSourceKinds, missing completionRewardBundle on a quest row, wrong XP amount vs freeze table. |
Open questions / risks
| Question or risk | Agent recommendation | Status |
|---|---|---|
| Constants drift vs future NEO-125 C# loader | Sync comment on PROTOTYPE_E7M2_COMPLETION_BUNDLES pointing at E7.M2 module freeze + future server rules class. |
adopted |
Backlog prose still says item_grants / skill_xp_grants |
Update E7M2 backlog + E7.M2 module to camelCase when implementing (kickoff decision). | adopted |
| Bundle compare stability (grant row order) | Normalize/sort grant rows in freeze gate before compare. | adopted |
None beyond the above.