diff --git a/server/NeonSprawl.Server/Game/Combat/CombatOperations.cs b/server/NeonSprawl.Server/Game/Combat/CombatOperations.cs index 8de24f2..af6119c 100644 --- a/server/NeonSprawl.Server/Game/Combat/CombatOperations.cs +++ b/server/NeonSprawl.Server/Game/Combat/CombatOperations.cs @@ -8,7 +8,7 @@ namespace NeonSprawl.Server.Game.Combat; public static class CombatOperations { // --- Reserved telemetry hook sites (NEO-84): no emit until future modules --- - // encounter_start — future E5.M3 encounter spawn/engage path (not wired in Slice 1). + // encounter_start — E5.M3 anchor: EncounterProgressOperations.TryActivateOnFirstEngagement (NEO-109). // player_death — future player HP / death resolution (player HP out of scope in Slice 1). /// /// Resolves one ability against one prototype combat target using catalog damage and the health store. diff --git a/server/NeonSprawl.Server/Game/Encounters/EncounterCombatWiring.cs b/server/NeonSprawl.Server/Game/Encounters/EncounterCombatWiring.cs index 3072c58..a5a036b 100644 --- a/server/NeonSprawl.Server/Game/Encounters/EncounterCombatWiring.cs +++ b/server/NeonSprawl.Server/Game/Encounters/EncounterCombatWiring.cs @@ -88,10 +88,8 @@ public static class EncounterCombatWiring playerId, encounterId, completion.ReasonCode); - return; } - // --- Telemetry hook site (NEO-109): future E9.M1 catalog event `encounter_complete` --- - // TODO(E9.M1): catalog emit — once per player+encounter when grants commit (NEO-106 cast path). + // encounter_complete / reward_attribution hooks: EncounterCompletionOperations.TryCompleteAndGrant (NEO-109). } } diff --git a/server/NeonSprawl.Server/Game/Encounters/EncounterCompleteEvent.cs b/server/NeonSprawl.Server/Game/Encounters/EncounterCompleteEvent.cs index 3b4e238..c48e4c4 100644 --- a/server/NeonSprawl.Server/Game/Encounters/EncounterCompleteEvent.cs +++ b/server/NeonSprawl.Server/Game/Encounters/EncounterCompleteEvent.cs @@ -4,6 +4,8 @@ namespace NeonSprawl.Server.Game.Encounters; /// Internal completion payload returned on successful encounter grant (NEO-105). /// Persisted via on success (NEO-107). /// E7.M2 quest-credit routing consumes this record (hook stub on NEO-107). +/// NEO-109 telemetry: encounter_complete / reward_attribution hook sites in +/// . /// public readonly record struct EncounterCompleteEvent( string PlayerId, diff --git a/server/NeonSprawl.Server/Game/Encounters/EncounterCompletionOperations.cs b/server/NeonSprawl.Server/Game/Encounters/EncounterCompletionOperations.cs index 7bb2136..5e621f4 100644 --- a/server/NeonSprawl.Server/Game/Encounters/EncounterCompletionOperations.cs +++ b/server/NeonSprawl.Server/Game/Encounters/EncounterCompletionOperations.cs @@ -5,6 +5,8 @@ namespace NeonSprawl.Server.Game.Encounters; /// /// Applies encounter reward-table grants and marks completion idempotently (NEO-105). /// Combat defeat wiring: (NEO-106). +/// NEO-109 telemetry hook sites: (encounter_complete, reward_attribution), +/// (encounter_complete_denied). /// public static class EncounterCompletionOperations { @@ -122,7 +124,16 @@ public static class EncounterCompletionOperations // TODO(E7.M2): reward_delivery — QuestRewardBundle router consumes EncounterCompleteEvent // (playerId, encounterId, idempotencyKey, grantedItemSummary) → idempotent RewardDeliveryEvent. // Prototype quest credit: contract_handoff_token item loot + this event record; full router: E7.M2. - // TODO(E9.M1): catalog emit — encounter_complete on successful grant commit (NEO-109). + + // --- Telemetry hook site (NEO-109): future E9.M1 catalog event `encounter_complete` --- + // TODO(E9.M1): catalog emit — once per player+encounter on successful grant + completion mark commit. + // Planned payload: playerId, encounterId, rewardTableId, completedAt, idempotencyKey, grantedItems summary + // (EncounterCompleteEvent). No ingest or ILogger here (comments-only). EncounterCombatWiring delegates here. + + // --- Telemetry hook site (NEO-109): future E9.M1 catalog event `reward_attribution` --- + // TODO(E9.M1): catalog emit — once per successful completion (batch attribution, not per TryAddStack). + // Planned payload: playerId, encounterId, rewardTableId, grantedItems[] (EncounterGrantApplied batch). + // Inventory layer still emits item_created per stack via PlayerInventoryOperations (NEO-56). return new EncounterCompletionResult( Success: true, diff --git a/server/NeonSprawl.Server/Game/Encounters/EncounterCompletionResult.cs b/server/NeonSprawl.Server/Game/Encounters/EncounterCompletionResult.cs index a8c66f5..de2b0f4 100644 --- a/server/NeonSprawl.Server/Game/Encounters/EncounterCompletionResult.cs +++ b/server/NeonSprawl.Server/Game/Encounters/EncounterCompletionResult.cs @@ -3,6 +3,7 @@ namespace NeonSprawl.Server.Game.Encounters; /// /// Server-internal encounter completion resolution envelope (NEO-105). /// Combat wiring promotes via NEO-106; HTTP DTOs via NEO-108. +/// NEO-109 telemetry hook sites live in . /// /// Item grants on success; empty when denied. /// Completion payload on success; null when denied. diff --git a/server/NeonSprawl.Server/Game/Encounters/EncounterProgressOperations.cs b/server/NeonSprawl.Server/Game/Encounters/EncounterProgressOperations.cs index 10128e5..19707ec 100644 --- a/server/NeonSprawl.Server/Game/Encounters/EncounterProgressOperations.cs +++ b/server/NeonSprawl.Server/Game/Encounters/EncounterProgressOperations.cs @@ -3,6 +3,7 @@ namespace NeonSprawl.Server.Game.Encounters; /// /// Encounter activation and defeat-all progress orchestration (NEO-104). /// Combat cast wiring: (NEO-106). +/// NEO-109 telemetry hook site: (encounter_start). /// public static class EncounterProgressOperations { @@ -63,7 +64,10 @@ public static class EncounterProgressOperations } // --- Telemetry hook site (NEO-109): future E9.M1 catalog event `encounter_start` --- - // TODO(E9.M1): catalog emit — on first activation for player+encounter (NEO-106 wires cast path). + // TODO(E9.M1): catalog emit — when TryActivate returns true (first activation for player+encounter). + // Planned payload: playerId, encounterId, npcInstanceId (engagement target). No ingest or ILogger here + // (comments-only). EncounterCombatWiring calls this on first damaging hit vs a required NPC (NEO-106). + return progressStore.TryActivate(playerId, encounterId); } diff --git a/server/README.md b/server/README.md index 06d6bb5..f189617 100644 --- a/server/README.md +++ b/server/README.md @@ -206,13 +206,26 @@ Prototype: defeat all three E5.M2 NPCs via cast → row **`prototype_combat_pock curl -sS -i "http://localhost:5253/game/players/dev-local-1/encounter-progress" ``` +## Encounter telemetry hooks (NEO-109) + +Comment-only hook sites for Epic 5 **Slice 3** catalog events ([epic_05 Slice 3](../../docs/decomposition/epics/epic_05_pve_combat.md#epic-5-slice-3)). **`TODO(E9.M1)`** — no production ingest. **`POST …/ability-cast`** and **`EncounterCombatWiring`** delegate to the engines below (no duplicate API-layer hooks). + +| Event | Anchor | +|-------|--------| +| **`encounter_start`** | **`EncounterProgressOperations.TryActivateOnFirstEngagement`** — emit when **`TryActivate`** returns true (first engagement). Activates NEO-84 reserved **`encounter_start`** pointer in **`CombatOperations`**. | +| **`encounter_complete`** | **`EncounterCompletionOperations.TryCompleteAndGrant`** — once per successful grant + completion mark commit. | +| **`reward_attribution`** | Same success path — once per completion (batch **`grantedItems`**); per-stack **`item_created`** remains in **`PlayerInventoryOperations`** ([NEO-56](#player-inventory-neo-54-store-neo-55-http)). | +| **`encounter_complete_denied`** | **`EncounterCompletionOperations.Deny`** — structured deny + **`EncounterCompletionReasonCodes`**. | + +Plan: [NEO-109 implementation plan](../../docs/plans/NEO-109-implementation-plan.md). + ## Encounter combat wiring (NEO-106) On every successful **`POST …/ability-cast`** accept, **`EncounterCombatWiring.TryProcessCastOutcome`** (best-effort; does not change cast JSON): | Cast outcome | Encounter effect | |--------------|------------------| -| **`damageDealt > 0`** vs a required NPC | **`EncounterProgressOperations.TryActivateOnFirstEngagement`** — starts **`prototype_combat_pocket`** progress (`encounter_start` hook site in NEO-104). | +| **`damageDealt > 0`** vs a required NPC | **`EncounterProgressOperations.TryActivateOnFirstEngagement`** — starts **`prototype_combat_pocket`** progress (**`encounter_start`** hook site, [NEO-109](#encounter-telemetry-hooks-neo-109)). | | **`targetDefeated: true`** | **`TryMarkTargetDefeated`**; when all three E5.M2 NPC ids defeated, **`EncounterCompletionOperations.TryCompleteAndGrant`**. | | Inventory full on completion | Defeat progress retained; completion/grants fail closed (retry when bag has space). | @@ -363,7 +376,7 @@ Comment-only hook sites in **`CombatOperations.TryResolve`** for future E9.M1 ca - **`ability_used`** — on every successful resolve (zero-damage abilities included). - **`enemy_defeat`** — only when lethal damage drives **`after.Defeated`** to **`true`** (re-hit on defeated targets denies at **`target_defeated`** before damage). -- **Reserved (comments only):** **`encounter_start`** (E5.M3 encounters). +- **Reserved (comments only):** **`encounter_start`** — E5.M3 anchor: [Encounter telemetry hooks (NEO-109)](#encounter-telemetry-hooks-neo-109). - **`player_death`** — only when lethal NPC damage drives player **`currentHp`** to **0** (comment hook in NEO-95; full event deferred). E1.M4 cast funnel events **`ability_cast_requested`** / **`ability_cast_denied`** remain in **`AbilityCastApi`** ([NEO-30](#ability-cast-neo-31-neo-82)). E9.M1 ingest should correlate route **`playerId`** with engine payload fields at accept. Plan: [NEO-84 implementation plan](../../docs/plans/NEO-84-implementation-plan.md).