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:
+14
-18
@@ -1,9 +1,10 @@
|
||||
import type { StateSnapshot } from './game/state';
|
||||
import { formatFuel } from './format';
|
||||
|
||||
// Pure GeoJSON builders for the map's dynamic layers (v0.11.0): one Point feature
|
||||
// per plane, one LineString per accepted/in-flight contract. Kept out of
|
||||
// components/Map.tsx so the classification logic is unit-testable without a DOM.
|
||||
// Pure GeoJSON builders for the map's dynamic layers (v0.11.0, package model
|
||||
// v0.12.0): one Point feature per plane, one LineString per FLYING plane's
|
||||
// current leg (origin → dest_icao). Kept out of components/Map.tsx so the
|
||||
// classification logic is unit-testable without a DOM.
|
||||
|
||||
export interface PlaneFeatureProps {
|
||||
id: string;
|
||||
@@ -15,8 +16,7 @@ export interface PlaneFeatureProps {
|
||||
}
|
||||
|
||||
export interface RouteFeatureProps {
|
||||
contractId: string;
|
||||
kind: 'pending' | 'inflight';
|
||||
planeId: string;
|
||||
}
|
||||
|
||||
export interface MapPointFeature {
|
||||
@@ -41,12 +41,10 @@ const EMPTY_LINES: FeatureCollection<MapLineFeature> = { type: 'FeatureCollectio
|
||||
|
||||
export function planesGeoJson(state: StateSnapshot | null): FeatureCollection<MapPointFeature> {
|
||||
if (!state) return EMPTY_POINTS;
|
||||
const contractById = new Map(state.contracts.map((c) => [c.id, c]));
|
||||
// Catalog display name ("ATR 72-600") for the popup — raw model id as fallback.
|
||||
const labelByModel = new Map(state.catalog.map((e) => [e.model, e.label]));
|
||||
const features: MapPointFeature[] = state.planes.map((p) => {
|
||||
const flying = p.status === 'flying';
|
||||
const contract = p.contractId ? contractById.get(p.contractId) : undefined;
|
||||
return {
|
||||
type: 'Feature',
|
||||
geometry: { type: 'Point', coordinates: [p.lon, p.lat] },
|
||||
@@ -55,7 +53,7 @@ export function planesGeoJson(state: StateSnapshot | null): FeatureCollection<Ma
|
||||
modelLabel: labelByModel.get(p.model) ?? p.model,
|
||||
status: p.status,
|
||||
fuelText: formatFuel(p.fuel, p.maxFuel),
|
||||
routeText: flying && contract ? `${contract.originName} → ${contract.destName}` : '',
|
||||
routeText: flying && p.destName ? `${p.locationName} → ${p.destName}` : '',
|
||||
flying,
|
||||
},
|
||||
};
|
||||
@@ -69,22 +67,20 @@ export interface AirportCoord {
|
||||
lon: number;
|
||||
}
|
||||
|
||||
// One LineString per contract that is accepted but not yet dispatched ('pending')
|
||||
// or already flying ('inflight' — planeId set and that plane's status is 'flying').
|
||||
// Open/completed/expired contracts draw nothing.
|
||||
// One LineString per FLYING plane — its current leg from locationIcao to dest_icao.
|
||||
// Grounded planes draw nothing; a flying plane missing either endpoint from the
|
||||
// dataset is skipped, never guessed.
|
||||
export function routesGeoJson(
|
||||
state: StateSnapshot | null,
|
||||
airportsByIcao: ReadonlyMap<string, AirportCoord>,
|
||||
): FeatureCollection<MapLineFeature> {
|
||||
if (!state) return EMPTY_LINES;
|
||||
const planeById = new Map(state.planes.map((p) => [p.id, p]));
|
||||
const features: MapLineFeature[] = [];
|
||||
for (const c of state.contracts) {
|
||||
if (c.status !== 'accepted') continue;
|
||||
const from = airportsByIcao.get(c.originIcao);
|
||||
const to = airportsByIcao.get(c.destIcao);
|
||||
for (const p of state.planes) {
|
||||
if (p.status !== 'flying' || !p.destIcao) continue;
|
||||
const from = airportsByIcao.get(p.locationIcao);
|
||||
const to = airportsByIcao.get(p.destIcao);
|
||||
if (!from || !to) continue; // endpoint missing from the dataset — skip, don't guess
|
||||
const plane = c.planeId ? planeById.get(c.planeId) : undefined;
|
||||
features.push({
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
@@ -94,7 +90,7 @@ export function routesGeoJson(
|
||||
[to.lon, to.lat],
|
||||
],
|
||||
},
|
||||
properties: { contractId: c.id, kind: plane?.status === 'flying' ? 'inflight' : 'pending' },
|
||||
properties: { planeId: p.id },
|
||||
});
|
||||
}
|
||||
return { type: 'FeatureCollection', features };
|
||||
|
||||
Reference in New Issue
Block a user