skull_raiders: Stake Engine math port (certified, all modes 96%) - #115
Closed
mdicillo wants to merge 19 commits into
Closed
skull_raiders: Stake Engine math port (certified, all modes 96%)#115mdicillo wants to merge 19 commits into
mdicillo wants to merge 19 commits into
Conversation
Full math-SDK port of Camp Deadwater: 5x5 lines game, merit-badge wild multiplier, additive Helping Hands tumble, 3 tiers, 2 antes, 3 buys, Dig Deeper tier upgrade. All 6 bet modes optimized to 96.00% RTP; wincap 5000x; buys 100/250/500. 3-star volatility verified (platform gates cost-normalized per mode). Generated library/ is gitignored (regenerate via `make run`). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the emitted book a superset of the client's internal event vocabulary so the TypeScript adapter (fromStakeBook) is a clean 1:1 map with no fidelity loss. Math is unchanged — only extra event fields are added; RTP holds at 0.96 on all 6 modes, SHA-256 + payout hash verify OK. - reveal: attach this spin's merit badge as `wildMultiplier` on every reveal (base + free, incl. losing/no-wild spins). Roll the badge before draw_board so it's set at reveal time (gamestate), attach in a draw_board override. - winInfo: attach `hasWildWin` so the client sets its callout multiplier exactly when a wild is in a winning line (meta.globalMult collapses 1x-wild and no-wild). - handClear: emit the `cleared` cells the hands pulled (unpadded board coords). - freeSpinTrigger: level, count, baseLevel (Dig Deeper upgrade), maxHands, tier floor multiplier — everything bonusTrigger + bonusStart need. - freeSpinRetrigger: level, added, capped (SDK never enforces bonus_max, so capped stays False — faithful to the certified math; do not wire the cap without re-cert). - updateFreeSpin: cumulative feature `totalWin`; freeSpinEnd: level + maxWin flag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regenerated BR0 / BR_ante2 / BR_ante3 from the updated TS model (npm run reels:export): highs 30->34, lows 70->68, base WILD 3.97->3.90. Feature strips (FR0 / FR_tier2 / FR_tier3 / FRWCAP) are byte-identical — the free-spins H/L densities are frozen, so feature EV and every buy mode stay put. 100k cert: all 6 modes optimize to 0.9600 RTP, SHA-256 + payout hashes OK, 5000x wincap preserved, all cost-normalized volatility gates pass (CVaR/cost < 800, ETL/cost < 0.9). run.py kept at 10k dev default. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
camp_deadwater: A-minus base-reel reweight (100k certified)
Sync the SDK game to the dev model's mid-juice + first-aid work (camp-deadwater #143/#144/#145) and add the mystery reveal. - Paytable: de-top-heavied to match SYMBOLS (WILD 5-OAK 60->45; H1 3/12/55->3/14/40; low ranks regrouped 1-3-1). - FIRSTAID mystery symbol on BR0/ANTE reels (re-exported), revealed before scoring: reveal_mystery() ports the TS "match & extend" (best-completing left-anchored run; cosmetic left-neighbour then a real-symbol fallback, so a kit never reveals as itself/wild/scatter). Registered via a create_symbol_map override (framework only auto-registers paytable + special symbols). - Buys draw a FIRSTAID-free base reel (BR0_buy) so a bought bonus never lands an unrevealed kit on its trigger board. - Reels re-exported (BONUS 1.47, FEATURE_WILD 30.5, ante mults). Re-cert: 100k, all 6 modes 0.9600 RTP, SHA-256 + payout hash OK; cost-normalized volatility within limits (buys 4.3-8.5 < 800, ante_allout 0.40 < 0.9). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
camp_deadwater: first-aid mystery re-cert (base + antes)
Ports the stake-game-two (Jelly Jamboree) TS fake-math into the math-SDK, following the camp_deadwater milestone pattern. A 5x3 243-ways CASCADING slot. Milestone A: grid, Rage-Quit paytable, exported reels (WILD->W / SCATTER->S / MYSTERY->M), 25,000x wincap, and the ways-evaluation + tumble loop in both base and free spins. The multiplier LADDER is neutralized (global_multiplier held at 1) and the mystery "?" is inert, so base ways + cascade math can be verified before the multiplier mechanics land. Verified against 2k debug sims: every win row equals paytable*ways*100 (book units), globalMult is 1 everywhere, and cascades chain (depth up to 25). Milestones B (ladder + WILD flat pay), C (wheel + tiers + retrigger), and D (six bet modes + optimizer + cert) follow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Ladder: global_multiplier climbs +1 per winning tumble (opening drop 1x, then 2x, 3x, …) via update_global_mult in the base + free tumble loops. Reset each base spin; reset each free spin for now (tier persistence is C). - WILD flat pay: add_wild_pay() awards a flat 5x total bet ONCE when a wild sits on all five reels, x the ladder, stacked on the ways wins the wilds complete. Not a per-way pay, so it is appended to win_data directly. Verified: all symbol wins pay paytable*ways*ladder*100; globalMult climbs 1..25 across cascades; max payout now 343x (was 47x). Wild-pay path unit-tested directly (fires once at 5x*ladder on all-reels; no-ops otherwise) since a full 5-reel wild is naturally rare with base wilds pushed right. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…etrigger - Mystery "?" wheel folded into the tumble loop at the locked ordering: each "?" spins first (boosting the ladder), then the drop's win pays at the boosted ladder, then +1. Base activates a "?" only on a winning drop; free spins always. Multiple "?" stack. Each activated "?" is consumed. - Wheel results (add 5/10/20/50/100, mult x2) with the +5->Upgrade swap in a 3-scatter (untilUpgrade) free round, reverting to +5 once an Upgrade lands. - Three tiers by scatter count (3/4/5 -> t1/t2/t3). Ladder persistence: t2/t3 carry the ladder across the whole feature; t1 resets each spin until the Upgrade flips it persistent. Tier 3 opens with 1 free wheel spin. - Retrigger awards a flat +5 spins. Verified against debug sims: wheelSpin from->to math 0 errors (924 spins); every winning drop with a "?" pays at the boosted ladder (426/426); tier-3 opening spins fire once per tier-3 feature (10/10); start-multiplier sequences confirm persistence (t2 [1,6,6,22,32,36], t3 [21,31,51,57]) and t1 reset ([1,1,1,...]); 0 persistence bugs. Model is intentionally hot pre-optimizer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the three feature-entry bet modes on top of the base game:
- Bonus (100x): forces 3 scatters -> tier 1 (FR1, untilUpgrade).
- Super Bonus (200x): forces 4 scatters -> tier 2 (FR0, persistent).
- Mystery Bonus (500x): scatter_triggers {3:45,4:45,5:10} natively rolls the
tier 45/45/10 -> tier 1/2/3 (FR1/FR0/FR3).
Feature reel is tier-driven for natural AND bought features via fs_feature_reel
(get_current_distribution_conditions returns a per-tier copy — the TS provider
draws natural features from the per-tier pool too). A buy LOCKS its tier to the
forced opening scatter count; accumulation during the base cascade only upgrades
a natural trigger, never a buy (TS: level = boughtLevel).
Verified across debug sims: all four modes run clean; tier mixes are
bonus 100% t1, super_bonus 100% t2, mystery_bonus 46/43/11 (~45/45/10). Raw
(pre-optimizer) buy EVs are hot as expected (the optimizer down-weights to
0.967 in the full run).
Remaining for Milestone D: the two boost modes (3X Chance fee, Mystery Chance
stake), their reel exports + a WCAP reel, and the optimizer/cert run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All six SDK bet modes now exist. Adds: - 3X Chance (chance3x): a fee-priced (cost 3) base spin on a richer-scatter pool (BR0_chance3x, scatter 4.0 -> 7.4). Pays stay on the base bet. - Mystery Chance (mysteryChance): a stake-priced (cost 50) base spin. Priced via mode_bet_multiplier=50 — every pay scales x50 and evaluate_wincap scales the cap to 25,000x the 50x stake, so the cost cancels out of RTP and the thinned pool (BR0_mysteryChance) prices it. A "?" is planted on every base opening board (inside draw_board, before the reveal, so the client sees it). Boost reels were built from the TS model's own buildReelStrips (symbolDensity preserved) so the SDK opening boards match the fake-math. Verified across debug sims: Mystery Chance x50 pay scaling (2978/0) and a forced "?" on 100% of base boards; 3X Chance richer scatter (1.21 vs 0.86 scatters/board); all six modes run clean. Remaining for Milestone D: WCAP reel + wincap distributions, game_optimization per-mode targets, the full Rust `make run`, volatility gate, and locking the certified MODE_RTP. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the cert pipeline: - WCAP reel (wild+mystery-rich) to force the 25,000x max-win tail; wired as the freegame reel for the wincap criteria via get_current_distribution_conditions. - wincap criteria added to every mode's distributions, with the stake-scaled cap (25,000x the 50x stake for Mystery Chance) matching win_manager's clamp. - game_optimization.py: per-mode RTP targets — base-style (wincap/0/freegame/ basegame, per-mode base/feature split) and buy-style (freegame + wincap tail), with far-tail suppression to hold the 3-star band. - run.py: all six modes at 100k (publish set). First cert result (20k/mode validation): the optimizer converged EVERY mode to RTP 0.967, and all six PASS the 3-star volatility gate after cost-normalization (cvar/cost <=229 vs 800; etl40b <=0.69 vs 0.9). Pricing validated end-to-end: average_win = 0.967*cost for fee (chance3x), stake (mysteryChance, cap 25,000x the 50x stake), and all buys. SHA-256 + payout hashes OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…6.70% 100k/mode publish run: every bet mode converges to RTP 0.967 and passes the 3-star volatility gate after cost-normalization (cvar/cost <=303 vs 800, etl40b <=0.69 vs 0.9, etl10k <=0.21 vs 0.8, prob5k <=0.001 vs 0.01). Pricing validated: avg payout = 0.967*cost for the fee mode (3X Chance), the stake mode (Mystery Chance, cap 25,000x the 50x stake) and the three buys. Publish files (books_*.jsonl.zst + lookUpTable_*_0.csv + index.json) generated. Only remaining item is locking the certified 96.70% into MODE_RTP in the game repo's gameConfig.ts (player-facing regulated copy). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add stake_game_two — certified math-SDK port (96.70%, all six modes)
New games/skull_raiders port of the Skull Raiders TS fake-math model. 5x5, 15 paylines, multiplier WILDs, wincap 10,000x, every mode targets 96%. Milestone A: grid + 15 paylines + paytable (displayed-x units, WILD pays on 5 only) + symbols + base (BR0) and feature (FR0, FR_tier3) reelstrips exported from the TS reel model (WILD->W, BONUS->S) + base LINES evaluation + stock free-spins loop + the base/feature multiplier-wild bags. Multiplier wilds roll in base too (not just feature); the stock "symbol" strategy already implements our rule (sum only >=2 wilds within a winning line, floor factor at 1). Units verified on 10k base sims: 10,472 unmultiplied line wins all exactly paytable*100; multiplied wins scale by the summed factor (e.g. H4 3-OAK 1x with two x2 wilds -> 200 = 1*100*2). Bonus mode raw feature EV ~98.5x at cost 100 (optimizer will pull to 96% in F). The forced-wincap distribution is intentionally deferred to Milestone F (no round can reach 10,000x until the feature + WCAP reel exist; forcing it now resamples forever). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… cap + per-tier reels) update_freespin_amount override: scatter count 3/4/5 -> tier 1/2/3 (8/12/15 spins). A natural trigger (base game / ante) clamps to naturalMaxTier=2 so it can never award HIDDEN; a buy runs the exact forced tier (Mystery can roll tier 3). The feature reel follows (tier, is_buy): tier 3 -> FR3 (wild 22), bought tier 1/2 -> FRB (wild 14), natural tier 1/2 -> FR0 (wild 13.5), repointed per round in get_current_distribution_conditions. New FR_buy.csv reelstrip (exported via an added FR_buy branch in the game repo's export-reels.ts). update_fs_retrigger_amt override: retrigger adds the landed tier's spins, capped so total never exceeds bonus_max=30; once capped, further scatters add nothing. Verified on debug sims: base (natural) yields only tiers 1/2; bonus (buy) yields 1/2/3 with 8/12/15 spins; tot_fs never exceeds 30 (2 capped retriggers observed); tier-3 feature boards draw wild-22 density (6.13 wild/board incl padding) vs wild-14 for tiers 1/2 (~4.16). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The wheel is a base-game criteria (force_wheel in run_spin). A wheel round replaces the normal line spin: draw a land, strip scatters + natural wilds to lows, then ATTACK or STEAL (50/50) builds the whole win. ATTACK: redraw N paylines (weighted 1..5) — each with a payout symbol T filling the line and 1..3 wheel-multiplier wilds scattered along it — then score as ordinary line wins (one reel-0 conversion completes every payline through it, which is where the richness comes from). Land line-wins are broken first so only the redrawn lines pay; a full-wild fallback guarantees a win. STEAL: plant a present symbol on 3..5 cells + 1..3 banked wheel-multiplier wilds, then score position-agnostically (evaluate_steal in game_calculations: sweep every 3+ group, sum them, multiply by the summed wild factor; wilds are pure multipliers that don't pad counts and aren't stealable). Custom events wheelSpin / wheelConvert / wheelSteal (game_events.py) plus the standard win-accounting events so analysis/verification still computes payoutMultiplier. Verified on debug sims: attack 237/237 winInfo==finalWin; steal 263/263 board recompute match and event totals internally consistent; zero fs leak on wheel rounds; wheel win mean ~43x, max ~905x. The wheel criteria (quota 0.05 for now) is the optimizer's lever for the ~22.9% wheel RTP contribution. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Define the full published mode set, matching the client's publishedModeName taxonomy (PR engineio#75) exactly: base 1x normal spin (wheel 0.0055, natural bonus clamped to tier 2) base_bonuschance 3x ante: boosted bonus frequency (natural path, clamped) base_wheelchance 5x ante: boosted wheel frequency base_bonuschance_wheelchance 8x ante: both boosts bonus_1 100x buy tier 1 (8 FS, FRB pool) bonus_2 150x buy tier 2 (12 FS, FRB pool) bonus_mystery 300x buy: tier roll {3:1,4:1,5:2} -> only route to HIDDEN (tier 3, FR3) Antes reuse the base spin math (same BR0/FR0 reels + wheel); they differ only in cost and in the per-criteria quotas that oversample the boosted feature (bonus for base_bonuschance, wheel for base_wheelchance). Buys force the feature and lock/roll the tier via scatter_triggers. Buy PRICES are the premium for immediacy and are decoupled from RTP — every mode targets 96%, weighted by the optimizer. Verified: all 7 modes generate books with no errors; index.json names + costs (1/3/5/8/100/150/300) match the client taxonomy; raw buy EVs ~95x/138x/266x are within optimizer reach of 96%. The forced wincap tail + per-mode opt targets come in Milestone F. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ate)
Feature wincap early-end (run_freespin stops + clamps at 10,000x) + a WILD-rich WCAP reel (43% wild) +
a forced-max-win `wincap` distribution per mode, so every mode reaches exactly 10,000x fast (no resample
hang).
Optimizer targets (game_optimization.py): each mode's 0.96 is split across criteria that are BUCKETED by
search_conditions (per docs/math_docs/optimization_section + force_info.md) — the fix that made the base
modes converge: wincap by win value (first), freegame {"symbol":"scatter"}, wheel {"symbol":"wheel"} (via
a record() tag added in run_wheel_round), "0" by win value, and basegame the searchless REMAINDER placed
LAST (previously it greedily claimed freegame/wheel sims, collapsing them to ~0 and the mode to 0.35).
RTP split follows the TS economics: base line ~30% / bonus feature ~43% / raid wheel ~23%; antes keep the
non-advertised feature at natural rates and boost the advertised one; the combined ante boosts both.
100k-sims/mode cert run: ALL SEVEN MODES optimize to RTP 0.9600 and PASS the cost-normalized 3-star
volatility gate (cvar/cost <=158, etl40b_n <=0.63, prob5k 0, rtp <=0.967) with no tail scaling needed.
SHA-256 + payout hash OK (100000 entries/mode). publish_files + PAR sheet generated. index.json mode set
+ costs match the client publishedModeName taxonomy. The local un-normalized 3-star warnings are expected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author
|
Closing — opened against the wrong base repository by mistake. This belongs on the mdicillo/math-sdk fork, not upstream. Reopening there. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
games/skull_raiders— the Stake Engine math-SDK port of the Skull Raiders (castle-raid) TSfake-math model. 5×5, 15 paylines, multiplier WILDs, a three-tier free-spins bonus, a random raid-wheel
event (ATTACK/STEAL), two per-spin ante boosts, and a mystery bonus buy. Every bet mode certifies at
96.00% RTP and passes the cost-normalized 3-star volatility gate. Wincap 10,000×.
Built and verified in milestones (one commit each):
reels exported from the TS reel model, multiplier-wild bags (base tame / feature fat, summed within a
line via the "symbol" strategy). Units verified: line wins == paytable×100.
tier 2 (HIDDEN is Mystery-only); retrigger capped at 30 spins; per-tier feature reels (natural FR0 /
bought FRB / tier-3 FR3).
force_wheel); ATTACK redraws whole paylines, STEAL sweepsevery 3+ group position-agnostically (wilds as pure multipliers); custom
wheelSpin/wheelConvert/ wheelStealevents + standard win-accounting. 500/500 recompute checks matched.base(1×), antesbase_bonuschance(3×),base_wheelchance(5×),base_bonuschance_wheelchance(8×), buysbonus_1(100×),bonus_2(150×),bonus_mystery(300×).index.jsonnames + costs match the client'spublishedModeNametaxonomy exactly.per-mode
opt_paramssumming to 0.96 with correct criteria bucketing viasearch_conditions(the fixthat made the multi-criteria base modes converge —
basegameis the searchless remainder, placedlast;
freegame/wheelmatched by theirrecord()tags). RTP split faithful to the TS economics(base line ~30% / feature ~43% / wheel ~23%).
Cert results (100k sims/mode)
3-star limits (cost-normalized): cvar ≤ 800, etl40b ≤ 0.9, prob5k ≤ 1%, rtp ≤ 0.967 — all pass.
SHA-256 + payout hash OK (100000 entries/mode). Buy prices are the premium for immediacy, decoupled from
RTP — the optimizer weights each mode's outcomes to 96% independently. The local un-normalized 3-star
warnings
make runprints for the high-cost modes are expected (platform gates on the normalized values).Reels are regenerated from the game repo via
npm run reels:export(library/stays gitignored).🤖 Generated with Claude Code