Merge pull request #169 from ViPro-Technologies/NEO-130-reward-telemetry-hooks
NEO-130: Reward telemetry hook sites (comment-only)pull/172/head
commit
a7579776a6
|
|
@ -75,3 +75,5 @@ Epic 7 **Slice 2** — `reward_delivery`, `unlock_granted`; no double-claim on r
|
|||
**NEO-128 (E7M2-05 quest-state wiring):** **`QuestStateOperations.TryMarkComplete`** calls **`RewardRouterOperations.TryDeliverQuestCompletion`** on the first-time completion path (deliver-then-mark); idempotent **`completed`** replay skips the router. **`QuestObjectiveWiring`** threads reward dependencies through gather/craft/encounter/GET refresh into the same path. See [NEO-128 implementation plan](../../plans/NEO-128-implementation-plan.md); [server README — Quest state operations (NEO-117)](../../../server/README.md#quest-state-operations-neo-117).
|
||||
|
||||
**NEO-129 (E7M2-06 HTTP read):** **`GET /game/players/{id}/quest-progress`** projects optional **`completionRewardSummary`** on **`completed`** rows from **`IRewardDeliveryStore.TryGet`** — nested **`itemGrants`** + **`skillXpGrants`** mirroring delivery event snapshots; omitted when no delivery record. **`QuestAcceptApi`** embed rows use the same mapper. See [NEO-129 implementation plan](../../plans/NEO-129-implementation-plan.md); [server README — Per-player quest progress (NEO-119)](../../../server/README.md#per-player-quest-progress-neo-119); Bruno `bruno/neon-sprawl-server/quest-progress/Get quest progress after gather intro complete.bru`.
|
||||
|
||||
**NEO-130 (E7M2-07 telemetry hooks):** comment-only **`reward_delivery`** + **`unlock_granted`** stub hook sites in **`RewardRouterOperations.TryDeliverQuestCompletion`** — first-time **`TryRecord`** success and future **`UnlockGrant`** apply region respectively; no production ingest. See [NEO-130 implementation plan](../../plans/NEO-130-implementation-plan.md); [server README — Reward telemetry hooks (NEO-130)](../../../server/README.md#reward-telemetry-hooks-neo-130).
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,33 @@
|
|||
# Manual QA — NEO-130 (reward telemetry hook sites)
|
||||
|
||||
Reference: [implementation plan](../plans/NEO-130-implementation-plan.md), [NEO-127 implementation plan](../plans/NEO-127-implementation-plan.md) (reward router), [NEO-128 implementation plan](../plans/NEO-128-implementation-plan.md) (quest-state wiring).
|
||||
|
||||
## Preconditions
|
||||
|
||||
- Run `NeonSprawl.Server` (`dotnet run` from `server/NeonSprawl.Server`; default `http://localhost:5253`) if exercising Bruno; not required for code review only.
|
||||
- This story adds **comments only**; reward delivery behavior matches NEO-127/128.
|
||||
|
||||
## Code review check
|
||||
|
||||
- [ ] Open `server/NeonSprawl.Server/Game/Rewards/RewardRouterOperations.cs`.
|
||||
- [ ] Confirm **`unlock_granted`** stub comment block after item + skill XP apply loops, before **`RewardDeliveryEvent`** construction.
|
||||
- [ ] Confirm **`reward_delivery`** comment block immediately after **`deliveryStore.TryRecord`** returns **`true`** (inside the success branch).
|
||||
- [ ] Confirm **`TODO(E9.M1)`** markers and planned payload fields; **no** new `ILogger` or metrics calls.
|
||||
- [ ] Confirm **no** duplicate hook comments in **`QuestStateOperations`**, **`QuestAcceptApi`**, or **`QuestProgressApi`**.
|
||||
- [ ] Open `server/NeonSprawl.Server/Game/Rewards/RewardDeliveryEvent.cs` — xml summary cross-references **`reward_delivery`** hook site.
|
||||
- [ ] Open [server README — Reward telemetry hooks (NEO-130)](../../server/README.md#reward-telemetry-hooks-neo-130) — NEO-130 subsection present with event table.
|
||||
|
||||
## Regression (optional)
|
||||
|
||||
```bash
|
||||
cd server && dotnet test NeonSprawl.sln
|
||||
```
|
||||
|
||||
Expect all tests pass (comments-only change).
|
||||
|
||||
## Bruno (optional sanity)
|
||||
|
||||
From `bruno/neon-sprawl-server/quest-progress/` against a running server:
|
||||
|
||||
- **Get quest progress after gather intro complete** — **`completionRewardSummary`** unchanged when quest completes via wiring.
|
||||
- **Health after quest complete reward wiring** — server health unchanged.
|
||||
|
|
@ -241,7 +241,9 @@ Combat intro bundle is **skill XP only** — encounter loot already granted **`c
|
|||
|
||||
**Acceptance criteria**
|
||||
|
||||
- [ ] Hook sites documented in README; no behavior change beyond comments.
|
||||
- [x] Hook sites documented in README; no behavior change beyond comments.
|
||||
|
||||
**Landed ([NEO-130](https://linear.app/neon-sprawl/issue/NEO-130)):** comment-only **`reward_delivery`** + **`unlock_granted`** stub in **`RewardRouterOperations`**; [server README — Reward telemetry hooks (NEO-130)](../../server/README.md#reward-telemetry-hooks-neo-130); plan [NEO-130](../../plans/NEO-130-implementation-plan.md).
|
||||
|
||||
**Client counterpart:** none (infrastructure-only).
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
# NEO-130 — Implementation plan
|
||||
|
||||
## Story reference
|
||||
|
||||
| Field | Value |
|
||||
|--------|--------|
|
||||
| **Key** | NEO-130 |
|
||||
| **Title** | E7M2-07: Reward telemetry hook sites (comment-only) |
|
||||
| **Linear** | https://linear.app/neon-sprawl/issue/NEO-130/e7m2-07-reward-telemetry-hook-sites-comment-only |
|
||||
| **Module** | [E7.M2 — RewardAndUnlockRouter](../decomposition/modules/E7_M2_RewardAndUnlockRouter.md) · Epic 7 Slice 2 · backlog **E7M2-07** |
|
||||
| **Branch** | `NEO-130-reward-telemetry-hooks` |
|
||||
| **Precursor** | [NEO-128](https://linear.app/neon-sprawl/issue/NEO-128) **`Done`** — `QuestStateOperations.TryMarkComplete` → **`RewardRouterOperations`** (deliver-then-mark) |
|
||||
| **Pattern** | [NEO-121](NEO-121-implementation-plan.md) / [NEO-49](NEO-49-implementation-plan.md) — comment-only engine anchors + **`TODO(E9.M1)`**; engine-only (no duplicate API-layer hooks) |
|
||||
| **Client counterpart** | none (infrastructure-only per [E7M2-07](E7M2-prototype-backlog.md#e7m2-07--reward-telemetry-hook-sites-comment-only)) |
|
||||
|
||||
## Kickoff clarifications
|
||||
|
||||
**No clarifications needed.** [E7M2-07](E7M2-prototype-backlog.md#e7m2-07--reward-telemetry-hook-sites-comment-only) backlog, Linear AC, landed router + quest wiring (NEO-127/128), and comment-only telemetry precedents (NEO-49/121) settle scope:
|
||||
|
||||
- **Runtime behavior:** comments-only + **`TODO(E9.M1)`** — no production ingest, no **`ILogger`** (NEO-49/121 precedent).
|
||||
- **Hook anchor:** **`RewardRouterOperations.TryDeliverQuestCompletion`** only — **`QuestStateOperations`** and HTTP quest routes delegate here; no duplicate hooks at quest-state or API layers (NEO-121 engine-only precedent).
|
||||
- **`reward_delivery` timing:** after **`deliveryStore.TryRecord`** returns **`true`** on first-time delivery (fresh grant + store commit). **Not** on early **`TryGet`** idempotent replay (**`already_delivered`**), deny paths, race rollback, or **`SuccessFromEvent`** after a lost **`TryRecord`** race.
|
||||
- **`unlock_granted` stub:** comment-only anchor for future **`UnlockGrant`** apply from bundle rows — prototype Slice 2 bundles are item + skill XP only ([E7M2-01 freeze](../decomposition/modules/E7_M2_RewardAndUnlockRouter.md#prototype-slice-2-freeze-e7m2-01)); no runtime unlock apply loop. Distinct from **`perk_unlock`** (**NEO-49** in **`PerkUnlockEngine`**) which may fire as a side effect of skill XP grants.
|
||||
- **Manual QA doc:** **`docs/manual-qa/NEO-130.md`** — NEO-121/49 comment-only verification pattern.
|
||||
|
||||
## Goal, scope, and out-of-scope
|
||||
|
||||
**Goal:** Document and place **comment-only hook sites** on the server-authoritative quest completion reward path for future E9.M1 catalog events **`reward_delivery`** and **`unlock_granted`**. Align with Epic 7 Slice 2 telemetry vocabulary ([E7.M2 module](../decomposition/modules/E7_M2_RewardAndUnlockRouter.md#related-implementation-slices)).
|
||||
|
||||
**In scope (from Linear + [E7M2-07](E7M2-prototype-backlog.md#e7m2-07--reward-telemetry-hook-sites-comment-only)):**
|
||||
|
||||
- **`reward_delivery`** hook site in **`RewardRouterOperations`** (first-time delivery success).
|
||||
- **`unlock_granted`** stub comment (no runtime unlock apply in Slice 2).
|
||||
- **`server/README.md`** reward telemetry subsection.
|
||||
- **`E7_M2_RewardAndUnlockRouter.md`** implementation anchor.
|
||||
- **`docs/manual-qa/NEO-130.md`** — verify comments + no behavior change.
|
||||
- E7M2 backlog + alignment register updates when implementation completes.
|
||||
|
||||
**Out of scope (from Linear + backlog):**
|
||||
|
||||
- E9.M1 ingest pipeline; runtime telemetry emit.
|
||||
- **`ILogger`** / metrics / dev-only log lines.
|
||||
- Duplicate hook comments in **`QuestStateOperations`**, **`QuestAcceptApi`**, or **`QuestProgressApi`** (**`quest_complete`** remains NEO-121; **`reward_delivery`** is router-layer grant audit).
|
||||
- Unlock engine / **`UnlockGrant`** runtime apply (pre-production).
|
||||
- Client counterpart (none).
|
||||
|
||||
## Acceptance criteria checklist
|
||||
|
||||
- [x] Hook sites documented in README; no behavior change beyond comments.
|
||||
|
||||
## Implementation reconciliation (shipped)
|
||||
|
||||
- **Engine anchor:** **`RewardRouterOperations.TryDeliverQuestCompletion`** — two comment-only hook sites with **`TODO(E9.M1)`** (no runtime emit); class xml summary cites NEO-130 telemetry anchor.
|
||||
- **`reward_delivery`:** after **`deliveryStore.TryRecord`** returns **`true`** on first-time delivery.
|
||||
- **`unlock_granted`:** stub after item + skill XP apply loops, before delivery event build — future **`UnlockGrant`** apply region; prototype bundles have no unlock rows.
|
||||
- **Docs:** `server/README.md` reward telemetry subsection; E7.M2 module + alignment register + E7M2 backlog updated.
|
||||
- **Manual QA:** `docs/manual-qa/NEO-130.md`.
|
||||
|
||||
## Technical approach
|
||||
|
||||
### 1. Authoritative surface
|
||||
|
||||
**`RewardRouterOperations.TryDeliverQuestCompletion`** — all quest completion bundle grants funnel here after NEO-128 wiring. Encounter loot remains on **`EncounterCompletionOperations`** (E5.M3); quest bundles use this router only.
|
||||
|
||||
### 2. Hook site A — `reward_delivery`
|
||||
|
||||
Immediately after **`deliveryStore.TryRecord(deliveryEvent)`** returns **`true`** (first-time store commit):
|
||||
|
||||
- Comment block names future E9.M1 event **`reward_delivery`**.
|
||||
- **`TODO(E9.M1): catalog emit`** — once per first-time quest completion delivery (idempotent **`TryGet`** replay and race-loser paths excluded).
|
||||
- Planned payload fields from **`RewardDeliveryEvent`**: `playerId`, `questId`, `deliveredAt`, `grantedItems` (`itemId`, `quantity`), `grantedSkillXp` (`skillId`, `amount`), `idempotencyKey` (`{playerId}:quest_complete:{questId}`).
|
||||
|
||||
### 3. Hook site B — `unlock_granted` (stub)
|
||||
|
||||
Comment block after item + skill XP apply loops, **before** building/recording **`RewardDeliveryEvent`** (hypothetical future **`UnlockGrant`** apply region):
|
||||
|
||||
- Names future E9.M1 event **`unlock_granted`**.
|
||||
- **`TODO(E9.M1): catalog emit`** — once per applied unlock row when bundle unlock grants land (pre-production).
|
||||
- Note: prototype **`completionRewardBundle`** has **no** unlock rows; stub anchors future apply loop only — no runtime behavior in Slice 2.
|
||||
- Planned payload fields (from module **`UnlockGrant`** contract): `playerId`, `questId`, unlock id / kind — finalize at E9.M1 wiring time.
|
||||
|
||||
### 4. Cross-references (minimal)
|
||||
|
||||
- Update **`RewardRouterOperations`** class summary → NEO-130 telemetry hook anchor.
|
||||
- Optional one-line pointer in **`RewardDeliveryEvent.cs`** xml summary → **`reward_delivery`** hook site (mirror **`PerkUnlockEvent`** / NEO-49).
|
||||
- **`server/README.md`:** new **Reward telemetry hooks (NEO-130)** subsection after [Reward router (NEO-127)](../../server/README.md#reward-router-neo-127) with event table.
|
||||
- **`E7_M2_RewardAndUnlockRouter.md`:** NEO-130 landed line under implementation anchor.
|
||||
- **`documentation_and_implementation_alignment.md`** + **`E7M2-prototype-backlog.md`:** E7M2-07 when complete.
|
||||
|
||||
### 5. Relationship to adjacent hooks
|
||||
|
||||
| Event | Layer | Story |
|
||||
|-------|-------|-------|
|
||||
| **`quest_complete`** | **`QuestStateOperations.TryMarkComplete`** after store mark | NEO-121 |
|
||||
| **`reward_delivery`** | **`RewardRouterOperations`** after delivery store record | NEO-130 |
|
||||
| **`perk_unlock`** | **`PerkUnlockEngine.TryUnlockPerks`** when skill XP grants unlock perks | NEO-49 |
|
||||
| **`unlock_granted`** | **`RewardRouterOperations`** stub (future **`UnlockGrant`** apply) | NEO-130 |
|
||||
|
||||
Deliver-then-mark (NEO-128): **`reward_delivery`** fires **before** **`quest_complete`** in the same completion call stack — expected ordering for E9.M1 correlation.
|
||||
|
||||
## Files to add
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `docs/manual-qa/NEO-130.md` | Manual QA: verify comment blocks + no behavior/API change. |
|
||||
| `docs/plans/NEO-130-implementation-plan.md` | This plan. |
|
||||
|
||||
## Files to modify
|
||||
|
||||
| Path | Rationale |
|
||||
|------|-----------|
|
||||
| `server/NeonSprawl.Server/Game/Rewards/RewardRouterOperations.cs` | **NEO-130:** comment-only **`reward_delivery`** + **`unlock_granted`** stub hook sites. |
|
||||
| `server/NeonSprawl.Server/Game/Rewards/RewardDeliveryEvent.cs` | Cross-reference **`reward_delivery`** hook anchor in type summary (no behavior change). |
|
||||
| `server/README.md` | Document reward telemetry hook placement and Epic 7 Slice 2 vocabulary (NEO-130). |
|
||||
| `docs/decomposition/modules/E7_M2_RewardAndUnlockRouter.md` | Implementation snapshot: NEO-130 telemetry hooks landed. |
|
||||
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E7.M2 row: NEO-130 hook sites. |
|
||||
| `docs/plans/E7M2-prototype-backlog.md` | E7M2-07 checkboxes + landed note. |
|
||||
|
||||
## Tests
|
||||
|
||||
| Test file | What it covers |
|
||||
|-----------|----------------|
|
||||
| *(none added or changed)* | Comments-only change; no behavior or wire contract change. Regression: `dotnet test NeonSprawl.sln` (existing **`RewardRouterOperationsTests`**, quest wiring tests unchanged). Manual verification: **`docs/manual-qa/NEO-130.md`**. |
|
||||
|
||||
## Open questions / risks
|
||||
|
||||
| Question / risk | Agent recommendation | Status |
|
||||
|-----------------|----------------------|--------|
|
||||
| **Duplicate `reward_delivery` vs `quest_complete`** | Keep both — state transition vs grant audit are distinct catalog events; E9.M1 correlates by `playerId` + `questId` + timestamp. | **adopted** |
|
||||
| **`unlock_granted` vs `perk_unlock`** | Stub documents future content **`UnlockGrant`** rows; do not conflate with perk engine side effects from skill XP. | **adopted** |
|
||||
| **Encounter `reward_delivery` TODO (NEO-107)** | Leave E5.M3 producer comments as-is; NEO-130 anchors quest-router consumer path only. | **adopted** |
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
# Code review — NEO-130 (E7M2-07)
|
||||
|
||||
**Date:** 2026-06-07
|
||||
**Scope:** Branch `NEO-130-reward-telemetry-hooks` vs `origin/main` — commits `7493a6b` … `c461a82`
|
||||
**Base:** `origin/main`
|
||||
**Follow-up:** Review suggestions/nits addressed in a subsequent commit on the same branch.
|
||||
|
||||
## Verdict
|
||||
|
||||
**Approve with nits**
|
||||
|
||||
## Summary
|
||||
|
||||
NEO-130 adds **comment-only** E9.M1 telemetry hook sites to **`RewardRouterOperations.TryDeliverQuestCompletion`** for Epic 7 Slice 2 events **`reward_delivery`** (after first-time **`IRewardDeliveryStore.TryRecord`** success) and **`unlock_granted`** (stub region after item + skill XP apply, before **`RewardDeliveryEvent`** construction). Hook placement correctly excludes idempotent **`TryGet`** replay, deny paths, and **`TryRecord`** race-loser rollback. No runtime ingest, **`ILogger`**, wire contract, or API changes. Documentation matches NEO-121/NEO-49 precedent: implementation plan reconciliation, **`server/README.md`** event table, E7.M2 module snapshot, alignment register, E7M2-07 backlog checkboxes, **`RewardDeliveryEvent`** xml cross-ref, and **`docs/manual-qa/NEO-130.md`**. Infrastructure-only slice with no client counterpart. Risk is negligible.
|
||||
|
||||
## Documentation checked
|
||||
|
||||
| Path | Result |
|
||||
|------|--------|
|
||||
| `docs/plans/NEO-130-implementation-plan.md` | **Matches** — kickoff timing decisions adopted; acceptance checklist checked; reconciliation accurate. |
|
||||
| `docs/plans/E7M2-prototype-backlog.md` (E7M2-07) | **Matches** — acceptance checkbox checked; landed note + README link. |
|
||||
| `docs/decomposition/modules/E7_M2_RewardAndUnlockRouter.md` | **Matches** — NEO-130 telemetry hook snapshot added under implementation anchor. |
|
||||
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Matches** — E7M2-07 / NEO-130 landed note appended; row stays **Planned** until NEO-132 capstone. |
|
||||
| `docs/decomposition/modules/module_dependency_register.md` | **Matches** — no register row change required (detail lives in alignment note). |
|
||||
| `server/README.md` | **Matches** — Reward telemetry hooks subsection with event table, delegation note, and **`perk_unlock`** distinction. |
|
||||
| `docs/manual-qa/NEO-130.md` | **Matches** — comment-only verification checklist; optional regression/Bruno steps. |
|
||||
| Full-stack epic decomposition | **N/A** — infrastructure-only; plan and backlog explicitly document no client counterpart. |
|
||||
|
||||
**Register / tracking:** E7.M2 correctly remains **Planned** until client capstone NEO-132; E7M2-07 completion noted in alignment register and module snapshot only — no further status change required.
|
||||
|
||||
## Blocking issues
|
||||
|
||||
(none)
|
||||
|
||||
## Suggestions
|
||||
|
||||
1. ~~**E9.M1 `unlock_granted` wiring note** — The stub sits before **`TryRecord`**, so a future literal emit at this line would also run on the race-rollback path (grants applied then reverted). When **`UnlockGrant`** rows land, place per-row emits inside the apply loop and/or gate on durable commit (mirror **`reward_delivery`** after **`TryRecord`** success). The stub comment already says “once per applied UnlockGrant row”; consider adding one line that durable emit should not fire on rollback paths when E9.M1 implements the loop.~~ **Done.** Stub comment now gates E9.M1 durable emit to apply-loop and/or post-**`TryRecord`** paths only (not race rollback).
|
||||
|
||||
## Nits
|
||||
|
||||
- ~~Nit: **`unlock_granted`** stub runs even for empty bundles (no item/skill rows) — correct for a region marker; E9.M1 loop will simply emit nothing until unlock rows exist in content.~~ **Done.** Acknowledged; no change.
|
||||
|
||||
- ~~Nit: Plan technical approach lists class-summary update on **`RewardRouterOperations`**; shipped in xml summary — reconciliation bullet could mention it for parity with other stories (cosmetic doc only).~~ **Done.** Reconciliation bullet updated.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
# Router-focused subset (14 passed at review time)
|
||||
dotnet test NeonSprawl.sln --filter "FullyQualifiedName~RewardRouterOperations"
|
||||
|
||||
# Full regression (comments-only; no new tests)
|
||||
dotnet test NeonSprawl.sln
|
||||
```
|
||||
|
||||
Manual (per `docs/manual-qa/NEO-130.md`):
|
||||
|
||||
- Walk **`RewardRouterOperations.cs`** hook comment blocks; confirm **`TODO(E9.M1)`**, no **`ILogger`**, and no duplicate hooks in **`QuestStateOperations`**, **`QuestAcceptApi`**, or **`QuestProgressApi`**.
|
||||
- Optional Bruno sanity in `bruno/neon-sprawl-server/quest-progress/` — gather intro complete + health after reward wiring unchanged.
|
||||
|
|
@ -3,6 +3,7 @@ namespace NeonSprawl.Server.Game.Rewards;
|
|||
/// <summary>
|
||||
/// Idempotent quest completion reward delivery record (NEO-126).
|
||||
/// Persisted via <see cref="IRewardDeliveryStore"/> after router apply succeeds (NEO-127).
|
||||
/// E9.M1 <c>reward_delivery</c> hook site: <see cref="RewardRouterOperations"/> <c>TryDeliverQuestCompletion</c> (NEO-130).
|
||||
/// </summary>
|
||||
public readonly record struct RewardDeliveryEvent(
|
||||
string PlayerId,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace NeonSprawl.Server.Game.Rewards;
|
|||
/// <summary>
|
||||
/// Applies quest <see cref="QuestRewardBundleRow"/> completion grants idempotently (NEO-127).
|
||||
/// Quest-state wiring: <see cref="QuestStateOperations"/> (NEO-128).
|
||||
/// E9.M1 telemetry hook sites: <see cref="TryDeliverQuestCompletion"/> (NEO-130).
|
||||
/// </summary>
|
||||
public static class RewardRouterOperations
|
||||
{
|
||||
|
|
@ -134,6 +135,14 @@ public static class RewardRouterOperations
|
|||
perksUnlockedByThisCall);
|
||||
}
|
||||
|
||||
// --- Telemetry hook site (NEO-130): future E9.M1 catalog event `unlock_granted` (stub) ---
|
||||
// TODO(E9.M1): catalog emit — once per applied UnlockGrant row when bundle unlock grants land (pre-production).
|
||||
// Prototype completionRewardBundle has item + skill XP rows only; no runtime unlock apply in Slice 2.
|
||||
// Planned payload fields: playerId, questId, unlock id / kind — finalize at E9.M1 wiring time.
|
||||
// Distinct from `perk_unlock` (PerkUnlockEngine, NEO-49) which may fire as a side effect of skill XP grants.
|
||||
// E9.M1 wiring: place per-row emits inside the apply loop and/or after TryRecord success only — not on
|
||||
// TryRecord race-rollback paths (grants applied then reverted below); mirror reward_delivery gating.
|
||||
|
||||
var deliveredAt = timeProvider.GetUtcNow();
|
||||
var deliveryEvent = new RewardDeliveryEvent(
|
||||
normalizedPlayerId,
|
||||
|
|
@ -145,6 +154,13 @@ public static class RewardRouterOperations
|
|||
|
||||
if (deliveryStore.TryRecord(deliveryEvent))
|
||||
{
|
||||
// --- Telemetry hook site (NEO-130): future E9.M1 catalog event `reward_delivery` ---
|
||||
// TODO(E9.M1): catalog emit — once per first-time quest completion delivery (TryRecord success).
|
||||
// Not on TryGet idempotent replay, deny paths, or TryRecord race-loser rollback paths.
|
||||
// Planned payload fields: playerId, questId, deliveredAt, grantedItems (itemId, quantity),
|
||||
// grantedSkillXp (skillId, amount), idempotencyKey ({playerId}:quest_complete:{questId}).
|
||||
// No ingest or ILogger here (comments-only).
|
||||
|
||||
return SuccessFromEvent(deliveryEvent, reasonCode: null);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -306,6 +306,17 @@ Encounter loot remains on **`IEncounterCompleteEventStore`** (E5.M3); quest comp
|
|||
|
||||
Apply order: **items first**, then **skill XP**. Inventory deny or skill deny fails closed without store record (compensating item rollback on partial apply or skill failure after items). Idempotent replay checks **`TryGet`** before apply. If grants succeed but **`TryRecord`** returns `false` (concurrent race), this call **rolls back** its item grants and calls **`SkillProgressionGrantOperations.TryRevertAppliedSkillGrants`** — negative XP delta per applied skill row plus removal of perks unlocked during this call only — then **`ReevaluateAfterLevelUp`** at post-rollback levels so a concurrent winner's path-auto unlocks are preserved — then returns the existing store event (mirror **`EncounterCompletionOperations`** compensating rollback when **`TryMarkCompleted`** loses). **Quest-state wiring (NEO-128):** **`QuestStateOperations.TryMarkComplete`** and **`QuestObjectiveWiring`** terminal-step completion call this operation before **`IPlayerQuestStateStore.TryMarkComplete`**. Plans: [NEO-127](../../docs/plans/NEO-127-implementation-plan.md), [NEO-128](../../docs/plans/NEO-128-implementation-plan.md).
|
||||
|
||||
### Reward telemetry hooks (NEO-130)
|
||||
|
||||
Comment-only hook sites in **`RewardRouterOperations.TryDeliverQuestCompletion`** for future E9.M1 catalog events ([Epic 7 Slice 2](../../docs/decomposition/modules/E7_M2_RewardAndUnlockRouter.md#related-implementation-slices)). **`TODO(E9.M1)`** — no production ingest. **`QuestStateOperations`** and HTTP quest routes delegate to the router only (no duplicate API-layer or quest-state hooks for these names). **`quest_complete`** remains in **`QuestStateOperations`** ([NEO-121](#quest-telemetry-hooks-neo-121)); **`reward_delivery`** fires earlier on the deliver-then-mark path (NEO-128).
|
||||
|
||||
| Event | Anchor | When |
|
||||
|-------|--------|------|
|
||||
| **`reward_delivery`** | **`TryDeliverQuestCompletion`** | After **`TryRecord`** returns **`true`** on first-time delivery (not idempotent **`TryGet`** replay or race-loser paths). |
|
||||
| **`unlock_granted`** | **`TryDeliverQuestCompletion`** (stub) | Future **`UnlockGrant`** apply from bundle rows — prototype bundles have item + skill XP only; no runtime unlock apply in Slice 2. |
|
||||
|
||||
**`perk_unlock`** side effects from skill XP grants use **`PerkUnlockEngine`** ([NEO-49](#perk-unlock-engine-and-telemetry-hooks-neo-47-neo-49)) — distinct from content **`UnlockGrant`** rows. Manual QA: [`docs/manual-qa/NEO-130.md`](../../docs/manual-qa/NEO-130.md); plan: [NEO-130 implementation plan](../../docs/plans/NEO-130-implementation-plan.md).
|
||||
|
||||
## Encounter definitions (NEO-103)
|
||||
|
||||
**`GET /game/world/encounter-definitions`** returns a versioned JSON body (`schemaVersion` **1**, **`encounters`**) backed by **`IEncounterDefinitionRegistry`** and **`IRewardTableDefinitionRegistry`** — the same prototype rows loaded at startup (no second source of truth). Each row includes **`id`**, **`displayName`**, nested **`completionCriteria`** (`kind`), **`requiredNpcInstanceIds`**, and nested **`rewardTable`** (`id`, `displayName`, **`fixedGrants`** with `itemId` + `quantity`). Plan: [NEO-103 implementation plan](../../docs/plans/NEO-103-implementation-plan.md); Bruno: `bruno/neon-sprawl-server/encounter-definitions/`.
|
||||
|
|
|
|||
Loading…
Reference in New Issue