import type { Agent, Delta, Snapshot } from './generated/city_pb'; interface Sample { previous: Agent; current: Agent; fromTick: number; toTick: number } /** Keep only subscribed entities. Time is aligned to the latest server tick. */ export class EntityState { private entities = new Map(); private tick = 0; private received = 0; snapshot(snapshot: Snapshot, now = performance.now()) { this.entities.clear(); this.tick = Number(snapshot.tick); this.received = now; for (const a of snapshot.entities) this.entities.set(a.id, { previous: a, current: a, fromTick: this.tick, toTick: this.tick }); } delta(delta: Delta, now = performance.now()) { const tick = Number(delta.tick); if (tick < this.tick) return; this.tick = tick; this.received = now; for (const id of delta.despawned) this.entities.delete(id); for (const a of delta.spawned) this.entities.set(a.id, { previous: a, current: a, fromTick: tick, toTick: tick }); for (const a of delta.changed) { const old = this.entities.get(a.id); if (old) this.entities.set(a.id, { previous: old.current, current: a, fromTick: old.toTick, toTick: tick }); } } positions(now = performance.now()): { id: number; x: number; y: number }[] { const renderTick = this.tick + (now - this.received - 200) / 100; return [...this.entities].map(([id, s]) => { const t = s.toTick === s.fromTick ? 1 : Math.max(0, Math.min(1, (renderTick - s.fromTick) / (s.toTick - s.fromTick))); return { id, x: (s.previous.x + (s.current.x - s.previous.x) * t) / 100, y: (s.previous.y + (s.current.y - s.previous.y) * t) / 100 }; }); } clear() { this.entities.clear(); } get size() { return this.entities.size; } }