Show leg distance in km on job list and on-board contracts (ISS-095, v0.48.0)
CI / build + unit + e2e (pull_request) Successful in 10m21s
CI / build + unit + e2e (pull_request) Successful in 10m21s
This commit is contained in:
@@ -36,9 +36,10 @@ const basePackage = (id: string): PackageState => ({
|
||||
reward: 1234.5,
|
||||
cargoType: 'mail',
|
||||
cargoCount: 2,
|
||||
weightKg: 600,
|
||||
special: null, // v0.37.0 (ISS-073) — no special status by default in fixtures
|
||||
deadlineSimTime: 0,
|
||||
weightKg: 600,
|
||||
special: null, // v0.37.0 (ISS-073) — no special status by default in fixtures
|
||||
distanceKm: 1839, // v0.48.0 (ISS-095) — EDDF→LEMD great-circle distance
|
||||
deadlineSimTime: 0,
|
||||
status: 'open',
|
||||
planeId: null,
|
||||
createdSimTime: 0,
|
||||
|
||||
+9
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { formatCargo, formatCash, formatDeadlineRelative, formatDuration, formatFuel, formatSimClock } from './format';
|
||||
import { formatCargo, formatCash, formatDeadlineRelative, formatDuration, formatFuel, formatKm, formatSimClock } from './format';
|
||||
|
||||
describe('formatCash', () => {
|
||||
it('formats EUR with thousands separators and two decimals', () => {
|
||||
@@ -30,6 +30,14 @@ describe('formatCargo', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatKm', () => {
|
||||
it('rounds to integer km with no space before "km" (v0.48.0, ISS-095)', () => {
|
||||
expect(formatKm(554)).toBe('554km');
|
||||
expect(formatKm(214.6)).toBe('215km');
|
||||
expect(formatKm(0)).toBe('0km');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatSimClock', () => {
|
||||
it('formats epoch ms as a UTC wall-clock string with seconds (deterministic across timezones)', () => {
|
||||
expect(formatSimClock(Date.UTC(2026, 8, 17, 14, 5))).toBe('2026-09-17 14:05:00 UTC');
|
||||
|
||||
@@ -16,6 +16,11 @@ export function formatCargo(cargoType: string, weightKg: number): string {
|
||||
return `${cargoType} ${(weightKg / 1000).toFixed(1)}t`;
|
||||
}
|
||||
|
||||
// Leg distance for contract rows (v0.48.0, ISS-095): "554km" — integer km, no space before "km".
|
||||
export function formatKm(km: number): string {
|
||||
return `${Math.round(km)}km`;
|
||||
}
|
||||
|
||||
// Special-status badge label (v0.37.0, ISS-073): "priority" → "Priority".
|
||||
export function formatSpecialLabel(special: string): string {
|
||||
return special.charAt(0).toUpperCase() + special.slice(1);
|
||||
|
||||
@@ -62,6 +62,8 @@ export interface PackageState {
|
||||
weightKg: number;
|
||||
// Special status (v0.37.0, ISS-073) — key into lib/game/specials.ts SPECIALS; null = none.
|
||||
special: string | null;
|
||||
// Great-circle leg distance in km (v0.48.0, ISS-095) — computed from airport coords, not stored.
|
||||
distanceKm: number;
|
||||
deadlineSimTime: number;
|
||||
// 'ignored' (v0.29.0, ISS-047 part B) = a dismissed open job — terminal, leaves the market.
|
||||
status: 'open' | 'loaded' | 'delivered' | 'expired' | 'abandoned' | 'ignored';
|
||||
@@ -100,6 +102,12 @@ export async function getStateSnapshot(): Promise<StateSnapshot> {
|
||||
const nameByIcao = new Map(fc.features.map((f) => [f.properties.icao, f.properties.name]));
|
||||
const nameOf = (icao: string) => nameByIcao.get(icao) ?? icao;
|
||||
const coordsByIcao = new Map(fc.features.map((f) => [f.properties.icao, f.properties]));
|
||||
// v0.48.0 (ISS-095): leg distance for the UI — same haversine as live-plane math; 0 if a coord is missing.
|
||||
const legDistanceKm = (originIcao: string, destIcao: string) => {
|
||||
const from = coordsByIcao.get(originIcao);
|
||||
const to = coordsByIcao.get(destIcao);
|
||||
return from && to ? haversineKm(from.lat, from.lon, to.lat, to.lon) : 0;
|
||||
};
|
||||
|
||||
// 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.
|
||||
@@ -162,6 +170,7 @@ export async function getStateSnapshot(): Promise<StateSnapshot> {
|
||||
cargoCount: p.cargoCount,
|
||||
weightKg: p.weightKg,
|
||||
special: p.special,
|
||||
distanceKm: legDistanceKm(p.originIcao, p.destIcao), // v0.48.0 (ISS-095)
|
||||
deadlineSimTime: p.deadlineSimTime,
|
||||
status: p.status,
|
||||
planeId: p.planeId,
|
||||
|
||||
@@ -136,6 +136,7 @@ describe('OpenAPI spec', () => {
|
||||
'deadlineSimTime',
|
||||
'destIcao',
|
||||
'destName',
|
||||
'distanceKm', // v0.48.0 (ISS-095) — great-circle leg distance in km
|
||||
'id',
|
||||
'originIcao',
|
||||
'originName',
|
||||
|
||||
@@ -633,6 +633,7 @@ export const openApiSpec = {
|
||||
cargoCount: { type: 'integer' },
|
||||
weightKg: { type: 'number', description: 'Package weight in kg (v0.27.0) — drives load validation and the per-kg payout part.' },
|
||||
special: { type: ['string', 'null'], description: 'Special status from the specials registry (v0.37.0); null = none.' },
|
||||
distanceKm: { type: 'number', description: 'Great-circle leg distance in km (v0.48.0) — computed server-side from airport coordinates, not stored.' },
|
||||
deadlineSimTime: { type: 'integer', description: 'Sim-clock epoch ms (game time)' },
|
||||
// 'expired' = an open OR loaded package whose deadline passed on a tick;
|
||||
// loaded expired packages keep their slot, deliver no reward, abandon free.
|
||||
@@ -651,6 +652,7 @@ export const openApiSpec = {
|
||||
'cargoType',
|
||||
'cargoCount',
|
||||
'weightKg', // v0.27.0 (ISS-045) — package weight in kg
|
||||
'distanceKm', // v0.48.0 (ISS-095) — great-circle leg distance in km
|
||||
'deadlineSimTime',
|
||||
'status',
|
||||
'planeId',
|
||||
|
||||
Reference in New Issue
Block a user