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:
2026-09-18 11:02:56 +00:00
parent 0208673da7
commit ab2e9fa54a
49 changed files with 2424 additions and 1409 deletions
+5 -19
View File
@@ -20,13 +20,13 @@ export const MAX_ZOOM = 11;
const AIRPORTS_SOURCE_ID = 'airports';
const AIRPORTS_LAYER_ID = 'airports-circles';
// Dynamic game-state layers (v0.11.0): planes as circles, active contract routes as lines.
// Dynamic game-state layers (v0.11.0, package model v0.12.0): planes as circles,
// each FLYING plane's current leg (origin → dest_icao) as a line.
const PLANES_SOURCE_ID = 'planes';
const PLANES_GROUNDED_LAYER_ID = 'planes-grounded';
const PLANES_FLYING_LAYER_ID = 'planes-flying';
const ROUTES_SOURCE_ID = 'routes';
const ROUTES_PENDING_LAYER_ID = 'routes-pending';
const ROUTES_INFLIGHT_LAYER_ID = 'routes-inflight';
const ROUTES_LEGS_LAYER_ID = 'routes-legs';
function escapeHtml(s: string): string {
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
@@ -198,25 +198,11 @@ export default function FlightMap({ state }: MapProps) {
data: routesGeoJson(stateRef.current, airportsIndexRef.current ?? new Map()),
});
}
if (!map.getLayer(ROUTES_PENDING_LAYER_ID)) {
if (!map.getLayer(ROUTES_LEGS_LAYER_ID)) {
map.addLayer({
id: ROUTES_PENDING_LAYER_ID,
id: ROUTES_LEGS_LAYER_ID,
type: 'line',
source: ROUTES_SOURCE_ID,
filter: ['==', ['get', 'kind'], 'pending'],
paint: {
'line-color': '#94a3b8', // muted slate — accepted but not yet dispatched
'line-width': 2,
'line-dasharray': [2, 2],
},
});
}
if (!map.getLayer(ROUTES_INFLIGHT_LAYER_ID)) {
map.addLayer({
id: ROUTES_INFLIGHT_LAYER_ID,
type: 'line',
source: ROUTES_SOURCE_ID,
filter: ['==', ['get', 'kind'], 'inflight'],
paint: {
'line-color': '#f59e0b', // bright amber — actively flying the leg
'line-width': 3,