NEO-109: Add Slice 3 encounter telemetry hook comments.

Document encounter_start, encounter_complete, reward_attribution, and
encounter_complete_denied at engine anchors; remove wiring duplicate.
pull/148/head
VinPropane 2026-05-31 19:03:03 -04:00
parent e6debc2968
commit 1a7b5312c5
7 changed files with 37 additions and 8 deletions

View File

@ -8,7 +8,7 @@ namespace NeonSprawl.Server.Game.Combat;
public static class CombatOperations public static class CombatOperations
{ {
// --- Reserved telemetry hook sites (NEO-84): no emit until future modules --- // --- 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). // player_death — future player HP / death resolution (player HP out of scope in Slice 1).
/// <summary> /// <summary>
/// Resolves one ability against one prototype combat target using catalog damage and the health store. /// Resolves one ability against one prototype combat target using catalog damage and the health store.

View File

@ -88,10 +88,8 @@ public static class EncounterCombatWiring
playerId, playerId,
encounterId, encounterId,
completion.ReasonCode); completion.ReasonCode);
return;
} }
// --- Telemetry hook site (NEO-109): future E9.M1 catalog event `encounter_complete` --- // encounter_complete / reward_attribution hooks: EncounterCompletionOperations.TryCompleteAndGrant (NEO-109).
// TODO(E9.M1): catalog emit — once per player+encounter when grants commit (NEO-106 cast path).
} }
} }

View File

@ -4,6 +4,8 @@ namespace NeonSprawl.Server.Game.Encounters;
/// Internal completion payload returned on successful encounter grant (NEO-105). /// Internal completion payload returned on successful encounter grant (NEO-105).
/// Persisted via <see cref="IEncounterCompleteEventStore"/> on success (NEO-107). /// Persisted via <see cref="IEncounterCompleteEventStore"/> on success (NEO-107).
/// E7.M2 quest-credit routing consumes this record (hook stub on NEO-107). /// E7.M2 quest-credit routing consumes this record (hook stub on NEO-107).
/// NEO-109 telemetry: <c>encounter_complete</c> / <c>reward_attribution</c> hook sites in
/// <see cref="EncounterCompletionOperations.TryCompleteAndGrant"/>.
/// </summary> /// </summary>
public readonly record struct EncounterCompleteEvent( public readonly record struct EncounterCompleteEvent(
string PlayerId, string PlayerId,

View File

@ -5,6 +5,8 @@ namespace NeonSprawl.Server.Game.Encounters;
/// <summary> /// <summary>
/// Applies encounter reward-table grants and marks completion idempotently (NEO-105). /// Applies encounter reward-table grants and marks completion idempotently (NEO-105).
/// Combat defeat wiring: <see cref="AbilityInput.AbilityCastApi"/> (NEO-106). /// Combat defeat wiring: <see cref="AbilityInput.AbilityCastApi"/> (NEO-106).
/// NEO-109 telemetry hook sites: <see cref="TryCompleteAndGrant"/> (<c>encounter_complete</c>, <c>reward_attribution</c>),
/// <see cref="Deny"/> (<c>encounter_complete_denied</c>).
/// </summary> /// </summary>
public static class EncounterCompletionOperations public static class EncounterCompletionOperations
{ {
@ -122,7 +124,16 @@ public static class EncounterCompletionOperations
// TODO(E7.M2): reward_delivery — QuestRewardBundle router consumes EncounterCompleteEvent // TODO(E7.M2): reward_delivery — QuestRewardBundle router consumes EncounterCompleteEvent
// (playerId, encounterId, idempotencyKey, grantedItemSummary) → idempotent RewardDeliveryEvent. // (playerId, encounterId, idempotencyKey, grantedItemSummary) → idempotent RewardDeliveryEvent.
// Prototype quest credit: contract_handoff_token item loot + this event record; full router: E7.M2. // 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( return new EncounterCompletionResult(
Success: true, Success: true,

View File

@ -3,6 +3,7 @@ namespace NeonSprawl.Server.Game.Encounters;
/// <summary> /// <summary>
/// Server-internal encounter completion resolution envelope (NEO-105). /// Server-internal encounter completion resolution envelope (NEO-105).
/// Combat wiring promotes via NEO-106; HTTP DTOs via NEO-108. /// Combat wiring promotes via NEO-106; HTTP DTOs via NEO-108.
/// NEO-109 telemetry hook sites live in <see cref="EncounterCompletionOperations.TryCompleteAndGrant"/>.
/// </summary> /// </summary>
/// <param name="GrantsApplied">Item grants on success; empty when denied.</param> /// <param name="GrantsApplied">Item grants on success; empty when denied.</param>
/// <param name="CompleteEvent">Completion payload on success; null when denied.</param> /// <param name="CompleteEvent">Completion payload on success; null when denied.</param>

View File

@ -3,6 +3,7 @@ namespace NeonSprawl.Server.Game.Encounters;
/// <summary> /// <summary>
/// Encounter activation and defeat-all progress orchestration (NEO-104). /// Encounter activation and defeat-all progress orchestration (NEO-104).
/// Combat cast wiring: <see cref="AbilityInput.AbilityCastApi"/> (NEO-106). /// Combat cast wiring: <see cref="AbilityInput.AbilityCastApi"/> (NEO-106).
/// NEO-109 telemetry hook site: <see cref="TryActivateOnFirstEngagement"/> (<c>encounter_start</c>).
/// </summary> /// </summary>
public static class EncounterProgressOperations public static class EncounterProgressOperations
{ {
@ -63,7 +64,10 @@ public static class EncounterProgressOperations
} }
// --- Telemetry hook site (NEO-109): future E9.M1 catalog event `encounter_start` --- // --- 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); return progressStore.TryActivate(playerId, encounterId);
} }

View File

@ -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" 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) ## Encounter combat wiring (NEO-106)
On every successful **`POST …/ability-cast`** accept, **`EncounterCombatWiring.TryProcessCastOutcome`** (best-effort; does not change cast JSON): On every successful **`POST …/ability-cast`** accept, **`EncounterCombatWiring.TryProcessCastOutcome`** (best-effort; does not change cast JSON):
| Cast outcome | Encounter effect | | 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`**. | | **`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). | | 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). - **`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). - **`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). - **`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). 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).