feat: v0.12.0 — contracts → packages: load/fly/abandon gameplay (ATR 72-600 capacity 6, manual flight to any airport, time-scaled abandon penalty 15–60%), one-time DB migration preserving wallet/clock; new endpoints /api/packages/:id/load|abandon + /api/planes/:planeId/fly
This commit is contained in:
+21
-21
@@ -23,7 +23,7 @@ export function mulberry32(seed: number): Rng {
|
||||
a |= 0;
|
||||
a = (a + 0x6d2b79f5) | 0;
|
||||
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
||||
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
||||
t = (t + Math.imul(t ^ (a >>> 7), 61 | t)) ^ t;
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
}
|
||||
@@ -35,12 +35,12 @@ export interface SeedPlane {
|
||||
fuel: number;
|
||||
maxFuel: number;
|
||||
locationIcao: string;
|
||||
contractId: null;
|
||||
destIcao: null;
|
||||
lat: number;
|
||||
lon: number;
|
||||
}
|
||||
|
||||
export interface SeedContract {
|
||||
export interface SeedPackage {
|
||||
id: string;
|
||||
originIcao: string;
|
||||
destIcao: string;
|
||||
@@ -55,7 +55,7 @@ export interface SeedContract {
|
||||
|
||||
export interface SeedData {
|
||||
plane: SeedPlane;
|
||||
contracts: SeedContract[];
|
||||
packages: SeedPackage[];
|
||||
account: { cash: number; reputation: number };
|
||||
simState: { clockMs: number; timeScale: number; paused: false; lastRealTickMs: number };
|
||||
}
|
||||
@@ -70,19 +70,19 @@ export const STARTING_REPUTATION = 10;
|
||||
// failing first run. (EDDF = Frankfurt; EDDM is Munich — don't mix them up.)
|
||||
export const HUB_ICAO = 'EDDF'; // Frankfurt Main
|
||||
|
||||
export const CONTRACT_COUNT_MIN = 8;
|
||||
export const CONTRACT_COUNT_MAX = 12; // inclusive
|
||||
export const PACKAGE_COUNT_MIN = 8;
|
||||
export const PACKAGE_COUNT_MAX = 12; // inclusive
|
||||
|
||||
// Market target (v0.7.0): the sim engine tops open contracts back up to this
|
||||
// Market target (v0.7.0): the sim engine tops open packages back up to this
|
||||
// count after expiries, so the job board never runs dry.
|
||||
export const MARKET_TARGET_OPEN_CONTRACTS = 10;
|
||||
export const MARKET_TARGET_OPEN_PACKAGES = 10;
|
||||
|
||||
// Reward formula (v0.6.0 — deliberately simple; real revenue/cost modeling is a
|
||||
// later increment): reward = BASE_REWARD + REWARD_PER_KM * great-circle km.
|
||||
export const BASE_REWARD = 500;
|
||||
export const REWARD_PER_KM = 2.5;
|
||||
|
||||
// Contract deadlines: now + a plausible game-time window of 6–72 hours.
|
||||
// Package deadlines: now + a plausible game-time window of 6–72 hours.
|
||||
const DEADLINE_MIN_HOURS = 6;
|
||||
const DEADLINE_MAX_HOURS = 72;
|
||||
|
||||
@@ -92,16 +92,16 @@ export function rewardForDistanceKm(km: number): number {
|
||||
return Math.round(BASE_REWARD + REWARD_PER_KM * km);
|
||||
}
|
||||
|
||||
// Shared contract generator, used by BOTH the first-run seed and the runtime
|
||||
// market spawn (v0.7.0) so spawned contracts match seeded ones exactly: random
|
||||
// Shared package generator, used by BOTH the first-run seed and the runtime
|
||||
// market spawn (v0.7.0) so spawned packages match seeded ones exactly: random
|
||||
// distinct airports from the dataset, distance-based reward, cargo flavor,
|
||||
// deadline now + 6–72h game time (or now + deadlineOffsetMs when given — used by
|
||||
// the e2e test hook to force a near-term expiry). The RNG call order per contract
|
||||
// the e2e test hook to force a near-term expiry). The RNG call order per package
|
||||
// is fixed: origin, dest, cargoType, cargoCount, [deadline window].
|
||||
// v0.8.0: originIcao/destIcao optionally FORCE an endpoint (spawn coverage rule +
|
||||
// the e2e create-contract hook); a forced endpoint consumes no RNG draws, so
|
||||
// the e2e create-package hook); a forced endpoint consumes no RNG draws, so
|
||||
// callers that omit both get byte-identical behavior to before.
|
||||
export function generateContract(input: {
|
||||
export function generatePackage(input: {
|
||||
id: string;
|
||||
airports: SeedAirport[];
|
||||
rng: Rng;
|
||||
@@ -109,9 +109,9 @@ export function generateContract(input: {
|
||||
deadlineOffsetMs?: number;
|
||||
originIcao?: string;
|
||||
destIcao?: string;
|
||||
}): SeedContract {
|
||||
}): SeedPackage {
|
||||
const { airports, rng, nowMs } = input;
|
||||
if (airports.length < 2) throw new Error('contract generation needs at least two airports');
|
||||
if (airports.length < 2) throw new Error('package generation needs at least two airports');
|
||||
|
||||
let oi: number;
|
||||
if (input.originIcao !== undefined) {
|
||||
@@ -179,22 +179,22 @@ export function buildSeed(input: {
|
||||
fuel: type.maxFuel, // starts full
|
||||
maxFuel: type.maxFuel,
|
||||
locationIcao: hub.icao,
|
||||
contractId: null,
|
||||
destIcao: null,
|
||||
lat: hub.lat,
|
||||
lon: hub.lon,
|
||||
};
|
||||
|
||||
const count = CONTRACT_COUNT_MIN + Math.floor(rng() * (CONTRACT_COUNT_MAX - CONTRACT_COUNT_MIN + 1));
|
||||
const count = PACKAGE_COUNT_MIN + Math.floor(rng() * (PACKAGE_COUNT_MAX - PACKAGE_COUNT_MIN + 1));
|
||||
// Stable index-based ids for the seed set; runtime-generated entities must use
|
||||
// random UUIDs to avoid colliding with these.
|
||||
const contracts: SeedContract[] = [];
|
||||
const packages: SeedPackage[] = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
contracts.push(generateContract({ id: `contract-${i + 1}`, airports, rng, nowMs }));
|
||||
packages.push(generatePackage({ id: `package-${i + 1}`, airports, rng, nowMs }));
|
||||
}
|
||||
|
||||
return {
|
||||
plane,
|
||||
contracts,
|
||||
packages,
|
||||
account: { cash: STARTING_CASH, reputation: STARTING_REPUTATION },
|
||||
// lastRealTickMs starts at the seed moment so the first tick-on-read advances
|
||||
// by ~0 instead of treating "time since install" as elapsed play time.
|
||||
|
||||
Reference in New Issue
Block a user