v1.4.0: per-player snapshot + shared market view with owner info (ISS-112)
CI / build + unit + e2e (pull_request) Successful in 14m29s
CI / build + unit + e2e (pull_request) Successful in 14m29s
This commit is contained in:
+18
-12
@@ -1,7 +1,7 @@
|
||||
import { randomInt, randomUUID } from 'node:crypto';
|
||||
import { eq, sql } from 'drizzle-orm';
|
||||
import { getGameDb, persist, type GameDb } from '@/lib/db/client';
|
||||
import { LOCAL_PLAYER, getLocalPlayer, getPlayer, updatePlayerEconomy } from '@/lib/db/localPlayer';
|
||||
import { getLocalPlayer, getPlayer, updatePlayerEconomy } from '@/lib/db/localPlayer';
|
||||
import * as schema from '@/lib/db/schema';
|
||||
import { loadAirportsGeoJson } from '@/lib/airports';
|
||||
import { haversineKm, initialBearingDeg } from '@/lib/geo';
|
||||
@@ -9,7 +9,7 @@ import { weatherFor } from '@/lib/weather';
|
||||
import { FUEL_DISCOUNT_PER_REP, FUEL_PRICE_PER_L, MAX_FUEL_DISCOUNT, PLANE_TYPES, REPAIR_DURATION_MS, repRate, WEAR_PER_KM } from '@/lib/game/catalog';
|
||||
import { SPECIALS } from '@/lib/game/specials';
|
||||
import { getStateSnapshot, parseIcaoQueue, poolAirports, sseFrame } from '@/lib/game/state';
|
||||
import { broadcast } from '@/lib/sse';
|
||||
import { broadcast, subscribedPlayerIds } from '@/lib/sse';
|
||||
|
||||
import {
|
||||
JOB_LIFETIME_MS,
|
||||
@@ -385,9 +385,9 @@ export async function spawnPoolPackages(
|
||||
}));
|
||||
const rand = rng ?? mulberry32(randomInt(0, 2 ** 31));
|
||||
// v0.38.0 (ISS-072): spawned jobs carry a reputation-based job quality — read once per spawn batch.
|
||||
// ponytail: stays on the seeded 'local' row in v1.3.0 (ISS-110) — pools are SHARED world state
|
||||
// (packages have no owner), so there is no single owning player whose rep should set job quality;
|
||||
// per-player job quality / pools is a later multiplayer step (#112).
|
||||
// ponytail: stays on the seeded 'local' row — pools are SHARED world state (packages have no
|
||||
// owner), so there is no single owning player whose rep should set job quality; per-player job
|
||||
// quality is a later multiplayer step.
|
||||
const player = await getLocalPlayer(inst);
|
||||
|
||||
let n = 0;
|
||||
@@ -436,15 +436,21 @@ export async function spawnPoolPackages(
|
||||
// package history is dropped, everything else matches GET /api/state exactly. Failures are logged,
|
||||
// not fatal: polling still works either way. Background ticks (~1 s) cover action mutations for
|
||||
// other clients too.
|
||||
// v1.3.0 (ISS-110): the shared broadcast channel has no acting session — it carries the seeded
|
||||
// local player's account until per-player SSE frames land (#112). Session-scoped reads (/api/state,
|
||||
// /api/events connect frame, action responses) all source their own row.
|
||||
// v1.4.0 (ISS-112): per-player frames — the tick projects ONE snapshot per connected session's
|
||||
// player id (subscribedPlayerIds; N is tiny) and fans each frame out to that player's group only,
|
||||
// so every client sees its own account row in every push. Same shared projection as GET /api/state
|
||||
// (getStateSnapshot + sseFrame), so pushed frames can never drift from polled ones. A session whose
|
||||
// players row vanished mid-stream is skipped per-tick with a log — the others keep flowing.
|
||||
async function broadcastState(): Promise<void> {
|
||||
try {
|
||||
broadcast(JSON.stringify(sseFrame(await getStateSnapshot(LOCAL_PLAYER.id))));
|
||||
} catch (err) {
|
||||
console.error('flight: state broadcast failed', err);
|
||||
const frames = new Map<string, string>();
|
||||
for (const playerId of subscribedPlayerIds()) {
|
||||
try {
|
||||
frames.set(playerId, JSON.stringify(sseFrame(await getStateSnapshot(playerId))));
|
||||
} catch (err) {
|
||||
console.error('flight: state broadcast failed', err);
|
||||
}
|
||||
}
|
||||
if (frames.size > 0) broadcast(frames);
|
||||
}
|
||||
|
||||
export function tickNow(): Promise<schema.SimStateRow> {
|
||||
|
||||
Reference in New Issue
Block a user