Mechanic Math Changelog — hold-and-win

Record every mechanic-math document revision. Do not rewrite or remove historical entries.

Versions

VersionStateDateAuthor/agentDocument pathDocument SHA-256Backend config SHA-256Backend Git SHAApproval
draft-v0.1Draft, superseded2026-08-09agent (Claude Opus 5)math/mechanics/hold-and-win/hold-and-win-math-draft-v0.1.mdcomputed at FREEZE & HANDOFF888f134c29a59434d259316e9b83a8ec760bec6bcfb182334c6e42f167917d8888a730a8512995651c7c0791d25d435425c3a7a2DESIGN approved 2026-08-09; superseded by draft-v0.2
draft-v0.2Draft, current2026-08-09agent (Claude Opus 5)math/mechanics/hold-and-win/hold-and-win-math-draft-v0.2.mdcomputed at FREEZE & HANDOFFa22129d99d21eb14acdb303efdf666f4065925fa461012d8e99f2144538d0508a3f644eDRAFT MATH approved 2026-08-09 with tuning direction; FREEZE & HANDOFF awaiting

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.md at workspace commit c44bc01).
  • 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 weights Regular 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_win feature type in libs/mathengine (the existing hold_and_spin is untouched) and a new game-engine migration 023_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.

DecisionResolution
How the 500× bound layers against the engine's round-level capBoth the mechanic cap and the pack's Paytable.MaxWinMultiplier are 500, so the whole round (base + feature) is bounded at exactly 500× — one validated bound. Config validation rejects a Grand multiplier above the cap
Whole-game RTP target, absent from the design specTotal ≈96%, base ≈76%, mechanic ≈20%
Whether exact enumeration would be attemptedNo. hold_and_win stays out of the engine's grid-determined allowlist; all mechanic evidence is seeded Monte Carlo, labelled estimated

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-256 888f134c29a59434d259316e9b83a8ec760bec6bcfb182334c6e42f167917d88, backend Git SHA 88a730a8512995651c7c0791d25d435425c3a7a2.
  • 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 seed haw-draft-v0.1-sim, backend 13700ae, config SHA-256 888f134c…917d88, RNG sha256-trunc64-be/go1-alfg#1, driven through the canonical RunBatch/MergeBatches path. 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. LandingChancePPM is 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%] needs PPM ≤ ≈51,200; the total-RTP band [95.7%, 96.3%] needs PPM ≥ ≈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 LandingChancePPM alone 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:

    Parameterdraft-v0.1draft-v0.2
    LandingChancePPM15000043000
    regular weight, 1×4030
    regular weight, 2×2825
    regular weight, 3×1618
    regular weight, 5×1015
    regular weight, 10×510
    regular weight, 20×12
  • 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 version hold_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 seed haw-draft-v0.2-confirm-independent, which shares nothing with the sweep's seed stream.

    CriterionRequiredMeasuredVerdict
    Total RTP96.0% ±0.3pp96.0031% ±0.1189pp, CI [95.7701%, 96.2362%]PASS
    Feature contribution≈20.884%20.8871%PASS
    Conditional mean≈32.4545×32.4593× ±0.1848PASS
    Full-grid conditional0.10%, band 0.05–0.25%0.0783% (10 of 12,773); refined to 0.090053% belowPASS
    Base RTPexactly 75.1160%75.1160% exactPASS
    Trigger1 in 155.40351 in 155.4035 exactPASS
    Max awardexactly 500×500.0000×, never exceededPASS
  • Config identity regenerated: SHA-256 a22129d99d21eb14acdb303efdf666f4065925fa461012d8e99f2144538d0508 (was 888f134c…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 canonical RunHoldAndWinToCompletion, trigger counts weighted by the exact enumerated distribution, seed prefix haw-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. LandingChancePPM remains 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.2 was measured through RunHoldAndWinToCompletion, 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. Backend cd4c093.
  • Parity: the live orchestrator and RunHoldAndWinToCompletion are compared over complete state — every cell, prize, newly_landed delta, 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_win promoted to SupportFull in 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 push main first. 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 to main and pushed. No merge commit, so the SHA sealed in the protocol is the SHA on main.
  • Config hash: a22129d9…8d0508, unchanged since draft-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 023 is additive. An unknown persisted mechanic version fails closed and never falls back to hold_and_spin.
  • Production state: dormant. hold_and_win_enabled and hold_and_win:<operator> default to DENY; the dev deploy triggered by the main push 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 lint stage failed on cd4c093. The OpenAPI drift test imported a YAML parser that apps/game-engine never required. It compiled locally because go.work unions the build list across modules and apps/auth-service requires that parser; CI lints each module separately — deliberately, since golangci-lint v1 does not understand go.work — where the requirement does not exist.
  • Fix: switched the test to sigs.k8s.io/yaml, the parser libs/mathengine already uses for these same pack YAMLs, and declared it in apps/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 cd4c093 and b55426d, 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.0 was 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.