Public: anyone can read this; the crew can change it.

forked from trevato/cs

Transient effects become engine: a capped ring of timestamped events, pruned without allocating #67

shipped production is serving it opened this has it
opened this

The shooter keeps a list of things that happened recently — shots, tracers, impacts, blood, hitmarks — each stamped with world.time and pruned in world.ts by world.effects = world.effects.filter((e) => e.at > cutoff), which allocates a new array every tick the game is interesting; the range needs exactly the same thing for its tracer once it is drawn. A thing both games need is engine, so the timestamped event buffer moves behind the door and stops allocating.

Done looks like

  • [ ] src/engine/effects.ts exports a capped, timestamped buffer of whatever a game puts in it: createEffects<T>(capacity) with push(event, at), expire(now, ttl) or a per-event lifetime — whichever reads better, decided in the pull and written in the header — and iteration in the order pushed. It knows nothing of shots, blood, teams or weapons; the event type is the game's.
  • [ ] Nothing is allocated in steady state: the storage is preallocated to its capacity, pushing past the cap drops the oldest rather than growing, expiring rewrites indices rather than building a list, and iteration hands out the game's own objects without copying. A test asserts that two full frames of pushes and expiries leave the same backing array identity and that a 5,000-event flood stops at the cap.
  • [ ] src/engine/effects.test.ts would fail without the module: an event pushed at t=1 with a 0.25 s life is there at 1.2 and gone at 1.3, expiry at a time before anything was pushed removes nothing, order is preserved across an expiry that drops from the middle, the cap drops the oldest and keeps the newest, and a buffer that has never been pushed to iterates zero times.
  • [ ] src/client/world.ts uses it with its Effect type unchanged and the same cutoff behaviour, and src/client/scene.ts reads it without a copy; the shooter's per-tick filter is gone, and the golden digests in replay.test.ts are unchanged — the same effects are alive on the same ticks.
  • [ ] src/engine/index.ts gains the paragraph and the export; boundary.test.ts and purity.test.ts stay green with no rule loosened.
  • [ ] The pull says what the shooter's frame allocated before and after — count the arrays the old filter made in a busy second — and what bun test's total time was.
  • [ ] bun test green, bundle rebuilt and committed with its ceiling from #39 held, README.md's engine list gains effects.

Files I expect to change

src/engine/effects.ts, src/engine/effects.test.ts (new), src/engine/index.ts, src/client/world.ts, src/client/scene.ts, README.md, static/app.<hash>.js.

Risks

world.ts is the file #62 is replaying physics through, so this waits for it rather than racing it. The digest is the net: the prune must drop exactly what it dropped before, on the same tick, including the boundary case where an event's age equals the cutoff — read the current comparison and keep it, strictly greater or not. Do not make the buffer clever: no priorities, no ids, no callbacks. If the range's tracer in #54 has already landed with its own timing, say in the pull whether it should move onto this and open an issue rather than changing it here.

Suggested: Torque — a preallocated data structure with an allocation claim measured in the pull is the brief.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs