14 KiB
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-117 — QuestStateOperations accept/advance/complete (Done on main) |
| Pattern | NEO-106 — EncounterCombatWiring best-effort side effect on success paths; NEO-117 — QuestStateOperations 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
quantityfor 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_completeobjectives treat satisfaction as counter ≥ 1 (no quantity field in catalog).- Step completion: when all objectives on the current step are satisfied →
TryAdvanceStepto next index, orTryMarkCompleteon terminal step; after advance, runinventory_has_itemsnapshot 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 viaQuestStateOperations.- Hook from
GatherOperations.TryGathersuccess —gather_itemobjectives (item id + quantity delta). - Hook from
CraftOperations.TryCraftsuccess —craft_recipeobjectives (recipe id; quantity = craft batch size). - Hook from
EncounterCompletionOperations.TryCompleteAndGrantsuccess —encounter_completeobjectives (encounter id). inventory_has_item— inventory snapshot read after successful inventory mutations;TryProcessInventoryHasItemon quest accept and after step advance (current/new step only).- Counter accumulation for quantity objectives; auto
TryAdvanceStep/TryMarkCompletewhen 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_completecredit 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_introadvances whenscrap_metal_bulkgather succeeds. - Active
prototype_quest_refine_introadvances onrefine_scrap_standardcraft success. - Active
prototype_quest_combat_introadvances whenprototype_combat_pocketcompletes. - 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.mdobjective wiring section;docs/manual-qa/NEO-118.md.
Technical approach
-
QuestObjectiveKinds— string constants:gather_item,craft_recipe,encounter_complete,inventory_has_item(mirror schema enum). -
QuestObjectiveWiring(Game/Quests/)TryProcessGatherSuccess(playerId, itemId, quantityGranted, …)— scan all catalog quest ids; for each active row, match current-stepgather_itemobjectives byitemId; bump counter byquantityGranted(capped at objectivequantity).TryProcessCraftSuccess(playerId, recipeId, batchQuantity, …)— matchcraft_recipebyrecipeId; increment counter bybatchQuantity(capped).TryProcessEncounterCompleteSuccess(playerId, encounterId, …)— matchencounter_complete; set counter to 1 when encounter id matches.TryProcessInventoryHasItemForPlayer(playerId, …)/TryProcessInventoryHasItemForQuest(playerId, questId, …)— readIPlayerInventoryStore.TryGetSnapshot; for current-stepinventory_has_itemobjectives, set counter tomin(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)thenTryProcessInventoryHasItemForQuestfor the advanced quest (chain terminal token case). - Best-effort: swallow/log at debug; never throw to callers.
-
Inventory quantity helper
- Private static on wiring (or small internal helper): sum
PlayerInventorySnapshotbag slot quantities for anitemId(same semantics as craft/encounter tests).
- Private static on wiring (or small internal helper): sum
-
Call sites
GatherOperations.TryGather— before successful return, callTryProcessGatherSuccesswithyield.ItemId/yield.Quantity(add quest + inventory deps to method signature).CraftOperations.TryCraft— before successful return, callTryProcessCraftSuccesswith normalized recipe id and batchquantity.EncounterCompletionOperations.TryCompleteAndGrant— after successful grant + completion mark, callTryProcessEncounterCompleteSuccesswith normalized encounter id.QuestStateOperations.TryAccept— after successful activation, callTryProcessInventoryHasItemForQuest(addIPlayerInventoryStore+TimeProviderparams).QuestStateOperations.TryAdvanceStep— after successful advance, callTryProcessInventoryHasItemForQuestfor the same quest id.- Propagate new dependencies through
InteractionApi,PlayerCraftApi,EncounterCombatWiring, andAbilityCastApiDI resolves (factory tests already resolve full host).
-
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_standard→ completed. - Combat intro: accept →
TryProcessEncounterCompleteSuccessforprototype_combat_pocket→ completed. - Chain: accept chain → gather 5 scrap → advance to step 1 → craft refine → step 2 → craft stim → step 3 → seed
contract_handoff_tokenin inventory → advance triggers step 4inventory_has_item→ completed. - Parallel credit: gather intro + chain step 0 both active; one gather credits both counters appropriately.
- Update
QuestStateOperationsTestssignatures if accept/advance gain inventory/time params (pass real store from factory).
- Use
-
Docs
server/README.md— replace “Objective wiring is E7M1-07” stub with wiring summary, hook table,inventory_has_itemtrigger 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 |