v0.69.0: remove job ignore feature
CI / build + unit + e2e (pull_request) Successful in 14m31s

This commit is contained in:
2026-09-23 22:01:39 +00:00
parent 7d117f6809
commit d2defe4cd3
20 changed files with 48 additions and 171 deletions
+11 -7
View File
@@ -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) {