deadhead-space-freight-tycoon/packages/sim/test/loan.test.ts

106 lines
3.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { content } from "@deadhead/content";
import { describe, expect, it } from "vitest";
import {
createDispatchRecord,
resolveDispatch,
splitLoanRepayment,
type Contract,
pilotShare,
} from "../src/index";
/** Fuel enough for any leg in these tests (decision 0.5.94), bought at 10 cr/fu. */
const PLENTY = { fuelFu: 10_000, avgPrice: 10 };
describe("loan repayment split", () => {
it("takes min(balance, credits × share), rounded down to whole credits", () => {
expect(splitLoanRepayment(1000, 5000, 0.3)).toEqual({ repayment: 300, net: 700 });
expect(splitLoanRepayment(1001, 5000, 0.3)).toEqual({ repayment: 300, net: 701 });
expect(splitLoanRepayment(1000, 120, 0.3)).toEqual({ repayment: 120, net: 880 });
});
it("takes nothing when there is no balance, or no positive payout", () => {
expect(splitLoanRepayment(1000, 0, 0.3)).toEqual({ repayment: 0, net: 1000 });
expect(splitLoanRepayment(0, 5000, 0.3)).toEqual({ repayment: 0, net: 0 });
expect(splitLoanRepayment(-60, 5000, 0.3)).toEqual({ repayment: 0, net: -60 });
});
it("always splits the whole amount and never repays more than the balance", () => {
for (const credits of [-500, -1, 0, 1, 7, 99, 1000, 123_457]) {
for (const balance of [0, 1, 50, 299, 5000, 1_000_000]) {
for (const share of [0, 0.1, 0.3, 1]) {
const { repayment, net } = splitLoanRepayment(credits, balance, share);
expect(net + repayment).toBe(credits);
expect(repayment).toBeLessThanOrEqual(balance);
expect(repayment).toBeGreaterThanOrEqual(0);
expect(repayment).toBe(
credits > 0 && balance > 0 ? Math.min(balance, Math.floor(credits * share)) : 0,
);
}
}
}
});
});
describe("resolution with a loan", () => {
const now = Date.parse("2100-01-01T00:30:00Z");
const contract: Contract = {
id: "c1",
originId: "earth",
destinationId: "moon",
cargoTypeId: "standard",
goodId: "consumer-goods",
shipper: "Pacific Orbital Logistics",
priority: false,
deliveryWindowGameHours: null,
tier: 0,
massTonnes: 10,
payout: 2000,
requiredSkill: null,
requiredLongHaulLevel: 0,
offeredAt: now,
expiresAt: now + 3_600_000,
firstContact: false,
};
const record = createDispatchRecord(
{
id: "d1",
ship: { id: "s1", modelId: "courier", locationId: "earth", inFlight: false, tank: PLENTY },
pilot: {
id: "p1",
isAvatar: false,
assignedShipId: "s1",
inFlight: false,
skills: {},
restEndsAt: null,
},
contract,
tutorial: false,
now,
worldSeed: 7,
},
content,
);
it("returns the repayment alongside the credits to add, after pilot pay", () => {
const result = resolveDispatch(
{
record,
pilotIsAvatar: false,
discoveredIds: new Set(["earth", "moon"]),
loanBalance: 5000,
pilotDutyHours: 0,
pilotSkills: {},
now: record.arrivalAt,
},
content,
);
if (result.status !== "resolved") throw new Error("expected a resolution");
const afterPay = 2000 - pilotShare(2000, record.fuelCost, content.config);
const repayment = Math.floor(afterPay * content.config.loanRepaymentShare);
expect(result.resolution).toMatchObject({
loanRepayment: repayment,
credits: afterPay - repayment,
});
});
});