20 KiB
E5.M3 — Prototype story backlog (EncounterAndRewardTables)
Working backlog for Epic 5 — Slice 3 (Epic 5 · Slice 3 — encounters and loot routing). Decomposition and contracts: E5.M3 — EncounterAndRewardTables.
Full-stack policy: Epics deliver playable game features. Every player-visible story has a client Linear issue created in this same decomposition pass — not an undocumented follow-up. Reference: E5M2 (paired server+client).
Labels (Linear): every issue E5.M3; add server or client + Story as listed.
Precursor (do not re-scope): E5.M2 NPC combat pocket — three NPC instance ids (prototype_npc_melee, prototype_npc_ranged, prototype_npc_elite) with defeat via CombatOperations (NEO-82). Per-defeat gig XP stays on NEO-44 — encounter complete adds loot + quest-credit stand-in, not a second gig XP bundle.
Upstream (must be landed): E5.M2 Ready; E3.M3 inventory PlayerInventoryOperations (NEO-54–NEO-55). E7.M2 full QuestRewardBundle router is not required — prototype uses contract_handoff_token loot + EncounterCompleteEvent hook for future quest credit (NEO-43 pattern).
Prototype encounter spine (frozen in E5M3-01): one EncounterDef — id prototype_combat_pocket — completion defeat_all_targets over the three E5.M2 NPC instance ids. One RewardTable — id prototype_combat_pocket_clear — deterministic fixed item grants (no RNG).
Linear issues (created): attach docs/plans/NEO-*-implementation-plan.md on the story branch when work starts.
| Slug | Layer | Linear |
|---|---|---|
| E5M3-01 | server | NEO-100 |
| E5M3-02 | server | NEO-101 |
| E5M3-03 | server | NEO-102 |
| E5M3-04 | server | NEO-103 |
| E5M3-05 | server | NEO-104 |
| E5M3-06 | server | NEO-105 |
| E5M3-07 | server | NEO-106 |
| E5M3-08 | server | NEO-108 |
| E5M3-09 | server | NEO-107 |
| E5M3-10 | server | NEO-109 |
| E5M3-11 | client | NEO-110 |
| E5M3-12 | client | NEO-111 |
Dependency graph in Linear: E5M3-02 blocked by E5M3-01. E5M3-03 blocked by E5M3-02. E5M3-04 blocked by E5M3-03. E5M3-05 blocked by E5M3-04. E5M3-06 blocked by E5M3-05. E5M3-07 blocked by E5M3-06. E5M3-08 blocked by E5M3-07. E5M3-09 blocked by E5M3-06 (may parallel E5M3-08). E5M3-10 blocked by E5M3-07. E5M3-11 blocked by E5M3-08 and E5M3-09. E5M3-12 blocked by E5M3-11. (Linear ids: E5M3-08 = NEO-108, E5M3-09 = NEO-107 — creation order swap.)
Board order: estimates 1–12 matching slug order (E5M3-01 = 1 … E5M3-12 = 12). On the Epic 5 project board, sort by Estimate (ascending).
Story order (recommended)
| Order | Slug | Layer | Depends on |
|---|---|---|---|
| 1 | E5M3-01 | server | E5.M2 Ready, E3.M3 inventory landed |
| 2 | E5M3-02 | server | E5M3-01 |
| 3 | E5M3-03 | server | E5M3-02 |
| 4 | E5M3-04 | server | E5M3-03 |
| 5 | E5M3-05 | server | E5M3-04 |
| 6 | E5M3-06 | server | E5M3-05 |
| 7 | E5M3-07 | server | E5M3-06 |
| 8 | E5M3-08 | server | E5M3-07 |
| 9 | E5M3-09 | server | E5M3-06 |
| 10 | E5M3-10 | server | E5M3-07 |
| 11 | E5M3-11 | client | E5M3-08, E5M3-09 |
| 12 | E5M3-12 | client | E5M3-11 |
Downstream (separate modules): E5.M4 group scaling; E7.M1 / E7.M2 consume EncounterCompleteEvent; E4.M2 spawn ecology.
Kickoff decisions (decomposition defaults)
| Topic | Decision | Rationale |
|---|---|---|
| Encounter count | 1 frozen EncounterDef |
Vision prototype minimum; epic Slice 3 |
| Completion rule | defeat_all_targets over three E5.M2 NPC ids |
Reuses static registry; order-independent |
| Reward table | Deterministic fixed grants (no RNG rolls) | Readable tests; RNG is pre-production |
| Frozen loot | scrap_metal_bulk ×10, contract_handoff_token ×1 |
Material + quest-token stand-in for E7.M2 credit |
| Idempotency | Once per player per encounterId |
Slice 3 AC: grants expected payout once |
| Gig XP | Keep NEO-44 per-defeat | Combat → gig XP is separate from encounter loot bundle |
| Quest credit | contract_handoff_token item + EncounterCompleteEvent record |
Full E7.M2 router deferred; visible in inventory HUD |
| Encounter start | Auto on first damaging cast vs any required NPC | Activates progress tracking; encounter_start hook site |
| Progress store | In-memory IEncounterProgressStore + IEncounterCompletionStore |
Session prototype; Postgres optional follow-on |
| HTTP read | GET /game/players/{id}/encounter-progress |
Client HUD without trusting local defeat math |
| HUD fidelity | Prototype Labels / debug panels (not final art) | NEO-110 precedent |
E5M3-01 — Prototype EncounterDef + RewardTable catalog + schemas + CI
Goal: Lock content shape and CI validation for one frozen encounter + one reward table before server load.
In scope
content/schemas/encounter-def.schema.json,content/schemas/reward-table.schema.json,content/schemas/reward-grant-row.schema.json.content/encounters/prototype_encounters.json— stableid:prototype_combat_pocket;completionCriteria.kind:defeat_all_targets;requiredNpcInstanceIds: three E5.M2 NPC ids;rewardTableId:prototype_combat_pocket_clear.content/reward-tables/prototype_reward_tables.json— stableid:prototype_combat_pocket_clear;fixedGrants:scrap_metal_bulk×10,contract_handoff_token×1.scripts/validate_content.py: schema validation, duplicate ids, exact one-encounter / one-table allowlist, cross-refitemId→ frozen item catalog,requiredNpcInstanceIds→ frozen NPC instance ids,rewardTableId→ reward table ids.- Designer note in E5_M3 +
content/README.md.
Out of scope
- Server loader, runtime engine, HTTP, Godot.
Acceptance criteria
- PR gate validates encounter + reward-table JSON against schema.
- Exactly one prototype encounter id and one reward table id; duplicate
idfails CI. - Stable id list documented in module doc freeze box.
Landed (NEO-100): encounter + reward-table schemas, prototype_encounters.json, prototype_reward_tables.json, CI gates in validate_content.py; plan NEO-100-implementation-plan.md.
E5M3-02 — Server encounter + reward catalog load (fail-fast)
Goal: Disk → host: startup load of content/encounters/*.json and content/reward-tables/*.json with CI-parity validation.
In scope
- Loader + catalog types under
server/NeonSprawl.Server/Game/Encounters/(path finalized in plan). - Fail-fast on malformed files, duplicate ids, allowlist mismatch with CI, broken cross-refs.
- Unit tests (AAA) for loader happy path and failure modes.
server/README.mdsection.
Out of scope
- Injectable registry interface (E5M3-03), HTTP projection, runtime completion.
Acceptance criteria
- Host fails startup on invalid encounter or reward-table JSON (mirror CI rules).
- Tests cover at least one happy path and one malformed catalog rejection.
Landed (NEO-101): fail-fast encounter + reward-table catalog loaders, DI registration, 22 AAA tests; plan NEO-101-implementation-plan.md.
E5M3-03 — Injectable encounter/reward registries + DI
Goal: Replace direct catalog access with injectable registries; wire into host DI.
In scope
IEncounterDefinitionRegistry,IRewardTableDefinitionRegistry+ implementations backed by E5M3-02 catalogs.TryNormalizeKnown/ list-in-id-order parity with ability/item registry patterns.- DI extension method; unit tests (AAA).
Out of scope
- HTTP routes, progress store, client.
Acceptance criteria
- Registries resolve frozen encounter + reward table ids.
- Unknown id normalization fails closed.
Landed (NEO-102): injectable encounter + reward-table registries + DI; 18 AAA tests; plan NEO-102-implementation-plan.md.
E5M3-04 — GET /game/world/encounter-definitions
Goal: Read-only world projection of encounter defs (+ nested or linked reward table summary) for client labels and Bruno smokes.
In scope
EncounterDefinitionsWorldApi+ versioned DTOs inGame/Encounters/.- Integration tests (AAA); Bruno folder
bruno/neon-sprawl-server/encounter-definitions/. server/README.mdroute section.
Out of scope
- Per-player progress, completion grants, Godot.
Acceptance criteria
- GET returns frozen encounter in ascending
idorder withschemaVersion1. - Bruno happy GET passes in CI Bruno step.
Landed (NEO-103): GET /game/world/encounter-definitions + nested rewardTable summary; Bruno + AAA integration test; plan NEO-103-implementation-plan.md.
E5M3-05 — Encounter progress + completion stores
Goal: Server-owned per-player encounter activation, defeat progress, and idempotent completion flag.
In scope
IEncounterProgressStore— tracksstarted, defeated subset ofrequiredNpcInstanceIds, keyed by(playerId, encounterId).IEncounterCompletionStore— recordscompletedAt(or boolean) once rewards applied; replays are no-op.EncounterProgressOperations.TryMarkTargetDefeated/TryActivateOnFirstEngagement(first damaging cast vs required NPC).- Unit tests (AAA) with injected stores.
Out of scope
- Inventory grants (E5M3-06), HTTP DTOs (E5M3-08), Godot.
Acceptance criteria
- Defeating required NPCs accumulates progress in any order.
- Completion predicate true only when all required ids defeated.
- Second completion attempt on same player+encounter is idempotent no-op at store layer.
Landed (NEO-104): IEncounterProgressStore + IEncounterCompletionStore + EncounterProgressOperations; in-memory DI; AAA unit tests; plan NEO-104-implementation-plan.md.
E5M3-06 — EncounterCompletionOperations + inventory reward apply
Goal: On first completion, apply RewardTable.fixedGrants via PlayerInventoryOperations; emit internal EncounterCompleteEvent payload for downstream routers.
In scope
EncounterCompletionOperations.TryCompleteAndGrant— checks completion store, applies fixed grants, marks complete atomically (best-effort rollback on inventory deny documented in plan).- Structured deny reason codes (
inventory_full,already_completed,not_ready, …). - Integration tests: defeat all targets → inventory stacks increase exactly once.
- Bruno spine optional in plan (may land in E5M3-08).
Out of scope
- Godot HUD — client counterpart NEO-110.
- Full E7.M2 quest router (E5M3-09 hook stub).
Acceptance criteria
- First completion grants
scrap_metal_bulk×10 andcontract_handoff_token×1 deterministically. - Second completion does not duplicate grants.
- Inventory deny fails closed without marking complete.
Landed (NEO-105): EncounterCompletionOperations.TryCompleteAndGrant + EncounterCompleteEvent result payload; AAA unit tests; plan NEO-105-implementation-plan.md.
Client counterpart: NEO-110 — encounter progress + loot feedback labels.
E5M3-07 — Wire combat defeat → encounter progress/completion
Goal: Hook AbilityCastApi (or shared combat defeat helper) so NPC targetDefeated updates encounter progress and invokes E5M3-06 when the encounter completes.
In scope
- Call
EncounterProgressOperationsafter successful player → NPC defeat (NEO-82 path). TryActivateOnFirstEngagementon first damaging cast vs encounter NPC (encounter_startcomment hook site).- Integration tests: Bruno or in-proc defeat-all-three → completion + inventory grant once.
- Preserve existing NEO-44 gig XP on each defeat (do not remove).
Out of scope
- Godot presentation (E5M3-11).
- Quest router beyond completion event record (E5M3-09).
Acceptance criteria
- Defeating third required NPC triggers exactly one completion grant.
- Partial progress (1–2 defeats) does not grant loot.
- Gig XP still awarded per defeat via
CombatDefeatGigXpGrant.
Landed (NEO-106): EncounterCombatWiring on AbilityCastApi; in-proc defeat-all-three integration tests; plan NEO-106-implementation-plan.md.
E5M3-08 — GET /game/players/{id}/encounter-progress + wire DTOs
Goal: Authoritative per-player encounter rows for client poll / event refresh.
In scope
EncounterProgressApi+ DTOs: per encounterstate(inactive/active/completed),defeatedTargetIds,completedAt, lastrewardGrantSummary(item ids + quantities on complete).- Integration tests (AAA); Bruno
bruno/neon-sprawl-server/encounter-progress/. server/README.mdsection.
Out of scope
- Godot poll client (E5M3-11).
Acceptance criteria
- GET reflects progress after each defeat without client-side inference.
- Completed encounter shows
completedstate and grant summary. - Bruno smoke: defeat chain → GET shows
completedonce.
Client counterpart: NEO-110 — poll/refresh from this GET.
E5M3-09 — EncounterCompleteEvent + E7.M2 quest-credit hook stub
Goal: Persist EncounterCompleteEvent record and document E7.M2 consumer hook for quest credit (beyond quest-token item loot).
In scope
EncounterCompleteEventinternal type (playerId, encounterId, idempotencyKey, grantedItemSummary, completedAt).- Comment-only
reward_delivery/ quest-credit hook site in completion path (TODO E7.M2). server/README.mdpointer; module doc cross-link to E7_M2.
Out of scope
- Production quest state machine, Godot.
Acceptance criteria
- Completion path emits/records event exactly once per player+encounter.
- Hook documents E7.M2
QuestRewardBundleconsumer contract. - No runtime behavior beyond event record + comments.
E5M3-10 — Slice 3 encounter telemetry hook sites
Goal: Comment-only encounter_complete and reward_attribution hook sites in encounter completion path (activates reserved encounter_start from E5M3-07).
In scope
- TODO(E9.M1) markers at encounter start, completion commit, and per-item grant attribution.
server/README.mdpointer; epic Slice 3 vocabulary cross-link.
Out of scope
- Production ingest, Godot.
Acceptance criteria
- Hook names match epic_05 Slice 3 vocabulary.
- No runtime behavior change beyond comments.
E5M3-11 — Client encounter progress + loot feedback HUD
Goal: Godot shows server-owned encounter progress and completion loot using encounter-progress GET.
In scope
client/scripts/encounter_progress_client.gd— poll or event-drivenGET /game/players/{id}/encounter-progressafter combat defeats.- HUD labels:
EncounterProgressLabel,EncounterCompleteLabelunderUICanvas; refreshInventoryLabelon completion (reuse NEO-72 client). - GdUnit tests with HTTP doubles (testing-expectations.md).
docs/manual-qa/NEO-110.md— Godot steps (server + client running).client/README.mdencounter section.
Out of scope
- Final loot VFX art; encounter selection UI beyond single frozen encounter.
Acceptance criteria
- Player-visible progress updates after each NPC defeat (e.g. 2/3 targets).
- Completion label shows grant summary matching server GET.
- Manual QA checklist exercisable without Bruno.
Client counterpart: this issue (NEO-110).
E5M3-12 — Playable encounter clear capstone (Godot)
Goal: Prove Epic 5 Slice 3 acceptance in Godot: defeat all three NPCs, receive loot + quest token once, verify inventory — without Bruno.
In scope
docs/manual-qa/NEO-111.mdsingle-session script (includes idempotency check — no duplicate loot on re-login / fresh defeat attempt after complete).client/README.mdend-to-end encounter loop section.- Update documentation_and_implementation_alignment.md E5.M3 row when complete.
Out of scope
Acceptance criteria
- Human completes script with server + client; loot readable in inventory HUD.
- Epic 5 Slice 3 AC: completing defined encounter grants expected items + quest token once.
- Re-read epic_05 Definition of Done for prototype minimums.
Client counterpart: this issue (NEO-111).
After this backlog
- Track delivery in Linear; keep
blockedBysynchronized if scope changes. - For each issue kickoff:
docs/plans/{NEO-XX}-implementation-plan.mdper story-kickoff. - Add
docs/manual-qa/{NEO-XX}.mdfor player-visible client stories when implementation lands.
Decomposition complete checklist
- Every player-visible AC has a
clientLinear issue (NEO-110, NEO-111) - No “optional follow-up” / “future client” without NEO-XX
- Capstone client issue exists for playable Godot verification (NEO-111)
- documentation_and_implementation_alignment.md updated (decomposed row)
- module_dependency_register.md E5.M3 note updated when issues are created