NEO-147: document generator operations and reconcile plan
parent
87a0eb25ec
commit
60aa0be4f0
|
|
@ -57,6 +57,8 @@ Epic 7 **Slice 4** — `contract_issued`, `contract_complete`, reward anomalies.
|
|||
|
||||
**Runtime stores (NEO-146):** per-player **`IContractInstanceStore`** (one active instance) + append-only **`IContractOutcomeStore`**; [server README — Contract instance store](../../../server/README.md#contract-instance-store-neo-146).
|
||||
|
||||
**Generator operations (NEO-147):** **`ContractGeneratorOperations.TryIssue`** — template selection, deterministic instance ids, structured deny codes; [server README — Contract generator operations](../../../server/README.md#contract-generator-operations-neo-147).
|
||||
|
||||
## Source anchors
|
||||
|
||||
- Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 7.
|
||||
|
|
|
|||
|
|
@ -62,9 +62,16 @@ Issue a validated **`active`** contract instance from **`ContractSeed`** inputs
|
|||
|
||||
## Acceptance criteria checklist
|
||||
|
||||
- [ ] Issue creates **`active`** instance with correct encounter binding.
|
||||
- [ ] Second issue while active returns structured deny.
|
||||
- [ ] `dotnet test` covers generator gates.
|
||||
- [x] Issue creates **`active`** instance with correct encounter binding.
|
||||
- [x] Second issue while active returns structured deny.
|
||||
- [x] `dotnet test` covers generator gates.
|
||||
|
||||
## Implementation reconciliation (shipped)
|
||||
|
||||
- **Types:** `ContractGeneratorReasonCodes`, `ContractIssueOperationResult`, `ContractInstanceIds.MakeDeterministicInstanceId`.
|
||||
- **Orchestrator:** `ContractGeneratorOperations.TryIssue` — band + faction template selection, deterministic id, store persist.
|
||||
- **Tests:** `ContractGeneratorOperationsTests` (11 unit), `ContractGeneratorOperationsIntegrationTests` (DI), `ContractGeneratorPersistenceIntegrationTests` (`RequirePostgresFact`); **888** tests green.
|
||||
- **Docs:** `server/README.md` contract generator section; `E7_M4_ContractMissionGenerator.md` NEO-147 note.
|
||||
|
||||
## Technical approach
|
||||
|
||||
|
|
|
|||
|
|
@ -282,10 +282,38 @@ Per-player contract runtime state lives in **`IContractInstanceStore`**, keyed b
|
|||
- **`TryMarkComplete`** — first completion returns `true`; replay returns `false` without changing **`completedAt`**.
|
||||
- **`TryClearInstance`** — dev fixture only; removes one instance row.
|
||||
|
||||
Completed instances are immutable (no status regression). Player, template, and instance ids are normalized (trim + lowercase for player/template/instance; seed bucket trim only). Template id validation against **`IContractTemplateRegistry`** is deferred to NEO-147.
|
||||
Completed instances are immutable (no status regression). Player, template, and instance ids are normalized (trim + lowercase for player/template/instance; seed bucket trim only). Template id validation and selection run in **`ContractGeneratorOperations`** (NEO-147).
|
||||
|
||||
**Storage:** in-memory singleton when **`ConnectionStrings:NeonSprawl`** is unset (seeds configured dev player only). When Postgres is configured, **`PostgresContractInstanceStore`** persists to **`contract_instance`** ([`V011__contract_instance.sql`](../db/migrations/V011__contract_instance.sql)) with a partial unique index enforcing one active row per player. Plan: [NEO-146 implementation plan](../../docs/plans/NEO-146-implementation-plan.md).
|
||||
|
||||
## Contract generator operations (NEO-147)
|
||||
|
||||
**`ContractGeneratorOperations.TryIssue`** issues an **`active`** contract instance from seed inputs + player context. Call this orchestrator (or future HTTP wrapper) — not **`IContractInstanceStore.TryCreateActive`** directly — so template selection, faction gates, and deterministic ids stay centralized.
|
||||
|
||||
**Inputs:** `playerId`, optional `templateId`, `seedBucket`, optional `zoneDifficultyBand` (defaults to **1**).
|
||||
|
||||
**Template selection:**
|
||||
|
||||
- **Explicit `templateId`:** registry lookup; deny **`unknown_template`** when missing; deny **`no_eligible_template`** when band or faction standing floor fails.
|
||||
- **Omitted/empty `templateId`:** first template from **`GetDefinitionsInIdOrder()`** matching band + standing.
|
||||
- Faction floor uses **`FactionGateOperations`** on the template **`minFactionStanding`** row.
|
||||
|
||||
**Deterministic instance id:** **`ContractInstanceIds.MakeDeterministicInstanceId`** — SHA-256 of `{playerId}|{templateId}|{seedBucket}` (normalized) → **`ci_{32 lowercase hex}`**.
|
||||
|
||||
**Reason codes:**
|
||||
|
||||
| Code | When |
|
||||
|------|------|
|
||||
| **`invalid_ids`** | Empty player or seed bucket after normalize |
|
||||
| **`player_not_writable`** | Player not in dev bucket / Postgres `player_position` |
|
||||
| **`active_contract_exists`** | Player already has an active instance |
|
||||
| **`unknown_template`** | Explicit template id not in registry |
|
||||
| **`no_eligible_template`** | No template matches band + standing filters |
|
||||
|
||||
**Result:** **`ContractIssueOperationResult`** — `success`, optional `reasonCode`, optional **`ContractInstanceState`** snapshot, **`encounterTemplateId`** from the selected template.
|
||||
|
||||
Economy band-cap lint at issue time lands in **NEO-150** (E7M4-07). HTTP **`POST …/contracts/issue`** lands in **NEO-151** (E7M4-08). Plan: [NEO-147 implementation plan](../../docs/plans/NEO-147-implementation-plan.md).
|
||||
|
||||
## Contract outcome store (NEO-146)
|
||||
|
||||
Append-only contract completion audit rows live in **`IContractOutcomeStore`**. Each **`ContractOutcomeRow`** captures instance id, delivery idempotency key **`{playerId}:contract_complete:{contractInstanceId}`**, grant snapshot summary (items, skill XP, reputation — same shapes as **`RewardDeliveryEvent`**), and **`recordedAt`**.
|
||||
|
|
|
|||
Loading…
Reference in New Issue