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.md — E7M3-08
Module: E7_M3_FactionReputationLedger.md
Pattern: NEO-129 — completionRewardSummary nested grant lines from RewardDeliveryEvent; NEO-115 — world GET definition projection
Precursors: NEO-138 Done — RewardDeliveryEvent.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 projected — factionId 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-definitionswith optionalfactionGateRulesper quest. - Extend
GET …/quest-progresscompletionRewardSummarywithreputationGrantslines (mirror items/skill XP). QuestAcceptApiembedded quest rows inherit updated mapper (no extra accept-only DTO work).- Integration tests + Bruno assertions on
prototype_quest_grid_contractgate row and operator-chain rep summary. server/README.mdquest + 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:
reasonCodeonly).
Client counterpart: NEO-142 — standing label + gate feedback + reward label rep lines after NEO-139/NEO-140.
Acceptance criteria checklist
- Gated quest definition includes
factionGateRulesin world GET. - Completed quest rows include rep lines in
completionRewardSummarywhen delivered. dotnet testcovers grid-contract gate projection and operator-chain rep summary.- Bruno asserts grid-contract
factionGateRulesand operator-chainreputationGrants.
Implementation reconciliation (shipped)
- World GET:
QuestDefinitionsWorldApimapsQuestDefRow.FactionGateRules→ optionalfactionGateRulesonQuestDefinitionJson(QuestFactionGateRuleJson:factionId,minStanding); omitted when empty. - Quest progress GET:
QuestProgressApi.MapCompletionRewardSummarymapsRewardDeliveryEvent.GrantedReputation→reputationGrants(QuestReputationGrantJson:factionId,amount); required array when summary present. - Accept POST: inherits updated mapper via
QuestProgressApi.MapQuestProgressRow— no accept-specific changes. - Tests:
QuestDefinitionsWorldApiTestsgrid-contract gate row + wire-levelfactionGateRulesomission;QuestProgressApiTestsoperator-chain +15 rep, grid-contract +10 rep, gather-intro empty rep; 823 tests green. - Manual QA: none — server-only HTTP projections; Bruno +
dotnet testcover verification (manual QA checklists reserved for client-facing stories per project convention).
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, ifd.FactionGateRules.Count > 0, setFactionGateRulesto 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
MapCompletionRewardSummaryto mapdeliveryEvent.GrantedReputation→reputationGrantsvia newMapReputationGrantshelper (commit-time order preserved). QuestAcceptApi.MapResponseunchanged — already callsQuestProgressApi.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
server/README.md: extend quest-definitions table withfactionGateRules; extend quest-progresscompletionRewardSummarytable withreputationGrantssource (GrantedReputation).- On ship: update E7_M3_FactionReputationLedger.md status line and documentation_and_implementation_alignment.md E7.M3 row.
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 GrantedReputation → reputationGrants 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 only — schemaVersion stays 1; NEO-142 is first consumer |
adopted |
| Grid-contract completion rep Bruno needs full hand-in flow | adopted — done: 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 |