Mechanic Math Changelog — hold-and-win
Record every mechanic-math document revision. Do not rewrite or remove historical entries.
Versions
Document hashes are sealed at the FREEZE & HANDOFF gate, not while the Draft is still being written — a hash recorded against a file that is about to change is worse than no hash, because it looks authoritative.
Change entries
draft-v0.1
- Reason: initial mechanic math for the approved Hold & Win reference design
(
docs/superpowers/specs/2026-08-09-hold-and-win-reference-design.mdat workspace commitc44bc01). - Math changes: first revision. Establishes 3×5 grid with normative reel-major cell
order, 6-symbol trigger, 3 initial respins with reset-on-land and miss-only decrement,
15% draft landing chance per empty cell, regular value weights
1:40 2:28 3:16 5:10 10:5 20:1, prize-type weightsRegular 1000 / Mini 10× 20 / Minor 25× 5 / Major 100× 1, full-grid Grand 500× replacing the prize sum, and a single 500× settlement cap. - Locked parameters changed: none — this revision defines them.
- Open parameters changed: initial values for landing chance PPM, regular value weights, prize-type weights and base trigger weights.
- Evidence added/invalidated: none added; none invalidated. No simulation, parity or max-win evidence exists yet.
- Runtime or migration impact: backend implementation begins under this revision.
Planned additive-only changes: a new
hold_and_winfeature type inlibs/mathengine(the existinghold_and_spinis untouched) and a new game-engine migration023_create_hold_and_win_sequences.sql. No existing table, vector, RNG mapping or frozen config is modified. - Approval decision and notes: DESIGN approved 2026-08-09 by user, scope bound to
design spec
c44bc01. DRAFT MATH and FREEZE & HANDOFF remain awaiting.
Decisions recorded during implementation of draft-v0.1
These were resolved with the user on 2026-08-09 because the approved design left them underdetermined. None of them changes a locked rule from the design spec.
draft-v0.1 — canonical config created (plan Task 6, same revision)
The mechanic contract is unchanged; this records the config that now executes it.
- Reason: the Draft previously named the canonical config as a candidate that did not exist. It exists now, in the Go backend.
- Config identity:
libs/mathengine/games/hold_and_win_reference/hold-and-win-reference.yaml, SHA-256888f134c29a59434d259316e9b83a8ec760bec6bcfb182334c6e42f167917d88, backend Git SHA88a730a8512995651c7c0791d25d435425c3a7a2. - Math changes: none to the mechanic. The base game (reel strips, paytable, paylines) is
new, and it is the pack's own design input rather than part of the approved mechanic
contract: 3 reels × 5 rows,
lines, 5 paylines, 20 weighted entries per reel with BONUS contiguous at indices 0-4, BONUS paying nothing on lines, round cap 500×. - Locked parameters changed: none.
- Open parameters changed: none. Landing chance, regular weights and prize weights remain at their approved draft values.
- Evidence added: design-probe enumeration of all 8000 reachable base grids. Base RTP 75.1160% (exact) against a 76% target; trigger 1 in 155.4 (exact) against 1 in 150. The mechanic figures from the probe are sampled and reported without confidence intervals, so they are directional only.
- Evidence invalidated: none — there was none.
- Observed drift, recorded and NOT tuned: mechanic RTP ≈79% against a 20% target. Attribution: ordinary settlement contributes ≈29.9× (the 30× target, essentially exactly) and full grids contribute ≈92.9×, because 18.6% of triggered features fill all 15 cells. 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. Tuning is prohibited before DRAFT MATH approval and is scheduled as plan Task 11.
- Process note: while scaling the base paytable toward its 76% target, the 1× regular value's weight was changed from 40 to 36 by accident. The pinned-value tests caught it and it was corrected before any figure above was recorded.
- Runtime or migration impact: none yet. Durable runtime persistence is plan Task 8.
- Approval decision: still DESIGN-approved only. DRAFT MATH remains awaiting.
draft-v0.1 — Draft simulation measured (plan Task 10, same revision)
No parameter changed. This records the authoritative Draft evidence and the observed drift.
- Reason: the DRAFT MATH gate requires measured behaviour before any tuning direction is set.
- Evidence added:
evidence/simulation/2026-08-09-draft-v0.1-2m.md— 2,000,000 rounds, 8 merged shards, master seedhaw-draft-v0.1-sim, backend13700ae, config SHA-256888f134c…917d88, RNGsha256-trunc64-be/go1-alfg#1, driven through the canonicalRunBatch/MergeBatchespath. Identical seeds reproduced identical aggregates. - Evidence invalidated: none.
- Locked parameters changed: none. Open parameters changed: none.
- On target: base RTP 75.1025% simulated against 75.1160% enumerated exact and a 76% target; trigger 1 in 154.74 with a 95% CI of [1 in 152.12, 1 in 157.44] against a 1-in-150 target. Both inside their draft bands, and the two methods cross-validate each other.
- Outside target: mechanic RTP 79.7860% against 20%, conditional mean 123.4600× ±3.12 against 30×, total RTP 154.8803% against 96%. Roughly 4× on the mechanic.
- Attribution: ordinary settlement contributes 29.92× — the 30× target, essentially exactly — and full grids contribute 93.54×, because 18.71% of triggered features fill all 15 cells. At 150000 PPM a step over 9 empty cells lands nothing with probability 0.2316, so the counter resets about half the time per step, a feature runs 8.87 steps and reaches 12.81 of 15 cells. The prize tables are not the cause; the landing chance against the reset rule is.
- Max win: 500× confirmed REACHABLE (observed exactly 500.0000×) and never exceeded across 2,000,000 rounds. Cap probability 0.001209 per round.
- Weighted selection validated: observed prize-kind frequencies per locked cell (regular 0.975028, mini 0.019042, minor 0.004916, major 0.001015) agree with the configured 1000:20:5:1 pool to within noise.
- Runtime or migration impact: none from this entry.
- Approval decision: submitted as
AWAITING_USER_APPROVAL — DRAFT MATH. No tuning performed.
draft-v0.1 — tuning pass 1 attempted and STOPPED (no revision created)
DRAFT MATH was approved on 2026-08-09 with a tuning direction: sweep only LandingChancePPM.
No parameter was changed, and no new revision exists, because no value satisfies the approved
acceptance targets simultaneously.
- Reason: first tuning pass under the approved direction.
- Evidence added:
../../evidence/simulation/2026-08-09-tuning-pass-1-pareto.md— coarse sweep 30000/50000/70000/90000/110000 plus refinement at 45000/52000/55000/58000/61000/64000, conditional feature sampling weighted by the exact trigger-count distribution, common random numbers across every candidate. - Fixed points confirmed unchanged: base RTP exactly 75.1160% and trigger exactly
1 in 155.4035, both re-enumerated.
LandingChancePPMis read only inside the step loop, after a trigger, so it cannot move either. - Derived targets: required feature RTP 20.8840%, required conditional mean 32.4545×.
- Result — the two acceptance criteria are DISJOINT in
LandingChancePPM: the full-grid band [0.05%, 0.25%] needsPPM ≤ ≈51,200; the total-RTP band [95.7%, 96.3%] needsPPM ≥ ≈64,700. A gap of ≈13,500 PPM separates them. At the highest full-grid-compliant value total RTP is ≈92.8% (≈2.9 pp under the floor); at total RTP 96.0% the full-grid rate is ≈0.86% (≈8.6× the 0.10% target). - Root cause: the conditional mean is approximately cells × E[landed prize], and E[landed prize] = 3065/1026 = 2.9873×. Reaching 32.4545× therefore needs 10.86 of 15 cells, and a grid that routinely reaches 10.9 of 15 cannot make a full grid rare. At the full-grid-compliant 45,000 PPM the feature locks 8.4949 cells, which would need E[prize] = 3.8205× — +27.9% on the prize tables. The ordinary settlement mean there is 25.348×, already short of 32.4545× before full grids are counted at all: landing chance buys cells, and the shortfall is value per cell.
- Locked parameters changed: none. Open parameters changed: none.
- Evidence invalidated: none. The draft-v0.1 simulation stands.
- Runtime or migration impact: none.
- Approval decision: STOPPED per the approved direction — "if no LandingChancePPM can simultaneously satisfy total RTP, conditional mean and full-grid frequency: STOP and report the Pareto table before changing regular values or prize weights." A second parameter family was NOT touched.
draft-v0.2 — tuning pass 2, option A3 (NEW REVISION)
draft-v0.1 is preserved unchanged. This is a new document, not an edit.
-
Reason: tuning pass 1 proved
LandingChancePPMalone cannot satisfy both acceptance bands. The user approved option A3 at the DRAFT MATH gate: move the landing chance AND the regular-value weights, while leaving coin values, jackpot values and prize-type weights alone. -
Parameters changed — both OPEN, old → new:
-
Parameters explicitly NOT changed, each pinned by a test: coin values 1×/2×/3×/5×/10×/20× (
TestRegularCoinValuesAreLocked); Mini 10×, Minor 25×, Major 100×, Grand 500× and the prize-type weights 1000/20/5/1 (TestJackpotTiersUnchangedByTuning); base reels, trigger weights and base paytable (TestBasePaytable,TestBonusRunLayout,TestReelWindowBonusCounts); RNG mapping and draw order; mechanic versionhold_and_win/v1. -
Derived values:
E[regular]2.64× → 3.49× (349/100);E[prize]2.9873× → 3.815789× (3915/1026). Both asserted arithmetically before any sampling, so the sweep measured the proposal that was approved. -
Selection: common-random-number sweep of 43000/44000/45000/46000/47000. 43000 and 44000 both passed; 43000 chosen as closest to 96.0% exactly. 45000–47000 exceeded the RTP ceiling.
-
Confirmation on independent seeds:
../../../evidence/simulation/2026-08-09-draft-v0.2-confirmation-2m.md— 2,000,000 rounds, master seedhaw-draft-v0.2-confirm-independent, which shares nothing with the sweep's seed stream. -
Config identity regenerated: SHA-256
a22129d99d21eb14acdb303efdf666f4065925fa461012d8e99f2144538d0508(was888f134c…917d88). -
Evidence INVALIDATED and regenerated: the draft-v0.1 golden vector corpus and the draft-v0.1 2M simulation both describe the old parameters. The golden corpus was re-recorded against draft-v0.2 with seeds re-discovered by search. Two v0.1 scenarios are now unreachable by search and were dropped: a full grid that also lands a major (≈1 in 11,000,000 rounds) and a capped round; the single-round 500× cap remains covered by a constructed engine-level test. The draft-v0.1 simulation report is retained as the historical record of why tuning happened.
-
Evidence still preserved as valid: the base-game vectors are byte-identical to their v0.1 recordings, which independently confirms neither tuned parameter can reach the base game.
-
Side effect worth recording: volatility fell from σ = 17.78 to σ = 4.02 per round, and mean feature duration from 8.87 to 5.60 steps. The top award is now genuinely rare and the feature's value comes from the coins rather than from a routine Grand. That was the intent of the direction, not an accident.
-
Runtime or migration impact: none. No schema, no mechanic version change, no migration.
-
Approval decision: DRAFT MATH approved with this tuning direction. FREEZE & HANDOFF remains awaiting and is blocked on the runtime routing and parity work.
draft-v0.2 — full-grid frequency confirmed (no parameter change)
Confirmation evidence for the accepted revision. Nothing was varied.
- Reason: the round-level confirmation saw only 10 full grids, so its estimate of a ~0.08% conditional rate had a Poisson interval about twice the width of the acceptance band. Resolving it from base rounds would need hundreds of millions, because 99.36% of rounds never enter the feature.
- Evidence added:
../../../evidence/simulation/2026-08-09-draft-v0.2-fullgrid-confirmation.md— 2,200,000 triggered features through the canonicalRunHoldAndWinToCompletion, trigger counts weighted by the exact enumerated distribution, seed prefixhaw-v0.2-fullgrid-confirm-*(independent of both earlier runs). - Result: full-grid 0.090053% conditional, SE 0.002022pp, 95% CI [0.086089%, 0.094016%]; 1 in 1,110 triggered features; 1 in 172,570 base rounds, CI [1 in 165,294, 1 in 180,515]. Conditional mean 32.4461×, feature RTP 20.8786%, total RTP 95.9947%, max 500.0000×. All inside the accepted bands.
- Relationship to the 10-event estimate: consistent. The round-level figure's own interval (~[0.030%, 0.127%]) contains 0.090053%. The conditioned run is ~12× more precise and is the figure to quote; the round-level figure is retained as what that run observed.
- Decision: the confirmation does NOT materially contradict the accepted band and is closer to
the 0.10% target.
LandingChancePPMremains 43,000. - Correction recorded: the earlier draft-v0.2 table reported the unconditional rate as "1 in ~127,700 base rounds". That multiplied the 1-in-1,277 conditional figure by 100 instead of by the 155.4035-round trigger interval. Every report now carries all three figures together.
- Locked/open parameters changed: none. Evidence invalidated: none.
draft-v0.2 — runtime routing and live parity (no parameter change)
Runtime evidence for the accepted revision. Nothing was varied.
- Reason:
draft-v0.2was measured throughRunHoldAndWinToCompletion, and the handoff was blocked on two related gaps — no request path routed to the durable runtime, and no reference↔runtime parity existed. Until both closed, every RTP figure described the mechanic rather than production play. - What landed: the paid trigger opens the durable feature inside the paid gameplay transaction;
each respin is a separate wager-free request keyed on
(sequence_id, step_index); settlement writes exactly one credit intent; the client contract is a single projection bound to live fixtures, OpenAPI and the handler's serialized output. Backendcd4c093. - Parity: the live orchestrator and
RunHoldAndWinToCompletionare compared over complete state — every cell, prize,newly_landeddelta, respin counter, step index, raw and awarded multiplier and full-grid flag — at 0, 43,000 and 1,000,000 PPM (TestHAWOrch_MatchesTheReferenceDriverStepForStep). The comparison was mutation-checked: perturbing the step sub-seed fails it, so a green result is evidence rather than a tautology. - Why the seeds match: the runtime recovers the feature seed from the trigger round's own
stored seed, so its sub-seeds are the same
<roundSeed>:hold_and_win:step:<n>strings the reference driver builds. A feature whose trigger round cannot be read is refused, not resumed under a guess. - Capability:
hold_and_winpromoted toSupportFullin the same commit as that evidence. The detail still states the mechanic is not enumerable, so its RTP contribution remains a seeded estimate with a stated interval. - Locked/open parameters changed: none. Evidence invalidated: none — no RNG mapping, evaluation order, rounding or cap moved.
final-v1.0 — FREEZE & HANDOFF approved, Final sealed
- Date / approval: 2026-08-09, user (Bogdan), verbatim
APPROVE FREEZE & HANDOFF, with the instruction to merge and pushmainfirst. The Final was created after that answer, never before — an unapproved Final is the failure mode the gate exists to prevent. - Source:
draft-v0.2, copied verbatim. No mathematics changed. Only the front matter, the immutability note and a closing status section differ, and the body was diffed against the draft to prove it. - Backend Git SHA:
cd4c093c6b875376e547cd31123de254d2cb2f93, fast-forward merged tomainand pushed. No merge commit, so the SHA sealed in the protocol is the SHA onmain. - Config hash:
a22129d9…8d0508, unchanged sincedraft-v0.2. - Changed math parameters: none.
- Evidence impact: none invalidated. All draft-v0.2 evidence carries forward unchanged and is now bound by SHA-256 in the sealed protocol.
- Migration notes: migration
023is additive. An unknown persisted mechanic version fails closed and never falls back tohold_and_spin. - Production state: dormant.
hold_and_win_enabledandhold_and_win:<operator>default to DENY; the dev deploy triggered by themainpush ships the code inert. - Certification: NOT externally certified. Internal approval is not certification, and the mechanic's RTP contribution remains a seeded estimate with a stated interval.
final-v1.0 — CI lint fix, seal unaffected
- Date: 2026-08-09. Backend
b55426d. - What happened: the
lintstage failed oncd4c093. The OpenAPI drift test imported a YAML parser thatapps/game-enginenever required. It compiled locally becausego.workunions the build list across modules andapps/auth-servicerequires that parser; CI lints each module separately — deliberately, since golangci-lint v1 does not understandgo.work— where the requirement does not exist. - Fix: switched the test to
sigs.k8s.io/yaml, the parserlibs/mathenginealready uses for these same pack YAMLs, and declared it inapps/game-engine/go.mod. One YAML parser in the repo rather than two that could disagree about the document they are both reading. - Seal impact: NONE. All eight bound backend artifacts — the config, the OpenAPI schema, the
wire-contract source and the five live fixtures — are byte-identical between
cd4c093andb55426d, verified file by file. No math parameter, locked rule, RNG mapping, evaluation order, rounding or cap moved. The approval stands; only the recorded build SHA was corrected in the living documents.final-v1.0was not touched, because it is immutable and correctly records the commit the math was approved at.
Entry requirements for later revisions
For every later entry, state what changed and why, old → new parameter values, affected formulas, state transitions and RNG mapping, evidence regenerated or invalidated, compatibility and migration impact, and the approval outcome. Creating a Final must cite its source Draft path and hash. A post-Final change starts a new Draft; never modify the historical Final.