Hold & Win Reference Mechanic Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add a reusable, persistent hold_and_win mechanic to the canonical Go math runtime and deliver a separate 3-column × 5-row reference pack with versioned Draft math, reproducible simulation evidence and gated handoff artifacts.

Architecture: Implement one pure versioned state machine (Start, Step, Settle) in libs/mathengine; production persists each step while RunToCompletion drives the identical core for batch simulation. Add a new feature type instead of changing the existing partial hold_and_spin, and keep all executable parameters canonical in /Users/admin/kiro/backend.

Tech Stack: Go, libs/mathengine, deterministic libs/rng, Math Studio domain/config mapping, PostgreSQL game-engine state, YAML/JSON config, Go tests with race detector, seeded Monte Carlo evidence, Markdown GDD/Math Spec/handoff.

Global Constraints

  • Approved design: docs/superpowers/specs/2026-08-09-hold-and-win-reference-design.md at workspace commit c44bc01.
  • Grid is exactly 3 columns × 5 rows; normative cell order is column 0 rows 0–4, column 1 rows 0–4, column 2 rows 0–4.
  • Trigger requires at least 6 BONUS symbols; initial respins are 3; a new landed prize resets respins to 3.
  • Draft landing chance is 15% per empty cell.
  • Regular value/weight pairs are 1:40, 2:28, 3:16, 5:10, 10:5, 20:1.
  • Prize-type weights are Regular 1000, Mini 10× 20, Minor 25× 5, Major 100× 1.
  • Full grid awards exactly Grand 500× instead of the prize sum; ordinary settlement is capped once at 500×.
  • No collector, multiplier, feature buy or additional wager in v0.1.
  • hold_and_spin and frozen classic-fruits-3x3 v1 must remain unchanged.
  • Runtime and simulation must call the same state-transition implementation and preserve the documented draw order.
  • Every backend behavior change follows RED → observed failure → minimal GREEN → regression run.
  • Draft evidence is DRAFT / NOT FOR CERTIFICATION; Monte Carlo is never labelled exact.
  • Stop at the three mechanic approval gates defined in the design and skills/kiro-slot-mechanic-builder/SKILL.md.

File structure

Math workspace

  • Create games/hold-and-win-reference/README.md — artifact index and lifecycle status.
  • Create games/hold-and-win-reference/gdd/hold-and-win-reference-gdd.md — player-facing and normative feature rules.
  • Create games/hold-and-win-reference/gdd/hold-and-win-backend-response.md — actual serialized trigger/respin/replay/reconnect/settlement contract derived from backend fixtures.
  • Create games/hold-and-win-reference/math/hold-and-win-reference-math-spec.md — whole-game targets, decomposition and backend provenance.
  • Create games/hold-and-win-reference/math/mechanics/hold-and-win/hold-and-win-math-draft-v0.1.md — mechanic-owned Draft math.
  • Create games/hold-and-win-reference/math/mechanics/hold-and-win/hold-and-win-math-changelog.md — immutable revision history.
  • Create games/hold-and-win-reference/math/mechanics/hold-and-win/hold-and-win-math-handoff-v0.1.md — pending handoff manifest.
  • Create games/hold-and-win-reference/evidence/{simulation,parity,max-win}/README.md — evidence routing.
  • Create games/hold-and-win-reference/test-vectors/README.md — vector identity and generation commands.
  • Create games/hold-and-win-reference/changelog.md — game-level changes.
  • Modify games/README.md and root README.md — link the reference game.

Backend

  • Modify libs/mathengine/config.go — typed config and prize definitions.
  • Create libs/mathengine/hold_and_win.go — pure state, transitions, validation and settlement.
  • Create libs/mathengine/hold_and_win_test.go — core contract tests.
  • Create libs/mathengine/hold_and_win_executor.go — feature-loop adapter and outcome mapping.
  • Create libs/mathengine/hold_and_win_executor_test.go — engine integration and regression tests.
  • Modify libs/mathengine/feature_def_json.go and tests — typed JSON round trip.
  • Modify libs/mathengine/mathengine.go — outcome field and feature registration.
  • Modify libs/mathengine/capabilities.go and tests — support status.
  • Modify libs/mathengine/batch.go and tests — mechanic duration/reset/full-grid/jackpot aggregates.
  • Modify apps/math-studio/internal/domain/feature.go and tests — authoring model and validation.
  • Modify apps/math-studio/internal/domain/build_config.go and tests — domain-to-engine mapping.
  • Create apps/math-studio/internal/domain/packs/hold_and_win_reference.go and tests — reference pack.
  • Create libs/mathengine/games/hold_and_win_reference/pack.go, YAML config and tests — canonical reference config.
  • Create apps/game-engine/migrations/023_create_hold_and_win_sequences.sql — durable sequence and step-operation state.
  • Create apps/game-engine/internal/domain/round/hold_and_win_sequence.go — persistence interfaces and statuses.
  • Create apps/game-engine/internal/infra/postgres/hold_and_win_store.go and integration tests — locking, replay and atomic advance.
  • Create apps/game-engine/internal/app/service/spin_orchestrator_hold_and_win.go and tests — trigger/start/resume routing.
  • Modify apps/game-engine/internal/domain/round/round.go and API schema — typed next_state.kind="hold_and_win".
  • Create focused handler serialization fixtures/tests — canonical 15-cell snapshots and newly-landed deltas used by OpenAPI and workspace response documentation.
  • Modify apps/game-engine/cmd/main.go — dormant dependency wiring behind explicit eligibility.

Task 1: Author Draft artifacts and record the approved design gate

Files: Math workspace files listed above.

Interfaces:

  • Consumes: approved design spec and mechanic placeholder templates.

  • Produces: versioned Draft contract paths that every backend/evidence task must cite.

  • Step 1: Copy the mechanic Draft, changelog and handoff templates to their exact target paths

Replace every placeholder with the approved contract, current backend Git SHA bde8c0539f8b621c4d6d13e5834be56741a2862f, current RNG algorithm ID read from code, and status DRAFT / NOT FOR CERTIFICATION. Record canonical config as candidate — not created rather than inventing a path/hash.

  • Step 2: Write GDD, whole-game Math Spec and README index

The GDD must state 3 columns × 5 rows, 6+ trigger, 3 respins, reel-major order, Mini/Minor/Major and full-grid Grand. The Math Spec must keep targets separate from results and link the mechanic Draft.

  • Step 3: Run document checks

Run:

rg -n 'T[B]D|TO[D]O|PLACEHOLD[E]R' games/hold-and-win-reference
git diff --check

Expected: no unresolved placeholders and no whitespace errors.

  • Step 4: Record design approval and commit workspace artifacts

Use approval state DESIGN approved 2026-08-09 by user; scope bound to design spec c44bc01. Commit only workspace documents:

git add README.md games/README.md games/hold-and-win-reference docs/superpowers/specs/2026-08-09-hold-and-win-reference-design.md
git commit -m "docs: add hold and win reference draft"
  • Step 5: Stop at mechanic DESIGN gate if the written Draft differs from the approved spec

If it matches exactly, continue under the recorded approval. Any rule/value/RNG-order deviation requires AWAITING_USER_APPROVAL — DESIGN before backend code.

Task 2: Add typed config and strict validation

Files: libs/mathengine/config.go, feature_def_json.go, apps/math-studio/internal/domain/feature.go, build_config.go and focused tests.

Interfaces:

  • Produces:
const HoldAndWinProbabilityScale = 1_000_000

type HoldAndWinWeightedValue struct {
    Multiplier float64
    Weight     int
}

type HoldAndWinPrizeWeight struct {
    Kind       string // regular|mini|minor|major
    Multiplier float64
    Weight     int
}

type HoldAndWinConfig struct {
    MechanicVersion  string
    Columns          int
    Rows             int
    TriggerSymbolID  string
    TriggerCount     int
    InitialRespins   int
    LandingChancePPM int
    RegularValues    []HoldAndWinWeightedValue
    PrizeWeights     []HoldAndWinPrizeWeight
    GrandMultiplier  float64
    MaxWinMultiplier float64
}
  • Step 1: Write failing engine JSON/validation tests

Cover valid v0.1 and rejection of wrong dimensions, trigger outside 1..15, respins below one, probability outside 0..1_000_000, empty/duplicate/non-positive weights, unknown prize kinds, non-positive multipliers and Grand above cap.

  • Step 2: Run RED
cd /Users/admin/kiro/backend
go test ./libs/mathengine -run 'TestHoldAndWinConfig|TestFeatureDefJSON_HoldAndWin' -count=1

Expected: compile/test failure because HoldAndWinConfig and JSON mapping do not exist.

  • Step 3: Implement the minimal types, validation and JSON mapping

Use integer PPM for the landing mapping. Reject duplicate prize kinds and require exactly one each of regular, mini, minor and major for v0.1.

  • Step 4: Add Math Studio domain/build round-trip tests, observe RED, then implement mapping

Run:

go test ./apps/math-studio/internal/domain/... -run HoldAndWin -count=1
  • Step 5: Run GREEN and commit
go test ./libs/mathengine ./apps/math-studio/internal/domain/... -count=1
git add libs/mathengine/config.go libs/mathengine/feature_def_json.go libs/mathengine/*hold_and_win* apps/math-studio/internal/domain
git commit -m "feat(mathengine): add hold and win config contract"

Task 3: Implement Start and prize assignment

Files: Create libs/mathengine/hold_and_win.go and hold_and_win_test.go.

Interfaces:

type HoldAndWinCell struct {
    Locked     bool
    PrizeKind  string
    Multiplier float64
}

type HoldAndWinStepResult struct {
    StepID             string
    NewlyLockedIndices []int
    RespinsRemaining   int
    Settled            bool
    AwardMultiplier    float64
}

type HoldAndWinState struct {
    MechanicVersion      string
    Columns              int
    Rows                 int
    Cells                []HoldAndWinCell
    RespinsRemaining     int
    StepIndex            int
    AccumulatedMultiplier float64
    Settled              bool
    AwardMultiplier      float64
    ReplayIdentity       string
    LastStep             HoldAndWinStepResult
}

func StartHoldAndWin(cfg HoldAndWinConfig, grid [][]string, replayIdentity string, r *rng.RNG) (HoldAndWinState, error)
  • Step 1: Write failing tests for five/six trigger boundary and reel-major assignment

Use a scripted RNG test helper that records every Intn domain and drives exact prize choices. Assert five BONUS returns a typed no-trigger result and six locks the exact indices in column-major/reel-major order with three respins.

  • Step 2: Run RED, implement the smallest Start function, then run GREEN
go test ./libs/mathengine -run 'TestStartHoldAndWin' -count=1
  • Step 3: Add failing regular/jackpot weighted-boundary tests

Assert cumulative intervals exactly match configured integer weights and a regular prize consumes one additional value draw while Mini/Minor/Major do not.

  • Step 4: Implement weighted selection with overflow-safe integer totals

Invalid or overflowing totals return an error; never modulo-map a raw draw.

  • Step 5: Run focused and package tests, then commit
go test ./libs/mathengine -run 'HoldAndWin' -count=1
go test ./libs/mathengine -count=1
git add libs/mathengine/hold_and_win.go libs/mathengine/hold_and_win_test.go
git commit -m "feat(mathengine): start hold and win state"

Task 4: Implement Step, replay and settlement

Files: libs/mathengine/hold_and_win.go, hold_and_win_test.go.

Interfaces:

func StepHoldAndWin(cfg HoldAndWinConfig, state HoldAndWinState, stepID string, r *rng.RNG) (HoldAndWinState, HoldAndWinStepResult, error)
func SettleHoldAndWin(cfg HoldAndWinConfig, state HoldAndWinState) (HoldAndWinState, HoldAndWinStepResult, error)
func RunHoldAndWinToCompletion(cfg HoldAndWinConfig, grid [][]string, replayIdentity, featureSeed string) (HoldAndWinState, error)
  • Step 1: RED tests for empty-only draws, reset and decrement

Assert locked cells consume no draw; misses decrement once per step; any land resets to three; iteration order is normative.

  • Step 2: Implement minimal Step and run GREEN
go test ./libs/mathengine -run 'TestStepHoldAndWin' -count=1
  • Step 3: RED tests for duplicate step identity

Call the same stepID twice with an RNG that panics if read on the retry. Assert identical state/result and zero retry draws. Reject an older non-last step ID with a typed reconciliation error rather than guessing history.

  • Step 4: Implement one-step replay contract and GREEN

  • Step 5: RED tests for zero-respin settlement, ordinary cap and full-grid Grand override

Assert 499× remains 499×, 520× caps to 500×, and a full grid with a raw sum below or above 500× settles at exactly 500×.

  • Step 6: Implement settlement and RunToCompletion using documented sub-seeds

Use featureSeed + ":hold_and_win:start" and featureSeed + ":hold_and_win:step:" + stepIndex. The production adapter must use the identical strings.

  • Step 7: Prove step mode equals RunToCompletion and commit
go test ./libs/mathengine -run 'HoldAndWin' -count=20
go test -race ./libs/mathengine -run 'HoldAndWin' -count=1
git add libs/mathengine/hold_and_win.go libs/mathengine/hold_and_win_test.go
git commit -m "feat(mathengine): complete hold and win transitions"

Task 5: Wire the engine executor and outcome

Files: hold_and_win_executor.go, mathengine.go, capabilities.go, their tests.

Interfaces:

type HoldAndWinOutcome struct {
    Triggered       bool
    InitialLocked   int
    FinalLocked     int
    Steps           int
    ResetCount      int
    FullGrid        bool
    JackpotCounts   map[string]int
    AwardMultiplier float64
    FinalState      HoldAndWinState
}
  • Step 1: Write failing RunRound trigger/no-trigger tests

Assert FeaturesTriggered and FeatureWins["hold_and_win"] appear only for 6+ BONUS, base win remains separate and total cap applies once.

  • Step 2: Run RED, add executor/registration/outcome plumbing, run GREEN
go test ./libs/mathengine -run 'TestHoldAndWinExecutor|TestRunRound_HoldAndWin' -count=1
  • Step 3: Add capability regression tests

The new feature reports partial until Task 8 persistence/parity is complete; the old hold_and_spin detail remains byte-for-byte unchanged.

  • Step 4: Run engine regression and commit
go test -race ./libs/mathengine -count=1
git add libs/mathengine
git commit -m "feat(mathengine): register hold and win executor"

Task 6: Add the canonical reference pack

Files: reference pack files in Math Studio and libs/mathengine/games/hold_and_win_reference.

Interfaces: Produces Load() (*mathengine.MathVersionConfig, error) and canonical YAML path libs/mathengine/games/hold_and_win_reference/hold-and-win-reference.yaml.

  • Step 1: Write failing pack tests

Assert Game ID, 3 columns, 5 rows, exactly one hold_and_win feature, approved config values and absence of hold_and_spin.

  • Step 2: Run RED and create the minimal pack/config

Base reels must expose BONUS and ordinary symbols; initial weights are design inputs chosen to place the trigger near 1/150 but must not be documented as achieved before simulation.

  • Step 3: Validate config and generate a SHA-256 identity
go test ./libs/mathengine/games/hold_and_win_reference ./apps/math-studio/internal/domain/packs -run HoldAndWin -count=1
shasum -a 256 libs/mathengine/games/hold_and_win_reference/hold-and-win-reference.yaml
  • Step 4: Update Draft provenance and changelog, then commit both repositories separately

Backend commit:

git add libs/mathengine/games/hold_and_win_reference apps/math-studio/internal/domain/packs
git commit -m "feat(mathengine): add hold and win reference pack"

Workspace commit records the resulting backend candidate path/hash/SHA without copying the YAML.

Task 7: Extend batch simulation metrics

Files: libs/mathengine/batch.go, batch_test.go, new hold_and_win_batch_test.go.

Interfaces: Add merge-safe HoldAndWinBatchAgg with trigger count, conditional award moments/histogram, total steps, reset count, full-grid count and jackpot counts.

  • Step 1: Write failing controlled-outcome aggregation tests

Drive deterministic outcomes and assert every additive field and merge boundary.

  • Step 2: Run RED, implement aggregation and MergeBatches support, run GREEN
go test ./libs/mathengine -run 'TestRunBatch_HoldAndWin|TestMergeBatches_HoldAndWin' -count=1
  • Step 3: Run same-seed single-shard versus merged-shard parity

Assert totals and mechanic aggregates are identical under the existing shard-seed contract.

  • Step 4: Commit
go test -race ./libs/mathengine -count=1
git add libs/mathengine/batch.go libs/mathengine/*batch*test.go
git commit -m "feat(mathengine): aggregate hold and win simulation metrics"

Task 8: Add durable game-engine step persistence and replay

Files: migration 023, domain/store/service/API files listed in File structure.

Interfaces: Durable sequence contains server UUID, operator/logical-session/game/config identities, mechanic version, canonical state JSON, current step index, status open|completed|quarantined, and timestamps. Unique active/quarantined identity prevents a second sequence. Step operation key is (sequence_id, step_index).

  • Step 1: Write failing real-PostgreSQL migration/store tests

Cover open, FOR UPDATE, atomic step advance, duplicate replay, concurrent requests, completion, quarantine, config mismatch, rollback and restart.

  • Step 2: Run RED
TEST_DATABASE_URL='postgres://game@127.0.0.1:55432/game_engine?sslmode=disable' \
go test -tags integration ./apps/game-engine/internal/infra/postgres -run HoldAndWin -count=1
  • Step 3: Implement migration/domain/store minimally and run GREEN

  • Step 4: Write failing orchestrator/API tests

Assert trigger creates the durable state in the paid round transaction; subsequent steps charge no wager; duplicate expected step replays; Redis expiry restores from PostgreSQL; quarantined/config-unavailable state blocks paid play before debit.

  • Step 5: Implement service/API wiring behind default-off eligibility

Use the hardened paid-operation/latch/credit-intent infrastructure already on main. Do not create a second wallet retry or allocator.

  • Step 6: Promote capability to full only after persistence/parity tests pass

  • Step 7: Run integration/race tests and commit

go test -race ./apps/game-engine/... ./libs/mathengine/... -count=1
TEST_DATABASE_URL='postgres://game@127.0.0.1:55432/game_engine?sslmode=disable' \
go test -tags integration -race ./apps/game-engine/... -run HoldAndWin -count=1
git add apps/game-engine libs/mathengine/capabilities.go libs/mathengine/capabilities_test.go
git commit -m "feat(game-engine): persist hold and win sequences"

Task 9: Golden vectors, parity and regression

Files: new reference pack golden tests, parity vectors/tests, workspace test-vectors index.

Interfaces: Vectors bind config hash, backend SHA, RNG algorithm/version, feature seed, incoming state/step ID and exact outcome/state.

  • Step 1: Add failing vectors for trigger, miss decrement, reset, Mini/Minor/Major, cap and full-grid Grand

  • Step 2: Generate expected results only through the approved canonical runtime, review them manually, then pin them

  • Step 3: Prove reference RunToCompletion equals persisted step execution

  • Step 4: Run full regression and verify protected artifacts

make test
make test-rng
make test-parity
go test -race ./apps/... ./libs/... -count=1
git diff --check

Expected: existing game vectors unchanged; new vectors additive.

  • Step 5: Commit backend vectors and workspace vector manifest

Task 9A: Implement and document the real backend response

Files: apps/game-engine/internal/domain/round/round.go, Hold & Win handler/service response types, apps/game-engine/api/openapi.yaml, focused serialization/integration tests, and games/hold-and-win-reference/gdd/hold-and-win-backend-response.md.

Interfaces: The real wire response is authoritative. Documentation consumes deterministic serialized fixtures; it must not invent fields independently.

  • Step 1: Write failing response-contract tests before adding response fields

Cover trigger, ordinary respin, duplicate-step replay, reconnect and both settlement reasons. Each response must serialize exactly 15 cells in normative reel-major order and include the exact newly_landed subset for the current step.

  • Step 2: Verify RED, then implement typed response mapping and orchestrator routing

The response must include mechanic version, status, current/expected step index, respins remaining, locked count and accumulated multiplier. Each cell must include stable index/column/row, locked state and nullable prize. Settlement adds reason, raw/awarded multiplier, cap flag, full-grid flag and jackpot counts.

  • Step 3: Add OpenAPI schemas and validate serialized fixtures against them

Do not hand-edit examples that disagree with handler output. Generate or capture examples from deterministic integration fixtures and keep internal sequence/identity secrets out of the response.

  • Step 4: Create backend-response documentation from those fixtures

Write games/hold-and-win-reference/gdd/hold-and-win-backend-response.md with the actual request/response lifecycle and deterministic JSON examples. Link it from README, GDD, Math Spec and handoff protocol.

  • Step 5: Add a drift guard

The test must fail when serialized backend output changes without updating the fixture/OpenAPI-bound response artifact. A changed response invalidates runtime parity and the handoff protocol.

  • Step 6: Run handler, integration, OpenAPI and parity tests, then commit backend and workspace changes separately

This task is mandatory before AWAITING_USER_APPROVAL — FREEZE & HANDOFF; a math-only executor/store is not a complete mechanic delivery.

Task 10: Run Draft simulation and stop at DRAFT MATH gate

Files: generated aggregate reports under games/hold-and-win-reference/evidence/simulation/, Draft math/results/changelog.

Interfaces: Report exact base results separately from estimated stateful feature results.

  • Step 1: Run a reproducible pilot

Use at least 1,000,000 rounds over recorded independent seeds/shards. Record Go version, backend SHA, config SHA, RNG version, rounds, seeds, elapsed time and commands.

  • Step 2: Calculate and record uncertainty

Report RTP, standard error and 95% confidence interval; trigger rate with interval; conditional feature mean/median/percentiles; duration; reset/full-grid/jackpot rates; observed max and validated 500× bound.

  • Step 3: Reconcile target versus observed

Separate base RTP, Hold & Win RTP contribution and total RTP. Do not tune yet. Mark every result estimated unless an exact method was actually implemented.

  • Step 4: Update Draft/changelog and commit evidence manifest

  • Step 5: Stop

Return AWAITING_USER_APPROVAL — DRAFT MATH with locked/open parameters and observed drift. Do not tune or prepare Final until the user approves direction.

Task 11: Tune approved open parameters

Files: new backend config revision, tuning records under games/hold-and-win-reference/math/tuning/, regenerated evidence.

Interfaces: Only landing PPM, regular weights, jackpot weights and base trigger weights are tunable without a new DESIGN approval.

  • Step 1: Apply only user-approved tuning direction in a new config revision

  • Step 2: Run identical-seed A/B plus independent final seeds

  • Step 3: Re-run full tests, parity, max validation and simulation evidence

  • Step 4: Record every old→new value and invalidated evidence in the mechanic changelog

  • Step 5: Commit each traceable tuning revision separately

Task 12: Freeze and handoff

Files: Final mechanic math, completed handoff protocol, reconciled GDD/Math Spec/changelog/evidence manifests.

  • Step 1: Reconcile every bound artifact and compute SHA-256 hashes

The bound artifacts must include the implemented OpenAPI response schema, deterministic serialized response fixtures and gdd/hold-and-win-backend-response.md.

  • Step 2: Run clean-environment reproduction commands and record results

  • Step 3: Submit AWAITING_USER_APPROVAL — FREEZE & HANDOFF

The user must respond exactly APPROVE FREEZE & HANDOFF or CHANGES REQUESTED for the stated scope.

  • Step 4: After approval only, copy the approved Draft to immutable hold-and-win-math-final-v1.0.md

  • Step 5: Seal hashes, update changelog and declare developer-ready without claiming external certification


Plan self-review

  • Spec coverage: grid, trigger, values, jackpots, RNG order, state persistence, replay, reference pack, simulation, tuning and handoff each have an implementing task.
  • Type consistency: config/state/function names are defined once and reused by later tasks.
  • Scope: the existing hold_and_spin, Classic Fruits and unrelated backend hardening remain outside modifications.
  • Evidence: exact and Monte Carlo claims remain separated; capability cannot become full before runtime parity.