19 KiB
E7.M3 — Pre-production story backlog (FactionReputationLedger)
Working backlog for Epic 7 — Slice 3 (faction ledger). Decomposition and contracts: E7.M3 — FactionReputationLedger.
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), E7M1 (quest HUD + capstone).
Labels (Linear): every issue E7.M3; add server or client + Story as listed.
Precursor (do not re-scope): E7.M1 Ready — quest accept/advance/complete (NEO-112–NEO-123). E7.M2 Ready — idempotent completionRewardBundle delivery (NEO-124–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 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 |
| E7M3-02 | server | NEO-134 |
| E7M3-03 | server | NEO-135 |
| E7M3-04 | server | NEO-136 |
| E7M3-05 | server | NEO-137 |
| E7M3-06 | server | NEO-138 |
| E7M3-07 | server | NEO-139 |
| E7M3-08 | server | NEO-140 |
| E7M3-09 | server | NEO-141 |
| E7M3-10 | client | NEO-142 |
| E7M3-11 | client | 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 contract generator; E4.M1 TravelRule faction gates; E8.M2 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.jsonwith optionalfactionGateRulesarray. - Extend
content/schemas/quest-reward-bundle.schema.jsonwith optionalreputationGrantsarray. 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 +
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/(orGame/Reputation/). IFactionDefinitionRegistry+ DI registration.- Cross-check quest
factionGateRules/ bundlereputationGrantsfaction ids at startup (after quest catalog load). - Unit tests (AAA) for happy path and broken faction refs.
server/README.mdsection.
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.mdstore 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.TryApplyDeltainserver/NeonSprawl.Server/Game/Factions/.- Validates faction id via
IFactionDefinitionRegistry; fail-closed on unknown faction. - Emits audit
ReputationDeltarow 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 vsminStandingper rule.- Call from
TryAcceptafter prerequisite check, beforeTryActivate. - New reason code
faction_gate_blockedinQuestStateReasonCodes+ 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_blockedwhen standing belowminStanding. - Accept succeeds when all rules satisfied.
Client counterpart: 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.TryDeliverQuestCompletionto applyreputationGrantsviaReputationOperationsafter validation. - Extend
IRewardDeliveryStore/RewardDeliveryEventsnapshot 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.
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 inGame/Factions/.- Response lists each catalog faction with current standing (default 0).
- Integration tests + Bruno
bruno/neon-sprawl-server/faction-standing/. server/README.mdsection.
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.
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-definitionswith optionalfactionGateRulesprojection per quest. - Extend
GET …/quest-progresscompletionRewardSummarywithreputationGrantslines (mirror items/skill XP). - Update
QuestAcceptApiembedded quest rows if applicable. - Integration tests + Bruno assertions on grid-contract quest row.
server/README.mdquest + faction sections.
Out of scope
- Godot parse/HUD (E7M3-10).
Acceptance criteria
- Gated quest definition includes
factionGateRulesin world GET. - Completed quest rows include rep lines in
completionRewardSummarywhen delivered.
Client counterpart: NEO-142.
E7M3-09 — Faction telemetry hook sites (comment-only)
Goal: Anchor future E9.M1 catalog events without ingest.
In scope
- Comment-only
reputation_deltainReputationOperations(successful apply). - Comment-only
faction_gate_blockedinFactionGateOperations.TryEvaluate(threshold + standing read failure deny paths). server/README.mdhook list.
Out of scope
- E9.M1 ingest, dashboards.
Acceptance criteria
- Hook sites documented in README; no behavior change beyond comments.
Landed (NEO-141): comment-only reputation_delta in ReputationOperations.TryApplyDelta; faction_gate_blocked in FactionGateOperations.TryEvaluate (threshold + standing read failure); router duplicate stub removed; server README — Faction telemetry hooks (NEO-141).
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 (reusequest_hud_controller.gdsignals). - Surface
faction_gate_blockedon accept feedback label (NEO-122 pattern). - Extend
quest_progress_client.gd/ reward label forreputationGrantslines incompletionRewardSummary. - GdUnit parse tests;
client/README.mdsection. docs/manual-qa/NEO-142.mdcomponent 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.
Landed (NEO-142): faction_standing_client.gd, FactionStandingLabel, readable faction_gate_blocked accept deny, reputationGrants reward lines; GdUnit + docs/manual-qa/NEO-142.md; plan NEO-142; client README — Faction standing + gate feedback HUD (NEO-142).
Server counterpart: NEO-139, 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 → acceptprototype_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, documentation_and_implementation_alignment.md, 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.mdwith 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.
Client counterpart: this story is the Slice 3 client capstone.
Decomposition complete checklist
- Every player-visible AC has a
clientLinear issue (NEO-142, NEO-143) - No forbidden deferral language in backlog
- Capstone client story exists (E7M3-11 / NEO-143)
- Epic DoD updated for Godot verification (see epic_07 update)
- documentation_and_implementation_alignment.md E7.M3 row updated (decomposition pass)
- module_dependency_register.md E7.M3 note updated