Mechanic Math — hold-and-win — Draft v0.2
Supersedes draft-v0.1, which is preserved unchanged at
hold-and-win-math-draft-v0.1.md.
This revision changes exactly two OPEN parameter families, under the tuning direction approved at the DRAFT MATH gate on 2026-08-09 (option A3):
Coin values (1×, 2×, 3×, 5×, 10×, 20×) are unchanged and remain design-locked. Jackpot values and the prize-type weights are unchanged. Base reels, trigger weights and the base paytable are unchanged, so base RTP and trigger frequency are bit-for-bit the same.
This document owns the mathematics of the Hold & Win mechanic. The game Math Spec owns
totals and links here. Canonical executable parameters remain in the Go backend at
/Users/admin/kiro/backend; this document records their identity, never a second copy.
Design contract
- Purpose: a reusable persistent respin mechanic where coin symbols lock and keep buying respins, settling once into a single capped award.
- Trigger and eligibility: at least 6 visible
BONUSsymbols on the base grid of a wagered round. No additional wager. Fewer than 6 consumes no mechanic RNG. - Initial state: the trigger grid's
BONUSpositions are locked and assigned prizes in normative cell order;respinsRemaining = 3;stepIndex = 0. - State transitions and evaluation order: one step visits every empty cell in normative order; each visited cell draws land/miss; a land draws prize type, then a regular value only when the type is Regular. Locked cells are skipped entirely.
- Retrigger/reset rules: at least one newly landed prize in a step resets
respinsRemainingto 3. A step with no lands decrements it by exactly 1. - Termination:
respinsRemaining == 0, or all 15 cells locked. - Payout unit, award timing and aggregation: multiplier of total bet, awarded once at settlement. Ordinary settlement sums the locked prizes. A full grid awards exactly Grand 500×, replacing the sum rather than adding to it.
- Rounding and max-win cap stage: multipliers are
float64; money crosses to exact integer minor units only at the wallet boundary. The 500× cap applies once, at settlement, and the round-level cap boundsbase + featureat the same 500×. - Recovery/replay state: mechanic version, grid dimensions, per-cell locked flag and prize, respins remaining, step index, accumulated award, settlement state, award and replay identity. A repeated step returns the persisted result and consumes no RNG. An unknown persisted mechanic version fails closed.
Normative cell order
15 cells, reel-major:
This order is normative for masks, RNG draw sequence, persistence and replay vectors.
RNG contract
Sub-seed strings are exact and shared by production and simulation:
Rules:
- Fewer than 6
BONUSconsumes zero mechanic draws. Startvisits the initial locked cells in normative order, drawing prize type then a regular value only for Regular.Stepvisits empty cells in normative order, drawing land/miss first.- Locked cells consume no draws.
- Settlement consumes no draws.
- Weighted selection uses cumulative integer intervals over the configured weights. A raw draw is never modulo-mapped, and a non-positive or overflowing weight total is an error rather than a silent fallback.
Same mechanic version + initial state + step identity + RNG input reproduce the same state and result. A repeated step must not consume RNG again.
Reference math
These are design inputs, not measured results.
Parameter tables
Regular coin values (multipliers of total bet):
Total weight remains 100. Expected regular value:
Landed prize types (independent pool):
Total weight 1026, unchanged in draft-v0.2. Expected landed prize value:
The prize-type pool moved only because E[regular] moved. No jackpot value, and no weight in this
table, was touched.
Grand 500× is not in the landed pool. It is awarded only for a full grid.
Structural notes and dependencies
- Landing chance is stored as integer parts-per-million (
43000= 4.3%) so the probability has no floating-point representation in the config or in the draw. - Expected prize count is not a closed form: the respin-reset rule makes the number
of steps a random walk whose absorption depends on the current empty-cell count.
A step over
eempty cells lands nothing with probability(1-p)^e, so the reset probability rises steeply withe— early steps almost always reset, late steps almost never do. The award distribution is therefore measured, not derived. - Trigger probability is coarse-grained. The engine's reel model makes one weighted
stop draw per reel and reads a contiguous, wrapping 5-row window of the strip
(
libs/mathengine/mathengine.go:1119). Rows on a reel are correlated, so reaching 6+BONUSacross 15 cells requires contiguousBONUSruns in the strips. The trigger rate moves in discrete jumps as run lengths and stop weights change; it is not a smooth knob.
Exact enumeration
Not available. Every empty cell consumes its own RNG draw, so the round is not
grid-determined; hold_and_win is deliberately excluded from the engine's
gridDeterministicFeatures allowlist. A tractable exact state-space method was not
implemented, so the authoritative Draft evidence is a seeded backend simulation, labelled
estimated. Monte Carlo is never presented as exact.
Backend implementation map
- Config/schema and validation:
libs/mathengine/config.go(HoldAndWinConfig,HoldAndWinWeightedValue,HoldAndWinPrizeWeight,HoldAndWinProbabilityScale),libs/mathengine/feature_def_json.go,validateHoldAndWininlibs/mathengine/mathengine.go, plus the authoring mirror inapps/math-studio/internal/domain/feature.goandbuild_config.go. libs/mathengineexecutor/state:libs/mathengine/hold_and_win.go(StartHoldAndWin,StepHoldAndWin,SettleHoldAndWin,RunHoldAndWinToCompletion) andlibs/mathengine/hold_and_win_executor.go.- Registration/evaluation order:
getFeatureExecutorandRoundOutcomeinlibs/mathengine/mathengine.go; support grade inlibs/mathengine/capabilities.go. - Simulation wiring:
HoldAndWinBatchAgginlibs/mathengine/batch.go, merged byMergeBatchesunder the existingShardSeedcontract. - Runtime persistence/replay: required — execution spans requests.
apps/game-engine/migrations/023_create_hold_and_win_sequences.sql,internal/domain/round/hold_and_win_sequence.go,internal/infra/postgres/hold_and_win_store.go,internal/app/service/spin_orchestrator_hold_and_win.go. The trigger opens the durable sequence inside the existing paid-round transaction; steps charge no wager and reuse the existing allocator latch and credit-intent infrastructure rather than adding a second one.
Verification contract
- Failing tests to observe before implementation: config validation rejections;
five-versus-six trigger boundary; reel-major lock import; weighted-interval boundaries;
empty-cell-only redraw; reset versus decrement; duplicate step identity with an RNG that
panics on read; zero-respin and full-grid termination; ordinary sum, single cap and
full-grid Grand override; step execution equals
RunToCompletion; real-PostgreSQL open, advance, replay, concurrency, quarantine and restart. - Golden/replay vectors: pack golden test plus additive entries in
libs/mathengine/parity, each binding config hash, backend Git SHA, RNG algorithm ID, feature seed, incoming state, step ID and the exact outcome. Seeds are discovered by search, expectations reviewed by hand, then pinned as literals. - Exact calculation scope: not enumerable — see above.
- Monte Carlo rounds/seeds/CI requirement: at least 1,000,000 rounds over recorded independent seeds/shards, reporting standard error and a 95% confidence interval.
- Parity and regression scope:
make test,make test-rng,make test-parity,go test -race ./apps/... ./libs/..., and byte-unchanged existing vectors forclassic_fruits_3x3,parityandparsheet/testdata. - Reachable max or upper-bound method: 500× is a reachable value, not merely a bound — a full grid awards it exactly. Validation must observe it, and must also confirm no round exceeds it.
Canonical config identity
The executable parameters live in the Go backend. This workspace records their identity and never a second copy.
The authoring preset hold_and_win_reference in
apps/math-studio/internal/domain/packs mirrors the same parameters. It is compared to the
canonical YAML entry-for-entry, strip weights included, by
TestHoldAndWinReferencePackMatchesCanonical — two independent copies of a paytable is how a
simulated RTP comes to describe a config nobody ships.
Base-game structure
Because the engine draws one weighted stop per reel and reads a wrapping 5-row window, the visible BONUS count per reel is a function of distance from that run:
Six BONUS across 15 cells therefore needs at least two reels to stop near their run, which is why trigger probability moves roughly with the square of the nine bonus-adjacent stop weights — in discrete jumps, not smoothly.
Design-probe enumeration
Reproduce with:
The probe enumerates all 8000 reachable base grids, so the base and trigger figures below are exact for what they cover. They are not the authoritative Draft evidence — that is the seeded simulation at plan Task 10.
Observed drift and its attribution
The mechanic overshoots its RTP target by roughly 4×. The prize tables are not the cause. Splitting the conditional mean by settlement regime:
Ordinary settlement is essentially exactly the 30× design target. The entire overshoot is the full-grid rate: a 500× top award reached almost one time in five is a jackpot in name only.
The mechanism is the landing chance against the reset rule. At 150000 PPM a step over 9 empty cells lands nothing only 23.2% of the time, so the respin counter resets far more often than it decays and a triggered feature runs ≈9.3 steps.
Landing chance is an OPEN parameter. It is deliberately not changed before DRAFT MATH approval; tuning is plan Task 11.
Results
Authoritative evidence: ../../../evidence/simulation/2026-08-09-draft-v0.2-confirmation-2m.md —
2,000,000 rounds, 8 merged shards, independent master seed
haw-draft-v0.2-confirm-independent, backend a3f644e, config SHA-256
a22129d9…8d0508.
Base RTP and trigger probability are EXACT (enumeration of all 8,000 reachable base grids) and are unchanged from draft-v0.1 by construction: neither tuned parameter is read before the feature starts. Feature figures are ESTIMATED.
Decomposition residual: 0.000000 pp.
Full-grid frequency — two runs, three figures
The full-grid rate is reported three ways every time, because a conditional rate and an unconditional one differ by the 155.4035-round trigger interval and are easy to conflate.
The round-level estimate rests on 10 events, and its own Poisson interval (~[0.030%, 0.127%])
comfortably contains the conditioned figure. The conditioned run is ~12× more precise and is the one
to quote — see
../../../evidence/simulation/2026-08-09-draft-v0.2-fullgrid-confirmation.md.
It confirms rather than contradicts the band, and sits closer to the 0.10% target, so 43,000 PPM
stands.
How total RTP is quoted, and why
Total RTP is exact base RTP + exact trigger probability × simulated conditional mean. Base RTP
and trigger probability are known exactly by enumeration; substituting sampled estimates for known
constants adds variance and buys nothing.
The all-simulated figure for the same run is 95.6695% (SE 0.2842pp), which sits marginally below the band floor. The gap is fully attributable and is not a property of the game: the run's sampled base component came in 0.1766 pp light, and its sampled trigger count 0.75% light. Both figures, and this decomposition, are printed by the simulation runner.
Prize-kind frequency per locked cell
Weights unchanged, so these must still match the 1000:20:5:1 pool — and they do:
Tuning pass history
The pass-1 diagnosis predicted this outcome quantitatively: it identified that a compliant
full-grid rate allows ≈8.5 locked cells, so reaching 32.45× requires E[prize] ≈ 3.82×. Option A3
delivers 3.8158× and the measured conditional mean is 32.4593×.
Parameters and revisions
Locked
- Grid 3 columns × 5 rows, 15 cells.
- Normative reel-major cell order.
- Trigger at 6 or more
BONUS. - Initial respins 3; reset to 3 on any newly landed prize; decrement by 1 on a miss-only step.
- Locked cells are never redrawn and consume no RNG.
- Jackpot tier values: Mini 10×, Minor 25×, Major 100×, Grand 500×.
- Grand is not a landed prize type; a full grid awards exactly 500×, replacing the sum.
- The cap and the full-grid Grand each apply exactly once, at settlement.
- 500× is a single whole-round bound (set 2026-08-09 by user).
- No collector, multiplier, feature buy or additional wager in v0.1.
- RNG draw order and sub-seed strings as specified above.
Open for tuning
- Landing chance PPM (draft
150000). - Regular coin value weights.
- Prize-type weights.
- Base trigger weights, i.e. the pack's reel strips.
Nothing else may change without a new DESIGN approval.
Approval history
Risks and blockers
NOT FOR CERTIFICATIONuntil the required internal gates and an authoritative external approval are recorded. Internal tests and simulations are not certification.- Trigger frequency is coarse-grained in strip-construction space, so hitting ≈1/150 exactly may not be achievable without accepting a nearby value. Recorded as expected drift for the DRAFT MATH gate.
- No exact cross-check exists for the mechanic. The only defence against a systematic error in the state machine is the golden/parity corpus plus the reference-versus-runtime equality proof, not a second independent calculation.
- The mechanic RTP contribution and the whole-game split are unmeasured targets until Task 10 reports them.
Finalization record
- Source Draft path:
games/hold-and-win-reference/math/mechanics/hold-and-win/hold-and-win-math-draft-v0.1.md - Source Draft hash: computed at the FREEZE & HANDOFF gate
- Final path/version: not created
- Final approval: awaiting
- Final document hash: not created
Do not edit this Draft into Final in place. After final approval, create an immutable Final snapshot with resolved values and record both files in the mechanic changelog.