neon-sprawl/docs/plans/E7M3-pre-production-backlog.md

380 lines
18 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.

# 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**
- [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-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)