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

18 KiB
Raw Blame History

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-112NEO-123). E7.M2 Ready — idempotent completionRewardBundle delivery (NEO-124NEO-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 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 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.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 + 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 — 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.


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-standingFactionStandingApi + 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.


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.


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, 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, 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.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.

Client counterpart: this story is the Slice 3 client capstone.


Decomposition complete checklist