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

13 KiB
Raw Blame History

NEO-69 — Implementation plan

Story reference

Field Value
Key NEO-69
Title E3.M2: CraftResult engine (inputs, outputs, refine XP)
Linear https://linear.app/neon-sprawl/issue/NEO-69/e3m2-craftresult-engine-inputs-outputs-refine-xp
Module E3.M2 — RefinementAndRecipeExecution · Epic 3 Slice 3 · backlog E3M2-05
Branch NEO-69-craftresult-engine-inputs-outputs-refine-xp
Precursors NEO-67IRecipeDefinitionRegistry + DI (Done on main); NEO-54PlayerInventoryOperations (Done); NEO-42RefineActivitySkillXpGrant (Done)
Pattern NEO-62 — static *Operations orchestrator + subsystem reason codes; NEO-64 — engine-only scope (HTTP in follow-up)
Client counterpart NEO-74 — E3S5-03 craft UI + recipe list (blocked by NEO-70 craft HTTP; server-only in this story)
Blocks NEO-70 — craft POST + Bruno spine; NEO-71 — telemetry hook sites

Kickoff clarifications

Topic Question Agent recommendation Answer
Output-full deny strategy Pre-flight output capacity vs consume inputs then rollback? Pre-flight — simulate all scaled output adds on a cloned snapshot before any input removal; deny inventory_full with zero mutations (cleaner for multi-input recipes than compensating rollback). User: pre-flight.
Quantity in engine API Include quantity in TryCraft in NEO-69? Yes — E3M2-05 lists invalid_quantity; NEO-70 POST will pass recipeId + optional quantity default 1; engine owns validation now. Adopted — backlog + NEO-70 wiring.
Craft vs inventory reason codes Surface insufficient_quantity or craft-layer insufficient_materials? Craft-layer insufficient_materials — E3M2 backlog stable deny vocabulary; map from inventory checks at orchestration boundary (mirror gather passthrough for inventory_full only). Adopted — backlog.
XP failure after commit Roll back inventory on XP deny? Yes — compensating remove of outputs + re-add inputs (mirror NEO-62 gather rollback on XP failure). Adopted — NEO-62 precedent.
Manual QA doc Add docs/manual-qa/NEO-69.md? No — engine-only like NEO-62; automated tests + NEO-70 manual QA cover player-visible verification. Adopted — NEO-62 pattern.

Goal, scope, and out-of-scope

Goal: Single server operation resolving CraftResult: validate recipe + inventory, atomically remove inputs and add outputs via PlayerInventoryOperations, award refine XP via RefineActivitySkillXpGrant on success — engine-only, ready for craft HTTP (NEO-70).

In scope (from Linear + E3M2-05):

  • CraftOperations / CraftResult types in server/NeonSprawl.Server/Game/Crafting/.
  • Stable deny reason codes: unknown_recipe, insufficient_materials, inventory_full, invalid_quantity (+ store-missing craft-layer codes as needed).
  • Pre-flight output capacity before input removal (kickoff decision).
  • Compensating inventory rollback when XP grant fails after successful input/output mutations (NEO-62 pattern).
  • Unit tests (AAA) via InMemoryWebApplicationFactory with real catalogs and stores; RefineActivitySkillXpGrant invoked on success path.
  • Document CraftResult shape in module doc (server-internal until promoted to wire in NEO-70).

Out of scope (from Linear + backlog):

  • Craft HTTP, Bruno, docs/manual-qa/ (NEO-70).
  • Client HUD (NEO-74).
  • Skill level gates, station checks, craft failure RNG.
  • Telemetry hook sites (NEO-71).

Acceptance criteria checklist

  • Successful craft removes configured inputs and adds outputs deterministically.
  • Insufficient materials returns stable reasonCode with no inventory change.
  • Full bag on output returns stable reasonCode; inputs are not consumed.
  • refine skill XP increases by 10 on success (NEO-42).
  • make_prototype_armor respects equipment slot rules from E3.M3 when bag is full but equip slot empty.

Technical approach

  1. Entry point: CraftOperations.TryCraft(...) — static orchestrator accepting playerId, recipeId, quantity, and dependencies:

    • IRecipeDefinitionRegistry
    • IItemDefinitionRegistry + IPlayerInventoryStore
    • ISkillDefinitionRegistry, IPlayerSkillProgressionStore, ISkillLevelCurve, PerkUnlockEngine
  2. Validate quantity: quantity <= 0Denied + CraftReasonCodes.InvalidQuantity (no writes).

  3. Resolve recipe: registry.TryGetDefinition(recipeId.Trim(), out def) — miss → unknown_recipe.

  4. Pre-flight inputs (read-only): For each input row, required amount = row.Quantity * quantity. Sum held quantity in the correct container (bag vs equipment via catalog inventorySlotKind) from current snapshot. Any shortfall → insufficient_materials without mutations.

  5. Pre-flight outputs (read-only, kickoff decision): Clone current snapshot; simulate removing all scaled inputs on the clone, then simulate each scaled output add. If any output cannot be fully placed → inventory_full without mutations. Implementation: TrySimulateRemoveStack + TrySimulateAddStack on PlayerInventoryOperations.

  6. Commit mutations (only after steps 45 pass):

    • Remove inputs: foreach input row, TryRemoveStack scaled quantity. (Should succeed given pre-flight; defensive deny insufficient_materials if not.)
    • Add outputs: foreach output row, TryAddStack scaled quantity. (Should succeed given pre-flight; defensive deny + compensating re-add inputs if not.)
    • XP: RefineActivitySkillXpGrant.GrantOnSuccessfulCraftOrRefine. On failure (store missing / skill deny): compensating rollback — remove granted outputs, re-add removed inputs; return Denied + progression_store_missing or passthrough skill reasonCode.
  7. Success envelope — CraftResult:

    • Success = true, ReasonCode = null
    • InputsConsumed: list of { itemId, quantity } applied (scaled).
    • OutputsGranted: list of { itemId, quantity } applied (scaled).
    • XpGrantSummary: compact summary (refine, 10, activity) for tests / future wire.
  8. Reason codes (CraftReasonCodes):

    • unknown_recipe, insufficient_materials, invalid_quantity — craft-layer.
    • inventory_full — passthrough literal (same string as NEO-54).
    • progression_store_missing, inventory_store_missing — craft-layer store-missing (mirror gather).
  9. Equipment slot AC (make_prototype_armor): No special-case in craft engine — prototype_armor_shell routes to equipment via catalog metadata in pre-flight + TryAddStack. Test: fill all 24 bag slots, leave equipment empty, craft succeeds and places armor in equipmentSlots[0].

  10. Primary demo recipe for happy path tests: refine_scrap_standard (5× scrap_metal_bulk → 1× refined_plate_stock) and/or make_field_stim_mk0.

  11. Docs (on land): Update E3_M2 Implementation snapshot + CraftResult contract note; documentation_and_implementation_alignment.md E3.M2 row; E3M2-05 checkboxes in E3M2-prototype-backlog.md; brief server/README.md craft-engine subsection (engine-only; HTTP in NEO-70). Cross-link NEO-74 / full-stack note per full-stack epic decomposition.

Files to add

Path Purpose
server/NeonSprawl.Server/Game/Crafting/CraftReasonCodes.cs Stable craft deny codes (craft-layer + passthrough).
server/NeonSprawl.Server/Game/Crafting/CraftIoApplied.cs One consumed input or granted output row in success envelope.
server/NeonSprawl.Server/Game/Crafting/CraftXpGrantSummary.cs Compact refine XP summary on successful craft.
server/NeonSprawl.Server/Game/Crafting/CraftResult.cs Success/deny envelope: inputs consumed, outputs granted, reasonCode, xp summary.
server/NeonSprawl.Server/Game/Crafting/CraftOperations.cs Orchestrator: validate → pre-flight → remove inputs → add outputs → XP; rollback on XP failure.
server/NeonSprawl.Server.Tests/Game/Crafting/CraftOperationsTests.cs AAA: success, insufficient_materials, inventory_full (bag), make_prototype_armor + full bag, unknown_recipe, invalid_quantity, XP grant + rollback path.
docs/plans/NEO-69-implementation-plan.md This plan.

Files to modify

Path Rationale
server/NeonSprawl.Server/Game/Items/PlayerInventoryOperations.cs Add snapshot-based CanPlaceStack (or simulate helper) for craft output pre-flight without mutation.
server/README.md Document craft engine (CraftOperations, reason codes, refine XP hook); note HTTP deferred to NEO-70.
docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md Implementation snapshot — craft engine + CraftResult field table.
docs/decomposition/modules/documentation_and_implementation_alignment.md E3.M2 row — note NEO-69 craft engine when landed.
docs/plans/E3M2-prototype-backlog.md E3M2-05 acceptance checkboxes + landed note when complete.

Tests

Test file What it covers
server/NeonSprawl.Server.Tests/Game/Crafting/CraftOperationsTests.cs Success: seed scrap_metal_bulk, craft refine_scrap_standard, assert inputs removed, refined_plate_stock granted, refine XP +10, envelope fields. Insufficient materials: empty inventory → insufficient_materials, snapshot unchanged. Inventory full: fill bag so output cannot fit → inventory_full, inputs unchanged. Equipment routing: full bag + empty equip slot → make_prototype_armor succeeds, armor in equipment slot. Unknown recipeunknown_recipe. Invalid quantity (0 or negative) → invalid_quantity. XP path: assert RefineActivitySkillXpGrant effect via progression store (mirror GatherOperationsTests / RefineActivitySkillXpGrantTests). AAA per csharp-style.
server/NeonSprawl.Server.Tests/Game/Items/PlayerInventoryOperationsTests.cs Optional: one test for new CanPlaceStack helper if added as public surface (bag full vs equipment route).

No Bruno or manual QA in this story — NEO-70 owns player-visible HTTP verification.

Open questions / risks

Question / risk Agent recommendation Status
Pre-flight simulate vs rollback User chose pre-flight at kickoff. adopted
Multi-output pre-flight order Simulate adds in recipe outputs order on one cloned snapshot (order should not matter for prototype fixed outputs; document if later recipes introduce interactions). deferred
NEO-71 telemetry hooks Do not add item_crafted / craft_failed comments in NEO-69 — NEO-71 owns hook placement. adopted
Client not in this story Cross-link NEO-74; Bruno-only verification is not prototype-complete. adopted

Decisions (kickoff)

  • Pre-flight output capacity before any input removal (user confirmed).
  • quantity parameter on TryCraft with invalid_quantity deny in this story.
  • Craft-layer insufficient_materials at orchestration boundary (not raw insufficient_quantity on craft deny).
  • XP failure rollback — remove outputs, restore inputs (NEO-62 gather precedent).
  • No docs/manual-qa/NEO-69.md — engine-only; NEO-70 manual QA covers HTTP.