runstate
The one Paper Sky table that outlives a hot-reload. **Require `rules`, not this.**
runstate
The one Paper Sky table that outlives a hot-reload. Require rules, not this.
rules is edited constantly while the game is being tuned, and re-saving a
module runs its chunk again and hands every requirer a brand-new table. If the
event bus lived in rules, every save would mint a fresh one and silently
orphan every Rules.events:on(...) the HUD, the audio and the juice had
registered — they keep holding a live connection to a bus nobody emits on any
more, and the game goes quiet with no error anywhere.
A module is only re-run when it changes, not when something that requires it
changes. So the bus, the run state and the singleton slots live here, in a file
that is written once and then left alone, and rules re-exports them:
| Exported as | Reached as |
|---|---|
M.events | Rules.events |
M.state | Rules.state |
M.singletons | Rules.singletons |
The bus
Two guarantees it makes, both bought the hard way in a live session.
A subscriber cannot stall the emitter. emit is called from inside the
crane's update, and a handler that spawns paper scraps or starts a sound costs
real milliseconds — measured here, four separate crash handlers at ~204 ms
each, which put Crane.update over the 1000 ms script watchdog and halted the
player's flight. Dispatch is handed to one coroutine, so subscription order is
still exactly what it looks like and the emitting frame returns immediately.
Payloads carry everything a handler needs.
A subscription can survive its own component reloading. Pass a stable key:
Rules.events:on("crash", function(p) ... end, "PaperSkyHud.crash")
Re-registering under that key replaces the previous handler instead of stacking
a second one. This matters because a hot-reload re-runs a component's awake
without ever calling its onDestroy (OrigoZero/zero#8444), so a keyless
subscribe in awake leaks one handler per save — this bus was carrying 21 live
crash handlers for what should have been three. Without a key it warns once an
event passes 12 subscribers; Rules.events:counts() reports the live counts.
Change the game in rules. Change this file only to add a field that must
survive a reload — and expect to re-establish every subscription when you do.
Scoped to this part · feeds back into the world's score.