98 lines
4.2 KiB
TypeScript
98 lines
4.2 KiB
TypeScript
/// <reference types="node" />
|
||
/**
|
||
* Prints a balance run for both first-purchase policies (plan Step 12.1), then
|
||
* the fuel-to-payout check (CR-018 Step 18.1):
|
||
* pnpm --filter @deadhead/sim balance [worldSeed] [hours]
|
||
*/
|
||
import { MS_PER_HOUR } from "../src/index";
|
||
import { content } from "@deadhead/content";
|
||
import { formatFuelShares, fuelShares } from "./fuel-share";
|
||
import { simulate, type PurchasePolicy } from "./simulate";
|
||
|
||
const worldSeed = Number(process.argv[2] ?? 12345);
|
||
const hours = Number(process.argv[3] ?? 8);
|
||
for (const policy of ["lighter-first", "freighter-first"] as PurchasePolicy[]) {
|
||
const result = simulate({ worldSeed, policy, durationMs: hours * MS_PER_HOUR });
|
||
console.log(`\n=== ${policy} (seed ${worldSeed}) ===`);
|
||
console.log("milestones (real minutes):", result.milestones);
|
||
console.log(
|
||
`tutorial runs ${result.tutorialRuns}, contracts ${result.contractsDelivered}, ` +
|
||
`veteran contracts in first 2 h ${result.veteranContractsFirstSession}, purchases ${result.purchases.join(", ")}`,
|
||
);
|
||
console.log("violations:", result.violations.length);
|
||
for (const v of result.violations.slice(0, 10))
|
||
console.log(` ${v.minute} min ${v.rule} ${v.detail}`);
|
||
console.log("minute credits loan ships pilot levels");
|
||
for (const s of result.samples.filter((_, i) => i % 3 === 0)) {
|
||
console.log(
|
||
`${String(s.minute).padStart(6)} ${String(s.credits).padStart(7)} ${String(s.loanBalance).padStart(6)} ${s.ships} ${s.pilotLevels.join(" ")}`,
|
||
);
|
||
}
|
||
}
|
||
|
||
console.log("\n=== fuel share of median payout, per ship and route (CR-018) ===");
|
||
console.log(formatFuelShares(fuelShares(content)));
|
||
|
||
// The away scenario (CR-021 Step 21.8): 3 hours of play, then 8 hours either way.
|
||
const window = (mode: "active" | "away") =>
|
||
simulate({
|
||
worldSeed,
|
||
policy: "lighter-first",
|
||
durationMs: 12 * MS_PER_HOUR,
|
||
scenario: { atMinute: 180, minutes: 8 * 60, mode },
|
||
});
|
||
const active = window("active");
|
||
const away = window("away");
|
||
console.log("\n=== away scenario: 8 h with Independent pilots vs active play (CR-021) ===");
|
||
console.log(
|
||
`active ${active.scenarioEarnings} cr, away ${away.scenarioEarnings} cr, ` +
|
||
`ratio ${((away.scenarioEarnings! / active.scenarioEarnings!) * 100).toFixed(0)}% (target 40–80%), ` +
|
||
`violations ${away.violations.length}`,
|
||
);
|
||
console.log(
|
||
`away service (CR-027): ${away.service.bookings} auto-repair bookings, ${away.service.started} done, ` +
|
||
`most trips in a row below 25% hull ${away.service.maxLowHullTrips} (≤ 1), ` +
|
||
`repairs ${away.events.windowRepairSpend} cr`,
|
||
);
|
||
|
||
// A week away (CR-024 Step 24.7): the fleet goes quiet 24 hours into the absence.
|
||
const absence = (hours: number) =>
|
||
simulate({
|
||
worldSeed,
|
||
policy: "lighter-first",
|
||
durationMs: (3 + hours) * MS_PER_HOUR,
|
||
scenario: { atMinute: 180, minutes: hours * 60, mode: "away" },
|
||
});
|
||
const day = absence(24);
|
||
const week = absence(7 * 24);
|
||
console.log("\n=== a week away vs a day away, fleet asleep after 24 h (CR-024) ===");
|
||
console.log(
|
||
`day ${day.scenarioEarnings} cr, week ${week.scenarioEarnings} cr, ` +
|
||
`in flight at sleep ${week.payoutInFlightAtSleep} cr: ` +
|
||
(week.scenarioEarnings! <= day.scenarioEarnings! + week.payoutInFlightAtSleep!
|
||
? "OK"
|
||
: "TOO MUCH"),
|
||
);
|
||
|
||
// Events (CR-026 Step 26.9): a long-haul fleet over 48 h, events left to their defaults.
|
||
const longHaul = simulate({
|
||
worldSeed,
|
||
policy: "lighter-first",
|
||
durationMs: 54 * MS_PER_HOUR,
|
||
scenario: { atMinute: 300, minutes: 48 * 60, mode: "active" },
|
||
events: "defaults",
|
||
eventsFromMinute: 300,
|
||
longHaulMinGameHours: 12,
|
||
firstEventDone: true,
|
||
});
|
||
const ev = longHaul.events;
|
||
const pct = (v: number) => `${(v * 100).toFixed(1)}%`;
|
||
console.log("\n=== events on a long-haul fleet, 48 h (CR-026) ===");
|
||
console.log(
|
||
`trips ${ev.windowTrips}, with an event ${pct(ev.windowWithEvent / ev.windowTrips)} (8–15%), ` +
|
||
`good ${pct(ev.good / Math.max(1, ev.withEvent))} (28–38%), ` +
|
||
`defaults ${pct(1 + ev.windowDefaultValue / longHaul.scenarioEarnings!)} (≥ 93%), ` +
|
||
`answered well ${pct(1 + ev.windowBestValue / longHaul.scenarioEarnings!)} (≥ 100%), ` +
|
||
`repairs ${pct(ev.windowRepairSpend / longHaul.scenarioEarnings!)} of income (5–10%)`,
|
||
);
|