Description
Starting a new run via menu + start on a long-lived instance does not fully
reset run state. We catalogued four distinct cross-run leaks, each confirmed by
replaying the identical action trace on a vanilla-faithful simulator (fresh
G.GAME every run) and diffing the full gamestate after every action.
G.GAME.won — after any win in the session, a later run's GAME_OVER
loss reports won = true. 16 instances observed; e.g. seed ESVPE7W2: 696
chips vs Amber Acorn, unambiguous loss, live gamestate won = true — leaked
from the previous run's ante-8 win on the same instance.
src/lua/utils/gamestate.lua:739 serializes the raw global
(state_data.won = G.GAME.won), so the stale value flows straight to
clients; play.lua:125 also branches on it.
G.GAME.last_tarot_planet — a fresh run's first Fool copied the tarot
last used in the previous seed's run.
G.GAME.pool_flags — gros_michel_extinct leaked: a run that never
owned a Gros Michel was offered Cavendish in the shop (seed LS4ECDCB).
Extinction only rolls for owned jokers at round end, so this is
vanilla-impossible in-run.
G.GAME.used_jokers key loss (mechanism unidentified, same family):
live's shop offered j_banner while Banner sat in play (seed LS6VZ3ES) —
impossible under the no-repeat pool. RNG forensics showed both sides drawing
identical raw picks from the same stream; the sim resampled per no-repeat,
live kept the duplicate, i.e. live had lost the key.
Root cause is presumably in how the run is rebuilt through the menu→start path
versus a fresh process launch (possibly SMODS-side), but balatrobot's start
endpoint is the natural place to defend against it.
Impact
This corrupts benchmark results, not just convenience:
- balatrollm runs its whole task list through long-lived instances (the
executor's port pool starts instances once) and starts each run with
menu+start. bot.py ends a run as won when gamestate["won"] is truthy — so
a loss that occurs on an instance after that instance has produced any win can
be recorded and published as a win (run_won in the leaderboard data and
the CC0 dataset).
- Leaks 2–4 make seeded runs non-reproducible: shop and consumable outcomes
depend on what earlier seeds did in the same instance.
Suggested Fix
In start.lua, after the run initializes, explicitly reset the leak-prone keys:
G.GAME.won = false, clear last_tarot_planet, and verify pool_flags /
used_jokers are freshly built. Alternatively (or additionally), document that
benchmark-grade data requires one process per run.
Repro bundles (action trace + both gamestates + field diff) available for every
seed cited.
Description
Starting a new run via
menu+starton a long-lived instance does not fullyreset run state. We catalogued four distinct cross-run leaks, each confirmed by
replaying the identical action trace on a vanilla-faithful simulator (fresh
G.GAME every run) and diffing the full gamestate after every action.
G.GAME.won— after any win in the session, a later run'sGAME_OVERloss reports
won = true. 16 instances observed; e.g. seedESVPE7W2: 696chips vs Amber Acorn, unambiguous loss, live gamestate
won = true— leakedfrom the previous run's ante-8 win on the same instance.
src/lua/utils/gamestate.lua:739serializes the raw global(
state_data.won = G.GAME.won), so the stale value flows straight toclients;
play.lua:125also branches on it.G.GAME.last_tarot_planet— a fresh run's first Fool copied the tarotlast used in the previous seed's run.
G.GAME.pool_flags—gros_michel_extinctleaked: a run that neverowned a Gros Michel was offered Cavendish in the shop (seed
LS4ECDCB).Extinction only rolls for owned jokers at round end, so this is
vanilla-impossible in-run.
G.GAME.used_jokerskey loss (mechanism unidentified, same family):live's shop offered
j_bannerwhile Banner sat in play (seedLS6VZ3ES) —impossible under the no-repeat pool. RNG forensics showed both sides drawing
identical raw picks from the same stream; the sim resampled per no-repeat,
live kept the duplicate, i.e. live had lost the key.
Root cause is presumably in how the run is rebuilt through the menu→start path
versus a fresh process launch (possibly SMODS-side), but balatrobot's
startendpoint is the natural place to defend against it.
Impact
This corrupts benchmark results, not just convenience:
executor's port pool starts instances once) and starts each run with
menu+start.
bot.pyends a run as won whengamestate["won"]is truthy — soa loss that occurs on an instance after that instance has produced any win can
be recorded and published as a win (
run_wonin the leaderboard data andthe CC0 dataset).
depend on what earlier seeds did in the same instance.
Suggested Fix
In
start.lua, after the run initializes, explicitly reset the leak-prone keys:G.GAME.won = false, clearlast_tarot_planet, and verifypool_flags/used_jokersare freshly built. Alternatively (or additionally), document thatbenchmark-grade data requires one process per run.
Repro bundles (action trace + both gamestates + field diff) available for every
seed cited.