script-module-state
a behaviour's update, updateAll, pausedUpdate or an event hook (onEvent, onCollision, …) or a function they reach writes a module-level let or var (shots++, last = id), or changes a module-level array, Map, Set or object (seen.push(id), cache.set(k, v), marks[id] = 1), directly, through a local alias (const ref = st; ref.n++), by a helper handed it (add(seen, 1)), or through a module-level class instance's method (gun.fire()), or writes a variable a closure keeps that a module-level call returned (const tick = makeCounter()), in a host built for networked play (HostConfig::networked()); a rollback re-simulates ticks and does not restore module variables, so peers drift; a variable written only in init and dispose, or in a helper only those reach (a course read once, a per-entity registry filled in init and emptied in dispose), is not reported, because a re-simulation runs neither again, unless it is also written in a per-tick or event hook or in a function init or dispose makes (a listener, a timer), or the write reads the variable's old value (id++, n += 1, x = x + 1): an init run again counts twice; a lazy cache (a write guarded by its own unset test, if (!t) t = [1, 2] or t ??= build(), of a value built from constants alone) is not reported either, but a flag (ready = true) is; the caveat is that an entity spawned or despawned on a predicted tick runs init or dispose there and a restore does not undo it, so such a registry must tolerate a stale entry (check the entity is alive before using one); said once per module version and variable, at the first attach, with the line (the .ts line when the module has a source map) and the hook; the module still runs
| Severity | warning |
Meaning
a behaviour's update, updateAll, pausedUpdate or an event hook (onEvent, onCollision, …) or a function they reach writes a module-level let or var (shots++, last = id), or changes a module-level array, Map, Set or object (seen.push(id), cache.set(k, v), marks[id] = 1), directly, through a local alias (const ref = st; ref.n++), by a helper handed it (add(seen, 1)), or through a module-level class instance's method (gun.fire()), or writes a variable a closure keeps that a module-level call returned (const tick = makeCounter()), in a host built for networked play (HostConfig::networked()); a rollback re-simulates ticks and does not restore module variables, so peers drift; a variable written only in init and dispose, or in a helper only those reach (a course read once, a per-entity registry filled in init and emptied in dispose), is not reported, because a re-simulation runs neither again, unless it is also written in a per-tick or event hook or in a function init or dispose makes (a listener, a timer), or the write reads the variable's old value (id++, n += 1, x = x + 1): an init run again counts twice; a lazy cache (a write guarded by its own unset test, if (!t) t = [1, 2] or t ??= build(), of a value built from constants alone) is not reported either, but a flag (ready = true) is; the caveat is that an entity spawned or despawned on a predicted tick runs init or dispose there and a restore does not undo it, so such a registry must tolerate a stale entry (check the entity is alive before using one); said once per module version and variable, at the first attach, with the line (the .ts line when the module has a source map) and the hook; the module still runs
Fix
keep the state in ctx.state (ctx.state.shots = (ctx.state.shots ?? 0) + 1), which a snapshot restore puts back; a module-level const that is only read, a variable only init and dispose set (and not counted up there), a lazily built constant cache, and anything written while the module body evaluates, is not state