Boost Planner: 'extra cash now' input finds the best campaign combo (any level mix, up to 3 lanes — slots rule confirmed by Marty)
- boostSim(): generic lane simulator to 3xL7, exact same reserve rules as computePlan (verified: agrees on all 8 baseline starts) - enumerateBoostCombos(): every affordable add-on for the free slots, activation once per level - Overview card ranks every combo vs 'just bank it' by time to endgame; share link carries &boost= - test_scenarios section 8: baseline consistency, combo costs, ranking, the +$300 scenario (best: add L3+L2, 3 cycles faster than banking)
This commit is contained in:
+46
-2
@@ -12,9 +12,10 @@ if (start < 0 || end < 0) { console.error("FATAL: extraction markers missing");
|
||||
// Indirect eval: keeps the extracted declarations out of this module's scope;
|
||||
// the trailing expression hands back everything we need.
|
||||
const { LEVELS, HYBRIDS, PHASE_SLOTS, computePlan, phaseInfo, getPhaseSequence,
|
||||
estimateMonths, estimateYear1Cum, buildMonthlyTimeline } = (0, eval)(
|
||||
estimateMonths, estimateYear1Cum, buildMonthlyTimeline,
|
||||
boostSim, enumerateBoostCombos, boostPlan } = (0, eval)(
|
||||
html.slice(start, end) +
|
||||
";({LEVELS, HYBRIDS, PHASE_SLOTS, computePlan, phaseInfo, getPhaseSequence, estimateMonths, estimateYear1Cum, buildMonthlyTimeline})"
|
||||
";({LEVELS, HYBRIDS, PHASE_SLOTS, computePlan, phaseInfo, getPhaseSequence, estimateMonths, estimateYear1Cum, buildMonthlyTimeline, boostSim, enumerateBoostCombos, boostPlan})"
|
||||
);
|
||||
|
||||
let failures = 0;
|
||||
@@ -134,5 +135,48 @@ console.log("7) strategy modes:");
|
||||
check("parked timeline runs 10 collection cycles", tl.length === 10);
|
||||
}
|
||||
|
||||
console.log("8) Boost Planner (extra-funds combos, Marty 2026-07-31):");
|
||||
{
|
||||
// 8a. boostSim with $0 extra must agree with computePlan for every start.
|
||||
for (let eff = 0; eff <= 7; eff++) {
|
||||
const phases = getPhaseSequence(eff);
|
||||
const plan = computePlan(phases);
|
||||
const planTotal = phases.reduce((s, k) => s + plan[k].cycles, 0);
|
||||
const sim = boostSim([...PHASE_SLOTS[phases[0]]], 0);
|
||||
check(`eff=${eff}: boostSim baseline ${sim.cycles} == computePlan total ${planTotal}`,
|
||||
sim.cycles === planTotal, `sim path ${sim.path.join(" → ")}`);
|
||||
}
|
||||
// 8b. combo enumeration: costs + slot/activation rules from an L1-only start.
|
||||
const combos = enumerateBoostCombos([1], 300);
|
||||
const byKey = Object.fromEntries(combos.map(c => [c.lanes.join("+"), c.cost]));
|
||||
check("add L3 costs 165 (camp 150 + act 15)", byKey["3"] === 165, `got ${byKey["3"]}`);
|
||||
check("add L3+L1 costs 178 (L1 already activated: camp only)", byKey["3+1"] === 178, `got ${byKey["3+1"]}`);
|
||||
check("add L1+L1 costs 26 (two camp-only lanes)", byKey["1+1"] === 26, `got ${byKey["1+1"]}`);
|
||||
check("L4 (330) not affordable at $300", !("4" in byKey));
|
||||
check("no combo exceeds 2 added lanes (3 slots total)", combos.every(c => c.lanes.length <= 2));
|
||||
// 8c. Marty's scenario: extra cash buys a real speedup over banking it.
|
||||
const p300 = boostPlan("L1-only", 300);
|
||||
const best = p300.options[0];
|
||||
check(`+$300 from L1: best combo [add L${best.lanes.slice(1).join("+L")}] beats banking ` +
|
||||
`(${best.sim.cycles} < ${p300.baseline.cycles} cycles)`,
|
||||
best.sim.cycles < p300.baseline.cycles);
|
||||
// 8d. options are ranked (cycles ascending, cost tiebreak).
|
||||
const sorted = p300.options.every((o, i, a) =>
|
||||
i === 0 || a[i - 1].sim.cycles < o.sim.cycles ||
|
||||
(a[i - 1].sim.cycles === o.sim.cycles && a[i - 1].cost <= o.cost));
|
||||
check("options ranked by cycles then cost", sorted);
|
||||
// 8e. community claim probe: report best 1-added-lane vs 2-added-lanes at +$1000.
|
||||
const p1k = boostPlan("L1-only", 1000);
|
||||
const best1 = p1k.options.filter(o => o.lanes.length === 2)[0];
|
||||
const best2 = p1k.options.filter(o => o.lanes.length === 3)[0];
|
||||
if (best1 && best2) {
|
||||
console.log(` info: +$1000 from L1 — best 2-lane total [${best1.lanes.join("+")}] ${best1.sim.cycles}cy` +
|
||||
` vs best 3-lane total [${best2.lanes.join("+")}] ${best2.sim.cycles}cy`);
|
||||
}
|
||||
console.log(` info: +$300 from L1 top 3: ` + p300.options.slice(0, 3)
|
||||
.map(o => `[add ${o.lanes.slice(1).map(l => "L" + l).join("+") || "?"}] $${o.cost} → ${o.sim.cycles}cy/${o.sim.months}mo`)
|
||||
.join(" · ") + ` · bank: ${p300.baseline.cycles}cy/${p300.baseline.months}mo`);
|
||||
}
|
||||
|
||||
console.log(failures === 0 ? "\nALL PASS" : `\n${failures} FAILURE(S)`);
|
||||
process.exit(failures === 0 ? 0 : 1);
|
||||
|
||||
Reference in New Issue
Block a user