# 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-135–137 | Faction standing store, delta audit, gate evaluation on accept | | NEO-138 | `RewardRouterOperations` reputation apply + idempotent delivery | | NEO-139–143 | 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.