# 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 **1–11** 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** - [x] PR gate validates faction JSON and extended quest JSON. - [x] Exactly two prototype faction ids; fifth quest id present with gate + objective cross-refs. - [x] 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-133–NEO-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)