NEO-124: Add E7M2-01 implementation plan (quest reward bundle schema + CI).
Lock completionRewardBundle content shape, kickoff decisions, and file lists before catalog/validator work.pull/163/head
parent
85ead01a0b
commit
98a9602bb0
|
|
@ -0,0 +1,161 @@
|
|||
# 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](../decomposition/modules/E7_M2_RewardAndUnlockRouter.md) · Epic 7 Slice 2 · backlog **E7M2-01** |
|
||||
| **Branch** | `NEO-124-e7m2-questrewardbundle-schema-quest-catalog-extension-ci` |
|
||||
| **Precursor** | [E7.M1](../decomposition/modules/E7_M1_QuestStateMachine.md) **Ready** ([NEO-112](https://linear.app/neon-sprawl/issue/NEO-112)–[NEO-123](https://linear.app/neon-sprawl/issue/NEO-123)) · [NEO-43](https://linear.app/neon-sprawl/issue/NEO-43) **`MissionRewardSkillXpGrant`** prep · frozen item + skill catalogs |
|
||||
| **Pattern** | [NEO-112](NEO-112-implementation-plan.md) — extend row JSON Schema + catalog + `scripts/validate_content.py` gate |
|
||||
| **Blocks** | [NEO-125](https://linear.app/neon-sprawl/issue/NEO-125) (server catalog load), [NEO-126](https://linear.app/neon-sprawl/issue/NEO-126) (delivery store) |
|
||||
| **Client counterpart** | none — infrastructure-only ([E7M2-prototype-backlog](E7M2-prototype-backlog.md#e7m2-01--questrewardbundle-schema--quest-catalog-extension--ci)) |
|
||||
|
||||
## 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:** `$ref` existing [`reward-grant-row.schema.json`](../../content/schemas/reward-grant-row.schema.json) (`itemId` + `quantity`) — same shape as E5.M3 `fixedGrants`.
|
||||
- **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 optional `itemGrants` with `minItems: 0` when present.
|
||||
- **At least one grant:** bundle must contain ≥1 item or skill XP row (`skillXpGrants` non-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](E7M2-prototype-backlog.md#e7m2-01--questrewardbundle-schema--quest-catalog-extension--ci)):**
|
||||
|
||||
- `content/schemas/quest-reward-bundle.schema.json`, `content/schemas/quest-skill-xp-grant.schema.json`.
|
||||
- Extend [`quest-def.schema.json`](../../content/schemas/quest-def.schema.json) with optional **`completionRewardBundle`** (`$ref` bundle schema).
|
||||
- Update [`prototype_quests.json`](../../content/quests/prototype_quests.json) — bundles per freeze table (below).
|
||||
- [`scripts/validate_content.py`](../../scripts/validate_content.py): bundle schema validation via extended quest validator registry; item id cross-refs; skill id cross-refs; **`mission_reward`** allowlist alignment (skill row must list `mission_reward` in **`allowedXpSourceKinds`**); E7M2 prototype bundle freeze gate.
|
||||
- Designer note in [E7_M2](../decomposition/modules/E7_M2_RewardAndUnlockRouter.md) (camelCase array keys + CI line) + [`content/README.md`](../../content/README.md) E7 Slice 2 paragraph + [CT.M1](../decomposition/modules/CT_M1_ContentValidationPipeline.md) quest-bundle PR gate paragraph.
|
||||
|
||||
**Out of scope (from Linear):**
|
||||
|
||||
- Server loader, delivery store, router, HTTP, Godot (NEO-125+).
|
||||
- **`sourceKind`** field in content rows; unlock/flag grant kinds.
|
||||
|
||||
## Acceptance criteria checklist
|
||||
|
||||
- [ ] PR gate validates quest JSON including optional completion bundles.
|
||||
- [ ] All four prototype quests include **`completionRewardBundle`** matching 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):
|
||||
|
||||
```json
|
||||
"completionRewardBundle": {
|
||||
"skillXpGrants": [{ "skillId": "salvage", "amount": 25 }]
|
||||
}
|
||||
```
|
||||
|
||||
Example (operator chain):
|
||||
|
||||
```json
|
||||
"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 in `PROTOTYPE_E7M1_QUEST_IDS` must have **`completionRewardBundle`** object.
|
||||
- **`_prototype_e7m2_completion_bundle_freeze_gate`:** bundle contents must exactly match `PROTOTYPE_E7M2_COMPLETION_BUNDLES` (sorted grant rows for stable compare).
|
||||
- **`_prototype_e7m2_completion_bundle_cross_ref_gate`:** each **`itemGrants[].itemId`** ∈ frozen item ids; each **`skillXpGrants[].skillId`** ∈ frozen skill ids; each target skill’s **`allowedXpSourceKinds`** must include **`mission_reward`** (load skill rows from validated skill catalog or reuse existing skill id map if available in `main()` 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 to **`itemGrants` / `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.
|
||||
Loading…
Reference in New Issue