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:
+28
-3
@@ -37,6 +37,10 @@ export interface PlaneState {
|
||||
health: number;
|
||||
// Sim-clock epoch ms when an active repair finishes (v0.39.0, ISS-069); null when not in repair.
|
||||
repairUntilSimTime: number | null;
|
||||
// v1.4.0 (ISS-112): owner info — the snapshot carries ALL planes (shared world), so every row
|
||||
// names its owner (players.id + username) and the panes can tell whose plane is whose.
|
||||
ownerId: string;
|
||||
ownerName: string;
|
||||
}
|
||||
|
||||
// Parse the planes.queue JSON column into an ICAO list (v0.25.0). Guards against
|
||||
@@ -88,6 +92,9 @@ export interface PackageState {
|
||||
// Wall-clock expiry of an OPEN job (v0.65.0) — createdRealMs + JOB_LIFETIME_MS, shown as a live
|
||||
// countdown in the right pane; null for every non-open status (loaded cargo still uses deadlineSimTime).
|
||||
expiresRealMs: number | null;
|
||||
// v1.4.0 (ISS-112): carrier name — players.username of planes.owner_id via planeId, so an
|
||||
// assigned package shows WHO is carrying it in the shared world; null when unassigned.
|
||||
carrierName: string | null;
|
||||
}
|
||||
|
||||
export interface StateSnapshot {
|
||||
@@ -115,19 +122,25 @@ export function sseFrame(snapshot: StateSnapshot): StateSnapshot {
|
||||
}
|
||||
|
||||
// v1.3.0 (ISS-110): the snapshot's `account` field is the ACTING SESSION player's row — same
|
||||
// shape as before ({cash, reputation}), sourced per-player. Callers without a session context
|
||||
// (the shared SSE broadcast) pass LOCAL_PLAYER.id until per-player frames land (#112).
|
||||
// shape as before ({cash, reputation}), sourced per-player. v1.4.0 (ISS-112): this IS the shared
|
||||
// projection for every surface that serves state — GET /api/state, action responses, the SSE
|
||||
// connect frame and each tick's broadcast all call it with a player id (the broadcast projects
|
||||
// once PER CONNECTED SESSION), so no two surfaces can drift. Planes are ALL planes in the world,
|
||||
// each carrying owner info; packages carry carrierName when assigned to a plane.
|
||||
export async function getStateSnapshot(playerId: string): Promise<StateSnapshot> {
|
||||
const inst = await getGameDb();
|
||||
const { db } = inst;
|
||||
// NOTE: use plain select(), not the relational db.query.* API — in drizzle-orm
|
||||
// 0.45.x the sql-js driver drops the result mapper for relational queries and
|
||||
// returns raw rows keyed by SQL column names (snake_case, no type decoding).
|
||||
const [player, simRows, planeRows, packageRows] = await Promise.all([
|
||||
const [player, simRows, planeRows, packageRows, playerRows] = await Promise.all([
|
||||
getPlayer(inst, playerId),
|
||||
db.select().from(schema.simState),
|
||||
db.select().from(schema.planes),
|
||||
db.select().from(schema.packages),
|
||||
// v1.4.0 (ISS-112): one players read serves both owner joins — plane rows via ownerId and
|
||||
// package carrier names via planeId → planes.owner_id → players.username.
|
||||
db.select().from(schema.players),
|
||||
]);
|
||||
const simRow = simRows[0];
|
||||
if (!simRow) throw new Error('game state missing sim_state row');
|
||||
@@ -148,6 +161,11 @@ export async function getStateSnapshot(playerId: string): Promise<StateSnapshot>
|
||||
// body entirely, so clients never see jobs they could not dispatch.
|
||||
const pool = poolAirports(planeRows);
|
||||
|
||||
// v1.4.0 (ISS-112): owner joins — every plane row names its owner; a package's carrier is the
|
||||
// username of ITS PLANE'S owner (planeId → planes.owner_id → players.username), null when unassigned.
|
||||
const usernameByPlayerId = new Map(playerRows.map((pl) => [pl.id, pl.username]));
|
||||
const planeById = new Map(planeRows.map((p) => [p.id, p]));
|
||||
|
||||
// Analytic movement (v0.7.0): a flying plane's stored lat/lon/fuel are its
|
||||
// departure values — live position and fuel are pure functions of the clock.
|
||||
const livePlane = (p: schema.PlaneRow) => {
|
||||
@@ -196,6 +214,8 @@ export async function getStateSnapshot(playerId: string): Promise<StateSnapshot>
|
||||
queue: parseIcaoQueue(p.queue),
|
||||
health: p.health, // v0.39.0 (ISS-069) — stored value; wear accumulates at landing only
|
||||
repairUntilSimTime: p.repairUntilSimTime,
|
||||
ownerId: p.ownerId, // v1.4.0 (ISS-112)
|
||||
ownerName: usernameByPlayerId.get(p.ownerId) ?? p.ownerId, // missing players row → raw id, never a crash
|
||||
};
|
||||
}),
|
||||
packages: packageRows.filter((p) => p.status !== 'open' || pool.has(p.originIcao)).map((p) => ({
|
||||
@@ -215,6 +235,11 @@ export async function getStateSnapshot(playerId: string): Promise<StateSnapshot>
|
||||
planeId: p.planeId,
|
||||
createdSimTime: p.createdSimTime,
|
||||
expiresRealMs: p.status === 'open' ? p.createdRealMs + JOB_LIFETIME_MS : null,
|
||||
// v1.4.0 (ISS-112): carrier = the CARRYING PLANE's owner — unassigned packages stay null.
|
||||
carrierName: (() => {
|
||||
const plane = p.planeId ? planeById.get(p.planeId) : undefined;
|
||||
return plane ? usernameByPlayerId.get(plane.ownerId) ?? plane.ownerId : null;
|
||||
})(),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user