neon-sprawl/docs/plans/NEO-140-implementation-plan.md

12 KiB

NEO-140 — E7M3-08: Extend quest HTTP projections for rep + gates

Linear: NEO-140
Branch: NEO-140-e7m3-quest-http-rep-gate-projections
Backlog: E7M3-pre-production-backlog.mdE7M3-08
Module: E7_M3_FactionReputationLedger.md
Pattern: NEO-129completionRewardSummary nested grant lines from RewardDeliveryEvent; NEO-115 — world GET definition projection
Precursors: NEO-138 DoneRewardDeliveryEvent.GrantedReputation at commit time; NEO-139 Done — standing GET for client cross-check
Blocks: NEO-142 (Godot HUD gate + rep reward labels)
Client counterpart: NEO-142 — parses world factionGateRules and quest-progress completionRewardSummary.reputationGrants; blocked until this story lands

Goal

Client can see gate requirements and completion rep summary without local math.

Kickoff clarifications

No clarifications needed. Linear AC, E7M3-08 backlog scope, and NEO-129/NEO-138 precedents settle:

Topic Decision Evidence
factionGateRules JSON shape { factionId, minStanding } per row — flat mirror of content schema content/schemas/faction-gate-rule.schema.json; FactionGateRuleRow
factionGateRules omission Omit property when quest has no rules (JsonIgnore WhenWritingNull) Backlog “optional projection”; NEO-115 objective optional-field precedent
factionGateRules order Preserve catalog load order from QuestDefRow.FactionGateRules Same as prerequisite ids / step order in world GET
Faction display names on gates Not projectedfactionId only NEO-139 deferred GET /game/world/faction-definitions; client joins standing GET or future catalog
reputationGrants in summary Required array on completionRewardSummary when summary is present; empty [] when delivery had no rep rows NEO-129: itemGrants / skillXpGrants always present when summary exists
Rep line field names factionId, amount — mirror content reputation-grant-row.schema.json and RewardReputationGrantApplied NEO-138 store snapshot; content schema
Rep line source RewardDeliveryEvent.GrantedReputation at commit time (not live standing recompute) NEO-129 maps delivery event, not bundle definition
Rep line order Preserve commit-time grant order from delivery event MapItemGrants / MapSkillXpGrants XML doc precedent
Schema version Stay at 1 for both world quest-definitions and quest-progress Additive optional fields; no breaking rename
QuestAcceptApi No separate accept-specific changes — shared QuestProgressApi.MapQuestProgressRow / MapCompletionRewardSummary picks up rep lines automatically Backlog “if applicable”; accept embeds progress row via existing mapper
Bruno scope Extend quest-definitions/Get quest definitions.bru (grid-contract gate row) + add quest-progress GET bru asserting operator-chain reputationGrants (+15 Grid Operators) Backlog “Bruno assertions on grid-contract quest row”; closes NEO-138 deferral on summary shape

Scope and out-of-scope

In scope (from Linear + backlog):

  • Extend GET /game/world/quest-definitions with optional factionGateRules per quest.
  • Extend GET …/quest-progress completionRewardSummary with reputationGrants lines (mirror items/skill XP).
  • QuestAcceptApi embedded quest rows inherit updated mapper (no extra accept-only DTO work).
  • Integration tests + Bruno assertions on prototype_quest_grid_contract gate row and operator-chain rep summary.
  • server/README.md quest + faction sections.

Out of scope:

  • Godot parse/HUD (NEO-142), telemetry ingest (NEO-141).
  • GET /game/world/faction-definitions (no backlog item).
  • Player-specific gate pass/fail on world GET (gates evaluated only on accept; standing via NEO-139 GET).
  • Enriching accept deny responses with failing rule detail (NEO-137 adopted: reasonCode only).

Client counterpart: NEO-142 — standing label + gate feedback + reward label rep lines after NEO-139/NEO-140.

Acceptance criteria checklist

  • Gated quest definition includes factionGateRules in world GET.
  • Completed quest rows include rep lines in completionRewardSummary when delivered.
  • dotnet test covers grid-contract gate projection and operator-chain rep summary.
  • Bruno asserts grid-contract factionGateRules and operator-chain reputationGrants.

Implementation reconciliation (shipped)

  • World GET: QuestDefinitionsWorldApi maps QuestDefRow.FactionGateRules → optional factionGateRules on QuestDefinitionJson (QuestFactionGateRuleJson: factionId, minStanding); omitted when empty.
  • Quest progress GET: QuestProgressApi.MapCompletionRewardSummary maps RewardDeliveryEvent.GrantedReputationreputationGrants (QuestReputationGrantJson: factionId, amount); required array when summary present.
  • Accept POST: inherits updated mapper via QuestProgressApi.MapQuestProgressRow — no accept-specific changes.
  • Tests: QuestDefinitionsWorldApiTests grid-contract gate row + wire-level factionGateRules omission; QuestProgressApiTests operator-chain +15 rep, grid-contract +10 rep, gather-intro empty rep; 823 tests green.
  • Bruno: extended Get quest definitions.bru; new Get quest progress after operator chain complete.bru; gather-intro bru asserts empty reputationGrants.
  • Docs: server/README.md quest-definitions + quest-progress sections; E7.M3 module + alignment register updated.
  • Manual QA: docs/manual-qa/NEO-140.md.

Technical approach

1. World quest definitions — factionGateRules

DTOs (QuestDefinitionsListDtos.cs):

Type JSON Notes
QuestFactionGateRuleJson factionId, minStanding New grant-line type
QuestDefinitionJson optional factionGateRules JsonIgnore(WhenWritingNull); non-null when QuestDefRow.FactionGateRules.Count > 0

Mapper (QuestDefinitionsWorldApi.cs):

  • When mapping each QuestDefRow, if d.FactionGateRules.Count > 0, set FactionGateRules to mapped list; else leave null (omitted in JSON).
  • Preserve rule order from catalog.

Prototype assertion target:

{
  "id": "prototype_quest_grid_contract",
  "factionGateRules": [
    { "factionId": "prototype_faction_grid_operators", "minStanding": 15 }
  ]
}

Non-gated quests (gather intro, operator chain, etc.) omit factionGateRules.

2. Quest progress — completionRewardSummary.reputationGrants

DTOs (QuestProgressListDtos.cs):

Type JSON Notes
QuestReputationGrantJson factionId, amount New line type
QuestCompletionRewardSummaryJson add reputationGrants Required when summary present; empty array allowed

Mapper (QuestProgressApi.cs):

  • Extend MapCompletionRewardSummary to map deliveryEvent.GrantedReputationreputationGrants via new MapReputationGrants helper (commit-time order preserved).
  • QuestAcceptApi.MapResponse unchanged — already calls QuestProgressApi.MapQuestProgressRow.

Prototype assertion targets:

Quest reputationGrants when completed
prototype_quest_gather_intro []
prototype_quest_operator_chain [{ factionId: prototype_faction_grid_operators, amount: 15 }]
prototype_quest_grid_contract [{ factionId: prototype_faction_rust_collective, amount: 10 }] (after hand-in flow lands in tests/Bruno)

3. Docs

4. Flow (read paths only)

sequenceDiagram
    participant Client
    participant WorldAPI as QuestDefinitionsWorldApi
    participant ProgressAPI as QuestProgressApi
    participant Registry as IQuestDefinitionRegistry
    participant Delivery as IRewardDeliveryStore

    Client->>WorldAPI: GET /game/world/quest-definitions
    WorldAPI->>Registry: GetDefinitionsInIdOrder()
    WorldAPI-->>Client: quests[] (+ factionGateRules on gated rows)

    Client->>ProgressAPI: GET /game/players/{id}/quest-progress
    ProgressAPI->>Delivery: TryGet per completed quest
    ProgressAPI-->>Client: quests[] (+ completionRewardSummary.reputationGrants)

Files to add

Path Rationale
bruno/neon-sprawl-server/quest-progress/Get quest progress after operator chain complete.bru Bruno spine asserting operator-chain completionRewardSummary.reputationGrants (+15 Grid Operators); mirrors gather-intro complete bru pattern

Files to modify

Path Rationale
server/NeonSprawl.Server/Game/Quests/QuestDefinitionsListDtos.cs Add QuestFactionGateRuleJson; optional factionGateRules on QuestDefinitionJson
server/NeonSprawl.Server/Game/Quests/QuestDefinitionsWorldApi.cs Map QuestDefRow.FactionGateRules into world GET JSON
server/NeonSprawl.Server/Game/Quests/QuestProgressListDtos.cs Add QuestReputationGrantJson; extend QuestCompletionRewardSummaryJson
server/NeonSprawl.Server/Game/Quests/QuestProgressApi.cs Map GrantedReputationreputationGrants in MapCompletionRewardSummary
server/NeonSprawl.Server.Tests/Game/Quests/QuestDefinitionsWorldApiTests.cs Assert grid-contract factionGateRules; assert non-gated quests omit property
server/NeonSprawl.Server.Tests/Game/Quests/QuestProgressApiTests.cs Extend operator-chain summary helper (+15 rep); extend equality helper; optional grid-contract rep assertion
bruno/neon-sprawl-server/quest-definitions/Get quest definitions.bru Assert prototype_quest_grid_contract.factionGateRules freeze row
server/README.md Document new projection fields on both GET routes

Tests

File Coverage
QuestDefinitionsWorldApiTests.cs Add/change: grid-contract row includes [{ factionId: prototype_faction_grid_operators, minStanding: 15 }]; gather intro row has no factionGateRules in deserialized model (null / omitted)
QuestProgressApiTests.cs Change: AssertOperatorChainCompletionRewardSummary expects reputationGrants +15 Grid Operators; AssertCompletionRewardSummariesEqual compares rep lines; gather-intro summary asserts empty reputationGrants
bruno/.../quest-definitions/Get quest definitions.bru Grid-contract gate rule assertion
bruno/.../quest-progress/Get quest progress after operator chain complete.bru New — operator-chain complete → GET asserts rep grant line

Manual verification: run Bruno quest-definitions + new quest-progress bru against local server after operator-chain flow helper.

Open questions / risks

Question / risk Agent recommendation Status
Existing clients ignore unknown reputationGrants field Additive field onlyschemaVersion stays 1; NEO-142 is first consumer adopted
Grid-contract completion rep Bruno needs full hand-in flow Operator-chain Bruno first (primary AC); grid-contract rep assertion in integration test using existing quest wiring helpers adopteddone: integration test + accept bru assert +10 Rust Collective
QuestDefinitionsWorldApiTests missing grid-contract row coverage today Add grid-contract gate assertions in same test method adopted