neon-sprawl/docs/plans/E5M3-prototype-backlog.md

22 KiB
Raw Blame History

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-54NEO-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 112 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 — stable id: 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 — stable id: 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-ref itemId → 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 id fails 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.md section.

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 in Game/Encounters/.
  • Integration tests (AAA); Bruno folder bruno/neon-sprawl-server/encounter-definitions/.
  • server/README.md route section.

Out of scope

  • Per-player progress, completion grants, Godot.

Acceptance criteria

  • GET returns frozen encounter in ascending id order with schemaVersion 1.
  • 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 — tracks started, defeated subset of requiredNpcInstanceIds, keyed by (playerId, encounterId).
  • IEncounterCompletionStore — records completedAt (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 and contract_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 EncounterProgressOperations after successful player → NPC defeat (NEO-82 path).
  • TryActivateOnFirstEngagement on first damaging cast vs encounter NPC ( encounter_start comment 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 (12 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 encounter state (inactive / active / completed), defeatedTargetIds, completedAt, last rewardGrantSummary (item ids + quantities on complete).
  • Integration tests (AAA); Bruno bruno/neon-sprawl-server/encounter-progress/.
  • server/README.md section.

Out of scope

  • Godot poll client (E5M3-11).

Acceptance criteria

  • GET reflects progress after each defeat without client-side inference.
  • Completed encounter shows completed state and grant summary.
  • Bruno smoke: defeat chain → GET shows completed once.

Landed (NEO-108): GET /game/players/{id}/encounter-progress + Bruno defeat spine; plan NEO-108-implementation-plan.md.

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

  • EncounterCompleteEvent internal type (playerId, encounterId, idempotencyKey, grantedItemSummary, completedAt).
  • Comment-only reward_delivery / quest-credit hook site in completion path (TODO E7.M2).
  • server/README.md pointer; 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 QuestRewardBundle consumer contract.
  • No runtime behavior beyond event record + comments.

Landed (NEO-107): IEncounterCompleteEventStore + in-memory implementation; record on TryCompleteAndGrant success; E7.M2 reward_delivery hook comments. Plan: NEO-107-implementation-plan.md.


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.md pointer; 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.

Landed (NEO-109): comment-only encounter_start, encounter_complete, reward_attribution (once per completion), encounter_complete_denied; server README — Encounter telemetry hooks (NEO-109). Plan: NEO-109-implementation-plan.md.


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-driven GET /game/players/{id}/encounter-progress after combat defeats.
  • HUD labels: EncounterProgressLabel, EncounterCompleteLabel under UICanvas; refresh InventoryLabel on completion (reuse NEO-72 client).
  • GdUnit tests with HTTP doubles (testing-expectations.md).
  • docs/manual-qa/NEO-110.mdGodot steps (server + client running).
  • client/README.md encounter 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.

Landed (NEO-110): encounter_progress_client.gd + EncounterProgressLabel / EncounterCompleteLabel; boot + defeat-triggered GET; inventory auto-refresh on completed. Plan: NEO-110-implementation-plan.md; manual QA: NEO-110.md; client README — Encounter progress + loot HUD.

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.md single-session script (includes idempotency check — no duplicate loot on re-login / fresh defeat attempt after complete).
  • client/README.md end-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.

In progress (NEO-111): capstone manual QA NEO-111.md + client README — End-to-end encounter clear loop; plan NEO-111-implementation-plan.md. Manual QA pass pending human run.

Client counterpart: this issue (NEO-111).


After this backlog

  • Track delivery in Linear; keep blockedBy synchronized if scope changes.
  • For each issue kickoff: docs/plans/{NEO-XX}-implementation-plan.md per story-kickoff.
  • Add docs/manual-qa/{NEO-XX}.md for player-visible client stories when implementation lands.

Decomposition complete checklist