From 367f894b34b8f128791821121065ba47a05383a2 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Wed, 17 Jun 2026 19:05:54 -0400 Subject: [PATCH] NEO-140: add E7M3-08 quest HTTP projection kickoff plan Document factionGateRules world GET and completionRewardSummary reputationGrants scope before implementation. --- docs/plans/NEO-140-implementation-plan.md | 171 ++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 docs/plans/NEO-140-implementation-plan.md diff --git a/docs/plans/NEO-140-implementation-plan.md b/docs/plans/NEO-140-implementation-plan.md new file mode 100644 index 0000000..0b7036e --- /dev/null +++ b/docs/plans/NEO-140-implementation-plan.md @@ -0,0 +1,171 @@ +# NEO-140 — E7M3-08: Extend quest HTTP projections for rep + gates + +**Linear:** [NEO-140](https://linear.app/neon-sprawl/issue/NEO-140) +**Branch:** `NEO-140-e7m3-quest-http-rep-gate-projections` +**Backlog:** [E7M3-pre-production-backlog.md](E7M3-pre-production-backlog.md) — **E7M3-08** +**Module:** [E7_M3_FactionReputationLedger.md](../decomposition/modules/E7_M3_FactionReputationLedger.md) +**Pattern:** [NEO-129](NEO-129-implementation-plan.md) — `completionRewardSummary` nested grant lines from `RewardDeliveryEvent`; [NEO-115](NEO-115-implementation-plan.md) — world GET definition projection +**Precursors:** [NEO-138](https://linear.app/neon-sprawl/issue/NEO-138) **`Done`** — `RewardDeliveryEvent.GrantedReputation` at commit time; [NEO-139](https://linear.app/neon-sprawl/issue/NEO-139) **`Done`** — standing GET for client cross-check +**Blocks:** [NEO-142](https://linear.app/neon-sprawl/issue/NEO-142) (Godot HUD gate + rep reward labels) +**Client counterpart:** [NEO-142](https://linear.app/neon-sprawl/issue/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](E7M3-pre-production-backlog.md#e7m3-08--extend-quest-http-projections-for-rep-grants--gate-rules), 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-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](https://linear.app/neon-sprawl/issue/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`**. + +## 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:** + +```json +{ + "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.GrantedReputation` → `reputationGrants` 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 + +- **`server/README.md`**: extend quest-definitions table with **`factionGateRules`**; extend quest-progress **`completionRewardSummary`** table with **`reputationGrants`** source (`GrantedReputation`). +- On ship: update [E7_M3_FactionReputationLedger.md](../decomposition/modules/E7_M3_FactionReputationLedger.md) status line and [documentation_and_implementation_alignment.md](../decomposition/modules/documentation_and_implementation_alignment.md) E7.M3 row. + +### 4. Flow (read paths only) + +```mermaid +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 | **Operator-chain Bruno first** (primary AC); grid-contract rep assertion in integration test using existing quest wiring helpers | `adopted` | +| `QuestDefinitionsWorldApiTests` missing grid-contract row coverage today | **Add grid-contract gate assertions** in same test method | `adopted` |