How do I build an ECS where mechanics can be swapped while the game runs?
Keep all state in components as plain data and all code in modules that own no state. A snapshot is then just the stores, and swapping a mechanic is just swapping the systems that read them.
world.add(e, 'Napping', {}) // state: plain data, saved in every snapshot
export default defineModule({
id: 'siesta', version: '1.1.0',
components: { Napping: { version: 2, migrate: { 1: old => ({ ...old, since: 0 }) } } },
install(ctx) { /* one-time world changes */ },
setup(ctx) { ctx.extend(ACTIONS_REGISTRY, 'siesta_nap', nap) }, // runs on every attach: never writes the world
systems: [{ id: 'wake', every: 10, run(ctx) { /* query, then mutate */ } }],
})
In Glimtide every mechanic, even hunger, is a module installed into the live world between two ticks. The rules that make it work: no state in closures (an upgrade drops it), randomness only from the world's seeded RNG and time only from ticks, or a trial run on a snapshot can't be compared. More in the Glimtide write-up and the ECS FAQ.