Vault Breakers: Super Score — Evidence Manifest, draft-v0.2
DEV ONLY · NOT FOR PRODUCTION · NOT FOR CERTIFICATION. Binds a set of Draft results to one config and one backend commit. Not a freeze manifest; confers no lifecycle status.
Status: DRAFT MATH APPROVED 2026-08-19. Not frozen, not final, not certified, and not yet publishable to DEV — see the Freeze exit criteria below.
Supersedes
manifest-draft-v0.1.md, the APPROVED unbalanced reference baseline, whose evidence is retained unchanged.
Bound identities
Hashed from the bytes the binary embeds (RawYAML()); pinned by identity_test.go.
What changed from the approved baseline
The buys hold their reference prices because free spins are now worth buying, not because the prices were moved to fit a weak feature.
Documents
Reproduction
Results
Percentiles — estimated, 5,000,000 rounds
Exceedance — estimated, 100,000,000 rounds
Seeds and sample sizes
The seed strings still say v0.1. That is the seed scheme, not a version claim, and it is
left alone deliberately: changing it would make the two versions differ by both the config and
the seeds, and only one of those is under test.
Method boundaries
- The whole game is not enumerable;
CheckEnumerablerefuses this config and the refusal is asserted rather than worked around. - What IS exact follows from the per-cell independent draw model alone, and applies to both reel tables.
- Exact results come from the same engine that executes the game. Internally exact; not independent verification.
- Max win is a constructed reachability witness plus a containment argument.
- The recorded band is the interval the confirmation run supports. ±0.10 pp needs roughly 733 million rounds at this σ.
Unresolved and out of scope
- A free-spin snapshot pays on 8.23% of snapshots against the base game's 19.76%, because charges occupy 14.5% of the grid, and 95%+ of a sequence's award is the charge part. A deliberate high-volatility shape, recorded rather than smoothed, and worth a product decision before this leaves Draft.
- No math-studio authoring preset.
DrawModelneeds a column instudio_reel_setsand a migration (LayoutTypeis a column;InitialStopsis already dropped by that store), plusmultiplier_chargeandsuper_scatteras domain types with validation,build_configtranslation, feature-store and handler decoding, andcount_anywherein the accepted win types. Until then the pack cannot be seeded throughversions/from-pack, the routescripts/seed-magic-vault.shuses to publish to DEV. - No game-engine DEV admission gate. Deliberate — publishing is out of scope until this Draft is approved.
- The free-spins regular/charge split is derived from Run C, not measured inside Run A.
- Charges collected inside free spins are not itemised; only base-game charges are.
Freeze exit criteria
Freeze is blocked until every one of these is true. They are listed so the next person does not have to reconstruct them.
- DEV playtest of the free-spins shape completed, with a recorded product decision. The charge-dominant shape is approved for Draft only, and no retune may precede the playtest.
- The math-studio publishing path task is complete, all seven parts:
count_anywhereaccepted as a win type; reelset fields persisted without silent loss (DrawModelneeds a column and a migration, andInitialStopsis already dropped by that store);multiplier_chargeandsuper_scattersupported as domain feature configs;AtomicSimUsesVariantsandBuyOnlypreserved through the round trip; the exact canonical config hashfa6d0410d7de619e68b42083e7012b975d70be572a252d0dd60f3debe2ba4fc8reproduced;versions/from-packoutput proved equal to the approved backend pack; and no existing game hash changed. - A game-engine DEV admission gate exists, denies by default, and pins the canonical hash —
the shape
magic_vault_dev_gate.goalready uses. - Published to DEV and exercised on the real runtime: a natural trigger opening a durable free-spin sequence, a retrigger inside it, both buys, a reconnect mid-sequence, and a replay under the same Idempotency-Key.
ValidateFreezeEvidencesatisfied server-side — validation clean, exact-or-simulation evidence, a max-win finding, the config hash, andParityPassed. Assembled by the server, never asserted by the caller.- Every unresolved item in this manifest closed or explicitly accepted by name.
- Any config change re-opens all of the above. A new hash means new evidence; nothing above carries across a config that moved.
Status
draft-v0.2. DRAFT MATH APPROVED 2026-08-19. Not frozen. Not final. Not certified. Not yet
publishable to DEV.
Approved: whole-game RTP 95.8973% against a 96.00% target; Standard Buy at 100× returning 96.4173%; Super Buy at 500× returning 97.0453%; max win 50,000×; and the high-volatility, charge-dominant free-spins shape.
The free-spins shape is approved FOR DRAFT with a condition: it must pass a separate DEV playtest before Freeze. Do not smooth it and do not retune it without playtest results.
Not published. Not enabled for any operator. Internal simulation is not external certification.
AWAITING_USER_APPROVAL — DEV PUBLISHING PLAN. (granted 2026-08-20 — see the addendum below)
Addendum — 2026-08-20: DEV publishing plan approved
DEV PUBLISHING PLAN APPROVED 2026-08-20. Scope: DEV only. Not freeze, not stage, not prod.
Nothing above is rewritten. This records what changed after the draft-v0.2 approval was written.
Residual item 2 is closed
The manifest above says InitialStops "is already dropped by that store", and item 2 lists the
math-studio publishing path as open. Both were true when written. They are no longer:
The table is studio_reelsets, not studio_reel_sets as written above and in the Math Spec.
InitialStops was one of two silent losses, not one. The second was Paytable.WinTiers,
which decides RoundOutcome.WinTier — the big/ultra/mega label a client renders. The recorded
attribution in legacy_frompack_versions.json ("the feature store's JSON round-trip is lossy for
free-spins variants") was wrong: a typed FreeSpinsConfig with variants, a nested reelset
and initial stops survives its own JSON round trip. Both causes were found by comparing the two
assemblies field by field rather than by comparing their hashes.
With both closed, book_style_v2's Postgres assembly reproduces exactly its already-recorded
config_hash 5ec2df7fc62b1669431928253300b8dc5cab8735a601ab22ab153e8249699de5. The divergence
closed rather than moved. The superseded value is preserved as pg_config_hash_before_store_fixes.
That hash belongs to book_style_v2. It is not a Vault Breakers identity and must never be
quoted as one.
A third identity defect, found by the identity gate itself
The gate the DEV publishing plan asks for — in-memory, Postgres and published config hashes all equal — failed on its first honest run, after the field-by-field comparison had passed:
BuildMathVersionConfig allocated the slice unconditionally, so a paytable defining no lines
published an empty list of them rather than none. The same math, two canonical identities,
depending on which side of the store it was read from. It had never surfaced because every prior
game is a lines game with at least one line — Vault Breakers is the first count-anywhere pack and
the first with zero.
Fixed by leaving the slice nil when there is nothing to convert. The in-memory value did not move; the store path was corrected to reproduce it.
Canonical identities, full values
These are two different identities and are not interchangeable. The YAML hash is the bytes the binary embeds; the config hash is the canonicalized assembled struct, and it is the one the publishing path, freeze evidence and handoff manifest refer to.
Residual item 3 is closed
The game-engine DEV admission gate exists, denies by default, pins the canonical hash, and
requires ten conditions including RequireRuntimeSupport — so a binary older than the config it
loaded refuses to serve rather than silently omitting a mechanic.
Freeze blockers that remain
- DEV playtest of the free-spins shape, with a recorded product decision. Not to be smoothed or retuned before playtest results.
- Live DEV exercise of the runtime.
ValidateFreezeEvidencesatisfied server-side.- Every unresolved manifest item closed or explicitly accepted.
Criteria 2 and 3 are complete. Criterion 7 (a config change re-opens everything) stands as a rule.
The runbook for the publication itself is backend/doc/vault-breakers-dev-publication-runbook.md.
Still not frozen. Still not final. Still not certified. Internal evidence is not certification.
AWAITING_USER_APPROVAL — DEV PLAYTEST.
Addendum — 2026-08-20: published to DEV, and one mechanic does not pay there
Published as vault-breakers-super-score-v1 on the dev tier through the official
versions/from-pack path. All four identities matched:
The published config is the approved config. That is not in question. What follows is about what the runtime does with it.
BLOCKING: the Super Scatter instant prize never pays on the player path
A base spin never evaluates the published config. The orchestrator strips free_spins first,
because the durable runtime plays the sequence one request at a time and the atomic executor would
otherwise pay it twice. SuperScatterExecutor resolves its verdict through the free-spins trigger
resolution, so with free_spins removed it does not fire at all.
What this does to the recorded evidence. Every figure in this manifest remains a correct statement about the approved math. It is no longer a correct statement about what the dev runtime serves:
Nothing above is rewritten. The evidence describes the math that was approved; the gap is in the runtime, and closing it is what makes the evidence true of the product again.
This is the STOP condition the original brief named — a mechanic that works in the atomic simulator and not on the player path — and it is a Freeze blocker on its own.
Why the parity gate did not catch it
RuntimeBaseComparable() returns !HasFreeSpins(cfg), so every vector whose config carries
free_spins is skipped by the runtime half of the corpus. The super_scatter_prize vector rides
a config that has free spins, so its recorded FinalPayout: 50000 has never once been produced
through the real orchestrator — only through the reference engine.
make test-parity was never wrong. It does not compare these. Tracked as backend TODO GE7.
Fix options, none chosen
- Pay it orchestrator-side, the way the free-spins trigger is already resolved against the unstripped config. Config hash unmoved, so this manifest survives intact.
- Make the executor independent of
free_spinsviaScatterClassIDs/ScatterClassMin— two fields the design named and the published config does not carry at all, which suggests this was the original intent. Moves the pack, and therefore re-opens every figure recorded here. - Strip more narrowly: remove the atomic executor's payout, not the feature another executor reads.
Freeze blockers now
- DEV playtest of the free-spins shape — still open, and now qualified: playtest data on pacing and free-spins shape is valid; data on how big wins feel is not, because the largest payouts are the ones not being paid.
- Live DEV exercise of the runtime — partially done, stopped on the finding above. Ten checks pass: the count-anywhere boundary at 7 → 8, the paytable bands at 8 and 12, multi-step tumbles, the mixed scatter-class trigger to 15 free spins, Super Scatter paying nothing below class 4, one debit per spin, and publishing-alone-enables-nothing.
ValidateFreezeEvidence— open.- Unresolved manifest items — open, and this addendum adds one.
- NEW: the Super Scatter runtime gap must be closed and re-verified on DEV.
Still Draft. Still not frozen, not final, not certified. Internal evidence is not certification.
AWAITING_USER_APPROVAL — SUPER SCATTER RUNTIME FIX.
Addendum — 2026-08-22: the Super Scatter runtime gap is closed in the backend
The blocker recorded in the previous addendum is fixed. DEV re-verification is NOT done — blocker 7 asked for the gap to be closed and re-verified on DEV, and only the first half of that is complete.
Backend documentation: backend/doc/vault-breakers-super-scatter-runtime-fix.md.
What was done
Fix option 1, the one this manifest listed as leaving the config hash unmoved. The verdict is
resolved orchestrator-side against the unstripped config, the same way the free-spins trigger
already was, through the ENGINE's own resolveScatterTrigger over the per-snapshot census the
round already records. No second rule is written anywhere, and stripFreeSpinsFromCfg is
untouched.
Option 2 — ScatterClassIDs / ScatterClassMin on the executor — was not taken, for the reason
stated here when the options were listed: it moves the pack and re-opens every figure below.
Identity, re-checked
Both re-computed on the commit before the change and after it, and now pinned by a test
(libs/mathengine/canonical/pack_hashes_test.go) alongside the other four shipped packs. The
change adds fields to RoundOutcome, which is a result; canonical.Value hashes
MathVersionConfig, which is untouched.
What this restores
Every figure in this manifest is a correct statement about the approved math, and was never rewritten. It is now also a correct statement about what the runtime serves:
Awards graded against drawn seeds, not hand-placed grids: 1 Super → 100×, 2 → 500×, 3 → 5,000×,
and 4, 5 and 6 → the banded 4+ entry of 50,000×, which settles at the ceiling. A sequence opened
with no Super Scatter on the triggering snapshot pays nothing, and inside free spins the award
stays out (BaseGameOnly).
GE7, the parity gap
Also closed. RuntimeScatterComparable() grades the scatter verdict and the award on free-spins
vectors whose totals still are not comparable, so super_scatter_prize's recorded
InstantPrizeX: 100 is now produced through the real orchestrator for the first time. The gate
was verified non-vacuous by suppressing the award and watching both vectors fail.
Freeze blockers now
- DEV playtest of the free-spins shape — still open, and no longer qualified: big-win data is now worth collecting, because the largest payouts are being paid.
- Live DEV exercise of the runtime — still open, and must be re-run in full against the fix.
ValidateFreezeEvidence— open.- Unresolved manifest items — open.
- Super Scatter runtime gap — closed in the backend, DEV re-verification outstanding.
Still Draft. Still not frozen, not final, not certified. Internal evidence is not certification.