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:
+29
-27
@@ -15,15 +15,18 @@ export interface PlaneState {
|
||||
maxFuel: number;
|
||||
locationIcao: string;
|
||||
locationName: string;
|
||||
contractId: string | null;
|
||||
// Current flight destination (v0.12.0) — set by fly, null when grounded. The
|
||||
// origin of the current leg is always locationIcao while flying.
|
||||
destIcao: string | null;
|
||||
destName: string | null;
|
||||
// Sim-clock departure time of the current flight (v0.8.0) — null when grounded,
|
||||
// set by dispatch while flying so clients can compute progress/ETA from the clock.
|
||||
// set by fly while flying so clients can compute progress/ETA from the clock.
|
||||
departureSimTime: number | null;
|
||||
lat: number;
|
||||
lon: number;
|
||||
}
|
||||
|
||||
export interface ContractState {
|
||||
export interface PackageState {
|
||||
id: string;
|
||||
originIcao: string;
|
||||
originName: string;
|
||||
@@ -33,7 +36,7 @@ export interface ContractState {
|
||||
cargoType: string;
|
||||
cargoCount: number;
|
||||
deadlineSimTime: number;
|
||||
status: 'open' | 'accepted' | 'completed' | 'expired';
|
||||
status: 'open' | 'loaded' | 'delivered' | 'expired' | 'abandoned';
|
||||
planeId: string | null;
|
||||
createdSimTime: number;
|
||||
}
|
||||
@@ -42,7 +45,7 @@ export interface StateSnapshot {
|
||||
account: { cash: number; reputation: number };
|
||||
simState: { clockMs: number; timeScale: number; paused: boolean };
|
||||
planes: PlaneState[];
|
||||
contracts: ContractState[];
|
||||
packages: PackageState[];
|
||||
// Market reference data (v0.10.0) — static per build, sent with every snapshot so
|
||||
// the client can compute buy/sell/refuel previews without a separate endpoint.
|
||||
catalog: CatalogEntry[];
|
||||
@@ -55,11 +58,11 @@ export async function getStateSnapshot(): Promise<StateSnapshot> {
|
||||
// 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 [accountRows, simRows, planeRows, contractRows] = await Promise.all([
|
||||
const [accountRows, simRows, planeRows, packageRows] = await Promise.all([
|
||||
db.select().from(schema.account),
|
||||
db.select().from(schema.simState),
|
||||
db.select().from(schema.planes),
|
||||
db.select().from(schema.contracts),
|
||||
db.select().from(schema.packages),
|
||||
]);
|
||||
const accountRow = accountRows[0];
|
||||
const simRow = simRows[0];
|
||||
@@ -72,14 +75,12 @@ export async function getStateSnapshot(): Promise<StateSnapshot> {
|
||||
|
||||
// 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 contractById = new Map(contractRows.map((c) => [c.id, c]));
|
||||
const livePlane = (p: schema.PlaneRow) => {
|
||||
if (p.status !== 'flying' || p.departureSimTime == null || !p.contractId) return null;
|
||||
const contract = contractById.get(p.contractId);
|
||||
if (p.status !== 'flying' || p.departureSimTime == null || !p.destIcao) return null;
|
||||
const type = PLANE_TYPES[p.model];
|
||||
const from = contract ? coordsByIcao.get(contract.originIcao) : undefined;
|
||||
const to = contract ? coordsByIcao.get(contract.destIcao) : undefined;
|
||||
if (!contract || !type || !from || !to) return null;
|
||||
const from = coordsByIcao.get(p.locationIcao);
|
||||
const to = coordsByIcao.get(p.destIcao);
|
||||
if (!type || !from || !to) return null;
|
||||
const distanceKm = haversineKm(from.lat, from.lon, to.lat, to.lon);
|
||||
const progress = progressAt(p.departureSimTime, distanceKm, type.speedKmh, simRow.clockMs);
|
||||
const pos = positionAt(from.lat, from.lon, to.lat, to.lon, progress);
|
||||
@@ -102,25 +103,26 @@ export async function getStateSnapshot(): Promise<StateSnapshot> {
|
||||
maxFuel: p.maxFuel,
|
||||
locationIcao: p.locationIcao,
|
||||
locationName: nameOf(p.locationIcao),
|
||||
contractId: p.contractId,
|
||||
destIcao: p.destIcao,
|
||||
destName: p.destIcao ? nameOf(p.destIcao) : null,
|
||||
departureSimTime: p.departureSimTime,
|
||||
lat: live?.lat ?? p.lat,
|
||||
lon: live?.lon ?? p.lon,
|
||||
};
|
||||
}),
|
||||
contracts: contractRows.map((c) => ({
|
||||
id: c.id,
|
||||
originIcao: c.originIcao,
|
||||
originName: nameOf(c.originIcao),
|
||||
destIcao: c.destIcao,
|
||||
destName: nameOf(c.destIcao),
|
||||
reward: c.reward,
|
||||
cargoType: c.cargoType,
|
||||
cargoCount: c.cargoCount,
|
||||
deadlineSimTime: c.deadlineSimTime,
|
||||
status: c.status,
|
||||
planeId: c.planeId,
|
||||
createdSimTime: c.createdSimTime,
|
||||
packages: packageRows.map((p) => ({
|
||||
id: p.id,
|
||||
originIcao: p.originIcao,
|
||||
originName: nameOf(p.originIcao),
|
||||
destIcao: p.destIcao,
|
||||
destName: nameOf(p.destIcao),
|
||||
reward: p.reward,
|
||||
cargoType: p.cargoType,
|
||||
cargoCount: p.cargoCount,
|
||||
deadlineSimTime: p.deadlineSimTime,
|
||||
status: p.status,
|
||||
planeId: p.planeId,
|
||||
createdSimTime: p.createdSimTime,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user