import { describe, expect, it } from 'vitest'; import { BROKEN_BURN_MULTIPLIER, conditionBurnMultiplier, FUEL_DISCOUNT_PER_REP, MAX_FUEL_DISCOUNT, MAX_PLANE_DISCOUNT, PLANE_DISCOUNT_PER_REP, PLANE_TYPES, repRate, STARTER_MODEL } from './catalog'; import { STARTING_REPUTATION } from './seed'; // v0.28.0 (ISS-047) — catalog sanity: the progression ladder must stay well-formed as types are added. describe('PLANE_TYPES', () => { it('every entry has positive specs and a unique id', () => { const ids = Object.keys(PLANE_TYPES); expect(new Set(ids).size).toBe(ids.length); for (const t of Object.values(PLANE_TYPES)) { expect(t.id, `${t.name} id`).toBeTruthy(); expect(t.price, `${t.name} price`).toBeGreaterThan(0); expect(t.maxFuel, `${t.name} maxFuel`).toBeGreaterThan(0); expect(t.burnPerKm, `${t.name} burnPerKm`).toBeGreaterThan(0); expect(t.emptyWeightKg, `${t.name} emptyWeightKg`).toBeGreaterThan(0); expect(t.speedKmh, `${t.name} speedKmh`).toBeGreaterThan(0); expect(t.maxPayloadKg, `${t.name} maxPayloadKg`).toBeGreaterThan(0); } }); it('atr72 is the starter model', () => { expect(STARTER_MODEL).toBe('atr72'); expect(PLANE_TYPES[STARTER_MODEL]).toBeDefined(); }); }); // v0.38.0 (ISS-072) — the shared rep-discount shape: linear above the starting value, capped. describe('repRate', () => { it('is exactly 0 at and below the starting reputation', () => { expect(repRate(STARTING_REPUTATION, PLANE_DISCOUNT_PER_REP, MAX_PLANE_DISCOUNT)).toBe(0); expect(repRate(0, PLANE_DISCOUNT_PER_REP, MAX_PLANE_DISCOUNT)).toBe(0); expect(repRate(-5, FUEL_DISCOUNT_PER_REP, MAX_FUEL_DISCOUNT)).toBe(0); // below-start clamps too }); it('is linear in reputation above the starting value (exact mid-range values)', () => { expect(repRate(STARTING_REPUTATION + 10, PLANE_DISCOUNT_PER_REP, MAX_PLANE_DISCOUNT)).toBeCloseTo(0.1, 12); // 0.01 × 10 expect(repRate(STARTING_REPUTATION + 30, PLANE_DISCOUNT_PER_REP, MAX_PLANE_DISCOUNT)).toBeCloseTo(0.3, 12); // 0.01 × 30 expect(repRate(STARTING_REPUTATION + 40, FUEL_DISCOUNT_PER_REP, MAX_FUEL_DISCOUNT)).toBeCloseTo(0.2, 12); // 0.005 × 40 = cap }); it('clamps at the cap', () => { expect(repRate(STARTING_REPUTATION + 50, PLANE_DISCOUNT_PER_REP, MAX_PLANE_DISCOUNT)).toBe(MAX_PLANE_DISCOUNT); // 0.5 → capped to 0.4 expect(repRate(1_000_000, FUEL_DISCOUNT_PER_REP, MAX_FUEL_DISCOUNT)).toBe(MAX_FUEL_DISCOUNT); }); }); // v0.39.0 (ISS-069) — the broken-state burn factor: ×BROKEN_BURN_MULTIPLIER at/below zero health, exactly 1 above it. describe('conditionBurnMultiplier', () => { it('is exactly 1 for any positive health', () => { expect(conditionBurnMultiplier(100)).toBe(1); expect(conditionBurnMultiplier(42)).toBe(1); expect(conditionBurnMultiplier(0.5)).toBe(1); // worn but not broken }); it('is exactly BROKEN_BURN_MULTIPLIER at and below zero health', () => { expect(conditionBurnMultiplier(0)).toBe(BROKEN_BURN_MULTIPLIER); expect(conditionBurnMultiplier(-3)).toBe(BROKEN_BURN_MULTIPLIER); // defensive — wear clamps at 0, but the gate is <= 0 }); it('pins the broken multiplier at ×1.5', () => { expect(BROKEN_BURN_MULTIPLIER).toBe(1.5); }); });