Inspiration

House Edge gave us logs from simulated slot machines and asked us to predict the probability of each next payout (0×, 1×, 2×, 5×, 20×). Each machine secretly follows rules linking its wins to earlier spins, and the organizers warned that the rules would change mid-event. That made it a puzzle about discovering hidden structure, not just fitting a model, so we set ourselves one rule: no hard-coded patterns. Everything had to be found from the data.

How we built it

  1. Automatic rule finder. We code every spin as win (+1) or loss (−1) and test every short lag pattern (for example "spin t−3 × spin t−8") on every machine. Patterns that are the best fit on many machines become rules. Some patterns look like rules but are just algebraic copies ("echoes") of real ones. We detect them exactly by treating each pattern as a polynomial over GF(2) and checking divisibility.
  2. 354 features per spin, all computed only from earlier spins: rule matches, Bayesian averaging over rules, recent-window change detectors (for drift and shared changes), lag-pair and lag-triple statistics, and payout-size patterns.
  3. LightGBM trained on self-supervised rows: every past spin is predicted from the spins before it.
  4. Small causal transformers (PyTorch, GPU) that read each machine's raw spin sequence, blended 65/35 with the trees. They make different mistakes from the trees, so the blend beats both.
  5. Honest validation. We chose every setting on half of the validation episodes and checked the other, untouched half.
  6. A fully self-contained entry. We reimplemented LightGBM inference and the networks in pure numpy (matching to about 1e-16) and bundled numpy itself, so prediction runs offline on 2 CPUs in 1–4 minutes and reproduces our CSV exactly.

Results (validation, scored against the true odds): main round 57.4 / 56.8 out of 60 (selection half / untouched half; baseline 30). Round two: 19.02 / 18.94 out of 20 (baseline 10).

Challenges we faced

  • Fake rules. Our first rule finder kept adding echo patterns, and simple threshold fixes dropped real rules instead. The exact GF(2) test solved it.
  • Rules changed mid-event. In round two, all 8 rules were replaced by 7 new ones. Our pipeline re-found them and retrained with one command in under 8 minutes.
  • Slow prediction. Prediction first took 16–20 minutes because of CPU thread oversubscription. One thread per process plus process parallelism brought it down to under 4.
  • Reproducibility. The organizers' runner has no extra libraries, so we built numpy-only inference and verified it with two independent runs (difference: 0).

What we learned

  • A method can generalize while a model doesn't. Our frozen round-one model scored only 10 / 20 on round-two data (baseline level), because it had memorized which rules exist rather than how to find them. Retraining with the automatic finder brought it back to 18.85 / 20, and the network blend to 19.02.
  • Validation discipline matters. Choosing settings on one half and checking on the untouched half kept our estimates honest (57.4 vs 56.8).
  • Next step: a rule-free model (transfer.py in our repo) that scores all 92 candidate lag patterns per machine without naming them, so it can work on new rules without retraining.

Built With

Share this project:

Updates

Submission history