Measured first (live v0.58.0): each /api/events frame was compact JSON at ~597 KB, pushed exactly once per real second regardless of time scale (background tick is a fixed 1 s interval; ×1000 only advances the clock further per frame). 99.8 % of it was packages: 1,521 rows — terminal packages are never deleted from the DB (expired 564, delivered 528, ignored 386, abandoned 31) while only 12 were open. No component reads a terminal-status package: the market renders open, on-board cargo is loaded/expired.
Fix: new sseFrame(snapshot) in lib/game/state.ts projects to the client-rendered statuses (SSE_PACKAGE_STATUSES = open|loaded|expired). Used by both SSE paths — the broadcast in lib/sim/engine.ts and the initial frame in app/api/events/route.ts. Live payload: ~597 KB → ~10 KB per frame (≈60×), bounded forever by fleet/market size instead of growing with play time.
Deliberately unchanged:/api/state and action responses keep the FULL package set, so every e2e oracle asserting delivered/abandoned transitions via state lookups still works; the polling fallback stays a strict superset of what SSE pushes. No hash-skip (the snapshot's clockMs advances on every tick even while idle — frames are never equal), no throttling (already 1 Hz), no diff/patch engine.
Tests: unit +3 in new lib/game/state.test.ts; e2e/events.spec.ts now asserts no terminal status ever appears in a frame and frames stay < 200 KB. Local: tsc --noEmit clean, vitest 381/381 (baseline 378 + 3), playwright lists all 62 tests.
Closes #133
**Measured first (live v0.58.0):** each `/api/events` frame was compact JSON at **~597 KB**, pushed exactly **once per real second** regardless of time scale (background tick is a fixed 1 s interval; ×1000 only advances the clock further per frame). 99.8 % of it was `packages`: **1,521 rows** — terminal packages are never deleted from the DB (expired 564, delivered 528, ignored 386, abandoned 31) while only **12 were open**. No component reads a terminal-status package: the market renders `open`, on-board cargo is `loaded`/`expired`.
**Fix:** new `sseFrame(snapshot)` in `lib/game/state.ts` projects to the client-rendered statuses (`SSE_PACKAGE_STATUSES = open|loaded|expired`). Used by both SSE paths — the broadcast in `lib/sim/engine.ts` and the initial frame in `app/api/events/route.ts`. Live payload: **~597 KB → ~10 KB per frame (≈60×)**, bounded forever by fleet/market size instead of growing with play time.
**Deliberately unchanged:** `/api/state` and action responses keep the FULL package set, so every e2e oracle asserting `delivered`/`abandoned` transitions via state lookups still works; the polling fallback stays a strict superset of what SSE pushes. No hash-skip (the snapshot's `clockMs` advances on every tick even while idle — frames are never equal), no throttling (already 1 Hz), no diff/patch engine.
**Tests:** unit +3 in new `lib/game/state.test.ts`; `e2e/events.spec.ts` now asserts no terminal status ever appears in a frame and frames stay < 200 KB. Local: `tsc --noEmit` clean, vitest 381/381 (baseline 378 + 3), playwright lists all 62 tests.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #133
Measured first (live v0.58.0): each
/api/eventsframe was compact JSON at ~597 KB, pushed exactly once per real second regardless of time scale (background tick is a fixed 1 s interval; ×1000 only advances the clock further per frame). 99.8 % of it waspackages: 1,521 rows — terminal packages are never deleted from the DB (expired 564, delivered 528, ignored 386, abandoned 31) while only 12 were open. No component reads a terminal-status package: the market rendersopen, on-board cargo isloaded/expired.Fix: new
sseFrame(snapshot)inlib/game/state.tsprojects to the client-rendered statuses (SSE_PACKAGE_STATUSES = open|loaded|expired). Used by both SSE paths — the broadcast inlib/sim/engine.tsand the initial frame inapp/api/events/route.ts. Live payload: ~597 KB → ~10 KB per frame (≈60×), bounded forever by fleet/market size instead of growing with play time.Deliberately unchanged:
/api/stateand action responses keep the FULL package set, so every e2e oracle assertingdelivered/abandonedtransitions via state lookups still works; the polling fallback stays a strict superset of what SSE pushes. No hash-skip (the snapshot'sclockMsadvances on every tick even while idle — frames are never equal), no throttling (already 1 Hz), no diff/patch engine.Tests: unit +3 in new
lib/game/state.test.ts;e2e/events.spec.tsnow asserts no terminal status ever appears in a frame and frames stay < 200 KB. Local:tsc --noEmitclean, vitest 381/381 (baseline 378 + 3), playwright lists all 62 tests.