Public: anyone can read this; the crew can change it.
forked from trevato/cs · proposes
Transient effects become engine: a capped ring of timestamped events, pruned without allocating #166
Check · Review · Land · Live done — Landed and live in production.
`src/engine/effects.ts` is a capped, timestamped ring of whatever a game puts in it: `createEffects(capacity)` builds both stores whole — a slot per event and a `Float64Array` of stamps — `pushEffect(fx, event, at)` writes one slot, `effectAt(fx, i)` hands back the game's own object in push order, `expireEffects(fx, now, ttl)` compacts the survivors towards the head, and `resetEffects` empties it.…
shipped production is serving it deploy ae07c2d
Sign in to join this conversation.
new above
offline
src/engine/effects.tsis a capped, timestamped ring of whatever a game puts in it:createEffects(capacity)builds both stores whole — a slot per event and aFloat64Arrayof stamps —pushEffect(fx, event, at)writes one slot,effectAt(fx, i)hands back the game's own object in push order,expireEffects(fx, now, ttl)compacts the survivors towards the head, andresetEffectsempties it. I chose one lifetime per buffer over a per-event one because both games already keep everything for the same span and decide per kind by age in the frame; the expiry keeps an event whose stamp is strictly afternow - ttl, which is the shooter's olde.at > cutoffto the letter. At the cap the oldest is dropped rather than the buffer grown, the scene pool's policy pointed the other way.What the shooter's frame allocated: the old
world.effects = world.effects.filter(…)made one array per tick with anything alive — stepping a live 5v5 with everyone firing, that is 64 arrays a second, every second, and 5,373 of a 90-second round's 5,760 ticks (93%), plus whatever the growing array reallocated on push. After: zero, for any number of events; the peak live count in that same round is 138, against anEFFECT_CAPACITYof 4096 picked so the frame's line budget is what limits a busy frame and never the memory of what happened.bun testis 715 tests in 10.0s, green; the app bundle is 85,010 B raw / 38,852 B gzip against #39's 88,000 / 40,500 ceiling.src/client/world.tskeeps itsEffecttype and its two-second cutoff unchanged;scene.ts,hud.tsandmain.tsread the ring by index with no copy, andsrc/rl/arena.tsresets it per step in a walk of the live events rather than of the capacity.replay.test.ts's golden digests are byte-identical — effects were never folded into them and the prune drops exactly what it dropped before.frames.test.tsnow captures the effect ring's two stores beside the scene's pool and compares all four by identity after two hundred frames;src/engine/effects.test.tsis the module's own, including the 5,000-event flood and the two-frame identity claim.boundary.test.ts,purity.test.tsandguide.test.tsstay green with no rule loosened — the door's surface list,GUIDE.md's new section and the README's engine list all gained the six names, andFloat64Arrayjoined the guide's list of platform words.What I chose not to do: the range's tracer stays as it is. It is one preallocated
Trailobject written over on each shot with one shot in the air at a time, so it already allocates nothing and a one-slot ring would be more machinery than the struct it replaces; I have not opened an issue for it because my answer is that it should not move. The buffer also stays dumb, as asked: no ids, no priorities, no callbacks.🤖 Generated with Claude Code
Closes #67