neon-sprawl/docs/plans/E7M4-pre-production-backlog.md

21 KiB
Raw Blame History

E7.M4 — Pre-production story backlog (ContractMissionGenerator)

Working backlog for Epic 7 — Slice 4 (contract generator). Decomposition and contracts: E7.M4 — ContractMissionGenerator.

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: E7M3 (paired server+client), E7M2 (reward capstone).

Labels (Linear): every issue E7.M4; add server or client + Story as listed.

Precursor (do not re-scope): E7.M1 Ready — static quest catalog + accept/complete (NEO-112NEO-123). E7.M2 Ready — idempotent completionRewardBundle delivery (NEO-124NEO-132). E7.M3 Ready — faction standing + quest gates (NEO-133NEO-143). E5.M3 Readyprototype_combat_pocket encounter clear (NEO-100NEO-111). Slice 4 adds repeatable contract instances; does not migrate static quests or first-time encounter loot.

Upstream (must be landed): E7.M1 quest state; E7.M2 RewardRouterOperations + IRewardDeliveryStore; E7.M3 IFactionStandingStore; E5.M3 encounter progress/completion. quest_scope_and_party.md solo idempotency.

E4.M1 note (zone difficulty): Epic 7 Slice 4 AC keys contracts by zone difficulty band. Full E4.M1 zone graph is not a Slice 4 blocker — prototype uses content-only zoneDifficultyBand on templates and optional zoneDifficultyBand on issue POST until E4.M1 GET …/zone lands; replace stub input with live zone read when E4.M1 Slice 1 lands.

Prototype contract spine (frozen in E7M4-01): one frozen ContractTemplate id; one encounter objective (prototype_combat_pocket); one scaled repeat payout bundle; issuance filtered by zone difficulty band + optional faction standing floor.

Linear issues (created): attach docs/plans/NEO-*-implementation-plan.md on the story branch when work starts.

Slug Layer Linear
E7M4-01 server NEO-144
E7M4-02 server NEO-145
E7M4-03 server NEO-146
E7M4-04 server NEO-147
E7M4-05 server NEO-148
E7M4-06 server NEO-149
E7M4-07 server NEO-150
E7M4-08 server NEO-151
E7M4-09 server NEO-152
E7M4-10 client NEO-153
E7M4-11 client NEO-154

Dependency graph in Linear: E7M4-02 blocked by E7M4-01. E7M4-03 blocked by E7M4-02. E7M4-04 blocked by E7M4-03. E7M4-05 blocked by E7M4-02. E7M4-06 blocked by E7M4-04 and E7M4-05. E7M4-07 blocked by E7M4-04. E7M4-08 blocked by E7M4-06 and E7M4-07. E7M4-09 blocked by E7M4-04 and E7M4-08. E7M4-10 blocked by E7M4-08 and E7M4-09. E7M4-11 blocked by E7M4-10.

Board order: estimates 111 matching slug order (E7M4-01 = 1 … E7M4-11 = 11). On the Epic 7 project board, sort by Estimate (ascending).


Story order (recommended)

Order Slug Layer Depends on
1 E7M4-01 server E7.M2 bundle schemas; E5.M3 encounter freeze
2 E7M4-02 server E7M4-01
3 E7M4-03 server E7M4-02
4 E7M4-04 server E7M4-03
5 E7M4-05 server E7M4-02
6 E7M4-06 server E7M4-04, E7M4-05
7 E7M4-07 server E7M4-04
8 E7M4-08 server E7M4-06, E7M4-07
9 E7M4-09 server E7M4-04, E7M4-08
10 E7M4-10 client E7M4-08, E7M4-09
11 E7M4-11 client E7M4-10

Downstream (separate modules): E4.M1 live zone id for issuance input; E6.M4 full parity map; E3.M5 faucet/sink dashboards.


Kickoff decisions (decomposition defaults)

Topic Decision Rationale
Contract vs quest Separate ContractInstance state — not new static QuestDef rows Repeatable midgame jobs without polluting onboarding catalog
Template count One frozen template id Readable Slice 4 capstone; generator API still general
Objective kind (v1) encounter_clear — bind to E5.M3 encounterId Reuses combat pocket loop players already know
Zone difficulty Content zoneDifficultyBand on template + optional POST override until E4.M1 Epic AC without blocking on travel graph
Faction filter Optional minFactionStanding on template — prototype 0 (open) Standing-based template pick deferred to second template in follow-on
Issuance cap One active contract per player (prototype) Avoid HUD/stack complexity in Slice 4
Seed / determinism ContractSeed: playerId + templateId + seedBucket (date or client counter) → deterministic contractInstanceId suffix for audit Supports daily rotation without full proc-gen
Completion payout completionRewardBundle on template — deliver via extended RewardRouterOperations Reuse E7.M2 idempotency + inventory/XP/rep apply
Idempotency key {playerId}:contract_complete:{contractInstanceId} Per-instance repeat grants; distinct from quest and encounter keys
Encounter loot Unchanged — first-time prototype_combat_pocket_clear idempotent per player Contract payout is additional repeat incentive, not double first-clear
Economy validation Prototype band caps in content + issue-time lint (not full E6.M4) Slice 4 AC: generated contracts validate against economy rules
Party mode Solo only E8.M1 not landed
HUD fidelity Prototype labels / debug panels NEO-122 / NEO-142 precedent

Prototype contract template freeze (E7M4-01)

ContractTemplate.id zoneDifficultyBand encounterTemplateId minFactionStanding completionRewardBundle
prototype_contract_clear_combat_pocket 1 prototype_combat_pocket prototype_faction_grid_operators 0 scrap_metal_bulk ×5; salvage 15 (mission_reward)

Band policy (E7M4-07): difficulty band 1 caps: max 10 per item grant row, max 25 skill XP per grant, max 10 rep per grant (enforced at issue + CI).

Issuance: POST …/contracts/issue with optional zoneDifficultyBand (default 1 for prototype district). Server selects templates where zoneDifficultyBand matches and player standing ≥ minFactionStanding. Returns contractInstanceId, template summary, objective encounter id, status active.

Completion: When EncounterCompletionOperations marks prototype_combat_pocket complete for a player with matching active contract instance → ContractCompletionOperations delivers bundle → status completed → record ContractOutcome.


E7M4-01 — ContractTemplate catalog + schemas + CI

Goal: Lock contract content shape and CI validation before server load.

In scope

  • content/schemas/contract-template.schema.json, content/schemas/contract-seed.schema.json (issue request / audit shape).
  • Reuse quest-reward-bundle / reputation-grant-row $refs for completionRewardBundle on templates.
  • content/contracts/prototype_contract_templates.json — one frozen template row per freeze table.
  • scripts/validate_content.py: contract schema validation, encounter cross-ref, item/skill/faction cross-refs, band-cap lint, single-template allowlist.
  • Designer note in E7_M4 + content/README.md.

Out of scope

  • Server loader, stores, operations, HTTP, Godot.

Acceptance criteria

  • PR gate validates contract template JSON.
  • Exactly one prototype template id with encounter + bundle cross-refs.
  • Bundle exceeding band caps fails CI.

Client counterpart: none (infrastructure-only).


E7M4-02 — Server contract template catalog load (fail-fast)

Goal: Disk → host: startup load of content/contracts/*_contract_templates.json with CI-parity validation.

In scope

  • Loader + registry types under server/NeonSprawl.Server/Game/Contracts/.
  • IContractTemplateRegistry + DI registration.
  • Cross-check encounterTemplateId vs IEncounterDefinitionRegistry at startup.
  • Unit tests (AAA) for happy path and broken encounter refs.
  • server/README.md section.

Out of scope

  • Per-player instance store, generator, HTTP.

Acceptance criteria

  • Host exits on invalid contract catalog or broken encounter refs at startup.
  • Registry resolves template metadata by id.

Client counterpart: none (infrastructure-only).


E7M4-03 — Contract instance + outcome stores

Goal: Durable per-player ContractInstance (active/completed) and ContractOutcome audit records.

In scope

  • IContractInstanceStore, IContractOutcomeStore — in-memory implementations; optional Postgres migrations (NEO-116-style split).
  • Instance row: contractInstanceId, templateId, playerId, status (active | completed | expired), seedBucket, issuedAt, optional completedAt.
  • Outcome row: instance id, delivery idempotency key, grant snapshot summary.
  • Unit tests: issue, complete, idempotent complete replay.
  • server/README.md store sections.

Out of scope

  • Generator logic (E7M4-04), payout apply (E7M4-05).

Acceptance criteria

  • At most one active instance per player (prototype policy).
  • Completed instances immutable; outcome append-only.

Client counterpart: none (infrastructure-only).


E7M4-04 — ContractGeneratorOperations.TryIssue

Goal: Issue a validated contract instance from ContractSeed + player context.

In scope

  • ContractGeneratorOperations.TryIssue in server/NeonSprawl.Server/Game/Contracts/.
  • Inputs: playerId, optional templateId, seedBucket, zoneDifficultyBand (default 1).
  • Template selection: match band + faction standing floor; fail no_eligible_template when none.
  • Deny active_contract_exists when player already has active instance.
  • Deterministic contractInstanceId from seed hash + player + template.
  • Unit + integration tests (AAA): happy issue, deny paths, unknown template fail-closed.

Out of scope

  • HTTP (E7M4-08), encounter completion wiring (E7M4-06).

Acceptance criteria

  • Issue creates active instance with correct encounter binding.
  • Second issue while active returns structured deny.

Client counterpart: NEO-153 (blocked by E7M4-08).


E7M4-05 — Extend RewardRouterOperations for contract completion bundles

Goal: Contract completion payouts reuse E7.M2 delivery idempotency with contract-specific keys.

In scope

  • RewardRouterOperations.TryDeliverContractCompletion — same grant kinds as quest completion (items, skill XP, rep) with key {playerId}:contract_complete:{contractInstanceId}.
  • Extend IRewardDeliveryStore / RewardDeliveryEvent for contract source kind (contract_completion).
  • Compensating rollback policy mirrors E7.M2 quest path.
  • Integration tests: first delivery, idempotent replay, unknown item/faction deny.
  • server/README.md reward router section.

Out of scope

  • Encounter trigger (E7M4-06), HTTP projections (E7M4-08).

Acceptance criteria

  • First-time contract complete applies bundle exactly once per instance id.
  • Replay returns already_delivered without double grant.

Client counterpart: NEO-153.


E7M4-06 — ContractCompletionOperations + encounter wiring

Goal: Encounter clear completes matching active contract and triggers payout.

In scope

  • ContractCompletionOperations.TryCompleteOnEncounterClear — match encounterTemplateId on active instance.
  • Wire from EncounterCompletionOperations / EncounterCombatWiring success path (after encounter completion commit, before or after encounter loot — document order: encounter loot first, contract payout second).
  • Mark instance completed; record ContractOutcome; invoke TryDeliverContractCompletion.
  • Integration tests: issue → encounter clear → contract completed + delivery; clear without contract unchanged; wrong encounter no-op.
  • Bruno smoke: issue → encounter-progress clear → contract GET shows completed.

Out of scope

  • Godot HUD (E7M4-10).

Acceptance criteria

  • Active contract completes when bound encounter clears.
  • Encounter first-time loot idempotency unchanged (E5.M3).

Client counterpart: NEO-153, NEO-154.


E7M4-07 — Economy validation lint at issue time

Goal: Slice 4 AC — generated/issued contracts validate against prototype economy caps.

In scope

  • ContractEconomyValidation.TryValidateTemplate — band caps from content policy (item qty, skill XP, rep per grant row).
  • Call from TryIssue before instance persist; deny economy_cap_exceeded fail-closed.
  • Cross-ref unknown item/skill/faction ids (registry lookups).
  • Unit tests: at-cap pass, over-cap deny, unknown id deny.
  • Document parity stub toward E6.M4 / E3.M5 in module doc (full map deferred).

Out of scope

  • Full RewardParityMap (E6.M4); live ops dashboards (E9.M2).

Acceptance criteria

  • Template at freeze caps issues successfully.
  • Tampered catalog row over caps fails at startup (CI) and cannot issue if slipped through.

Client counterpart: none (server policy).


E7M4-08 — Contract issue POST + per-player contract GET

Goal: Client-readable contract issuance and state without local math.

In scope

  • POST /game/players/{id}/contracts/issueContractIssueApi + DTOs.
  • GET /game/players/{id}/contracts — active + recent completed instances with template summary, objective encounter id, completionRewardSummary when delivered.
  • Integration tests + Bruno bruno/neon-sprawl-server/contracts/.
  • server/README.md sections.

Out of scope

  • Godot parse/HUD (E7M4-10).

Acceptance criteria

  • POST issue returns active instance JSON.
  • GET lists active contract with encounter objective id.
  • GET completed row includes delivery summary after encounter clear.

Client counterpart: NEO-153.


E7M4-09 — Contract telemetry hook sites (comment-only)

Goal: Anchor future E9.M1 catalog events without ingest.

In scope

  • Comment-only contract_issued in ContractGeneratorOperations.TryIssue (successful issue).
  • Comment-only contract_complete in ContractCompletionOperations (successful complete + delivery).
  • Comment-only reward_anomaly stub at economy validation deny + delivery rollback paths.
  • server/README.md hook list.

Out of scope

  • E9.M1 ingest, dashboards.

Acceptance criteria

  • Hook sites documented in README; no behavior change beyond comments.

Landed (NEO-152): comment-only contract_issued, contract_complete, and reward_anomaly hooks; server README — Contract telemetry hooks (NEO-152); plan NEO-152.

Client counterpart: none (infrastructure-only).


E7M4-10 — Client contract issue + progress HUD (Godot)

Goal: Player can issue and track an active contract in Godot without Bruno.

In scope

  • contract_client.gd, ContractActiveLabel, ContractIssueFeedbackLabel (or extend quest HUD section).
  • POST …/contracts/issue on agreed binding (e.g. Shift+C); GET …/contracts on boot + after encounter-complete refresh (reuse encounter_progress_client.gd signal or quest HUD controller pattern).
  • Surface issue denies (active_contract_exists, no_eligible_template, economy_cap_exceeded) with readable copy.
  • GdUnit parse tests; client/README.md section.
  • docs/manual-qa/NEO-153.md component checklist.

Out of scope

  • Final contract board UI; zone picker (E4.M1).

Acceptance criteria

  • Active contract visible with encounter objective id after issue.
  • Completed contract shows reward summary line on HUD.

Landed (NEO-153): contract_client.gd, contract_hud_controller.gd, Shift+C prototype issue, contract HUD labels; client README — Contract issue + progress HUD (NEO-153); NEO-153 manual QA; plan NEO-153.

Server counterpart: NEO-151, NEO-152.


E7M4-11 — Playable repeatable contract capstone (Godot)

Goal: Prove Epic 7 Slice 4 acceptance in Godot: issue contract, clear combat pocket, receive repeat payout; economy validation holds.

In scope

  • docs/manual-qa/NEO-154.md: single-session capstone — extend NEO-143 or NEO-111 encounter path with Shift+C issue → clear pocket → contract reward scrap_metal_bulk ×5 + salvage XP → re-issue after complete → second full loop.
  • client/README.md: End-to-end repeatable contract loop (NEO-154) section; cross-links NEO-144NEO-153 and E5.M3 encounter capstone.
  • Module alignment on completion: E7_M4, documentation_and_implementation_alignment.md, module_dependency_register.md.

Out of scope

  • Multi-template rotation; E4.M1 zone travel; Bruno-only proof.

Acceptance criteria

  • Human completes docs/manual-qa/NEO-154.md with server + client.
  • Epic 7 Slice 4 AC: issued contract validates economy caps; repeat completion idempotent per instance.
  • Re-read epic_07 Slice 4 AC.

Client counterpart: this story is the Slice 4 client capstone.


Decomposition complete checklist