This commit is contained in:
+11
-7
@@ -1,5 +1,5 @@
|
||||
import { randomInt, randomUUID } from 'node:crypto';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { eq, sql } from 'drizzle-orm';
|
||||
import { getGameDb, persist, type GameDb } from '@/lib/db/client';
|
||||
import * as schema from '@/lib/db/schema';
|
||||
import { loadAirportsGeoJson } from '@/lib/airports';
|
||||
@@ -111,7 +111,7 @@ export async function tickOnce(inst: GameDb): Promise<schema.SimStateRow> {
|
||||
await finishRepairs(inst, clockMs);
|
||||
}
|
||||
// v0.65.0: market refresh runs on EVERY tick — frozen ones included (#135 follow-up carried over) —
|
||||
// because while grounded nothing else creates packages but removal paths (ignore) still run; without
|
||||
// because while grounded nothing else creates packages but removal paths (open-job expiry) still run; without
|
||||
// this a grounded fleet could drain its pools to zero and soft-lock forever. Open jobs age on the
|
||||
// WALL clock (JOB_LIFETIME_MS after createdRealMs), so even a fully paused world refreshes its board;
|
||||
// both passes are idempotent no-ops when satisfied, so advancing ticks behave exactly as before.
|
||||
@@ -147,9 +147,13 @@ export async function expireLoadedPackages(inst: GameDb, clockMs: number): Promi
|
||||
// v0.65.0: open jobs live on the WALL clock — JOB_LIFETIME_MS after createdRealMs they expire
|
||||
// regardless of time scale or fleet state, so a paused/frozen world still refreshes its board. Runs
|
||||
// every tick (~1 s), outside the advance gate. An expired job's slot respawns immediately (no
|
||||
// slotFreeAtRealMs is set — only TAKEN jobs start a refill cooldown). Returns the count flipped.
|
||||
// slotFreeAtRealMs is set — only TAKEN jobs start a refill cooldown). v0.69.0: legacy 'ignored' rows
|
||||
// (pre-v0.69.0 dismissals) are swept in too — each self-heals to 'expired' on the first tick past its
|
||||
// lifetime, and no new ones can ever be created. Returns the count flipped.
|
||||
export async function expireOpenPackages(inst: GameDb, nowMs: number): Promise<number> {
|
||||
const rows = await inst.db.select().from(schema.packages).where(eq(schema.packages.status, 'open'));
|
||||
// Raw SQL on purpose: 'ignored' is no longer in the status enum (v0.69.0) — it only exists as a
|
||||
// legacy value in pre-v0.69.0 databases, so the typed column can't express the query.
|
||||
const rows = await inst.db.select().from(schema.packages).where(sql`${schema.packages.status} IN ('open', 'ignored')`);
|
||||
let n = 0;
|
||||
for (const p of rows) {
|
||||
if (p.createdRealMs + JOB_LIFETIME_MS <= nowMs) {
|
||||
@@ -352,7 +356,7 @@ export async function deliverPackages(inst: GameDb, clockMs: number): Promise<nu
|
||||
// (v0.8.0). Every airport where a plane can take work — grounded there, flying TO it, or queued for
|
||||
// it (poolAirports in lib/game/state.ts) — keeps the POOL_BANDS composition of open packages: each
|
||||
// band is topped back up to its count using generatePoolPackage, so seeded and spawned packages are
|
||||
// indistinguishable. A TAKEN slot (loaded/ignored) stays empty for the band's refill delay after
|
||||
// indistinguishable. A TAKEN slot (loaded) stays empty for the band's refill delay after
|
||||
// slotFreeAtRealMs; expired/delivered/abandoned never block, so an untaken job's slot respawns on
|
||||
// the next tick. Orphaned pools (plane left without taking a job) simply expire out — no cleanup.
|
||||
// RNG and wall-clock now are injectable for deterministic tests. Returns the number created.
|
||||
@@ -385,8 +389,8 @@ export async function spawnPoolPackages(
|
||||
|
||||
let n = 0;
|
||||
for (const originIcao of pool) {
|
||||
// Open counts and refill cooldowns per band at this airport. Only open/loaded/ignored rows matter:
|
||||
// terminal statuses never block a slot, and loaded/ignored carry the slotFreeAtRealMs stamp.
|
||||
// Open counts and refill cooldowns per band at this airport. Only open/loaded rows matter:
|
||||
// terminal statuses never block a slot, and loaded carries the slotFreeAtRealMs stamp.
|
||||
const openByBand = [0, 0, 0, 0];
|
||||
let blockedUntil: number | null = null;
|
||||
for (const p of packageRows) {
|
||||
|
||||
Reference in New Issue
Block a user