NEO-108: Add Bruno smokes and document encounter-progress GET.
Bruno folder covers inactive default and three-NPC defeat spine; README and E5.M3 docs mark E5M3-08 landed with plan reconciliation.pull/147/head
parent
c1fd2053d0
commit
07ebb9efb8
|
|
@ -0,0 +1,151 @@
|
||||||
|
meta {
|
||||||
|
name: GET encounter progress after defeat spine
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
docs {
|
||||||
|
NEO-108 AC: three-NPC defeat chain via cast (slot 3), then GET shows completed once with frozen grant summary.
|
||||||
|
}
|
||||||
|
|
||||||
|
script:pre-request {
|
||||||
|
const axios = require("axios");
|
||||||
|
const { resetPrototypeCombatTargets } = require("../scripts/combat-targets-reset-helper.js");
|
||||||
|
const baseUrl = bru.getEnvVar("baseUrl") || bru.getVar("baseUrl");
|
||||||
|
const playerId = bru.getEnvVar("playerId") || bru.getVar("playerId");
|
||||||
|
const jsonHeaders = { headers: { "Content-Type": "application/json" } };
|
||||||
|
const slotIndex = 3;
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowForPocket(body) {
|
||||||
|
const row = body.encounters.find((x) => x.encounterId === "prototype_combat_pocket");
|
||||||
|
if (!row) {
|
||||||
|
throw new Error(`missing prototype_combat_pocket row: ${JSON.stringify(body)}`);
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getProgress() {
|
||||||
|
const response = await axios.get(
|
||||||
|
`${baseUrl}/game/players/${playerId}/encounter-progress`,
|
||||||
|
jsonHeaders,
|
||||||
|
);
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error(`encounter-progress GET failed: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function defeatNpc(targetId) {
|
||||||
|
await axios.post(
|
||||||
|
`${baseUrl}/game/players/${playerId}/target/select`,
|
||||||
|
{ schemaVersion: 1, targetId },
|
||||||
|
jsonHeaders,
|
||||||
|
);
|
||||||
|
const castBody = {
|
||||||
|
schemaVersion: 1,
|
||||||
|
slotIndex,
|
||||||
|
abilityId: "prototype_pulse",
|
||||||
|
targetId,
|
||||||
|
};
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const response = await axios.post(
|
||||||
|
`${baseUrl}/game/players/${playerId}/ability-cast`,
|
||||||
|
castBody,
|
||||||
|
jsonHeaders,
|
||||||
|
);
|
||||||
|
const body = response.data;
|
||||||
|
if (!body?.accepted || !body?.combatResolution) {
|
||||||
|
throw new Error(`cast ${i + 1} vs ${targetId} failed: ${JSON.stringify(body)}`);
|
||||||
|
}
|
||||||
|
if (body.combatResolution.targetDefeated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (i < 3) {
|
||||||
|
await sleep(3200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(`did not defeat ${targetId} within 4 pulses`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await resetPrototypeCombatTargets(bru);
|
||||||
|
|
||||||
|
await axios.post(
|
||||||
|
`${baseUrl}/game/players/${playerId}/move`,
|
||||||
|
{ schemaVersion: 1, target: { x: -5, y: 0.9, z: -5 } },
|
||||||
|
jsonHeaders,
|
||||||
|
);
|
||||||
|
|
||||||
|
await axios.post(
|
||||||
|
`${baseUrl}/game/players/${playerId}/hotbar-loadout`,
|
||||||
|
{
|
||||||
|
schemaVersion: 1,
|
||||||
|
slots: [{ slotIndex, abilityId: "prototype_pulse" }],
|
||||||
|
},
|
||||||
|
jsonHeaders,
|
||||||
|
);
|
||||||
|
|
||||||
|
await defeatNpc("prototype_npc_melee");
|
||||||
|
const afterOne = await getProgress();
|
||||||
|
const rowOne = rowForPocket(afterOne);
|
||||||
|
if (rowOne.state !== "active" || rowOne.defeatedTargetIds.length !== 1) {
|
||||||
|
throw new Error(`after melee: expected active with 1 defeat, got ${JSON.stringify(rowOne)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await defeatNpc("prototype_npc_ranged");
|
||||||
|
const afterTwo = await getProgress();
|
||||||
|
const rowTwo = rowForPocket(afterTwo);
|
||||||
|
if (rowTwo.state !== "active" || rowTwo.defeatedTargetIds.length !== 2) {
|
||||||
|
throw new Error(`after ranged: expected active with 2 defeats, got ${JSON.stringify(rowTwo)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await defeatNpc("prototype_npc_elite");
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{baseUrl}}/game/players/{{playerId}}/encounter-progress
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
script:post-response {
|
||||||
|
const axios = require("axios");
|
||||||
|
const baseUrl = bru.getEnvVar("baseUrl") || bru.getVar("baseUrl");
|
||||||
|
const playerId = bru.getEnvVar("playerId") || bru.getVar("playerId");
|
||||||
|
const second = await axios.get(`${baseUrl}/game/players/${playerId}/encounter-progress`);
|
||||||
|
bru.setVar("neo108SecondGetJson", JSON.stringify(second.data));
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("completed row shows three defeats and grant summary", function () {
|
||||||
|
const body = res.getBody();
|
||||||
|
expect(res.getStatus()).to.equal(200);
|
||||||
|
expect(body.schemaVersion).to.equal(1);
|
||||||
|
const row = body.encounters.find((x) => x.encounterId === "prototype_combat_pocket");
|
||||||
|
expect(row).to.be.an("object");
|
||||||
|
expect(row.state).to.equal("completed");
|
||||||
|
expect(row.defeatedTargetIds).to.eql([
|
||||||
|
"prototype_npc_elite",
|
||||||
|
"prototype_npc_melee",
|
||||||
|
"prototype_npc_ranged",
|
||||||
|
]);
|
||||||
|
expect(row.completedAt).to.be.a("string");
|
||||||
|
expect(row.rewardGrantSummary).to.eql([
|
||||||
|
{ itemId: "scrap_metal_bulk", quantity: 10 },
|
||||||
|
{ itemId: "contract_handoff_token", quantity: 1 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("second GET still completed (idempotent read)", function () {
|
||||||
|
const second = JSON.parse(bru.getVar("neo108SecondGetJson"));
|
||||||
|
const row = second.encounters.find((x) => x.encounterId === "prototype_combat_pocket");
|
||||||
|
expect(row.state).to.equal("completed");
|
||||||
|
expect(row.rewardGrantSummary).to.eql([
|
||||||
|
{ itemId: "scrap_metal_bulk", quantity: 10 },
|
||||||
|
{ itemId: "contract_handoff_token", quantity: 1 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
meta {
|
||||||
|
name: GET encounter progress inactive
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
docs {
|
||||||
|
NEO-108: fresh player row before engagement — inactive with empty defeatedTargetIds.
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{baseUrl}}/game/players/{{playerId}}/encounter-progress
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("returns 200 JSON with schema v1", function () {
|
||||||
|
expect(res.getStatus()).to.equal(200);
|
||||||
|
const body = res.getBody();
|
||||||
|
expect(body.schemaVersion).to.equal(1);
|
||||||
|
expect(body.playerId).to.equal(bru.getEnvVar("playerId") || bru.getVar("playerId"));
|
||||||
|
expect(body.encounters).to.be.an("array");
|
||||||
|
expect(body.encounters.length).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("prototype_combat_pocket is inactive with no defeats", function () {
|
||||||
|
const body = res.getBody();
|
||||||
|
const row = body.encounters.find((x) => x.encounterId === "prototype_combat_pocket");
|
||||||
|
expect(row).to.be.an("object");
|
||||||
|
expect(row.state).to.equal("inactive");
|
||||||
|
expect(row.defeatedTargetIds).to.eql([]);
|
||||||
|
expect(row.completedAt).to.equal(undefined);
|
||||||
|
expect(row.rewardGrantSummary).to.equal(undefined);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
meta {
|
||||||
|
name: encounter-progress
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
| **Module ID** | E5.M3 |
|
| **Module ID** | E5.M3 |
|
||||||
| **Epic** | [Epic 5 — PvE Combat](../epics/epic_05_pve_combat.md) |
|
| **Epic** | [Epic 5 — PvE Combat](../epics/epic_05_pve_combat.md) |
|
||||||
| **Stage target** | Prototype |
|
| **Stage target** | Prototype |
|
||||||
| **Status** | In Progress — Slice 3 backlog [E5M3-prototype-backlog.md](../../plans/E5M3-prototype-backlog.md): **E5M3-01** [NEO-100](https://linear.app/neon-sprawl/issue/NEO-100) catalog + CI landed · **E5M3-02** [NEO-101](https://linear.app/neon-sprawl/issue/NEO-101) server load landed · **E5M3-03** [NEO-102](https://linear.app/neon-sprawl/issue/NEO-102) registries landed · **E5M3-04** [NEO-103](https://linear.app/neon-sprawl/issue/NEO-103) HTTP read landed · **E5M3-05** [NEO-104](https://linear.app/neon-sprawl/issue/NEO-104) progress + completion stores landed · **E5M3-06** [NEO-105](https://linear.app/neon-sprawl/issue/NEO-105) completion grants landed · **E5M3-07** [NEO-106](https://linear.app/neon-sprawl/issue/NEO-106) combat wiring landed · **E5M3-09** [NEO-107](https://linear.app/neon-sprawl/issue/NEO-107) event record landed · **E5M3-08** [NEO-108](https://linear.app/neon-sprawl/issue/NEO-108) → **E5M3-12** [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111) |
|
| **Status** | In Progress — Slice 3 backlog [E5M3-prototype-backlog.md](../../plans/E5M3-prototype-backlog.md): **E5M3-01** [NEO-100](https://linear.app/neon-sprawl/issue/NEO-100) catalog + CI landed · **E5M3-02** [NEO-101](https://linear.app/neon-sprawl/issue/NEO-101) server load landed · **E5M3-03** [NEO-102](https://linear.app/neon-sprawl/issue/NEO-102) registries landed · **E5M3-04** [NEO-103](https://linear.app/neon-sprawl/issue/NEO-103) HTTP read landed · **E5M3-05** [NEO-104](https://linear.app/neon-sprawl/issue/NEO-104) progress + completion stores landed · **E5M3-06** [NEO-105](https://linear.app/neon-sprawl/issue/NEO-105) completion grants landed · **E5M3-07** [NEO-106](https://linear.app/neon-sprawl/issue/NEO-106) combat wiring landed · **E5M3-09** [NEO-107](https://linear.app/neon-sprawl/issue/NEO-107) event record landed · **E5M3-08** [NEO-108](https://linear.app/neon-sprawl/issue/NEO-108) per-player GET landed · **E5M3-12** [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111) |
|
||||||
| **Linear** | Label **`E5.M3`** · [E5M3-prototype-backlog.md](../../plans/E5M3-prototype-backlog.md) **E5M3-01** [NEO-100](https://linear.app/neon-sprawl/issue/NEO-100) · **E5M3-02** [NEO-101](https://linear.app/neon-sprawl/issue/NEO-101) · **E5M3-03** [NEO-102](https://linear.app/neon-sprawl/issue/NEO-102) · **E5M3-04** [NEO-103](https://linear.app/neon-sprawl/issue/NEO-103) · **E5M3-05** [NEO-104](https://linear.app/neon-sprawl/issue/NEO-104) · **E5M3-06** [NEO-105](https://linear.app/neon-sprawl/issue/NEO-105) · **E5M3-07** [NEO-106](https://linear.app/neon-sprawl/issue/NEO-106) · **E5M3-08** [NEO-108](https://linear.app/neon-sprawl/issue/NEO-108) · **E5M3-09** [NEO-107](https://linear.app/neon-sprawl/issue/NEO-107) · **E5M3-10** [NEO-109](https://linear.app/neon-sprawl/issue/NEO-109) · **E5M3-11** [NEO-110](https://linear.app/neon-sprawl/issue/NEO-110) · **E5M3-12** [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111) |
|
| **Linear** | Label **`E5.M3`** · [E5M3-prototype-backlog.md](../../plans/E5M3-prototype-backlog.md) **E5M3-01** [NEO-100](https://linear.app/neon-sprawl/issue/NEO-100) · **E5M3-02** [NEO-101](https://linear.app/neon-sprawl/issue/NEO-101) · **E5M3-03** [NEO-102](https://linear.app/neon-sprawl/issue/NEO-102) · **E5M3-04** [NEO-103](https://linear.app/neon-sprawl/issue/NEO-103) · **E5M3-05** [NEO-104](https://linear.app/neon-sprawl/issue/NEO-104) · **E5M3-06** [NEO-105](https://linear.app/neon-sprawl/issue/NEO-105) · **E5M3-07** [NEO-106](https://linear.app/neon-sprawl/issue/NEO-106) · **E5M3-08** [NEO-108](https://linear.app/neon-sprawl/issue/NEO-108) · **E5M3-09** [NEO-107](https://linear.app/neon-sprawl/issue/NEO-107) · **E5M3-10** [NEO-109](https://linear.app/neon-sprawl/issue/NEO-109) · **E5M3-11** [NEO-110](https://linear.app/neon-sprawl/issue/NEO-110) · **E5M3-12** [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111) |
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
@ -86,14 +86,16 @@ The **first shipped encounter + reward spine** is **frozen** for prototype tunin
|
||||||
|
|
||||||
**HTTP read model landed ([NEO-103](https://linear.app/neon-sprawl/issue/NEO-103)):** **`GET /game/world/encounter-definitions`** — `EncounterDefinitionsWorldApi` + DTOs in `Game/Encounters/`; nested **`rewardTable`** summary via **`IRewardTableDefinitionRegistry`**. Plan: [NEO-103 implementation plan](../../plans/NEO-103-implementation-plan.md); [server README — Encounter definitions](../../../server/README.md#encounter-definitions-neo-103); Bruno `bruno/neon-sprawl-server/encounter-definitions/`.
|
**HTTP read model landed ([NEO-103](https://linear.app/neon-sprawl/issue/NEO-103)):** **`GET /game/world/encounter-definitions`** — `EncounterDefinitionsWorldApi` + DTOs in `Game/Encounters/`; nested **`rewardTable`** summary via **`IRewardTableDefinitionRegistry`**. Plan: [NEO-103 implementation plan](../../plans/NEO-103-implementation-plan.md); [server README — Encounter definitions](../../../server/README.md#encounter-definitions-neo-103); Bruno `bruno/neon-sprawl-server/encounter-definitions/`.
|
||||||
|
|
||||||
**Progress + completion stores landed ([NEO-104](https://linear.app/neon-sprawl/issue/NEO-104)):** **`IEncounterProgressStore`** + **`IEncounterCompletionStore`** (in-memory) + **`EncounterProgressOperations`** — activation, order-independent defeat tracking, idempotent completion flag; combat/HTTP wiring deferred to NEO-106/108. Plan: [NEO-104 implementation plan](../../plans/NEO-104-implementation-plan.md); [server README — Encounter progress](../../../server/README.md#encounter-progress--completion-stores-neo-104).
|
**Progress + completion stores landed ([NEO-104](https://linear.app/neon-sprawl/issue/NEO-104)):** **`IEncounterProgressStore`** + **`IEncounterCompletionStore`** (in-memory) + **`EncounterProgressOperations`** — activation, order-independent defeat tracking, idempotent completion flag; combat wiring NEO-106, HTTP read NEO-108. Plan: [NEO-104 implementation plan](../../plans/NEO-104-implementation-plan.md); [server README — Encounter progress](../../../server/README.md#encounter-progress--completion-stores-neo-104).
|
||||||
|
|
||||||
**Completion + inventory grants landed ([NEO-105](https://linear.app/neon-sprawl/issue/NEO-105)):** **`EncounterCompletionOperations.TryCompleteAndGrant`** + **`EncounterCompleteEvent`** result payload — reward-table fixed grants via **`PlayerInventoryOperations`**, fail-closed inventory deny, idempotent completion mark; event record persisted via **`IEncounterCompleteEventStore`** ([NEO-107](https://linear.app/neon-sprawl/issue/NEO-107)). Plan: [NEO-105 implementation plan](../../plans/NEO-105-implementation-plan.md); [server README — Encounter completion](../../../server/README.md#encounter-completion--inventory-grants-neo-105).
|
**Completion + inventory grants landed ([NEO-105](https://linear.app/neon-sprawl/issue/NEO-105)):** **`EncounterCompletionOperations.TryCompleteAndGrant`** + **`EncounterCompleteEvent`** result payload — reward-table fixed grants via **`PlayerInventoryOperations`**, fail-closed inventory deny, idempotent completion mark; event record persisted via **`IEncounterCompleteEventStore`** ([NEO-107](https://linear.app/neon-sprawl/issue/NEO-107)). Plan: [NEO-105 implementation plan](../../plans/NEO-105-implementation-plan.md); [server README — Encounter completion](../../../server/README.md#encounter-completion--inventory-grants-neo-105).
|
||||||
|
|
||||||
**Combat wiring landed ([NEO-106](https://linear.app/neon-sprawl/issue/NEO-106)):** **`EncounterCombatWiring.TryProcessCastOutcome`** on **`AbilityCastApi`** — activate on first damaging hit, mark defeats, invoke completion grants when all required NPCs defeated; NEO-44 gig XP preserved. Per-player progress HTTP deferred to NEO-108. Plan: [NEO-106 implementation plan](../../plans/NEO-106-implementation-plan.md); [server README — Encounter combat wiring](../../../server/README.md#encounter-combat-wiring-neo-106).
|
**Combat wiring landed ([NEO-106](https://linear.app/neon-sprawl/issue/NEO-106)):** **`EncounterCombatWiring.TryProcessCastOutcome`** on **`AbilityCastApi`** — activate on first damaging hit, mark defeats, invoke completion grants when all required NPCs defeated; NEO-44 gig XP preserved. Plan: [NEO-106 implementation plan](../../plans/NEO-106-implementation-plan.md); [server README — Encounter combat wiring](../../../server/README.md#encounter-combat-wiring-neo-106).
|
||||||
|
|
||||||
**Complete event record landed ([NEO-107](https://linear.app/neon-sprawl/issue/NEO-107)):** **`IEncounterCompleteEventStore`** + in-memory implementation — idempotent **`EncounterCompleteEvent`** record on successful grant commit; E7.M2 **`reward_delivery`** hook stub (comments only). Plan: [NEO-107 implementation plan](../../plans/NEO-107-implementation-plan.md); [server README — Encounter complete event (NEO-107)](../../../server/README.md#encounter-complete-event-record-neo-107).
|
**Complete event record landed ([NEO-107](https://linear.app/neon-sprawl/issue/NEO-107)):** **`IEncounterCompleteEventStore`** + in-memory implementation — idempotent **`EncounterCompleteEvent`** record on successful grant commit; E7.M2 **`reward_delivery`** hook stub (comments only). Plan: [NEO-107 implementation plan](../../plans/NEO-107-implementation-plan.md); [server README — Encounter complete event (NEO-107)](../../../server/README.md#encounter-complete-event-record-neo-107).
|
||||||
|
|
||||||
|
**Per-player progress HTTP landed ([NEO-108](https://linear.app/neon-sprawl/issue/NEO-108)):** **`GET /game/players/{id}/encounter-progress`** — **`EncounterProgressApi`** + DTOs; projects progress, completion, and event stores; **`rewardGrantSummary`** from commit-time **`GrantedItems`**. Plan: [NEO-108 implementation plan](../../plans/NEO-108-implementation-plan.md); [server README — Per-player encounter progress (NEO-108)](../../../server/README.md#per-player-encounter-progress-neo-108); Bruno `bruno/neon-sprawl-server/encounter-progress/`. Client counterpart: [NEO-110](https://linear.app/neon-sprawl/issue/NEO-110).
|
||||||
|
|
||||||
## Source anchors
|
## Source anchors
|
||||||
|
|
||||||
- Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 5.
|
- Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 5.
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -80,7 +80,7 @@ Fleshed-out scope, contracts, and integration notes live in **per-module documen
|
||||||
|
|
||||||
**E5.M2 note:** Epic 5 **Slice 2** backlog in Linear ([Epic 5 — PvE Combat and Encounter Content](https://linear.app/neon-sprawl/project/epic-5-pve-combat-and-encounter-content-cf3c94c3-5346-40e0-8aaf-281e846f2846)): [NEO-87](https://linear.app/neon-sprawl/issue/NEO-87) → [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98); label **`E5.M2`**. Client capstone **E5M2-12** [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98). See [E5M2-prototype-backlog.md](../../plans/E5M2-prototype-backlog.md), [E5_M2_NpcAiAndBehaviorProfiles.md](E5_M2_NpcAiAndBehaviorProfiles.md). Upstream **E5.M1 Ready**. **NEO-87 landed:** frozen three-behavior catalog + CI ([NEO-87 plan](../../plans/NEO-87-implementation-plan.md)). **NEO-88 landed:** fail-fast server load of `content/npc-behaviors/*_npc_behaviors.json` ([NEO-88 plan](../../plans/NEO-88-implementation-plan.md)); [server README — NPC behavior catalog](../../../server/README.md#npc-behavior-catalog-contentnpc-behaviors-neo-88). **NEO-89 landed:** injectable **`INpcBehaviorDefinitionRegistry`** + DI ([NEO-89 plan](../../plans/NEO-89-implementation-plan.md)). **NEO-90 landed:** **`GET /game/world/npc-behavior-definitions`** — `NpcBehaviorDefinitionsWorldApi` + DTOs in `Game/Npc/` ([NEO-90 plan](../../plans/NEO-90-implementation-plan.md)); [server README — NPC behavior definitions (NEO-90)](../../../server/README.md#npc-behavior-definitions-neo-90); Bruno `bruno/neon-sprawl-server/npc-behavior-definitions/`. **NEO-91 landed:** **`PrototypeNpcRegistry`** + combat-target migration — three NPC instance ids, per-archetype catalog max HP ([NEO-91 plan](../../plans/NEO-91-implementation-plan.md)); [server README — Combat entity health (NEO-91)](../../../server/README.md#combat-entity-health-neo-80-neo-91). **NEO-92–NEO-96 landed:** aggro/threat, runtime state machine, **`GET …/npc-runtime-snapshot`**, player combat HP + NPC attack resolve, telemetry hooks ([NEO-92](../../plans/NEO-92-implementation-plan.md)–[NEO-96](../../plans/NEO-96-implementation-plan.md)). **NEO-97 landed:** client telegraph HUD ([NEO-97 plan](../../plans/NEO-97-implementation-plan.md), [`NEO-97` manual QA](../../manual-qa/NEO-97.md)). **NEO-98 landed:** playable NPC telegraph combat capstone ([NEO-98 plan](../../plans/NEO-98-implementation-plan.md), [`NEO-98` manual QA](../../manual-qa/NEO-98.md)); [client README — End-to-end NPC telegraph combat loop (NEO-98)](../../../client/README.md#end-to-end-npc-telegraph-combat-loop-neo-98). **Epic 5 Slice 2 complete — register row Ready.** Replaces **`prototype_target_alpha` / `beta`** with three NPC archetype instances; owns **`ThreatState`**, **`TelegraphEvent`**, session **`IPlayerCombatHealthStore`**.
|
**E5.M2 note:** Epic 5 **Slice 2** backlog in Linear ([Epic 5 — PvE Combat and Encounter Content](https://linear.app/neon-sprawl/project/epic-5-pve-combat-and-encounter-content-cf3c94c3-5346-40e0-8aaf-281e846f2846)): [NEO-87](https://linear.app/neon-sprawl/issue/NEO-87) → [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98); label **`E5.M2`**. Client capstone **E5M2-12** [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98). See [E5M2-prototype-backlog.md](../../plans/E5M2-prototype-backlog.md), [E5_M2_NpcAiAndBehaviorProfiles.md](E5_M2_NpcAiAndBehaviorProfiles.md). Upstream **E5.M1 Ready**. **NEO-87 landed:** frozen three-behavior catalog + CI ([NEO-87 plan](../../plans/NEO-87-implementation-plan.md)). **NEO-88 landed:** fail-fast server load of `content/npc-behaviors/*_npc_behaviors.json` ([NEO-88 plan](../../plans/NEO-88-implementation-plan.md)); [server README — NPC behavior catalog](../../../server/README.md#npc-behavior-catalog-contentnpc-behaviors-neo-88). **NEO-89 landed:** injectable **`INpcBehaviorDefinitionRegistry`** + DI ([NEO-89 plan](../../plans/NEO-89-implementation-plan.md)). **NEO-90 landed:** **`GET /game/world/npc-behavior-definitions`** — `NpcBehaviorDefinitionsWorldApi` + DTOs in `Game/Npc/` ([NEO-90 plan](../../plans/NEO-90-implementation-plan.md)); [server README — NPC behavior definitions (NEO-90)](../../../server/README.md#npc-behavior-definitions-neo-90); Bruno `bruno/neon-sprawl-server/npc-behavior-definitions/`. **NEO-91 landed:** **`PrototypeNpcRegistry`** + combat-target migration — three NPC instance ids, per-archetype catalog max HP ([NEO-91 plan](../../plans/NEO-91-implementation-plan.md)); [server README — Combat entity health (NEO-91)](../../../server/README.md#combat-entity-health-neo-80-neo-91). **NEO-92–NEO-96 landed:** aggro/threat, runtime state machine, **`GET …/npc-runtime-snapshot`**, player combat HP + NPC attack resolve, telemetry hooks ([NEO-92](../../plans/NEO-92-implementation-plan.md)–[NEO-96](../../plans/NEO-96-implementation-plan.md)). **NEO-97 landed:** client telegraph HUD ([NEO-97 plan](../../plans/NEO-97-implementation-plan.md), [`NEO-97` manual QA](../../manual-qa/NEO-97.md)). **NEO-98 landed:** playable NPC telegraph combat capstone ([NEO-98 plan](../../plans/NEO-98-implementation-plan.md), [`NEO-98` manual QA](../../manual-qa/NEO-98.md)); [client README — End-to-end NPC telegraph combat loop (NEO-98)](../../../client/README.md#end-to-end-npc-telegraph-combat-loop-neo-98). **Epic 5 Slice 2 complete — register row Ready.** Replaces **`prototype_target_alpha` / `beta`** with three NPC archetype instances; owns **`ThreatState`**, **`TelegraphEvent`**, session **`IPlayerCombatHealthStore`**.
|
||||||
|
|
||||||
**E5.M3 note:** Epic 5 **Slice 3** backlog in Linear ([Epic 5 — PvE Combat and Encounter Content](https://linear.app/neon-sprawl/project/epic-5-pve-combat-and-encounter-content-cf3c94c3-5346-40e0-8aaf-281e846f2846)): [NEO-100](https://linear.app/neon-sprawl/issue/NEO-100) → [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111); label **`E5.M3`**. Client capstone **E5M3-12** [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111). See [E5M3-prototype-backlog.md](../../plans/E5M3-prototype-backlog.md), [E5_M3_EncounterAndRewardTables.md](E5_M3_EncounterAndRewardTables.md). Upstream **E5.M2 Ready**, **E3.M3** inventory landed. Prototype spine: one encounter **`prototype_combat_pocket`** (defeat all three E5.M2 NPC ids) → reward table **`prototype_combat_pocket_clear`** ( **`scrap_metal_bulk` ×10**, **`contract_handoff_token` ×1** ); idempotent completion per player; per-defeat gig XP unchanged ([NEO-44](../../plans/NEO-44-implementation-plan.md)). **NEO-100 landed:** encounter + reward-table schemas, prototype catalogs, CI gates ([NEO-100 plan](../../plans/NEO-100-implementation-plan.md)). **NEO-101 landed:** fail-fast server load of `content/encounters/*_encounters.json` + `content/reward-tables/*_reward_tables.json` ([NEO-101 plan](../../plans/NEO-101-implementation-plan.md)); [server README — Encounter + reward-table catalogs (NEO-101)](../../../server/README.md#reward-table-catalog-contentreward-tables-neo-101). **NEO-102 landed:** injectable **`IEncounterDefinitionRegistry`** + **`IRewardTableDefinitionRegistry`** + DI ([NEO-102 plan](../../plans/NEO-102-implementation-plan.md)); [server README — Encounter + reward-table registries (NEO-102)](../../../server/README.md#reward-table-catalog-contentreward-tables-neo-101). **NEO-103 landed:** **`GET /game/world/encounter-definitions`** — `EncounterDefinitionsWorldApi` + nested **`rewardTable`** summary ([NEO-103 plan](../../plans/NEO-103-implementation-plan.md)); [server README — Encounter definitions (NEO-103)](../../../server/README.md#encounter-definitions-neo-103); Bruno `bruno/neon-sprawl-server/encounter-definitions/`. **NEO-104 landed:** **`IEncounterProgressStore`** + **`IEncounterCompletionStore`** + **`EncounterProgressOperations`** ([NEO-104 plan](../../plans/NEO-104-implementation-plan.md)); [server README — Encounter progress (NEO-104)](../../../server/README.md#encounter-progress--completion-stores-neo-104). **NEO-105 landed:** **`EncounterCompletionOperations`** + **`EncounterCompleteEvent`** result payload ([NEO-105 plan](../../plans/NEO-105-implementation-plan.md)); [server README — Encounter completion (NEO-105)](../../../server/README.md#encounter-completion--inventory-grants-neo-105). **NEO-106 landed:** **`EncounterCombatWiring`** on **`AbilityCastApi`** ([NEO-106 plan](../../plans/NEO-106-implementation-plan.md)); [server README — Encounter combat wiring (NEO-106)](../../../server/README.md#encounter-combat-wiring-neo-106). **NEO-107 landed:** **`IEncounterCompleteEventStore`** + E7.M2 hook stub ([NEO-107 plan](../../plans/NEO-107-implementation-plan.md)); [server README — Encounter complete event (NEO-107)](../../../server/README.md#encounter-complete-event-record-neo-107). **HTTP progress read (NEO-108+) outstanding.**
|
**E5.M3 note:** Epic 5 **Slice 3** backlog in Linear ([Epic 5 — PvE Combat and Encounter Content](https://linear.app/neon-sprawl/project/epic-5-pve-combat-and-encounter-content-cf3c94c3-5346-40e0-8aaf-281e846f2846)): [NEO-100](https://linear.app/neon-sprawl/issue/NEO-100) → [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111); label **`E5.M3`**. Client capstone **E5M3-12** [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111). See [E5M3-prototype-backlog.md](../../plans/E5M3-prototype-backlog.md), [E5_M3_EncounterAndRewardTables.md](E5_M3_EncounterAndRewardTables.md). Upstream **E5.M2 Ready**, **E3.M3** inventory landed. Prototype spine: one encounter **`prototype_combat_pocket`** (defeat all three E5.M2 NPC ids) → reward table **`prototype_combat_pocket_clear`** ( **`scrap_metal_bulk` ×10**, **`contract_handoff_token` ×1** ); idempotent completion per player; per-defeat gig XP unchanged ([NEO-44](../../plans/NEO-44-implementation-plan.md)). **NEO-100 landed:** encounter + reward-table schemas, prototype catalogs, CI gates ([NEO-100 plan](../../plans/NEO-100-implementation-plan.md)). **NEO-101 landed:** fail-fast server load of `content/encounters/*_encounters.json` + `content/reward-tables/*_reward_tables.json` ([NEO-101 plan](../../plans/NEO-101-implementation-plan.md)); [server README — Encounter + reward-table catalogs (NEO-101)](../../../server/README.md#reward-table-catalog-contentreward-tables-neo-101). **NEO-102 landed:** injectable **`IEncounterDefinitionRegistry`** + **`IRewardTableDefinitionRegistry`** + DI ([NEO-102 plan](../../plans/NEO-102-implementation-plan.md)); [server README — Encounter + reward-table registries (NEO-102)](../../../server/README.md#reward-table-catalog-contentreward-tables-neo-101). **NEO-103 landed:** **`GET /game/world/encounter-definitions`** — `EncounterDefinitionsWorldApi` + nested **`rewardTable`** summary ([NEO-103 plan](../../plans/NEO-103-implementation-plan.md)); [server README — Encounter definitions (NEO-103)](../../../server/README.md#encounter-definitions-neo-103); Bruno `bruno/neon-sprawl-server/encounter-definitions/`. **NEO-104 landed:** **`IEncounterProgressStore`** + **`IEncounterCompletionStore`** + **`EncounterProgressOperations`** ([NEO-104 plan](../../plans/NEO-104-implementation-plan.md)); [server README — Encounter progress (NEO-104)](../../../server/README.md#encounter-progress--completion-stores-neo-104). **NEO-105 landed:** **`EncounterCompletionOperations`** + **`EncounterCompleteEvent`** result payload ([NEO-105 plan](../../plans/NEO-105-implementation-plan.md)); [server README — Encounter completion (NEO-105)](../../../server/README.md#encounter-completion--inventory-grants-neo-105). **NEO-106 landed:** **`EncounterCombatWiring`** on **`AbilityCastApi`** ([NEO-106 plan](../../plans/NEO-106-implementation-plan.md)); [server README — Encounter combat wiring (NEO-106)](../../../server/README.md#encounter-combat-wiring-neo-106). **NEO-107 landed:** **`IEncounterCompleteEventStore`** + E7.M2 hook stub ([NEO-107 plan](../../plans/NEO-107-implementation-plan.md)); [server README — Encounter complete event (NEO-107)](../../../server/README.md#encounter-complete-event-record-neo-107). **NEO-108 landed:** **`GET /game/players/{id}/encounter-progress`** — `EncounterProgressApi` + DTOs ([NEO-108 plan](../../plans/NEO-108-implementation-plan.md)); [server README — Per-player encounter progress (NEO-108)](../../../server/README.md#per-player-encounter-progress-neo-108); Bruno `bruno/neon-sprawl-server/encounter-progress/`. **E5M3-10+ outstanding.**
|
||||||
|
|
||||||
### Epic 6 — PvP Security
|
### Epic 6 — PvP Security
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -263,9 +263,11 @@ Working backlog for **Epic 5 — Slice 3** ([Epic 5 · Slice 3 — encounters an
|
||||||
|
|
||||||
**Acceptance criteria**
|
**Acceptance criteria**
|
||||||
|
|
||||||
- [ ] GET reflects progress after each defeat without client-side inference.
|
- [x] GET reflects progress after each defeat without client-side inference.
|
||||||
- [ ] Completed encounter shows **`completed`** state and grant summary.
|
- [x] Completed encounter shows **`completed`** state and grant summary.
|
||||||
- [ ] Bruno smoke: defeat chain → GET shows **`completed`** once.
|
- [x] Bruno smoke: defeat chain → GET shows **`completed`** once.
|
||||||
|
|
||||||
|
**Landed ([NEO-108](https://linear.app/neon-sprawl/issue/NEO-108)):** **`GET /game/players/{id}/encounter-progress`** + Bruno defeat spine; plan [NEO-108-implementation-plan.md](NEO-108-implementation-plan.md).
|
||||||
|
|
||||||
**Client counterpart:** [NEO-110](https://linear.app/neon-sprawl/issue/NEO-110) — poll/refresh from this GET.
|
**Client counterpart:** [NEO-110](https://linear.app/neon-sprawl/issue/NEO-110) — poll/refresh from this GET.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,17 @@
|
||||||
|
|
||||||
## Acceptance criteria checklist
|
## Acceptance criteria checklist
|
||||||
|
|
||||||
- [ ] GET reflects progress after each defeat without client-side inference.
|
- [x] GET reflects progress after each defeat without client-side inference.
|
||||||
- [ ] Completed encounter shows **`completed`** state and grant summary.
|
- [x] Completed encounter shows **`completed`** state and grant summary.
|
||||||
- [ ] Bruno smoke: defeat chain → GET shows **`completed`** once.
|
- [x] Bruno smoke: defeat chain → GET shows **`completed`** once.
|
||||||
|
|
||||||
|
## Implementation reconciliation (shipped)
|
||||||
|
|
||||||
|
- **Route:** `GET /game/players/{id}/encounter-progress` — `EncounterProgressApi.BuildSnapshot` projects progress, completion, and event stores; 404 via `IPositionStateStore`.
|
||||||
|
- **DTOs:** `EncounterProgressListResponse` (schema v1); row `state` inactive/active/completed; `rewardGrantSummary` from `IEncounterCompleteEventStore.GrantedItems`; optional fields omitted when not completed.
|
||||||
|
- **Tests:** five AAA tests in `EncounterProgressApiTests` (404, inactive, 1-defeat active, 2-defeat active, completed + grant summary).
|
||||||
|
- **Bruno:** `bruno/neon-sprawl-server/encounter-progress/` — inactive GET + three-NPC defeat spine.
|
||||||
|
- **Docs:** `server/README.md`; E5.M3 module snapshot; alignment register; module dependency register; backlog E5M3-08 AC.
|
||||||
|
|
||||||
## Technical approach
|
## Technical approach
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ Per-player encounter state is server-owned in **`IEncounterProgressStore`** (act
|
||||||
- **`TryMarkTargetDefeated(…)`** — requires prior activation; accumulates defeats in any order; idempotent per NPC.
|
- **`TryMarkTargetDefeated(…)`** — requires prior activation; accumulates defeats in any order; idempotent per NPC.
|
||||||
- **`IsAllRequiredTargetsDefeated(playerId, encounterId, …)`** — set equality vs catalog **`requiredNpcInstanceIds`**.
|
- **`IsAllRequiredTargetsDefeated(playerId, encounterId, …)`** — set equality vs catalog **`requiredNpcInstanceIds`**.
|
||||||
|
|
||||||
**`GET /game/players/{id}/encounter-progress`** lands in NEO-108. Plan: [NEO-104 implementation plan](../../docs/plans/NEO-104-implementation-plan.md).
|
**Per-player HTTP read:** see [Per-player encounter progress (NEO-108)](#per-player-encounter-progress-neo-108). Plan: [NEO-104 implementation plan](../../docs/plans/NEO-104-implementation-plan.md).
|
||||||
|
|
||||||
**Combat wiring (NEO-106):** see [Encounter combat wiring (NEO-106)](#encounter-combat-wiring-neo-106) — **`EncounterCombatWiring`** on **`POST …/ability-cast`** accept path.
|
**Combat wiring (NEO-106):** see [Encounter combat wiring (NEO-106)](#encounter-combat-wiring-neo-106) — **`EncounterCombatWiring`** on **`POST …/ability-cast`** accept path.
|
||||||
|
|
||||||
|
|
@ -186,6 +186,26 @@ Prototype first completion grants **`scrap_metal_bulk` ×10** and **`contract_ha
|
||||||
|
|
||||||
Plan: [NEO-107 implementation plan](../../docs/plans/NEO-107-implementation-plan.md).
|
Plan: [NEO-107 implementation plan](../../docs/plans/NEO-107-implementation-plan.md).
|
||||||
|
|
||||||
|
## Per-player encounter progress (NEO-108)
|
||||||
|
|
||||||
|
**`GET /game/players/{id}/encounter-progress`** returns a versioned JSON body (`schemaVersion` **1**, **`playerId`**, **`encounters`**) projecting in-memory stores — no client-side defeat inference. Unknown players return **404** (same **`IPositionStateStore`** gate as gig/skill progression).
|
||||||
|
|
||||||
|
Each row (catalog order via **`IEncounterDefinitionRegistry.GetDefinitionsInIdOrder()`**):
|
||||||
|
|
||||||
|
| Field | Source |
|
||||||
|
|-------|--------|
|
||||||
|
| **`encounterId`** | Catalog encounter **`id`**. |
|
||||||
|
| **`state`** | **`inactive`** (no progress), **`active`** (started, not completed), **`completed`**. |
|
||||||
|
| **`defeatedTargetIds`** | **`IEncounterProgressStore`** defeated NPC ids (ordinal sort); `[]` when inactive. |
|
||||||
|
| **`completedAt`** | **`IEncounterCompletionStore`** when **`completed`**; omitted otherwise. |
|
||||||
|
| **`rewardGrantSummary`** | **`IEncounterCompleteEventStore.GrantedItems`** when **`completed`**; omitted otherwise. |
|
||||||
|
|
||||||
|
Prototype: defeat all three E5.M2 NPCs via cast → row **`prototype_combat_pocket`** becomes **`completed`** with **`scrap_metal_bulk` ×10** and **`contract_handoff_token` ×1** in **`rewardGrantSummary`**. Plan: [NEO-108 implementation plan](../../docs/plans/NEO-108-implementation-plan.md); Bruno: `bruno/neon-sprawl-server/encounter-progress/`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -i "http://localhost:5253/game/players/dev-local-1/encounter-progress"
|
||||||
|
```
|
||||||
|
|
||||||
## 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):
|
||||||
|
|
@ -196,7 +216,7 @@ On every successful **`POST …/ability-cast`** accept, **`EncounterCombatWiring
|
||||||
| **`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). |
|
||||||
|
|
||||||
**Call order on lethal accept:** aggro → cooldown → NPC runtime stop → **`CombatDefeatGigXpGrant`** (NEO-44) → **`EncounterCombatWiring`**. Gig XP per defeat is unchanged (**25** to **`breach`** each). Verify loot with **`GET …/inventory`**; per-player progress HTTP lands in NEO-108. Plan: [NEO-106 implementation plan](../../docs/plans/NEO-106-implementation-plan.md).
|
**Call order on lethal accept:** aggro → cooldown → NPC runtime stop → **`CombatDefeatGigXpGrant`** (NEO-44) → **`EncounterCombatWiring`**. Gig XP per defeat is unchanged (**25** to **`breach`** each). Verify loot with **`GET …/inventory`** or **`GET …/encounter-progress`** (NEO-108). Plan: [NEO-106 implementation plan](../../docs/plans/NEO-106-implementation-plan.md).
|
||||||
|
|
||||||
## NPC behavior definitions (NEO-90)
|
## NPC behavior definitions (NEO-90)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue