deadhead-space-freight-tycoon/packages/sim/balance/run.ts

71 lines
3.0 KiB
TypeScript
Raw 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.

/// <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}`,
);
// 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"),
);