neon-sprawl/docs/plans/NEO-118-implementation-plan.md

14 KiB

NEO-118 — Implementation plan

Story reference

Field Value
Key NEO-118
Title E7M1-07: Quest objective wiring (gather, craft, encounter)
Linear https://linear.app/neon-sprawl/issue/NEO-118/e7m1-07-quest-objective-wiring-gather-craft-encounter
Module E7.M1 — QuestStateMachine · Epic 7 Slice 1 · backlog E7M1-07
Branch NEO-118-quest-objective-wiring
Precursor NEO-117QuestStateOperations accept/advance/complete (Done on main)
Pattern NEO-106EncounterCombatWiring best-effort side effect on success paths; NEO-117QuestStateOperations for advance/complete
Blocks NEO-119 — GET quest-progress; NEO-121 — telemetry hook sites
Client counterpart NEO-122 — quest progress HUD reads GET snapshot after gather/craft/encounter mutations (E7M1-11). Bruno-only progress verification is not prototype-complete per full-stack epic decomposition.

Kickoff clarifications

Topic Question Agent recommendation Answer
inventory_has_item triggers When to evaluate beyond gather/craft/encounter grant paths? Inventory mutations + quest accept + after step advance — backlog “post-grant or on polling hook from accept”; chain step 4 token often pre-held from combat intro Adopted — user confirmed
Accept / step-advance re-eval scope Which objective kinds to reconcile on accept/step entry? inventory_has_item only; gather_item / craft_recipe / encounter_complete stay event-driven Adopted — user confirmed

Additional defaults (no kickoff question — settled by backlog / NEO-116):

  • Counter accumulation uses event quantity (gather yield qty, craft batch qty); cap stored counter at objective quantity for satisfaction.
  • All active quests matching the event are credited (e.g. gather intro + chain step 1 on same gather).
  • Wiring is best-effort — quest evaluation failures must not fail gather/craft/encounter (mirror EncounterCombatWiring).
  • encounter_complete objectives treat satisfaction as counter ≥ 1 (no quantity field in catalog).
  • Step completion: when all objectives on the current step are satisfied → TryAdvanceStep to next index, or TryMarkComplete on terminal step; after advance, run inventory_has_item snapshot pass for the new step.

Goal, scope, and out-of-scope

Goal: Advance active quest steps when server-side gather, craft, and encounter events succeed; evaluate inventory_has_item from inventory snapshots on grant paths, quest accept, and step advance.

In scope (from Linear + E7M1-07):

  • QuestObjectiveWiring (static) — evaluate active quests against incoming events; update counters via store; auto advance/complete via QuestStateOperations.
  • Hook from GatherOperations.TryGather success — gather_item objectives (item id + quantity delta).
  • Hook from CraftOperations.TryCraft success — craft_recipe objectives (recipe id; quantity = craft batch size).
  • Hook from EncounterCompletionOperations.TryCompleteAndGrant success — encounter_complete objectives (encounter id).
  • inventory_has_item — inventory snapshot read after successful inventory mutations; TryProcessInventoryHasItem on quest accept and after step advance (current/new step only).
  • Counter accumulation for quantity objectives; auto TryAdvanceStep / TryMarkComplete when step objectives satisfied.
  • Integration-style tests (AAA): accept quest → simulate gather/craft/encounter → progress row advances through chain.
  • server/README.md — objective wiring subsection + hook table.
  • docs/manual-qa/NEO-118.md — Bruno/server-side verification checklist (no HTTP quest-progress yet).

Out of scope (from Linear + backlog):

  • Client-side objective tracking; item consume on turn-in; E7.M2 reward bundles.
  • HTTP GET …/quest-progress (NEO-119) and accept POST (NEO-120).
  • Telemetry hook comments (NEO-121 / E7M1-10).
  • Retroactive encounter_complete credit on accept (encounter already done before quest accept).
  • GET polling for inventory_has_item (deferred to NEO-119 if needed).

Acceptance criteria checklist

  • Active prototype_quest_gather_intro advances when scrap_metal_bulk gather succeeds.
  • Active prototype_quest_refine_intro advances on refine_scrap_standard craft success.
  • Active prototype_quest_combat_intro advances when prototype_combat_pocket completes.
  • Chain quest steps advance in order across mixed objective kinds.

Implementation reconciliation (shipped)

  • Types: QuestObjectiveKinds, QuestObjectiveWiring (TryProcessGatherSuccess, TryProcessCraftSuccess, TryProcessEncounterCompleteSuccess, TryProcessInventoryHasItemForQuest, TryCompleteStepIfSatisfied).
  • Hooks: GatherOperations, CraftOperations, EncounterCompletionOperations, QuestStateOperations.TryAccept / TryAdvanceStep.
  • Tests: QuestObjectiveWiringTests (6 AAA cases); existing gather/craft/encounter/state tests updated for new parameters.
  • Docs: server/README.md objective wiring section; docs/manual-qa/NEO-118.md.

Technical approach

  1. QuestObjectiveKinds — string constants: gather_item, craft_recipe, encounter_complete, inventory_has_item (mirror schema enum).

  2. QuestObjectiveWiring (Game/Quests/)

    • TryProcessGatherSuccess(playerId, itemId, quantityGranted, …) — scan all catalog quest ids; for each active row, match current-step gather_item objectives by itemId; bump counter by quantityGranted (capped at objective quantity).
    • TryProcessCraftSuccess(playerId, recipeId, batchQuantity, …) — match craft_recipe by recipeId; increment counter by batchQuantity (capped).
    • TryProcessEncounterCompleteSuccess(playerId, encounterId, …) — match encounter_complete; set counter to 1 when encounter id matches.
    • TryProcessInventoryHasItemForPlayer(playerId, …) / TryProcessInventoryHasItemForQuest(playerId, questId, …) — read IPlayerInventoryStore.TryGetSnapshot; for current-step inventory_has_item objectives, set counter to min(bagQuantity, objective.quantity); used after grants, on accept, after step advance.
    • Shared private TryEvaluateActiveQuests / TryCompleteStepIfSatisfied: when every objective on the current step meets threshold → if last step → QuestStateOperations.TryMarkComplete; else → TryAdvanceStep(currentIndex + 1) then TryProcessInventoryHasItemForQuest for the advanced quest (chain terminal token case).
    • Best-effort: swallow/log at debug; never throw to callers.
  3. Inventory quantity helper

    • Private static on wiring (or small internal helper): sum PlayerInventorySnapshot bag slot quantities for an itemId (same semantics as craft/encounter tests).
  4. Call sites

    • GatherOperations.TryGather — before successful return, call TryProcessGatherSuccess with yield.ItemId / yield.Quantity (add quest + inventory deps to method signature).
    • CraftOperations.TryCraft — before successful return, call TryProcessCraftSuccess with normalized recipe id and batch quantity.
    • EncounterCompletionOperations.TryCompleteAndGrant — after successful grant + completion mark, call TryProcessEncounterCompleteSuccess with normalized encounter id.
    • QuestStateOperations.TryAccept — after successful activation, call TryProcessInventoryHasItemForQuest (add IPlayerInventoryStore + TimeProvider params).
    • QuestStateOperations.TryAdvanceStep — after successful advance, call TryProcessInventoryHasItemForQuest for the same quest id.
    • Propagate new dependencies through InteractionApi, PlayerCraftApi, EncounterCombatWiring, and AbilityCastApi DI resolves (factory tests already resolve full host).
  5. Tests (QuestObjectiveWiringTests.cs)

    • Use InMemoryWebApplicationFactory + real catalog/registry/store.
    • Gather intro: accept → N gathers until counter ≥ 3 → quest completed (single-step).
    • Refine intro: prerequisite setup → accept → craft refine_scrap_standardcompleted.
    • Combat intro: accept → TryProcessEncounterCompleteSuccess for prototype_combat_pocketcompleted.
    • Chain: accept chain → gather 5 scrap → advance to step 1 → craft refine → step 2 → craft stim → step 3 → seed contract_handoff_token in inventory → advance triggers step 4 inventory_has_itemcompleted.
    • Parallel credit: gather intro + chain step 0 both active; one gather credits both counters appropriately.
    • Update QuestStateOperationsTests signatures if accept/advance gain inventory/time params (pass real store from factory).
  6. Docs

    • server/README.md — replace “Objective wiring is E7M1-07” stub with wiring summary, hook table, inventory_has_item trigger list.
    • Alignment register / module doc / E7M1 backlog checkboxes on story land.

Wiring flow (gather success)

flowchart TD
  G[GatherOperations.TryGather success] --> W[QuestObjectiveWiring.TryProcessGatherSuccess]
  W --> A{For each active quest}
  A --> M{Current step has matching gather_item?}
  M -->|yes| C[TryUpdateObjectiveCounter capped]
  M -->|no| A
  C --> S{All objectives satisfied?}
  S -->|terminal step| MC[QuestStateOperations.TryMarkComplete]
  S -->|more steps| AS[QuestStateOperations.TryAdvanceStep]
  AS --> I[TryProcessInventoryHasItemForQuest]
  S -->|no| A

Files to add

Path Purpose
server/NeonSprawl.Server/Game/Quests/QuestObjectiveKinds.cs Stable objective kind string constants.
server/NeonSprawl.Server/Game/Quests/QuestObjectiveWiring.cs Event + inventory snapshot evaluation; step auto-advance/complete.
server/NeonSprawl.Server.Tests/Game/Quests/QuestObjectiveWiringTests.cs AAA integration-style tests for all four prototype quests + chain.
docs/manual-qa/NEO-118.md Server-side Bruno/unit verification checklist until NEO-119 HTTP.
docs/plans/NEO-118-implementation-plan.md This plan.

Files to modify

Path Rationale
server/NeonSprawl.Server/Game/Gathering/GatherOperations.cs Call quest wiring on successful gather; add quest/inventory/time deps.
server/NeonSprawl.Server/Game/Crafting/CraftOperations.cs Call quest wiring on successful craft.
server/NeonSprawl.Server/Game/Encounters/EncounterCompletionOperations.cs Call quest wiring after successful completion + grants.
server/NeonSprawl.Server/Game/Quests/QuestStateOperations.cs Invoke inventory_has_item pass after accept/advance; add inventory + time deps.
server/NeonSprawl.Server/Game/Interaction/InteractionApi.cs Pass quest wiring deps into GatherOperations.TryGather.
server/NeonSprawl.Server/Game/Crafting/PlayerCraftApi.cs Pass quest wiring deps into CraftOperations.TryCraft.
server/NeonSprawl.Server/Game/Encounters/EncounterCombatWiring.cs Pass quest wiring deps through to EncounterCompletionOperations.
server/NeonSprawl.Server/Game/AbilityInput/AbilityCastApi.cs Pass quest wiring deps into EncounterCombatWiring (if signature changes).
server/NeonSprawl.Server.Tests/Game/Quests/QuestStateOperationsTests.cs Update TryAccept / TryAdvanceStep call sites for new parameters.
server/README.md Document objective wiring hooks and inventory_has_item triggers.
docs/decomposition/modules/documentation_and_implementation_alignment.md E7.M1 row — NEO-118 wiring landed (on story complete).
docs/decomposition/modules/E7_M1_QuestStateMachine.md NEO-118 wiring bullet + README link (on story complete).
docs/decomposition/modules/module_dependency_register.md E7.M1 note — NEO-118 (on story complete).
docs/plans/E7M1-prototype-backlog.md E7M1-07 acceptance checkboxes (on story complete).

Tests

Test file What it covers
QuestObjectiveWiringTests.cs Gather intro: accept → gather scrap_metal_bulk until complete. Refine intro: prerequisites met → accept → craft refine_scrap_standard → complete. Combat intro: accept → encounter complete event → complete. Chain: four-step mixed kinds including inventory_has_item terminal after token present. Parallel: gather intro + chain step 0 simultaneous credit. Inventory on advance: token pre-seeded → step advance to terminal → auto-complete without new grant.
QuestStateOperationsTests.cs Regression — existing accept/advance/complete cases with updated method signatures (inventory store + time provider from factory).
Existing gather/craft/encounter tests Smoke regression — no behavior change when no active quests (full suite in CI).

Open questions / risks

Question / risk Agent recommendation Status
QuestStateOperations signature growth Add IPlayerInventoryStore + TimeProvider to accept/advance for wiring; keep complete unchanged adopted
Terminal step auto-complete without explicit advance call Wiring calls TryMarkComplete when last step objectives satisfied (operations layer idempotent) adopted
Multi-objective steps Implement generic all-objectives-satisfied check even though prototype steps are single-objective adopted
Encounter done before combat quest accept Out of scope; manual QA accepts before combat deferred
NEO-117 review nits reused ids Use PrototypeE7M1QuestCatalogRules constants in wiring tests adopted