NEO-140: add E7M3-08 quest HTTP projection kickoff plan
Document factionGateRules world GET and completionRewardSummary reputationGrants scope before implementation.pull/180/head
parent
72668e1cab
commit
367f894b34
|
|
@ -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` |
|
||||
Loading…
Reference in New Issue