Reputation economy: rep-scaled buy/refuel discounts + job reward quality (ISS-072, v0.38.0)
CI / build + unit + e2e (pull_request) Successful in 9m3s
CI / build + unit + e2e (pull_request) Successful in 9m3s
This commit is contained in:
+37
-1
@@ -1,10 +1,12 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { haversineKm } from '@/lib/geo';
|
||||
import { PLANE_TYPES, STARTER_MODEL } from './catalog';
|
||||
import { PLANE_TYPES, repRate, STARTER_MODEL } from './catalog';
|
||||
import { SPECIALS } from './specials';
|
||||
import {
|
||||
BASE_REWARD,
|
||||
buildSeed,
|
||||
JOB_BONUS_PER_REP,
|
||||
MAX_JOB_BONUS,
|
||||
PACKAGE_COUNT_MAX,
|
||||
PACKAGE_COUNT_MIN,
|
||||
generatePackage,
|
||||
@@ -298,4 +300,38 @@ describe('generatePackage (shared seed/spawn builder)', () => {
|
||||
expect(countDraws()).toBeGreaterThanOrEqual(7); // pickSpecial draws 1–2 extra
|
||||
});
|
||||
});
|
||||
|
||||
describe('reputation job bonus (v0.38.0, ISS-072)', () => {
|
||||
it('multiplies the reward by 1 + repRate at elevated reputation', () => {
|
||||
const c = generatePackage({ id: 'c-rep', airports: AIRPORTS, rng: mulberry32(42), nowMs: NOW, originIcao: 'EDDF', destIcao: 'EHAM', weightKg: 1_000, special: null, reputation: 60 });
|
||||
const km = haversineKm(AIRPORTS[0].lat, AIRPORTS[0].lon, AIRPORTS[1].lat, AIRPORTS[1].lon); // EDDF → EHAM
|
||||
expect(repRate(60, JOB_BONUS_PER_REP, MAX_JOB_BONUS)).toBeCloseTo(0.5, 12); // rep 60 → ×1.5
|
||||
expect(c.reward).toBe(Math.round(rewardFor(km, 1_000) * (1 + repRate(60, JOB_BONUS_PER_REP, MAX_JOB_BONUS))));
|
||||
});
|
||||
|
||||
it('defaults to the starting reputation when omitted — byte-identical output', () => {
|
||||
const a = generatePackage({ id: 'c-r0', airports: AIRPORTS, rng: mulberry32(42), nowMs: NOW });
|
||||
const b = generatePackage({ id: 'c-r0', airports: AIRPORTS, rng: mulberry32(42), nowMs: NOW, reputation: STARTING_REPUTATION });
|
||||
expect(a).toEqual(b); // no bonus at the starting value
|
||||
});
|
||||
|
||||
it('composes with the special multiplier (base × jobBonus × special)', () => {
|
||||
const c = generatePackage({ id: 'c-rep-sp', airports: AIRPORTS, rng: mulberry32(42), nowMs: NOW, originIcao: 'EDDF', destIcao: 'EHAM', weightKg: 1_000, special: 'priority', reputation: 60 });
|
||||
const km = haversineKm(AIRPORTS[0].lat, AIRPORTS[0].lon, AIRPORTS[1].lat, AIRPORTS[1].lon); // EDDF → EHAM
|
||||
expect(c.reward).toBe(
|
||||
Math.round(rewardFor(km, 1_000) * (1 + repRate(60, JOB_BONUS_PER_REP, MAX_JOB_BONUS)) * SPECIALS.priority.rewardMultiplier),
|
||||
);
|
||||
});
|
||||
|
||||
it('consumes no extra RNG draw', () => {
|
||||
const countDraws = (reputation?: number): number => {
|
||||
let n = 0;
|
||||
const base = mulberry32(42);
|
||||
generatePackage({ id: 'c-rep-n', airports: AIRPORTS, rng: () => { n += 1; return base(); }, nowMs: NOW, special: null, reputation });
|
||||
return n;
|
||||
};
|
||||
expect(countDraws()).toBe(6); // forced-null special → the fixed order minus the appended draw
|
||||
expect(countDraws(60)).toBe(6); // the rep hook consumes no draws
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user