21 KiB
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-112–NEO-123). E7.M2 Ready — idempotent completionRewardBundle delivery (NEO-124–NEO-132). E7.M3 Ready — faction standing + quest gates (NEO-133–NEO-143). E5.M3 Ready — prototype_combat_pocket encounter clear (NEO-100–NEO-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 1–11 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 forcompletionRewardBundleon 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
encounterTemplateIdvsIEncounterDefinitionRegistryat startup. - Unit tests (AAA) for happy path and broken encounter refs.
server/README.mdsection.
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, optionalcompletedAt. - Outcome row: instance id, delivery idempotency key, grant snapshot summary.
- Unit tests: issue, complete, idempotent complete replay.
server/README.mdstore sections.
Out of scope
- Generator logic (E7M4-04), payout apply (E7M4-05).
Acceptance criteria
- At most one
activeinstance 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.TryIssueinserver/NeonSprawl.Server/Game/Contracts/.- Inputs:
playerId, optionaltemplateId,seedBucket,zoneDifficultyBand(default 1). - Template selection: match band + faction standing floor; fail
no_eligible_templatewhen none. - Deny
active_contract_existswhen player already has active instance. - Deterministic
contractInstanceIdfrom 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
activeinstance 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/RewardDeliveryEventfor 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.mdreward 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_deliveredwithout 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— matchencounterTemplateIdon active instance.- Wire from
EncounterCompletionOperations/EncounterCombatWiringsuccess path (after encounter completion commit, before or after encounter loot — document order: encounter loot first, contract payout second). - Mark instance
completed; recordContractOutcome; invokeTryDeliverContractCompletion. - 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
TryIssuebefore instance persist; denyeconomy_cap_exceededfail-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/issue—ContractIssueApi+ DTOs.GET /game/players/{id}/contracts— active + recent completed instances with template summary, objective encounter id,completionRewardSummarywhen delivered.- Integration tests + Bruno
bruno/neon-sprawl-server/contracts/. server/README.mdsections.
Out of scope
- Godot parse/HUD (E7M4-10).
Acceptance criteria
- POST issue returns
activeinstance 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_issuedinContractGeneratorOperations.TryIssue(successful issue). - Comment-only
contract_completeinContractCompletionOperations(successful complete + delivery). - Comment-only
reward_anomalystub at economy validation deny + delivery rollback paths. server/README.mdhook 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/issueon agreed binding (e.g. Shift+C);GET …/contractson boot + after encounter-complete refresh (reuseencounter_progress_client.gdsignal 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.mdsection. docs/manual-qa/NEO-153.mdcomponent 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 rewardscrap_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-144–NEO-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.mdwith 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
- Every player-visible AC has a
clientLinear issue (NEO-153, NEO-154) - No forbidden deferral language in backlog
- Capstone client story exists (E7M4-11 / NEO-154)
- Epic DoD updated for Godot verification (see epic_07 update)
- documentation_and_implementation_alignment.md E7.M4 row updated (decomposition pass)
- module_dependency_register.md E7.M4 note updated