feat: v0.7.0 — server-authoritative sim engine: tick-on-read clock (time-scale + pause via POST /api/sim), contract expiry + market spawn, analytic plane movement/finalization

This commit is contained in:
2026-09-17 12:38:52 +00:00
parent 7fdc43bc7e
commit 96835e935d
22 changed files with 1393 additions and 55 deletions
+62
View File
@@ -1,10 +1,47 @@
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import initSqlJs from 'sql.js';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { openGameDb, persist } from './client';
import * as schema from './schema';
// The v0.6.0 DDL — used to build a "legacy" DB file for the migration test.
const LEGACY_DDL = `
CREATE TABLE planes (
id TEXT PRIMARY KEY NOT NULL,
model TEXT NOT NULL,
status TEXT NOT NULL,
fuel REAL NOT NULL,
max_fuel REAL NOT NULL,
location_icao TEXT NOT NULL,
contract_id TEXT,
lat REAL NOT NULL,
lon REAL NOT NULL
);
CREATE TABLE contracts (
id TEXT PRIMARY KEY NOT NULL,
origin_icao TEXT NOT NULL,
dest_icao TEXT NOT NULL,
reward REAL NOT NULL,
cargo_type TEXT NOT NULL,
cargo_count INTEGER NOT NULL,
deadline_sim_time INTEGER NOT NULL,
status TEXT NOT NULL,
plane_id TEXT,
created_sim_time INTEGER NOT NULL
);
CREATE TABLE account (
cash REAL NOT NULL,
reputation REAL NOT NULL
);
CREATE TABLE sim_state (
clock_ms INTEGER NOT NULL,
time_scale REAL NOT NULL,
paused INTEGER NOT NULL
);
`;
let dir: string;
beforeAll(async () => {
@@ -66,4 +103,29 @@ describe('openGameDb', () => {
const reloaded = await openGameDb(file);
expect((await reloaded.db.select().from(schema.account))[0]?.cash).toBe(12345);
});
it('migrates a pre-v0.7.0 database: adds new columns and fixes last_real_tick_ms', async () => {
// Build a legacy file with the v0.6.0 schema (no departure_sim_time / last_real_tick_ms).
const SQL = await initSqlJs();
const raw = new SQL.Database();
raw.exec(LEGACY_DDL);
raw.exec(`INSERT INTO planes VALUES ('plane-starter', 'atr72', 'available', 6300, 6300, 'EDDF', NULL, 50.03, 8.57)`);
raw.exec(`INSERT INTO account VALUES (20000, 10)`);
raw.exec('INSERT INTO sim_state VALUES (1750000000000, 1, 0)');
const file = path.join(dir, 'legacy.db');
await fs.writeFile(file, Buffer.from(raw.export()));
const inst = await openGameDb(file);
// Not re-seeded (account row exists)…
expect((await inst.db.select().from(schema.account))[0]?.cash).toBe(20000);
// …but the new columns exist and are usable…
const plane = (await inst.db.select().from(schema.planes))[0];
expect(plane?.departureSimTime ?? null).toBeNull();
const sim = (await inst.db.select().from(schema.simState))[0];
expect(sim?.clockMs).toBe(1_750_000_000_000);
// …and the ALTER-default 0 was fixed up to a real timestamp so the first
// tick-on-read resumes cleanly instead of taking one clamped catch-up jump.
expect(sim?.lastRealTickMs).toBeGreaterThan(0);
});
});