House Edge: reading a slot machine's hidden rules

What inspired us

The TAMU Datathon 2026 challenge put us in a casino. Each machine pays out by a hidden rule that depends on its own recent spins, and sometimes on other machines' spins too. Our task was to forecast the next spin: the chance of a win, and how big the payout would be.

The rules said outcomes can interact at lags up to eight ticks, that machines of the same type can respond differently or even oppositely, and that the patterns would change in round two. That made it feel less like ordinary tabular prediction and more like system identification: work out the machine, then forecast it. We wanted a model that learns the rules from the data instead of memorizing them, so it would still hold up when the casino changed the game.

How we were scored

Each query needs a probability for every payout outcome. Scores are relative to a reference forecast, averaged over four conditions (stable, short history, drift, shared change):

$$ R = \frac{1}{4}\sum_{c} \frac{E[c]}{\max(B[c],\,10^{-9})}, \qquad \text{points} = \frac{\text{max_points}}{1 + R} $$

Here $E[c]$ is our error in condition $c$ and $B[c]$ is the reference's error. Getting to 60 points means driving $R$ toward zero in all four conditions. One weak condition, such as machines with very short histories, drags the whole score down.

How we built it

We split the forecast into two parts and modelled each separately:

$$ P(\text{outcome}) = \underbrace{P(\text{win})}{\texterbrace{P(\text{size} \mid \text{win})}{\text{payoutsplit}} $$ 1. Find the structure. A Walsh-style screen over chine census, showed that wins follow parity rules on lag pairs. On the main kit those pairs were $(1,3)$, $(2,5)$ and $(4,7)$. For a pair $(a,b)$ a machine is in "cell" $x_{t-a} \ox_{t-b}$, and the win chance depends on that cell. Eaance turned out to be a logit-normal draw around amean for its type, cell and signal (standard deviation about 0.5 to 0.7). On drift and shared-change episodes, the swap of cells finishes before the cutoff. 2. Build several different win models.

  • An exact Bayesian filter. It tracks the posterior over which lag-pair rule (and which direction) each machine follows, and updates it tick by tick through the episode's history
  • A sparse rule library (system identification). It learns lag identities and coefficients from training data only.
  • A pair-mixer and hidden-rule model (v13 and latdds patches for short-history machines and jackpotcells.

3. Blend them. The final win probability is an equal-weight average in log-odds space:
$$ \operatorname{logit} \hat p_{\text{win}} = \frac{1}{Kame{logit} p_k, \qquad \operatorname{logit} p =\log\frac{p}{1-p} $$ Payout sizes come from one member's size split, with ery outcome so a single confident miss can't wreck thescore.
4. Adapt for round two. Every member now learns its lag pairs from the new kit's training data instead of assuming the main-round pairs. The exact filter also learns 3-lag rules. A build-time member gate drops a blendmember only if leaving it out is better on both validation halves by at least 0.1 points.
5. Rank candidates automatically. One script screens the new kit's structure, builds every candidate model, scores each against the kit's validation answers, and recommends what to compared against the default with a paired bootstrap.A challenger only replaces the default if it wins by more than 0.1 points on the mean and on both halves.

Result: 57.97 out of 60 on main-round validation, cross-fitted (halves 57.98 and 57.96). On a stricter Docker test where each validation fold is scored with its own answers hidden 57.86.

What we learned

  • Work out where the points are. We estimated thay would be worth about +3.0 points, compared withabout +1.4 for perfect payout sizes. So we focused on the win model instead of fine-tuning the blend.
  • Cross-fit or you're fooling yourself. One promi on the same validation answers it was scored on, which inflated its score. After that we predicted each validation half using priors fit only on the other half.
  • Know when you've hit the ceiling. About 70 add- peer effects, gap handling) all landed within noise.That told us to stop tweaking and start making the model robust for round two.
  • Test structure rather than assume it. A self-su model scored 52.60 with causal per-machine summaries versus 34.47 without, at the same model size. Showing each machine's own local evidence beat assuming effects were shared acrossmachines.
  • Watch the effective sample size. Fractionally weighted pseudo-evidence from recent states doesn't have the same effective sasize as static evidence. Comparing raw likelihoods acormalizing gives misleading conclusions. ## Challenges we faced - The rules can change mid-competition. On simulat filter scored about 18 out of 40 when it assumed themain-round pairs. Learning the pairs from each kit's own training data fixed it. - Short-history machines have too few observed ti. We had to average over the missing lags rather thandrop those ticks. Dropping them also made evidence incomparable between models. - Reproducibility. The organizers re-run the codeo match to within $10^{-8}$. Late on, our test suitefound that the entry would crash if the kit had no validation folder. With a fix that only filled in the missing answers file,would still have produced different numbers, because rs as priors. We fixed both by bundling the publicvalidation data inside the code ZIP. Output is now byte-identical whether that data is fully present, present without answers, or missing entirely.
  • Time and coordination. Four people and several AI coding agents worked across many branches overnight. Clear rules kept things orderly: one source of truth for the default, expliciange tested before merging.

What's next

  • Rebuild the gated challengers on each new round's d
  • Use richer rule families beyond parity, AND and OR.
  • Cut the remaining loss on short-history machines, wcondition.

Built With

Share this project:

Updates

Submission history