neon-sprawl/docs/plans/NEO-133-implementation-plan.md

112 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# NEO-133 — E7M3-01: FactionDef catalog + quest gate/rep schemas + CI
**Linear:** [NEO-133](https://linear.app/neon-sprawl/issue/NEO-133)
**Branch:** `NEO-133-e7m3-factiondef-catalog-quest-gate-rep-schemas-ci`
**Backlog:** [E7M3-pre-production-backlog.md](E7M3-pre-production-backlog.md) — **E7M3-01**
**Module:** [E7_M3_FactionReputationLedger.md](../decomposition/modules/E7_M3_FactionReputationLedger.md)
**Pattern:** [NEO-124-implementation-plan.md](NEO-124-implementation-plan.md) (schemas + catalog + `validate_content.py` gates)
## Goal
Land faction and quest content contracts for Epic 7 Slice 3 so **CI** validates faction JSON, extended quest fields (`factionGateRules`, `completionRewardBundle.reputationGrants`), and the frozen five-quest prototype roster **before** NEO-134 server faction catalog load.
## Kickoff clarifications
Decisions are already frozen in [E7M3-pre-production-backlog.md — Kickoff decisions](E7M3-pre-production-backlog.md#kickoff-decisions) and [E7_M3 — Prototype Slice 3 freeze](../decomposition/modules/E7_M3_FactionReputationLedger.md#prototype-slice-3-freeze-e7m3-01). No blocking `AskQuestion` items.
| Topic | Decision | Evidence |
|-------|----------|----------|
| JSON naming | camelCase: `reputationGrants`, `factionGateRules`, `factionId`, `minStanding` | E7M2 bundle pattern; backlog kickoff table |
| Rep delivery | Via `completionRewardBundle.reputationGrants` (E7.M2 bundle idempotency path) | E7_M3 module doc; NEO-138 applies rep |
| Gate site | Quest **accept** only (Slice 3) | Backlog E7M3-06; zone gates deferred to E4.M1 |
| Faction roster | Exactly two ids: `prototype_faction_grid_operators`, `prototype_faction_rust_collective` | E7_M3 freeze table |
| Quest roster | Five quests: four E7.M1 onboarding + `prototype_quest_grid_contract` | E7_M3 freeze; expands `PROTOTYPE_E7M1_QUEST_IDS``PROTOTYPE_E7M3_QUEST_IDS` |
| Operator chain | Add Grid Operators **+15** `reputationGrants`; keep existing item + skill XP | E7_M3 freeze |
| Grid contract | Prereq operator chain; Grid Operators `minStanding` **15**; objective `inventory_has_item` `survey_drone_kit` ×1; completion Rust Collective **+10** | E7_M3 freeze |
| Server loader / stores / HTTP / Godot | **Out of scope** (NEO-134+) | Backlog E7M3-01 scope |
| `dotnet test` green | **Minimal roster parity** on existing E7.M1/E7.M2 catalog rules only (see below) | Content JSON change breaks four-quest server freeze without constant sync |
### Minimal server parity (CI / test green only)
Adding the fifth quest and updated `prototype_quests.json` will fail server startup tests until `PrototypeE7M1QuestCatalogRules.ExpectedQuestIds` includes five ids. This is **not** NEO-134 loader work — only constant + freeze-table sync mirroring NEO-124 → NEO-125:
- Extend `ExpectedQuestIds` + add `GridContractQuestId` / objective constants.
- Add `prototype_quest_grid_contract` to `PrototypeE7M2QuestCatalogRules.ExpectedCompletionBundles` with **empty** `itemGrants` / `skillXpGrants` (rep-only bundle; server `ParseCompletionRewardBundle` ignores `reputationGrants` until NEO-138).
- Operator chain JSON may include `reputationGrants`; server E7M2 freeze still compares item + skill XP only (unchanged normalized bundle).
No new faction loader, no `IFactionDefinitionRegistry`, no gate evaluation.
## Files to add
| Path | Purpose |
|------|---------|
| `content/schemas/faction-def.schema.json` | `FactionDef`: `id`, `displayName`, `minStanding`, `maxStanding`, `neutralStanding` |
| `content/schemas/faction-gate-rule.schema.json` | `factionId`, `minStanding` |
| `content/schemas/reputation-grant-row.schema.json` | `factionId`, `amount` (signed int) |
| `content/factions/prototype_factions.json` | Two frozen factions per E7_M3 freeze |
## Files to modify
| Path | Change |
|------|--------|
| `content/schemas/quest-def.schema.json` | Optional `factionGateRules` array (`$ref` faction-gate-rule) |
| `content/schemas/quest-reward-bundle.schema.json` | Optional `reputationGrants` array (`$ref` reputation-grant-row) |
| `content/quests/prototype_quests.json` | Operator chain `reputationGrants`; new `prototype_quest_grid_contract` |
| `scripts/validate_content.py` | Faction load/validate; `PROTOTYPE_E7M3_QUEST_IDS` (5); faction cross-ref gates; `PROTOTYPE_E7M3_COMPLETION_BUNDLES` freeze (item + skill + rep); rename/supersede E7M1 four-quest id gate |
| `content/README.md` | Document `content/factions/`, new schemas, five-quest roster |
| `docs/decomposition/modules/E7_M3_FactionReputationLedger.md` | Mark catalog landed when done |
| `server/.../PrototypeE7M1QuestCatalogRules.cs` | Five-quest roster + grid-contract constants |
| `server/.../PrototypeE7M2QuestCatalogRules.cs` | Grid-contract empty item/skill bundle in freeze |
## CI gates (`validate_content.py`)
1. **Faction catalog** — schema validate `content/factions/*_factions.json`; freeze exactly two faction ids + display names + standing band (-100…100, neutral 0).
2. **Quest roster**`PROTOTYPE_E7M3_QUEST_IDS` frozen set of five ids (includes all four E7M1 ids + `prototype_quest_grid_contract`).
3. **E7M1 gates retained** — acyclic prerequisites, objective cross-refs, chain terminal token (updated for fifth quest).
4. **Faction cross-refs** — every `factionGateRules[].factionId` and `reputationGrants[].factionId` must exist in faction catalog.
5. **E7M3 completion bundle freeze** — per-quest expected `itemGrants`, `skillXpGrants`, `reputationGrants` for all five quests (supersedes E7M2 freeze for operator chain + grid contract rep rows; first three gather/craft quests unchanged).
6. **Grid contract shape** — prereq operator chain; `factionGateRules` Grid Operators minStanding 15; objective `inventory_has_item` / `survey_drone_kit`.
## Tests
| Layer | What |
|-------|------|
| CI | `python3 scripts/validate_content.py` (existing PR gate) |
| Server | Existing quest catalog loader/registry tests — should pass after roster parity constants; no new `[Fact]` unless a regression needs explicit grid-contract coverage |
Manual QA: **none** (content-only; Godot capstone is NEO-143).
## Implementation order
1. Schemas (`faction-def`, `faction-gate-rule`, `reputation-grant-row`; extend quest-def + quest-reward-bundle).
2. `prototype_factions.json` + quest JSON edits.
3. `validate_content.py` gates + bundle freeze tables.
4. Minimal server roster/freeze constant sync.
5. `content/README.md` + E7_M3 module doc status line.
6. Run `python3 scripts/validate_content.py` and `dotnet test` on server project.
## Out of scope (follow-on tickets)
| Ticket | Work |
|--------|------|
| NEO-134 | Server faction catalog fail-fast load |
| NEO-135137 | Faction standing store, delta audit, gate evaluation on accept |
| NEO-138 | `RewardRouterOperations` reputation apply + idempotent delivery |
| NEO-139143 | HTTP read, telemetry, Godot HUD, capstone |
## Client counterpart
None for E7M3-01. Client faction HUD and capstone: **NEO-142** / **NEO-143**.
## Blocks
**NEO-134** (server faction catalog load) is blocked until this catalog + CI lands.
## Implementation reconciliation (shipped)
- **Schemas:** `faction-def`, `faction-gate-rule`, `reputation-grant-row`; extended `quest-def` + `quest-reward-bundle`.
- **Catalog:** `prototype_factions.json` (two factions); `prototype_quests.json` operator-chain rep + `prototype_quest_grid_contract`.
- **CI:** `PROTOTYPE_E7M3_*` gates in `validate_content.py` (roster, faction freeze, bundle freeze, cross-refs, grid-contract shape).
- **Server parity:** five-quest `ExpectedQuestIds`, grid-contract E7M2 bundle freeze, schema registry for new `$ref`s.
- **Docs:** `content/README.md` E7 Slice 3 paragraph; E7_M3 module CI line.