NEO-133: kickoff E7M3-01 — faction catalog schemas + CI plan

Land E7.M3 Slice 3 decomposition docs and NEO-133 implementation plan
before content/schema work; blocks NEO-134 server faction load.
pull/172/head
VinPropane 2026-06-14 14:51:22 -04:00
parent 33cfc62c66
commit 718346163f
8 changed files with 548 additions and 15 deletions

View File

@ -34,7 +34,7 @@ Provide a quest state machine and reward routing that ties gathering, crafting,
- Responsibility: Track faction standing and gate faction-specific contracts/content. - Responsibility: Track faction standing and gate faction-specific contracts/content.
- Key contracts: `FactionStanding`, `ReputationDelta`, `FactionGateRule` - Key contracts: `FactionStanding`, `ReputationDelta`, `FactionGateRule`
- Dependencies: E7.M1 - Dependencies: E7.M1, E7.M2
- Stage target: Pre-production - Stage target: Pre-production
### E7.M4 - ContractMissionGenerator ### E7.M4 - ContractMissionGenerator
@ -68,14 +68,19 @@ Provide a quest state machine and reward routing that ties gathering, crafting,
**Linear backlog:** [E7M2-prototype-backlog.md](../../plans/E7M2-prototype-backlog.md) — [NEO-124](https://linear.app/neon-sprawl/issue/NEO-124) → [NEO-132](https://linear.app/neon-sprawl/issue/NEO-132); client capstone **E7M2-09** [NEO-132](https://linear.app/neon-sprawl/issue/NEO-132). Verify quest completion rewards in **Godot**, not Bruno-only. Encounter loot ([E5.M3](../modules/E5_M3_EncounterAndRewardTables.md)) unchanged in Slice 2. **Linear backlog:** [E7M2-prototype-backlog.md](../../plans/E7M2-prototype-backlog.md) — [NEO-124](https://linear.app/neon-sprawl/issue/NEO-124) → [NEO-132](https://linear.app/neon-sprawl/issue/NEO-132); client capstone **E7M2-09** [NEO-132](https://linear.app/neon-sprawl/issue/NEO-132). Verify quest completion rewards in **Godot**, not Bruno-only. Encounter loot ([E5.M3](../modules/E5_M3_EncounterAndRewardTables.md)) unchanged in Slice 2.
<a id="slice-3---faction-ledger-pre-production"></a>
### Slice 3 - Faction ledger (pre-production) ### Slice 3 - Faction ledger (pre-production)
- Scope: E7.M3 gating at least one faction line and mission set. - Scope: E7.M3 gating at least one faction line and mission set.
- Dependencies: E7.M1 - Dependencies: E7.M1, E7.M2
- Acceptance criteria: - Acceptance criteria:
- Reputation deltas auditable; gates fail closed on tamper. - Reputation deltas auditable; gates fail closed on tamper.
- Player earns Grid Operators rep from operator chain; faction-gated quest accept denied below threshold.
- Telemetry hooks: `reputation_delta`, `faction_gate_blocked`. - Telemetry hooks: `reputation_delta`, `faction_gate_blocked`.
**Linear backlog:** [E7M3-pre-production-backlog.md](../../plans/E7M3-pre-production-backlog.md) — [NEO-133](https://linear.app/neon-sprawl/issue/NEO-133) → [NEO-143](https://linear.app/neon-sprawl/issue/NEO-143); client capstone **E7M3-11** [NEO-143](https://linear.app/neon-sprawl/issue/NEO-143). Verify faction standing + gate deny in **Godot**, not Bruno-only.
### Slice 4 - Contract generator (pre-production) ### Slice 4 - Contract generator (pre-production)
- Scope: E7.M4 emitting repeatable contracts from templates by zone difficulty. - Scope: E7.M4 emitting repeatable contracts from templates by zone difficulty.
@ -97,4 +102,5 @@ Provide a quest state machine and reward routing that ties gathering, crafting,
- Prototype quest counts and integrated chain meet master plan minimums **in the Godot client** (Slice 1 capstone [NEO-123](../../manual-qa/NEO-123.md)), not Bruno-only. - Prototype quest counts and integrated chain meet master plan minimums **in the Godot client** (Slice 1 capstone [NEO-123](../../manual-qa/NEO-123.md)), not Bruno-only.
- Slice 2 quest **completion** rewards (items + skill XP) visible in Godot with idempotent delivery (capstone [NEO-132](../../manual-qa/NEO-132.md)), not Bruno-only. - Slice 2 quest **completion** rewards (items + skill XP) visible in Godot with idempotent delivery (capstone [NEO-132](../../manual-qa/NEO-132.md)), not Bruno-only.
- Pre-production adds faction + contracts with validation. - Slice 3 faction rep + gated quest line visible in Godot (capstone [NEO-143](../../manual-qa/NEO-143.md)), not Bruno-only.
- Pre-production adds contracts (E7.M4) with validation beyond Slice 3.

View File

@ -7,38 +7,80 @@
| **Module ID** | E7.M3 | | **Module ID** | E7.M3 |
| **Epic** | [Epic 7 — Quest / Faction](../epics/epic_07_quest_faction.md) | | **Epic** | [Epic 7 — Quest / Faction](../epics/epic_07_quest_faction.md) |
| **Stage target** | Pre-production | | **Stage target** | Pre-production |
| **Status** | Planned (see [dependency register](module_dependency_register.md)) | | **Status** | In Progress — Slice 3 backlog [E7M3-pre-production-backlog.md](../../plans/E7M3-pre-production-backlog.md): **E7M3-01** [NEO-133](https://linear.app/neon-sprawl/issue/NEO-133) → **E7M3-11** [NEO-143](https://linear.app/neon-sprawl/issue/NEO-143) (decomposed). Upstream: E7.M1 **Ready**, E7.M2 **Ready**. |
| **Linear** | Label **`E7.M3`** · [E7M3-pre-production-backlog.md](../../plans/E7M3-pre-production-backlog.md) |
## Purpose ## Purpose
Faction standing, reputation deltas, and `FactionGateRule` evaluation for content and contracts. Character-wide **faction standing**, auditable **reputation deltas**, and **`FactionGateRule`** evaluation for quest accept (and future zone/travel gates via [E4.M1](E4_M1_ZoneGraphAndTravelRules.md)). Rep grants flow through [E7.M2](E7_M2_RewardAndUnlockRouter.md) **`completionRewardBundle`** idempotency.
## Responsibilities ## Responsibilities
- Persist auditable `FactionStanding`; apply `ReputationDelta`; fail closed on tamper for gates. - Persist **`FactionStanding`** per player per faction; apply **`ReputationDelta`** with clamp and audit log.
- Evaluate **`FactionGateRule`** on quest accept — fail closed on unknown faction or tampered rules (startup validation).
- Emit telemetry hook sites: **`reputation_delta`**, **`faction_gate_blocked`**.
## Key contracts ## Key contracts
| Contract | Role | | Contract | Role |
|----------|------| |----------|------|
| `FactionStanding` | Current rep per faction. | | `FactionDef` | Faction id, display name, min/max/neutral standing. |
| `ReputationDelta` | Change events with source attribution. | | `FactionStanding` | Current rep per faction for a player. |
| `FactionGateRule` | Unlock conditions for content. | | `ReputationDelta` | Signed change with source attribution (quest id, etc.). |
| `FactionGateRule` | Minimum standing required (e.g. on `QuestDef`). |
| `reputationGrants` | Rows on `QuestRewardBundle``factionId` + `amount`. |
## Module dependencies ## Module dependencies
- **E7.M1** — QuestStateMachine. - **E7.M1** — QuestStateMachine (`TryAccept` gate hook site).
- **E7.M2** — RewardAndUnlockRouter (bundle apply + idempotent delivery store).
## Dependents (by design) ## Dependents (by design)
- **E7.M4** — ContractMissionGenerator. - **E7.M4** — ContractMissionGenerator.
- **E8.M2** — GuildCorpProgressionState. - **E8.M2** — GuildCorpProgressionState.
- **E4.M1** — ZoneGraphAndTravelRules (`TravelRule` faction gates).
## Related implementation slices ## Related implementation slices
Epic 7 **Slice 3**`reputation_delta`, `faction_gate_blocked`. Epic 7 **Slice 3** — at least one faction quest line with rep grant + gated accept; auditable deltas; telemetry **`reputation_delta`**, **`faction_gate_blocked`**. Backlog: [E7M3-pre-production-backlog.md](../../plans/E7M3-pre-production-backlog.md).
## Linear backlog (decomposed)
| Slug | Linear |
|------|--------|
| E7M3-01 | [NEO-133](https://linear.app/neon-sprawl/issue/NEO-133) |
| E7M3-02 | [NEO-134](https://linear.app/neon-sprawl/issue/NEO-134) |
| E7M3-03 | [NEO-135](https://linear.app/neon-sprawl/issue/NEO-135) |
| E7M3-04 | [NEO-136](https://linear.app/neon-sprawl/issue/NEO-136) |
| E7M3-05 | [NEO-137](https://linear.app/neon-sprawl/issue/NEO-137) |
| E7M3-06 | [NEO-138](https://linear.app/neon-sprawl/issue/NEO-138) |
| E7M3-07 | [NEO-139](https://linear.app/neon-sprawl/issue/NEO-139) |
| E7M3-08 | [NEO-140](https://linear.app/neon-sprawl/issue/NEO-140) |
| E7M3-09 | [NEO-141](https://linear.app/neon-sprawl/issue/NEO-141) |
| E7M3-10 | [NEO-142](https://linear.app/neon-sprawl/issue/NEO-142) |
| E7M3-11 | [NEO-143](https://linear.app/neon-sprawl/issue/NEO-143) |
## Prototype Slice 3 freeze (E7M3-01)
### Faction catalog
| `FactionDef.id` | `displayName` | Standing band |
|-----------------|---------------|---------------|
| **`prototype_faction_grid_operators`** | Grid Operators | -100…100 (neutral 0) |
| **`prototype_faction_rust_collective`** | Rust Collective | -100…100 (neutral 0) |
### Quest content changes
| `QuestDef.id` | Rep / gate |
|---------------|------------|
| **`prototype_quest_operator_chain`** | **`completionRewardBundle.reputationGrants`**: **`prototype_faction_grid_operators`** **+15** (existing item + skill XP unchanged) |
| **`prototype_quest_grid_contract`** *(new)* | **`prerequisiteQuestIds`**: operator chain; **`factionGateRules`**: Grid Operators **`minStanding` 15**; objective **`inventory_has_item`** **`survey_drone_kit` ×1**; completion rep **Rust Collective +10** |
**Roster:** five quests total (four E7.M1 onboarding + one faction line). E7.M1 four-quest freeze expands under E7M3 CI gates.
## Source anchors ## Source anchors
- Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 7. - Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 7, faction reputation.
- Game design: [skills.md — Rep / story](../../../docs/game-design/skills.md) (character-wide faction rep).
- [Module dependency register](module_dependency_register.md) - [Module dependency register](module_dependency_register.md)

File diff suppressed because one or more lines are too long

View File

@ -113,7 +113,9 @@ Gameplay **content and curves** default to **boot load** with optional **dev-onl
| E7.M2 | RewardAndUnlockRouter | E2.M2, E3.M3, E7.M1 | QuestRewardBundle, UnlockGrant, RewardDeliveryEvent | Prototype | Ready | | E7.M2 | RewardAndUnlockRouter | E2.M2, E3.M3, E7.M1 | QuestRewardBundle, UnlockGrant, RewardDeliveryEvent | Prototype | Ready |
**E7.M2 note:** Epic 7 **Slice 2** backlog in Linear ([Epic 7 — Questing, Narrative, and Faction Reputation](https://linear.app/neon-sprawl/project/epic-7-questing-narrative-and-faction-reputation-a9416783ceee)): [NEO-124](https://linear.app/neon-sprawl/issue/NEO-124) → [NEO-132](https://linear.app/neon-sprawl/issue/NEO-132) **landed**; label **`E7.M2`**. See [E7M2-prototype-backlog.md](../../plans/E7M2-prototype-backlog.md), [E7_M2_RewardAndUnlockRouter.md](E7_M2_RewardAndUnlockRouter.md). Upstream: E7.M1 **Ready**, E2.M2 grant stack ([NEO-43](https://linear.app/neon-sprawl/issue/NEO-43)), E3.M3 inventory **Ready**. **E7M2-09 / NEO-132** client capstone landed — [`NEO-132` manual QA](../../manual-qa/NEO-132.md), [client README — End-to-end quest reward loop (NEO-132)](../../../client/README.md#end-to-end-quest-reward-loop-neo-132), plan [NEO-132](../../plans/NEO-132-implementation-plan.md). Epic 7 Slice 2 client capstone complete. **E7.M2 note:** Epic 7 **Slice 2** backlog in Linear ([Epic 7 — Questing, Narrative, and Faction Reputation](https://linear.app/neon-sprawl/project/epic-7-questing-narrative-and-faction-reputation-a9416783ceee)): [NEO-124](https://linear.app/neon-sprawl/issue/NEO-124) → [NEO-132](https://linear.app/neon-sprawl/issue/NEO-132) **landed**; label **`E7.M2`**. See [E7M2-prototype-backlog.md](../../plans/E7M2-prototype-backlog.md), [E7_M2_RewardAndUnlockRouter.md](E7_M2_RewardAndUnlockRouter.md). Upstream: E7.M1 **Ready**, E2.M2 grant stack ([NEO-43](https://linear.app/neon-sprawl/issue/NEO-43)), E3.M3 inventory **Ready**. **E7M2-09 / NEO-132** client capstone landed — [`NEO-132` manual QA](../../manual-qa/NEO-132.md), [client README — End-to-end quest reward loop (NEO-132)](../../../client/README.md#end-to-end-quest-reward-loop-neo-132), plan [NEO-132](../../plans/NEO-132-implementation-plan.md). Epic 7 Slice 2 client capstone complete.
| E7.M3 | FactionReputationLedger | E7.M1 | FactionStanding, ReputationDelta, FactionGateRule | Pre-production | Planned | | E7.M3 | FactionReputationLedger | E7.M1, E7.M2 | FactionStanding, ReputationDelta, FactionGateRule | Pre-production | In Progress |
**E7.M3 note:** Epic 7 **Slice 3** backlog in Linear ([Epic 7 — Questing, Narrative, and Faction Reputation](https://linear.app/neon-sprawl/project/epic-7-questing-narrative-and-faction-reputation-a9416783ceee)): [NEO-133](https://linear.app/neon-sprawl/issue/NEO-133) → [NEO-143](https://linear.app/neon-sprawl/issue/NEO-143); label **`E7.M3`**. See [E7M3-pre-production-backlog.md](../../plans/E7M3-pre-production-backlog.md), [E7_M3_FactionReputationLedger.md](E7_M3_FactionReputationLedger.md). Upstream: E7.M1 **Ready**, E7.M2 **Ready**. Prototype spine: two frozen factions; operator chain grants **+15 Grid Operators**; **`prototype_quest_grid_contract`** gated at **minStanding 15**; rep grants via **`completionRewardBundle.reputationGrants`**; accept deny **`faction_gate_blocked`**. Client capstone **E7M3-11** [NEO-143](https://linear.app/neon-sprawl/issue/NEO-143).
| E7.M4 | ContractMissionGenerator | E4.M1, E5.M3, E7.M3 | ContractTemplate, ContractSeed, ContractOutcome | Pre-production | Planned | | E7.M4 | ContractMissionGenerator | E4.M1, E5.M3, E7.M3 | ContractTemplate, ContractSeed, ContractOutcome | Pre-production | Planned |
### Epic 8 — Social / Guild ### Epic 8 — Social / Guild

View File

@ -52,7 +52,7 @@ Working backlog for **Epic 7 — Slice 1** ([quest core and persistence](../deco
| 11 | **E7M1-11** | client | E7M1-08, E7M1-09 | | 11 | **E7M1-11** | client | E7M1-08, E7M1-09 |
| 12 | **E7M1-12** | client | E7M1-11 | | 12 | **E7M1-12** | client | E7M1-11 |
**Downstream (separate modules):** [E7.M2](../decomposition/modules/E7_M2_RewardAndUnlockRouter.md) Slice 2 backlog [E7M2-prototype-backlog.md](E7M2-prototype-backlog.md) ([NEO-124](https://linear.app/neon-sprawl/issue/NEO-124)[NEO-132](https://linear.app/neon-sprawl/issue/NEO-132)); [E7.M3](../decomposition/modules/E7_M3_FactionReputationLedger.md) faction ledger (pre-production); [E8.M1](../decomposition/modules/E8_M1_PartyAndMatchAssembly.md) party-credit quests. **Downstream (separate modules):** [E7.M3](../decomposition/modules/E7_M3_FactionReputationLedger.md) Slice 3 backlog [E7M3-pre-production-backlog.md](E7M3-pre-production-backlog.md) ([NEO-133](https://linear.app/neon-sprawl/issue/NEO-133)[NEO-143](https://linear.app/neon-sprawl/issue/NEO-143)); [E8.M1](../decomposition/modules/E8_M1_PartyAndMatchAssembly.md) party-credit quests.
--- ---

View File

@ -46,7 +46,7 @@ Working backlog for **Epic 7 — Slice 2** ([reward and unlock routing](../decom
| 8 | **E7M2-08** | client | E7M2-06 | | 8 | **E7M2-08** | client | E7M2-06 |
| 9 | **E7M2-09** | client | E7M2-08 | | 9 | **E7M2-09** | client | E7M2-08 |
**Downstream (separate modules):** [E6.M4](../decomposition/modules/E6_M4_RewardParityEnforcer.md) lint against bundles; [E7.M3](../decomposition/modules/E7_M3_FactionReputationLedger.md) reputation grants (pre-production); E5.M3 **`EncounterCompleteEvent`** full router consumer (optional follow-on). **Downstream (separate modules):** [E7.M3](../decomposition/modules/E7_M3_FactionReputationLedger.md) Slice 3 backlog [E7M3-pre-production-backlog.md](E7M3-pre-production-backlog.md) ([NEO-133](https://linear.app/neon-sprawl/issue/NEO-133)[NEO-143](https://linear.app/neon-sprawl/issue/NEO-143)); [E8.M1](../decomposition/modules/E8_M1_PartyAndMatchAssembly.md) party-credit quests.
--- ---

View File

@ -0,0 +1,379 @@
# E7.M3 — Pre-production story backlog (FactionReputationLedger)
Working backlog for **Epic 7 — Slice 3** ([faction ledger](../decomposition/epics/epic_07_quest_faction.md#slice-3---faction-ledger-pre-production)). Decomposition and contracts: [E7.M3 — FactionReputationLedger](../decomposition/modules/E7_M3_FactionReputationLedger.md).
**Full-stack policy:** Epics deliver **playable game features**. Every **player-visible** story has a **`client`** Linear issue created in **this same decomposition pass** — not an undocumented follow-up. Reference: [E7M2 (paired server+client)](../plans/E7M2-prototype-backlog.md), [E7M1 (quest HUD + capstone)](../plans/E7M1-prototype-backlog.md).
**Labels (Linear):** every issue **`E7.M3`**; add **`server`** or **`client`** + **`Story`** as listed.
**Precursor (do not re-scope):** [E7.M1](../decomposition/modules/E7_M1_QuestStateMachine.md) **Ready** — quest accept/advance/complete ([NEO-112](https://linear.app/neon-sprawl/issue/NEO-112)[NEO-123](https://linear.app/neon-sprawl/issue/NEO-123)). [E7.M2](../decomposition/modules/E7_M2_RewardAndUnlockRouter.md) **Ready** — idempotent **`completionRewardBundle`** delivery ([NEO-124](https://linear.app/neon-sprawl/issue/NEO-124)[NEO-132](https://linear.app/neon-sprawl/issue/NEO-132)). Slice 3 **extends** E7.M2 bundles with **`reputationGrants`**; does not migrate encounter loot or duplicate quest completion grants.
**Upstream (must be landed):** E7.M1 quest state + accept gates; E7.M2 **`RewardRouterOperations`** + **`IRewardDeliveryStore`**; [quest_scope_and_party.md](../decomposition/modules/quest_scope_and_party.md) solo idempotency.
**Prototype faction spine (frozen in E7M3-01):** **two** frozen `FactionDef` ids; **one** reputation grant on **`prototype_quest_operator_chain`**; **one** faction-gated follow-on quest **`prototype_quest_grid_contract`**.
**Linear issues (created):** attach `docs/plans/NEO-*-implementation-plan.md` on the story branch when work starts.
| Slug | Layer | Linear |
|------|-------|--------|
| E7M3-01 | server | [NEO-133](https://linear.app/neon-sprawl/issue/NEO-133) |
| E7M3-02 | server | [NEO-134](https://linear.app/neon-sprawl/issue/NEO-134) |
| E7M3-03 | server | [NEO-135](https://linear.app/neon-sprawl/issue/NEO-135) |
| E7M3-04 | server | [NEO-136](https://linear.app/neon-sprawl/issue/NEO-136) |
| E7M3-05 | server | [NEO-137](https://linear.app/neon-sprawl/issue/NEO-137) |
| E7M3-06 | server | [NEO-138](https://linear.app/neon-sprawl/issue/NEO-138) |
| E7M3-07 | server | [NEO-139](https://linear.app/neon-sprawl/issue/NEO-139) |
| E7M3-08 | server | [NEO-140](https://linear.app/neon-sprawl/issue/NEO-140) |
| E7M3-09 | server | [NEO-141](https://linear.app/neon-sprawl/issue/NEO-141) |
| E7M3-10 | client | [NEO-142](https://linear.app/neon-sprawl/issue/NEO-142) |
| E7M3-11 | client | [NEO-143](https://linear.app/neon-sprawl/issue/NEO-143) |
**Dependency graph in Linear:** E7M3-02 blocked by E7M3-01. E7M3-03 blocked by E7M3-02. E7M3-04 blocked by E7M3-03. E7M3-05 blocked by E7M3-02 and E7M3-03. E7M3-06 blocked by E7M3-04. E7M3-07 blocked by E7M3-03. E7M3-08 blocked by E7M3-06 and E7M3-07. E7M3-09 blocked by E7M3-04 and E7M3-05. E7M3-10 blocked by E7M3-07 and E7M3-08. E7M3-11 blocked by E7M3-10.
**Board order:** estimates **111** matching slug order (E7M3-01 = 1 … E7M3-11 = 11). On the Epic 7 project board, sort by **Estimate** (ascending).
---
## Story order (recommended)
| Order | Slug | Layer | Depends on |
|-------|------|-------|------------|
| 1 | **E7M3-01** | server | E7.M1/E7.M2 content frozen |
| 2 | **E7M3-02** | server | E7M3-01 |
| 3 | **E7M3-03** | server | E7M3-02 |
| 4 | **E7M3-04** | server | E7M3-03 |
| 5 | **E7M3-05** | server | E7M3-02, E7M3-03 |
| 6 | **E7M3-06** | server | E7M3-04 |
| 7 | **E7M3-07** | server | E7M3-03 |
| 8 | **E7M3-08** | server | E7M3-06, E7M3-07 |
| 9 | **E7M3-09** | server | E7M3-04, E7M3-05 |
| 10 | **E7M3-10** | client | E7M3-07, E7M3-08 |
| 11 | **E7M3-11** | client | E7M3-10 |
**Downstream (separate modules):** [E7.M4](../decomposition/modules/E7_M4_ContractMissionGenerator.md) contract generator; [E4.M1](../decomposition/modules/E4_M1_ZoneGraphAndTravelRules.md) **`TravelRule`** faction gates; [E8.M2](../decomposition/modules/E8_M2_GuildCorpProgressionState.md) guild/corp rep coupling.
---
## Kickoff decisions (decomposition defaults)
| Topic | Decision | Rationale |
|-------|----------|-----------|
| Faction count | **Two** frozen ids (corp + street) | Readable contrast; Slice 3 AC needs **one** gated line — corp line is flagship |
| Standing range | **-100…100**, neutral **0** | Standard RPG band; clamp on apply |
| Rep grants | **`reputationGrants`** rows on **`completionRewardBundle`** | Reuse E7.M2 idempotent delivery path; audit via delivery store |
| Gate evaluation | **`TryAccept`** only (quest accept) | E4.M1 travel gates deferred; epic Slice 3 is quest/mission gating |
| Gate deny | **`faction_gate_blocked`** reason code | Epic telemetry vocabulary |
| Fail-closed | Unknown **`factionId`** in grant or gate → deny | Tamper / bad content cannot silently pass |
| Delta audit | Append-only **`ReputationDelta`** log per apply | Slice 3 AC: auditable deltas |
| Idempotency | Rep grants ride **`{playerId}:quest_complete:{questId}`** delivery key | Same as E7.M2; replay does not double-apply standing |
| Party mode | **Solo only** | E8.M1 not landed |
| Quest roster | **Five** quests (four E7.M1 + one faction line) | Operator chain grants rep; gated quest proves accept gate |
| HUD fidelity | Prototype labels / debug panels | NEO-122 / NEO-131 precedent |
### Prototype faction freeze (E7M3-01)
| `FactionDef.id` | `displayName` | `minStanding` | `maxStanding` | `neutralStanding` |
|-----------------|---------------|---------------|---------------|---------------------|
| **`prototype_faction_grid_operators`** | Grid Operators | -100 | 100 | 0 |
| **`prototype_faction_rust_collective`** | Rust Collective | -100 | 100 | 0 |
### Prototype reputation + gate freeze (E7M3-01)
| Quest id | Change | Detail |
|----------|--------|--------|
| **`prototype_quest_operator_chain`** | Extend **`completionRewardBundle`** | Add **`reputationGrants`**: **`prototype_faction_grid_operators`** **+15** (item + skill XP rows unchanged) |
| **`prototype_quest_grid_contract`** | **New** quest | **`prerequisiteQuestIds`**: **`prototype_quest_operator_chain`**; **`factionGateRules`**: **`prototype_faction_grid_operators`** **`minStanding` 15**; 1 step **`inventory_has_item`** **`survey_drone_kit` ×1**; completion bundle: **`prototype_faction_rust_collective`** **+10** rep only |
Accepting **`prototype_quest_grid_contract`** before operator-chain completion or before **15** Grid Operators standing returns **`faction_gate_blocked`** or **`prerequisite_incomplete`** respectively.
---
### E7M3-01 — FactionDef catalog + quest gate/rep schemas + CI
**Goal:** Lock faction content shape, extend quest schemas for gates + rep grants, and CI validation before server load.
**In scope**
- `content/schemas/faction-def.schema.json`, `content/schemas/faction-gate-rule.schema.json`, `content/schemas/reputation-grant-row.schema.json`.
- Extend `content/schemas/quest-def.schema.json` with optional **`factionGateRules`** array.
- Extend `content/schemas/quest-reward-bundle.schema.json` with optional **`reputationGrants`** array.
- `content/factions/prototype_factions.json` — two frozen faction rows.
- Update `content/quests/prototype_quests.json` — operator-chain rep grant + fifth quest per freeze table.
- `scripts/validate_content.py`: faction schema validation, quest gate cross-refs, rep grant cross-refs, five-quest allowlist (four E7.M1 + **`prototype_quest_grid_contract`**), E7M2 bundle freeze updated for operator-chain rep row.
- Designer note in [E7_M3](../decomposition/modules/E7_M3_FactionReputationLedger.md) + `content/README.md`.
**Out of scope**
- Server loader, stores, operations, HTTP, Godot.
**Acceptance criteria**
- [ ] PR gate validates faction JSON and extended quest JSON.
- [ ] Exactly two prototype faction ids; fifth quest id present with gate + objective cross-refs.
- [ ] Invalid faction refs in gates or grants fail CI.
**Client counterpart:** none (infrastructure-only).
---
### E7M3-02 — Server faction catalog load (fail-fast)
**Goal:** Disk → host: startup load of `content/factions/*_factions.json` with CI-parity validation.
**In scope**
- Loader + registry types under `server/NeonSprawl.Server/Game/Factions/` (or `Game/Reputation/`).
- **`IFactionDefinitionRegistry`** + DI registration.
- Cross-check quest **`factionGateRules`** / bundle **`reputationGrants`** faction ids at startup (after quest catalog load).
- Unit tests (AAA) for happy path and broken faction refs.
- `server/README.md` section.
**Out of scope**
- Per-player standing store, apply logic, HTTP.
**Acceptance criteria**
- [ ] Host exits on invalid faction catalog or broken quest faction refs at startup.
- [ ] Registry resolves faction metadata by id.
**Client counterpart:** none (infrastructure-only).
---
### E7M3-03 — Faction standing store + reputation delta audit store
**Goal:** Durable per-player **`FactionStanding`** snapshot and append-only **`ReputationDelta`** audit log.
**In scope**
- **`IFactionStandingStore`**, **`IReputationDeltaStore`** — in-memory implementations; optional Postgres migrations (NEO-116-style split).
- Default standing **0** per known faction when missing; clamp reads to faction min/max.
- Audit row: player, faction, delta amount, resulting standing, source kind (`quest_completion`), source id (quest id), timestamp/id.
- Unit tests: first apply, clamp at bounds, audit append.
- `server/README.md` store sections.
**Out of scope**
- Public apply API surface (E7M3-04), quest wiring (E7M3-06).
**Acceptance criteria**
- [ ] Standing readable per player+faction; deltas append-only and queryable for audit tests.
- [ ] Unknown faction id returns structured deny at store boundary.
**Client counterpart:** none (infrastructure-only).
---
### E7M3-04 — `ReputationOperations` (apply `ReputationDelta`)
**Goal:** Server-internal apply for standing changes with clamp, audit, and structured outcomes.
**In scope**
- **`ReputationOperations.TryApplyDelta`** in `server/NeonSprawl.Server/Game/Factions/`.
- Validates faction id via **`IFactionDefinitionRegistry`**; fail-closed on unknown faction.
- Emits audit **`ReputationDelta`** row on success.
- Unit + integration tests (AAA): happy path, clamp min/max, unknown faction deny.
**Out of scope**
- Quest bundle wiring (E7M3-06), accept gates (E7M3-05).
**Acceptance criteria**
- [ ] Delta apply updates standing once and records audit row.
- [ ] Standing never exceeds faction min/max after apply.
**Client counterpart:** none (server engine).
---
### E7M3-05 — `FactionGateOperations` + wire into `QuestStateOperations.TryAccept`
**Goal:** Quest accept evaluates **`FactionGateRule`** rows fail-closed before activation.
**In scope**
- **`FactionGateOperations.TryEvaluate`** — all rules must pass (AND); compare player standing vs **`minStanding`** per rule.
- Call from **`TryAccept`** after prerequisite check, before **`TryActivate`**.
- New reason code **`faction_gate_blocked`** in **`QuestStateReasonCodes`** + accept deny telemetry hook site.
- Integration tests: accept gated quest denied below threshold; succeeds at threshold; unknown faction in rule fails closed at startup (E7M3-02) not runtime.
- Bruno smoke for accept deny/success on **`prototype_quest_grid_contract`**.
**Out of scope**
- Zone/travel gates (E4.M1); Godot HUD (E7M3-10).
**Acceptance criteria**
- [ ] Accept returns **`faction_gate_blocked`** when standing below **`minStanding`**.
- [ ] Accept succeeds when all rules satisfied.
**Client counterpart:** [NEO-142](https://linear.app/neon-sprawl/issue/NEO-142) — HUD surfacing (blocked by E7M3-07/E7M3-08).
---
### E7M3-06 — Extend `RewardRouterOperations` for `reputationGrants`
**Goal:** Quest completion bundles apply faction rep grants idempotently with item/skill XP rows.
**In scope**
- Extend **`RewardRouterOperations.TryDeliverQuestCompletion`** to apply **`reputationGrants`** via **`ReputationOperations`** after validation.
- Extend **`IRewardDeliveryStore`** / **`RewardDeliveryEvent`** snapshot with rep grant lines.
- Compensating rollback policy: if rep apply fails after items/xp committed, document rollback behavior (mirror E7.M2 partial-failure policy).
- Integration tests: operator-chain completion → +15 Grid Operators once; replay idempotent.
- Extend Bruno quest-progress smoke for standing side effect (optional subfolder).
**Out of scope**
- HTTP read projection (E7M3-08), Godot.
**Acceptance criteria**
- [ ] First-time quest completion applies rep grants exactly once per delivery key.
- [ ] Unknown faction id in bundle denies delivery before store record.
**Client counterpart:** [NEO-142](https://linear.app/neon-sprawl/issue/NEO-142).
---
### E7M3-07 — `GET /game/players/{id}/faction-standing` HTTP read
**Goal:** Client-readable standing snapshot for all frozen factions.
**In scope**
- **`GET /game/players/{id}/faction-standing`** — `FactionStandingApi` + DTOs in `Game/Factions/`.
- Response lists each catalog faction with current standing (default 0).
- Integration tests + Bruno `bruno/neon-sprawl-server/faction-standing/`.
- `server/README.md` section.
**Out of scope**
- Godot HUD (E7M3-10).
**Acceptance criteria**
- [ ] GET returns stable JSON for prototype player after quest rep grants.
- [ ] Unknown player returns **404** (position gate precedent).
**Client counterpart:** [NEO-142](https://linear.app/neon-sprawl/issue/NEO-142).
---
### E7M3-08 — Extend quest HTTP projections for rep grants + gate rules
**Goal:** Client can see gate requirements and completion rep summary without local math.
**In scope**
- Extend **`GET /game/world/quest-definitions`** with optional **`factionGateRules`** projection per quest.
- Extend **`GET …/quest-progress`** **`completionRewardSummary`** with **`reputationGrants`** lines (mirror items/skill XP).
- Update **`QuestAcceptApi`** embedded quest rows if applicable.
- Integration tests + Bruno assertions on grid-contract quest row.
- `server/README.md` quest + faction sections.
**Out of scope**
- Godot parse/HUD (E7M3-10).
**Acceptance criteria**
- [ ] Gated quest definition includes **`factionGateRules`** in world GET.
- [ ] Completed quest rows include rep lines in **`completionRewardSummary`** when delivered.
**Client counterpart:** [NEO-142](https://linear.app/neon-sprawl/issue/NEO-142).
---
### E7M3-09 — Faction telemetry hook sites (comment-only)
**Goal:** Anchor future E9.M1 catalog events without ingest.
**In scope**
- Comment-only **`reputation_delta`** in **`ReputationOperations`** (successful apply).
- Comment-only **`faction_gate_blocked`** in **`FactionGateOperations`** / **`TryAccept`** deny path.
- `server/README.md` hook list.
**Out of scope**
- E9.M1 ingest, dashboards.
**Acceptance criteria**
- [ ] Hook sites documented in README; no behavior change beyond comments.
**Client counterpart:** none (infrastructure-only).
---
### E7M3-10 — Client faction standing + gate feedback HUD (Godot)
**Goal:** Player sees faction standing and accept gate denials in Godot without Bruno.
**In scope**
- **`faction_standing_client.gd`**, **`FactionStandingLabel`** (or extend economy HUD section).
- Parse **`GET …/faction-standing`**; refresh after quest completion transitions (reuse **`quest_hud_controller.gd`** signals).
- Surface **`faction_gate_blocked`** on accept feedback label (NEO-122 pattern).
- Extend **`quest_progress_client.gd`** / reward label for **`reputationGrants`** lines in **`completionRewardSummary`**.
- GdUnit parse tests; `client/README.md` section.
- `docs/manual-qa/NEO-142.md` component checklist.
**Out of scope**
- Final journal UI; faction art pass.
**Acceptance criteria**
- [ ] Standing visible after operator-chain completion (+15 Grid Operators).
- [ ] Accepting grid contract below rep shows readable deny copy on HUD.
**Server counterpart:** [NEO-139](https://linear.app/neon-sprawl/issue/NEO-139), [NEO-140](https://linear.app/neon-sprawl/issue/NEO-140).
---
### E7M3-11 — Playable faction reputation + gate capstone (Godot)
**Goal:** Prove Epic 7 Slice 3 acceptance **in Godot**: earn rep from operator chain, accept gated faction quest once; gates fail closed.
**In scope**
- **`docs/manual-qa/NEO-143.md`**: capstone session — complete operator chain → verify standing **15** → accept **`prototype_quest_grid_contract`** → complete hand-in → Rust Collective **+10**; retry accept gate before rep (denied).
- **`client/README.md`**: **End-to-end faction reputation loop** section; cross-links NEO-133NEO-142.
- Module alignment on completion: [E7_M3](../decomposition/modules/E7_M3_FactionReputationLedger.md), [documentation_and_implementation_alignment.md](../decomposition/modules/documentation_and_implementation_alignment.md), [module_dependency_register.md](../decomposition/modules/module_dependency_register.md).
**Out of scope**
- Contract generator (E7.M4); zone travel gates (E4.M1); Bruno-only proof.
**Acceptance criteria**
- [ ] Human completes **`docs/manual-qa/NEO-143.md`** with server + client.
- [ ] Epic 7 Slice 3 AC: reputation deltas auditable; gates fail closed on tamper (unknown faction denied at startup).
- [ ] Re-read [epic_07 Slice 3 AC](../decomposition/epics/epic_07_quest_faction.md#slice-3---faction-ledger-pre-production).
**Client counterpart:** this story **is** the Slice 3 client capstone.
---
## Decomposition complete checklist
- [x] Every player-visible AC has a **`client`** Linear issue (NEO-142, NEO-143)
- [x] No forbidden deferral language in backlog
- [x] Capstone client story exists (E7M3-11 / NEO-143)
- [x] Epic DoD updated for Godot verification (see epic_07 update)
- [x] [documentation_and_implementation_alignment.md](../decomposition/modules/documentation_and_implementation_alignment.md) E7.M3 row updated (decomposition pass)
- [x] [module_dependency_register.md](../decomposition/modules/module_dependency_register.md) E7.M3 note updated
## Related docs
- [epic_07_quest_faction.md](../decomposition/epics/epic_07_quest_faction.md)
- [E7M1-prototype-backlog.md](E7M1-prototype-backlog.md)
- [E7M2-prototype-backlog.md](E7M2-prototype-backlog.md)
- [quest_scope_and_party.md](../decomposition/modules/quest_scope_and_party.md)
- [neon_sprawl_vision.plan.md — Prototype Scope](../../neon_sprawl_vision.plan.md)

View File

@ -0,0 +1,103 @@
# 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.