From 4f16c848db20a5e6b84b584fcd7d979ffda3ded0 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sat, 30 May 2026 19:33:27 -0400 Subject: [PATCH] NEO-100: Kickoff plan for encounter + reward catalog schemas and CI Document frozen prototype_combat_pocket spine, schema shapes, validate_content gates, and file lists before E5M3-01 implementation. --- docs/plans/NEO-100-implementation-plan.md | 166 ++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 docs/plans/NEO-100-implementation-plan.md diff --git a/docs/plans/NEO-100-implementation-plan.md b/docs/plans/NEO-100-implementation-plan.md new file mode 100644 index 0000000..7b72b6d --- /dev/null +++ b/docs/plans/NEO-100-implementation-plan.md @@ -0,0 +1,166 @@ +# NEO-100 — Implementation plan + +## Story reference + +| Field | Value | +|--------|--------| +| **Key** | NEO-100 | +| **Title** | E5M3-01: Prototype EncounterDef + RewardTable catalog + schemas + CI | +| **Linear** | https://linear.app/neon-sprawl/issue/NEO-100/e5m3-01-prototype-encounterdef-rewardtable-catalog-schemas-ci | +| **Module** | [E5.M3 — EncounterAndRewardTables](../decomposition/modules/E5_M3_EncounterAndRewardTables.md) · Epic 5 Slice 3 · backlog **E5M3-01** | +| **Branch** | `NEO-100-e5m3-encounter-reward-catalog-schemas-ci` | +| **Precursor** | [E5.M2](../decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md) **Ready** (three NPC instance ids) · [E3.M3](../decomposition/modules/E3_M3_ItemizationAndInventorySchema.md) **Ready** (frozen item catalog incl. **`contract_handoff_token`**) | +| **Pattern** | [NEO-87](https://linear.app/neon-sprawl/issue/NEO-87) — row JSON Schema + catalog envelope + `scripts/validate_content.py` gate | +| **Blocks** | [NEO-101](https://linear.app/neon-sprawl/issue/NEO-101) — server encounter + reward catalog load | + +## Kickoff clarifications + +**No clarifications needed.** Scope, frozen ids, loot grants, completion rule, and cross-ref targets are fully specified in [E5M3 kickoff decisions](E5M3-prototype-backlog.md#kickoff-decisions-decomposition-defaults), [E5.M3 freeze table](../decomposition/modules/E5_M3_EncounterAndRewardTables.md#prototype-slice-3-freeze-e5m3-01), and `content/README.md` (NEO-100 paragraph already present from E5M3 decomposition). Catalog envelope and CI constant naming follow NEO-87 / NEO-76 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 **one** frozen `EncounterDef` + **one** frozen `RewardTable` before server load (NEO-101+). + +**In scope (from Linear + [E5M3-01](E5M3-prototype-backlog.md#e5m3-01--prototype-encounterdef--rewardtable-catalog--schemas--ci)):** + +- `content/schemas/encounter-def.schema.json`, `content/schemas/reward-table.schema.json`, `content/schemas/reward-grant-row.schema.json`. +- `content/encounters/prototype_encounters.json` — one row **`prototype_combat_pocket`**. +- `content/reward-tables/prototype_reward_tables.json` — one row **`prototype_combat_pocket_clear`**. +- `scripts/validate_content.py` — schema validation, duplicate `id`, one-encounter / one-table allowlists, cross-ref **`itemId`** → frozen item catalog, **`requiredNpcInstanceIds`** → three E5.M2 NPC instance ids, **`rewardTableId`** → reward table ids. +- Designer note / CI rules line in [E5_M3](../decomposition/modules/E5_M3_EncounterAndRewardTables.md) + **CT.M1** PR gate paragraph. +- `documentation_and_implementation_alignment.md` E5.M3 row → note NEO-100 catalog in progress / landed. + +**Out of scope (from Linear):** + +- Server loader, runtime engine, HTTP, Godot (NEO-101+). +- **`IEncounterProgressStore`**, completion events, inventory grant (E5M3-04+). +- **Client counterpart:** none — server-only content + CI; player-visible work starts at E5M3-08+ / [NEO-110](https://linear.app/neon-sprawl/issue/NEO-110) / [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111). + +## Acceptance criteria checklist + +- [ ] PR gate validates encounter + reward-table JSON against schema. +- [ ] Exactly one prototype encounter id and one reward table id; duplicate `id` fails CI. +- [ ] Stable id list documented in module doc freeze box. + +## Technical approach + +1. **`reward-grant-row.schema.json`:** Mirror [`recipe-io-row.schema.json`](../../content/schemas/recipe-io-row.schema.json) — required **`itemId`** (pattern `^[a-z][a-z0-9_]*$`), **`quantity`** integer ≥ 1. + +2. **`reward-table.schema.json`:** Draft 2020-12 object; `additionalProperties: false`. Required: **`id`**, **`displayName`**, **`fixedGrants`** (non-empty array of `$ref` reward-grant-row). `id` pattern same as other catalogs. + +3. **`encounter-def.schema.json`:** Required: **`id`**, **`displayName`**, **`completionCriteria`**, **`requiredNpcInstanceIds`**, **`rewardTableId`**. **`completionCriteria`:** object with required **`kind`** enum **`defeat_all_targets`** only (prototype). **`requiredNpcInstanceIds`:** array of 3 strings (pattern), **`minItems`/`maxItems`: 3**. **`rewardTableId`:** string pattern. + +4. **Catalog files:** + + **`content/reward-tables/prototype_reward_tables.json`** + + ```json + { + "schemaVersion": 1, + "rewardTables": [ + { + "id": "prototype_combat_pocket_clear", + "displayName": "Prototype Combat Pocket Clear", + "fixedGrants": [ + { "itemId": "scrap_metal_bulk", "quantity": 10 }, + { "itemId": "contract_handoff_token", "quantity": 1 } + ] + } + ] + } + ``` + + **`content/encounters/prototype_encounters.json`** + + ```json + { + "schemaVersion": 1, + "encounters": [ + { + "id": "prototype_combat_pocket", + "displayName": "Prototype Combat Pocket", + "completionCriteria": { "kind": "defeat_all_targets" }, + "requiredNpcInstanceIds": [ + "prototype_npc_melee", + "prototype_npc_ranged", + "prototype_npc_elite" + ], + "rewardTableId": "prototype_combat_pocket_clear" + } + ] + } + ``` + + **`requiredNpcInstanceIds`** order matches [E5.M3 freeze table](../decomposition/modules/E5_M3_EncounterAndRewardTables.md#prototype-slice-3-freeze-e5m3-01) (melee → ranged → elite); CI enforces **set equality** with `PROTOTYPE_E5M2_NPC_INSTANCE_IDS`, not order-sensitive beyond schema min/max 3. + +5. **`validate_content.py` constants (keep in sync with E5.M3 module doc + future NEO-101 loader):** + + | Constant | Value | + |----------|--------| + | `PROTOTYPE_E5M3_ENCOUNTER_IDS` | `{ "prototype_combat_pocket" }` | + | `PROTOTYPE_E5M3_REWARD_TABLE_IDS` | `{ "prototype_combat_pocket_clear" }` | + | `PROTOTYPE_E5M2_NPC_INSTANCE_IDS` | `{ "prototype_npc_melee", "prototype_npc_ranged", "prototype_npc_elite" }` | + +6. **`validate_content.py` validators:** + + - Add schema paths + dirs: `ENCOUNTER_DEF_SCHEMA`, `REWARD_TABLE_SCHEMA`, `REWARD_GRANT_ROW_SCHEMA`, `ENCOUNTERS_DIR`, `REWARD_TABLES_DIR`. + - **`_validate_reward_table_catalogs`:** envelope `schemaVersion: 1`, top-level **`rewardTables`** array; validate each row; track `seen_ids`; reject duplicates across files. + - **`_prototype_e5m3_reward_table_gate`:** ids must equal `PROTOTYPE_E5M3_REWARD_TABLE_IDS`. + - **`_prototype_e5m3_reward_grant_item_gate`:** each `fixedGrants[].itemId` ∈ `PROTOTYPE_SLICE1_ITEM_IDS`; quantities ≥ 1 (schema) — spot-check **`scrap_metal_bulk` ×10** and **`contract_handoff_token` ×1** in gate or integration-style assert in docstring test notes. + - **`_validate_encounter_catalogs`:** envelope `schemaVersion: 1`, top-level **`encounters`** array; validate rows; duplicate `id` rejection. + - **`_prototype_e5m3_encounter_gate`:** ids must equal `PROTOTYPE_E5M3_ENCOUNTER_IDS`. + - **`_prototype_e5m3_encounter_cross_ref_gate`:** each row's **`requiredNpcInstanceIds`** set must equal `PROTOTYPE_E5M2_NPC_INSTANCE_IDS`; **`rewardTableId`** must exist in reward-table `seen_ids`. + - Run **reward tables before encounters** in `main()` (encounters reference reward table ids). + - Extend module docstring, startup schema file checks, success summary with encounter + reward-table file/id counts. + +7. **Docs:** + + - `E5_M3_EncounterAndRewardTables.md` — add CI enforcement line under freeze table (ids + cross-ref rules); link to this plan when complete. + - `CT_M1_ContentValidationPipeline.md` — encounter + reward-table PR gate paragraph. + - `documentation_and_implementation_alignment.md` — E5.M3 row notes NEO-100 catalog when complete. + - `content/README.md` — table rows for `encounters/` and `reward-tables/` already present; verify paragraph matches shipped constants after implementation. + - `E5M3-prototype-backlog.md` — E5M3-01 checkboxes when implementation completes. + +8. **No server/C#/client changes** in this story. + +## Files to add + +| Path | Purpose | +|------|---------| +| `content/schemas/reward-grant-row.schema.json` | JSON Schema for a single fixed-grant row on a `RewardTable`. | +| `content/schemas/reward-table.schema.json` | JSON Schema for a single `RewardTable` row (uses reward-grant-row). | +| `content/schemas/encounter-def.schema.json` | JSON Schema for a single `EncounterDef` row. | +| `content/reward-tables/prototype_reward_tables.json` | Prototype one-table catalog (`schemaVersion` + `rewardTables` array). | +| `content/encounters/prototype_encounters.json` | Prototype one-encounter catalog (`schemaVersion` + `encounters` array). | +| `docs/plans/NEO-100-implementation-plan.md` | This plan. | + +## Files to modify + +| Path | Rationale | +|------|-----------| +| `scripts/validate_content.py` | Load/validate encounter + reward-table catalogs; duplicate `id`; E5M3 one-id gates; NPC instance + item + rewardTableId cross-refs; success summary. | +| `docs/decomposition/modules/E5_M3_EncounterAndRewardTables.md` | CI enforcement note under freeze table; link to plan when complete. | +| `docs/decomposition/modules/CT_M1_ContentValidationPipeline.md` | PR gate paragraph for encounter + reward-table catalog validation. | +| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E5.M3 / NEO-100 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 encounter or reward-table `id` → non-zero exit; (2) remove frozen id → gate error; (3) unknown `itemId` in `fixedGrants` → cross-ref error; (4) wrong NPC instance id set → cross-ref error; (5) broken `rewardTableId` on encounter → cross-ref error; (6) empty `displayName` or invalid `completionCriteria.kind` → schema error. | +| **Manual happy path** | `pip install -r scripts/requirements-content.txt && python3 scripts/validate_content.py` — reports encounter + reward-table catalogs + existing catalogs OK. | + +No dedicated pytest module in-repo today (same as NEO-87 / NEO-76). + +## Open questions / risks + +| Question / risk | Agent recommendation | Status | +|-----------------|----------------------|--------| +| NPC instance ids are server-registry constants, not content rows | **`PROTOTYPE_E5M2_NPC_INSTANCE_IDS` frozenset in CI** — mirror server `PrototypeNpcRegistry`; document sync obligation in module doc (same pattern as behavior ids without a content file). | **adopted** | +| `content/README.md` pre-documents NEO-100 before catalogs land | **Keep paragraph; verify constants match after ship** — decomposition already merged. | **adopted** | +| Cursor markdown `#anchor` deep links | Unrelated to this story; GitHub + stable ids remain source of truth. | **deferred** |