Inspiration

Each simulated slot machine hides its own rules: payouts depend on its outcomes up to 8 spins back, in a pattern (and direction) unique to that machine, and those rules can drift or change across a whole episode at once. The challenge was to recover as much of that structure as possible from the logs alone, without fooling ourselves about how well it works.

What it does

For each query, it predicts the probability that the next spin returns 0×, 1×, 2×, 5× or 20× a one-chip wager. It works across four conditions: stable, short history (only 64 observed ticks), gradual drift, and shared change across machines.

How we built it

  • Leak-free features. Every feature at tick t uses only ticks before t. That lets every observed tick become a training example (262k examples for the final model), not just the ~768 labelled queries per split.
  • Per-machine pattern statistics. These are the machine's own win rate conditioned on its win/loss state at each lag from 1 to 8, the single best lag and the best lag pair (picked from its own past), recency-weighted versions for drift, a "surprise" signal measuring how far recent results deviate from the machine's own pattern, payout-size mix, and episode-wide summaries of the other machines.
  • Per-machine online ridge regression. For every machine and every tick, it fits a small regression of win on all 8 lag states plus all 28 lag-pair interactions, using only past ticks. It's computed exactly from running sums, with both a full-history and a recency-weighted version. Its prediction becomes a feature. - This was our biggest late gain, mostly on the drift and shared-change conditions. Model. An ensemble of 5 scikit-learn HistGradientBoosting classifiers (5 classes, 400 trees each, each tree seeing a random 50% of features, seeds 0–4), with predictions averaged. Lightweight, reproducible runtime. The trees are exported to JSON and evaluated by a small NumPy evaluator, verified identical to scikit-learn's output. The submission needs only NumPy, pandas and SciPy, runs offline in about a minute, and reproduces the CSV byte-for-byte from the ZIP.

Challenges we ran into

  • Patterns are machine-specific. Global "lag 7" features failed because each machine has its own lags and signs. Statistics computed from each machine's own past were the key.
  • Honest evaluation. Validation includes the simulator's true odds, which makes it easy to inflate a validation score. We never trained or tuned on those targets, so our score is a fair estimate of test performance.
  • Noise in small gains. With 24 episodes per condition, gains of ±0.2 are often noise, so the bootstrap test mattered more than the raw score.

Accomplishments that we're proud of

  • Clear gain over the baseline: 52.73 vs. 30.0, with a fully leak-free pipeline.
  • A ceiling analysis. Even with perfect win probabilities, our validation score would top out near 57. The rest of the error is in payout size, which turned out to be unpredictable from history (the hidden state the organizers warned about). That told us where effort could and couldn't pay off.
  • Exact reproduction. The submission reproduces exactly, with minimal dependencies.

What we learned

Ideas that sounded good but didn't survive the bootstrap test:

  • short-history data augmentation
  • cross-machine lag features
  • neighbour machines in the regression
  • 3-way lag interactions
  • per-machine logistic regression
  • probability sharpening (the model was already calibrated)
  • hyperparameter variations
  • Measuring before believing saved us from shipping several "improvements" that were really noise.

Round two (adaptation)

The same pipeline as the main round, retrained on the adaptation kit with the main-round data included, scoring 17.81/20. Built with Python · NumPy · pandas · SciPy · scikit-learn

Built With

Share this project:

Updates

Submission history