diff --git a/index.html b/index.html
index 71dd9d6..a94eef3 100644
--- a/index.html
+++ b/index.html
@@ -1510,27 +1510,85 @@ const LEVELS = [
{ level: 7, act: 240, camp: 2400, total: 2640, payout: 3240.00,clicks: 20, cpc: 13.50, netProfit: 840.00 },
];
+// NOTE: cycles/months are NOT stored here — they are DERIVED per starting
+// scenario by computePlan() below. Hand-tuned counts kept drifting from the
+// money math (removed 2026-07-30). Payout/profit/reinvest are PDF-verified.
const HYBRIDS = {
- 'L1-only': { label: 'L1 Only', reinvest: 13, payout: 17.17, profit: 4.17, cycles: 9, months: 3.0, daysPer: 19 },
- 'L2+L1': { label: 'L2 + L1', reinvest: 90, payout: 118.91, profit: 28.91, cycles: 4, months: 2.5, daysPer: 19 },
- 'L3+L1': { label: 'L3 + L1', reinvest: 163, payout: 211.57, profit: 48.57, cycles: 4, months: 2.5, daysPer: 19 },
- 'L4+L3': { label: 'L4 + L3', reinvest: 450, payout: 583.20, profit: 133.20,cycles: 5, months: 3.0, daysPer: 19 },
- 'L5+L4': { label: 'L5 + L4', reinvest: 900, payout: 1166.40,profit: 266.40,cycles: 4, months: 2.0, daysPer: 19 },
- 'L6': { label: 'L6', reinvest: 1200, payout: 1555.20,profit: 355.20,cycles: 4, months: 2.0, daysPer: 19 },
- 'L7': { label: 'Single L7', reinvest: 2400, payout: 3240.00,profit: 840.00,cycles: 7, months: 3.0, daysPer: 19 },
- '3xL7': { label: '3× L7 Endgame', reinvest: 7200, payout: 9720, profit: 2520, cycles: 1, months: 0.63, daysPer: 19 },
+ 'L1-only': { label: 'L1 Only', reinvest: 13, payout: 17.17, profit: 4.17, daysPer: 19 },
+ 'L2+L1': { label: 'L2 + L1', reinvest: 90, payout: 118.91, profit: 28.91, daysPer: 19 },
+ 'L3+L1': { label: 'L3 + L1', reinvest: 163, payout: 211.57, profit: 48.57, daysPer: 19 },
+ 'L4+L3': { label: 'L4 + L3', reinvest: 450, payout: 583.20, profit: 133.20,daysPer: 19 },
+ 'L5+L4': { label: 'L5 + L4', reinvest: 900, payout: 1166.40,profit: 266.40,daysPer: 19 },
+ 'L6': { label: 'L6', reinvest: 1200, payout: 1555.20,profit: 355.20,daysPer: 19 },
+ 'L7': { label: 'Single L7', reinvest: 2400, payout: 3240.00,profit: 840.00,daysPer: 19 },
+ '3xL7': { label: '3× L7 Endgame', reinvest: 7200, payout: 9720, profit: 2520, daysPer: 19 },
};
+// Solo-climb cycle counts derived with the same reserve model (roll the
+// final payout; L7 extra lanes are campaign-only).
const LADDER_STEPS = [
- { step: 'L4 Funded', capital: 300, invest: 300, ret: 388.80, profit: 88.80, cycles: 5, months: 3.0 },
- { step: 'L5 Ready', capital: 660, invest: 660, ret: 777.60, profit: 177.60,cycles: 5, months: 3.0 },
- { step: 'L6 Ready', capital: 1320, invest: 1320, ret: 1555.20,profit: 355.20,cycles: 4, months: 2.0 },
- { step: 'L7 Ready', capital: 2640, invest: 2640, ret: 3240.00,profit: 840.00,cycles: 7, months: 3.0 },
+ { step: 'L4 Funded', capital: 300, invest: 300, ret: 388.80, profit: 88.80, cycles: 5, months: 3.1 },
+ { step: 'L5 Ready', capital: 660, invest: 660, ret: 777.60, profit: 177.60,cycles: 5, months: 3.1 },
+ { step: 'L6 Ready', capital: 1320, invest: 1320, ret: 1555.20,profit: 355.20,cycles: 5, months: 3.1 },
+ { step: 'L7 Ready', capital: 2640, invest: 2640, ret: 3240.00,profit: 840.00,cycles: 6, months: 3.8 },
{ step: '3× L7 Endgame',capital: 7200, invest: 7200, ret: 9720, profit: 2520, cycles: 0, months: 0 },
];
const THREE_L7_YOUR_CUT = 972;
+// ── Derived phase engine ────────────────────────────────────────────────────
+// The reserve model (agreed with Marty 2026-07-30): recycle the campaigns that
+// carry into the next phase, bank every profit as reserves, and on the upgrade
+// cycle roll the full payout (skipping re-buys of campaigns being dropped)
+// into the next phase's buy-in. Reserves carry across phases. Activation is a
+// one-time per-level unlock; extra lanes of an activated level (3x L7) cost
+// campaign only. Timeline is honest: cycles x 19 days.
+const PHASE_SLOTS = {
+ 'L1-only': [1], 'L2+L1': [2, 1], 'L3+L1': [3, 1], 'L4+L3': [4, 3],
+ 'L5+L4': [5, 4], 'L6': [6], 'L7': [7], '3xL7': [7, 7, 7],
+};
+
+function computePlan(phases) {
+ const plan = {};
+ let bal = 0;
+ const activated = new Set();
+ for (let lv = 1; lv <= Math.max(...PHASE_SLOTS[phases[0]]); lv++) activated.add(lv);
+ for (let i = 0; i < phases.length; i++) {
+ const k = phases[i];
+ const p = HYBRIDS[k];
+ if (k === '3xL7') { plan[k] = { cycles: 1, months: 0.63 }; break; }
+ const nextSlots = [...PHASE_SLOTS[phases[i + 1]]];
+ const carried = [];
+ for (const lv of PHASE_SLOTS[k]) {
+ const j = nextSlots.indexOf(lv);
+ if (j !== -1) { carried.push(lv); nextSlots.splice(j, 1); }
+ }
+ let cost = 0;
+ for (const lv of nextSlots) {
+ const L = LEVELS[lv - 1];
+ cost += L.camp + (activated.has(lv) ? 0 : L.act);
+ activated.add(lv);
+ }
+ const carriedCamp = carried.reduce((sum, lv) => sum + LEVELS[lv - 1].camp, 0);
+ let cycles = 0;
+ while (cycles < 200) {
+ cycles++;
+ if (bal + p.payout - carriedCamp >= cost) { bal += p.payout - carriedCamp - cost; break; }
+ bal += p.profit;
+ }
+ plan[k] = { cycles, months: Math.round(cycles * p.daysPer / 30.4 * 10) / 10 };
+ }
+ return plan;
+}
+
+// Per-sequence phase info: PDF-verified money constants + derived cycles/months.
+function phaseInfo(phases) {
+ const plan = computePlan(phases);
+ const out = {};
+ for (const k of phases) out[k] = { ...HYBRIDS[k], ...plan[k] };
+ return out;
+}
+
function getLevelCost(upTo) {
if (upTo <= 0) return 0;
return LEVELS.slice(0, upTo).reduce((s, l) => s + l.total, 0);
@@ -1541,6 +1599,8 @@ function getEffectiveStart(gift, selfF) {
}
function getPhaseSequence(effectiveStart) {
+ if (effectiveStart >= 7) return ['L7','3xL7'];
+ if (effectiveStart >= 6) return ['L6','L7','3xL7'];
if (effectiveStart >= 5) return ['L5+L4','L6','L7','3xL7'];
if (effectiveStart >= 4) return ['L4+L3','L5+L4','L6','L7','3xL7'];
if (effectiveStart >= 3) return ['L3+L1','L4+L3','L5+L4','L6','L7','3xL7'];
@@ -1549,21 +1609,28 @@ function getPhaseSequence(effectiveStart) {
}
function estimateMonths(phases) {
+ const PI = phaseInfo(phases);
let totalDays = 0;
for (const k of phases) {
if (k === '3xL7') break;
- totalDays += HYBRIDS[k].cycles * HYBRIDS[k].daysPer;
+ totalDays += PI[k].cycles * PI[k].daysPer;
}
return Math.round(totalDays / 30.4 * 10) / 10;
}
function estimateYear1Cum(phases, commPct, giftLevel) {
+ // Includes endgame cycles that land inside year 1 — well-funded starts
+ // reach 3x L7 within months and the old cutoff undercounted them.
+ const PI = phaseInfo(phases);
let cum = 0;
let daysLeft = 365;
for (const k of phases) {
- if (k === '3xL7') break;
- const p = HYBRIDS[k];
+ const p = PI[k];
const yourCut = p.payout * (commPct / 100);
+ if (k === '3xL7') {
+ cum += Math.max(0, Math.floor(daysLeft / p.daysPer)) * yourCut;
+ break;
+ }
const cyclesFit = Math.min(p.cycles, Math.floor(daysLeft / p.daysPer));
cum += cyclesFit * yourCut;
daysLeft -= cyclesFit * p.daysPer;
@@ -1573,30 +1640,21 @@ function estimateYear1Cum(phases, commPct, giftLevel) {
}
function buildMonthlyTimeline(phases, commPct) {
+ // One row per 19-day cycle, stamped with the real calendar month it lands
+ // in (the old version advanced a full month per cycle — 60% too slow).
+ const PI = phaseInfo(phases);
const months = [];
- let cumProf = 0, cumComm = 0;
- let m = 0;
+ let cumComm = 0, days = 0;
for (const k of phases) {
- if (k === '3xL7') {
- for (let i = 0; i < 6; i++) {
- m++;
- const p = HYBRIDS[k];
- const yourCut = p.payout * (commPct / 100);
- cumProf += p.profit;
- cumComm += yourCut;
- months.push({ m, phase: p.label, prof: Math.round(p.profit), comm: Math.round(yourCut), cumComm: Math.round(cumComm) });
- }
- break;
- }
- const p = HYBRIDS[k];
+ const p = PI[k];
const yourCut = p.payout * (commPct / 100);
- for (let c = 0; c < p.cycles; c++) {
- m++;
- cumProf += p.profit;
+ const n = k === '3xL7' ? 10 : p.cycles;
+ for (let c = 0; c < n; c++) {
+ days += p.daysPer;
cumComm += yourCut;
- months.push({ m, phase: c === 0 ? p.label : '', prof: Math.round(p.profit), comm: Math.round(yourCut), cumComm: Math.round(cumComm) });
+ months.push({ m: Math.ceil(days / 30.4), phase: c === 0 ? p.label : '', prof: Math.round(p.profit), comm: Math.round(yourCut), cumComm: Math.round(cumComm) });
}
- if (m >= 30) break;
+ if (k === '3xL7' || days > 913) break;
}
return months;
}
@@ -1619,9 +1677,11 @@ function buildSCENARIOS() {
const phases = getPhaseSequence(eff);
const t = Math.round(estimateMonths(phases));
const y = estimateYear1Cum(phases, 10, s.giftLvl);
+ const PI = phaseInfo(phases);
const eg = phases.filter(k => k !== '3xL7')
- .reduce((sum, k) => sum + HYBRIDS[k].payout * 0.10 * HYBRIDS[k].cycles, 0);
- return { ...s, time: t, yr1: Math.round(y), totalEG: Math.round(eg), monthly: 1860 };
+ .reduce((sum, k) => sum + PI[k].payout * 0.10 * PI[k].cycles, 0);
+ // Monthly passive at 3x L7: $972/cycle x (30.4/19) cycles per month.
+ return { ...s, time: t, yr1: Math.round(y), totalEG: Math.round(eg), monthly: Math.round(THREE_L7_YOUR_CUT * 30.4 / 19) };
});
}
const SCENARIOS = buildSCENARIOS();
@@ -1640,40 +1700,41 @@ function updateAll() {
const selfCost = getLevelCost(selfLevel);
const effective = getEffectiveStart(giftLevel, selfLevel);
const phases = getPhaseSequence(effective);
+ const PI = phaseInfo(phases);
const totalMonths = estimateMonths(phases);
const yr1Cum = estimateYear1Cum(phases, commPct, giftLevel) * numPeople;
const yr1PerPerson = estimateYear1Cum(phases, commPct, giftLevel);
const totalEG = Math.round(phases.filter(k => k !== '3xL7')
- .reduce((s, k) => s + HYBRIDS[k].payout * (commPct / 100) * HYBRIDS[k].cycles, 0) * 10) / 10;
+ .reduce((s, k) => s + PI[k].payout * (commPct / 100) * PI[k].cycles, 0) * 10) / 10;
const monthlyPassive = Math.round(THREE_L7_YOUR_CUT * mult * numPeople);
let totalToEGPerPerson = 0;
for (const k of phases) {
if (k === '3xL7') break;
- totalToEGPerPerson += HYBRIDS[k].payout * (commPct / 100) * HYBRIDS[k].cycles;
+ totalToEGPerPerson += PI[k].payout * (commPct / 100) * PI[k].cycles;
}
totalToEGPerPerson = Math.round(totalToEGPerPerson * 100) / 100;
let roiText = 'N/A', roiSub = '';
if (giftCost > 0) {
- let cumCheck = 0;
- for (const k of phases) {
- if (k === '3xL7') break;
- const p = HYBRIDS[k];
+ // Day-accurate: accumulate real elapsed days across the derived plan
+ // (the old formula assumed one cycle per prior phase).
+ let cumCheck = 0, daysAcc = 0;
+ outer: for (const k of phases) {
+ const p = PI[k];
const yourCut = p.payout * (commPct / 100);
- for (let i = 0; i < p.cycles; i++) {
+ const n = k === '3xL7' ? 60 : p.cycles;
+ for (let i = 0; i < n; i++) {
+ daysAcc += p.daysPer;
cumCheck += yourCut;
if (cumCheck >= giftCost) {
- const monthsTo = (phases.indexOf(k) * (p.daysPer / 30.4)) + (i + 1) * (p.daysPer / 30.4);
- roiText = `~${Math.round(monthsTo * 10) / 10} months`;
+ roiText = `~${Math.round(daysAcc / 30.4 * 10) / 10} months`;
roiSub = `during ${p.label}`;
- break;
+ break outer;
}
}
- if (roiSub) break;
}
- if (!roiSub) roiText = '~5.5 months';
}
const summaryHtml = `
@@ -1717,7 +1778,7 @@ ${numPeople > 0 ? `
let roiRow = ''; // phase key where ROI was achieved
let rows = '';
for (const k of phases) {
- const p = HYBRIDS[k];
+ const p = PI[k];
const yourCut = p.payout * (commPct / 100);
const cyclesNum = k === '3xL7' ? 1 : p.cycles;
// Track cumulative payout (always accumulates — this is the total to sponsor)
@@ -1748,15 +1809,15 @@ ${numPeople > 0 ? `
const chartData = [];
let chartCum = 0;
- let chartMonth = 0;
+ let chartDays = 0;
for (const k of phases) {
- const p = HYBRIDS[k];
+ const p = PI[k];
const yourCut = p.payout * (commPct / 100);
- const cycles = k === '3xL7' ? 6 : p.cycles;
+ const cycles = k === '3xL7' ? 10 : p.cycles;
for (let i = 0; i < cycles; i++) {
- chartMonth++;
+ chartDays += p.daysPer;
chartCum += yourCut;
- chartData.push({ m: chartMonth, cum: Math.round(chartCum * 100) / 100 });
+ chartData.push({ m: Math.ceil(chartDays / 30.4), cum: Math.round(chartCum * 100) / 100 });
}
if (k === '3xL7') break;
}
@@ -2118,6 +2179,7 @@ function generatePlan() {
const selfCost = getLevelCost(selfLevel);
const effective = getEffectiveStart(giftLevel, selfLevel);
const phases = getPhaseSequence(effective);
+ const PI = phaseInfo(phases);
const totalMonths = estimateMonths(phases);
const alreadyJoined = document.getElementById('alreadyJoined').checked;
@@ -2141,7 +2203,7 @@ function generatePlan() {
let rawPlan = alreadyJoined
? `📋 Your ClickBaitPays Plan — Full Roadmap\n\nSo what's ClickBaitPays? It's an ad platform where you run small ad campaigns, click a few ads each day, and every ~19 days you get paid more than the campaign cost. You reinvest those payouts into bigger campaigns and scale up. It's simple, takes a few minutes a day, and you can do it from your phone.\n\nHere's a calculator I put together so you can play with the numbers yourself: ${refLink}\n\nCome back to that link anytime — it'll walk you through what's possible and the best plan forward as you grow.\n\n${giftCost > 0 ? `${pifSteps}` : ''}\n\nYour full roadmap to $3,960/month:\n\n${phases.filter(k => k !== '3xL7').map((k, i) => {
- const p = HYBRIDS[k];
+ const p = PI[k];
const pocketTotal = (p.profit * p.cycles).toFixed(2);
const availableAfterPocket = Math.round(p.profit * p.cycles * 100 + p.reinvest * 100) / 100;
const nextK = phases[phases.indexOf(k) + 1];
@@ -2163,7 +2225,7 @@ function generatePlan() {
return `**Phase ${i+1}: ${p.label}**\n${p.cycles} cycles, ~${p.months} months — $${p.payout.toFixed(2)}/cycle payout\n\nYou can play this phase two ways:\n\n🔹 **Pocket mode** — hold onto the ~$${p.profit.toFixed(2)} profit each cycle as spending money (that's your balance after CBP's built-in fee, no extra charge if you keep it in your account). ${withdrawNote}${unlockText} Total time: ~${p.months} months.\n\n🔸 **Accelerate mode** — reinvest every penny instead of pocketing it. You reach the next level faster because nothing comes off the top. Total time is shorter, but you pocket nothing along the way.`;
}).join('\n\n')}\n\n**Phase ${phases.filter(k => k !== '3xL7').length+1}: 3× L7 Endgame** 🎯\nAll three L7 campaigns running at once\n$9,720.00/cycle payout → ~$3,960.00/month passive\nThis is the finish line\n\n${selfCost > 0 ? `**Your total self-fund: $${selfCost}**${selfCost > giftCost ? ` (you cover $${(selfCost - giftCost).toLocaleString()} after sponsor's gift)` : ''}` : ''}\n\nThe choice is yours — pocket spending money along the way, or reinvest everything to get to the endgame faster. Either way, no new money needed after the start. Just run the cycles, stack the profits, and level up.\n\nAs a side note — I also earn 10% of everything you get paid through this system, so eventually I'll get my gift back. But that's not why I'm doing this. I genuinely just want to pay it forward and help you get started. You win, I win — we both win together.\n\nJoin the sponsor community on Telegram to connect with other members and get help: https://t.me/+mzXTku1wR7pkODBh\n\nEstimated time to 3× L7 endgame: ~${totalMonths} months\n\nQuick Stats\n• Monthly at 3× L7: ~$3,960/mo passive\n• Each campaign cycle: 19 days\n• Min daily clicks: 3-20 (depending on level)\n• Reward per ad click: $0.48 - $13.50\n\nYour Referral Link\n${refLink}\n\nGood luck — the system works if you work the system. 💪`
: `📋 Your ClickBaitPays Plan\n\nSo what's ClickBaitPays? It's an ad platform where you run small ad campaigns, click a few ads each day, and every ~19 days you get paid more than the campaign cost. You reinvest those payouts into bigger campaigns and scale up. It's simple, takes a few minutes a day, and you can do it from your phone.\n\nHere's a calculator I put together so you can play with the numbers yourself: ${refLink}\n\nAfter you sign up, come back to that link — it'll walk you through what's possible and the best plan forward as you grow.\n\n${giftCost > 0 ? `With me as your sponsor, I'm covering **$${giftCost}** to get you started through Level ${giftLevel} (L${giftLevel === 1 ? '1 only' : `1-L${giftLevel}`}). You owe nothing for this — it's funded up front, and you keep 100% of your earnings.` : `I haven't pre-funded any levels on my end — you'll be building entirely on your own using the link below.`}\n\n${giftCost > 0 ? pifSteps : ''}\n\nYour full roadmap to $3,960/month:\n\n${phases.filter(k => k !== '3xL7').map((k, i) => {
- const p = HYBRIDS[k];
+ const p = PI[k];
const pocketTotal = (p.profit * p.cycles).toFixed(2);
const availableAfterPocket = Math.round(p.profit * p.cycles * 100 + p.reinvest * 100) / 100;
const nextK = phases[phases.indexOf(k) + 1];
@@ -2239,17 +2301,20 @@ function updateROI() {
}
// Phase data: [cycles_this_phase, cycles_needed_for_next, profit_per_cycle, next_phase_label]
+ // Cycle counts derived with the reserve model (from $0 reserves at phase
+ // start, roll-the-final-payout); needsCapital = the actual incremental
+ // buy-in of the next phase (carried campaigns excluded).
const PHASE_DATA = {
- l1: { cycles: 9, profitPer: 1.72, nextLabel: 'L2+L1', needsCapital: 90 },
- l2l1: { cycles: 4, profitPer: 11.89, nextLabel: 'L3+L1', needsCapital: 263 },
- l3l1: { cycles: 4, profitPer: 21.16, nextLabel: 'L4 Only', needsCapital: 450 },
+ l1: { cycles: 21, profitPer: 1.72, nextLabel: 'L2+L1', needsCapital: 84 },
+ l2l1: { cycles: 4, profitPer: 11.89, nextLabel: 'L3+L1', needsCapital: 165 },
+ l3l1: { cycles: 7, profitPer: 21.16, nextLabel: 'L4+L3', needsCapital: 330 },
l4: { cycles: 5, profitPer: 58.32, nextLabel: 'L5 Only', needsCapital: 660 },
- l4l3: { cycles: 5, profitPer: 133.20, nextLabel: 'L5+L3', needsCapital: 660 },
- l5: { cycles: 4, profitPer: 116.64, nextLabel: 'L6 Only', needsCapital: 1320 },
- l5l3: { cycles: 3, profitPer: 222.00, nextLabel: 'L6+L3', needsCapital: 1320 },
- l6: { cycles: 4, profitPer: 155.52, nextLabel: 'L7 Solo', needsCapital: 2640 },
- l6l3: { cycles: 3, profitPer: 399.60, nextLabel: 'L7 Solo', needsCapital: 2640 },
- l7solo: { cycles: 7, profitPer: 840.00, nextLabel: '3× L7 Endgame', needsCapital: 7200 },
+ l4l3: { cycles: 4, profitPer: 133.20, nextLabel: 'L5+L3', needsCapital: 660 },
+ l5: { cycles: 5, profitPer: 116.64, nextLabel: 'L6 Only', needsCapital: 1320 },
+ l5l3: { cycles: 4, profitPer: 222.00, nextLabel: 'L6+L3', needsCapital: 1320 },
+ l6: { cycles: 5, profitPer: 155.52, nextLabel: 'L7 Solo', needsCapital: 2640 },
+ l6l3: { cycles: 4, profitPer: 399.60, nextLabel: 'L7 Solo', needsCapital: 2640 },
+ l7solo: { cycles: 6, profitPer: 840.00, nextLabel: '3× L7 Endgame', needsCapital: 4800 },
l7x3: { cycles: 999, profitPer: 2520.00, nextLabel: null, needsCapital: 0 },
};
diff --git a/test_scenarios.js b/test_scenarios.js
index b8c1518..0e294c4 100644
--- a/test_scenarios.js
+++ b/test_scenarios.js
@@ -1,105 +1,114 @@
-const LEVELS = [
- {level:1, act:1, camp:13, total:14, payout:17.17, netProfit:4.17},
- {level:2, act:7, camp:77, total:84, payout:118.91, netProfit:34.91},
- {level:3, act:14, camp:163, total:177, payout:211.57, netProfit:48.57},
- {level:4, act:36, camp:310, total:346, payout:388.80, netProfit:78.80},
- {level:5, act:72, camp:620, total:692, payout:777.60, netProfit:157.60},
- {level:6, act:120, camp:1200, total:1320, payout:1555.20,netProfit:355.20},
- {level:7, act:240, camp:2400, total:2640, payout:3240.00,netProfit:840.00},
-];
+// CBP calculator engine tests. Extracts the LIVE code from index.html (no
+// duplicated constants — the old copy of this file drifted) and cross-checks
+// computePlan against an independently written event simulator.
+// node test_scenarios.js [path-to-index.html]
+const fs = require("fs");
+const path = process.argv[2] || __dirname + "/index.html";
+const html = fs.readFileSync(path, "utf8");
-const HYBRIDS = {
- 'L1-only': { label: 'L1 Only', reinvest: 13, payout: 17.17, profit: 4.17, cycles: 9, months: 3.0, daysPer: 19 },
- 'L2+L1': { label: 'L2 + L1', reinvest: 90, payout: 118.91, profit: 28.91, cycles: 4, months: 2.5, daysPer: 19 },
- 'L3+L1': { label: 'L3 + L1', reinvest: 163, payout: 211.57, profit: 48.57, cycles: 4, months: 2.5, daysPer: 19 },
- 'L4+L3': { label: 'L4 + L3', reinvest: 450, payout: 583.20, profit: 133.20,cycles: 5, months: 3.0, daysPer: 19 },
- 'L5+L4': { label: 'L5 + L4', reinvest: 900, payout: 1166.40,profit: 266.40,cycles: 4, months: 2.0, daysPer: 19 },
- 'L6': { label: 'L6', reinvest: 1200, payout: 1555.20,profit: 355.20,cycles: 4, months: 2.0, daysPer: 19 },
- 'L7': { label: 'Single L7', reinvest: 2400, payout: 3240.00,profit: 840.00,cycles: 7, months: 3.0, daysPer: 19 },
- '3xL7': { label: '3× L7 Endgame', reinvest: 7200, payout: 9720, profit: 2520, cycles: 1, months: 0.63, daysPer: 19 },
-};
+const start = html.indexOf("const LEVELS = [");
+const end = html.indexOf("let chart1");
+if (start < 0 || end < 0) { console.error("FATAL: extraction markers missing"); process.exit(1); }
+// 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)(
+ html.slice(start, end) +
+ ";({LEVELS, HYBRIDS, PHASE_SLOTS, computePlan, phaseInfo, getPhaseSequence, estimateMonths, estimateYear1Cum, buildMonthlyTimeline})"
+);
-function getLevelCost(upTo) {
- if (upTo <= 0) return 0;
- return LEVELS.slice(0, upTo).reduce((s, l) => s + l.total, 0);
+let failures = 0;
+function check(label, cond, detail) {
+ if (cond) { console.log(" ok " + label); }
+ else { failures++; console.log(" FAIL " + label + (detail ? " — " + detail : "")); }
}
-function getEffectiveStart(gift, selfF) {
- return Math.max(gift, selfF);
+// ── Independent simulator: same rules, different formulation ────────────────
+function simulate(phases) {
+ const out = {};
+ let bal = 0;
+ const activated = new Set();
+ for (let lv = 1; lv <= Math.max(...PHASE_SLOTS[phases[0]]); lv++) activated.add(lv);
+ for (let i = 0; i < phases.length; i++) {
+ const k = phases[i];
+ if (k === "3xL7") { out[k] = 1; break; }
+ const cur = PHASE_SLOTS[k];
+ const nxt = [...PHASE_SLOTS[phases[i + 1]]];
+ const keep = [];
+ for (const lv of cur) { const j = nxt.indexOf(lv); if (j >= 0) { keep.push(lv); nxt.splice(j, 1); } }
+ let buyIn = 0;
+ for (const lv of nxt) { buyIn += LEVELS[lv - 1].camp + (activated.has(lv) ? 0 : LEVELS[lv - 1].act); activated.add(lv); }
+ let cycles = 0;
+ while (cycles < 500) {
+ cycles++;
+ // payout event for the whole hybrid
+ let cash = bal;
+ for (const lv of cur) cash += LEVELS[lv - 1].payout;
+ // to upgrade now: re-buy only kept campaigns, then afford buy-in
+ const keptCamp = keep.reduce((s, lv) => s + LEVELS[lv - 1].camp, 0);
+ if (cash - keptCamp >= buyIn) { bal = cash - keptCamp - buyIn; break; }
+ // otherwise recycle everything, bank the profit
+ for (const lv of cur) cash -= LEVELS[lv - 1].camp;
+ bal = cash;
+ }
+ out[k] = cycles;
+ }
+ return out;
}
-function getPhaseSequence(effectiveStart) {
- if (effectiveStart >= 5) return ['L5+L4','L6','L7','3xL7'];
- if (effectiveStart >= 4) return ['L4+L3','L5+L4','L6','L7','3xL7'];
- if (effectiveStart >= 3) return ['L3+L1','L4+L3','L5+L4','L6','L7','3xL7'];
- if (effectiveStart >= 2) return ['L2+L1','L3+L1','L4+L3','L5+L4','L6','L7','3xL7'];
- return ['L1-only','L2+L1','L3+L1','L4+L3','L5+L4','L6','L7','3xL7'];
-}
-
-function estimateMonths(phases) {
- let total = 0;
+console.log("1) computePlan vs independent simulator, every start level 0-7:");
+for (let eff = 0; eff <= 7; eff++) {
+ const phases = getPhaseSequence(eff);
+ const plan = computePlan(phases);
+ const sim = simulate(phases);
for (const k of phases) {
- if (k === '3xL7') break;
- total += HYBRIDS[k].months;
+ check(`eff=${eff} ${k}: ${plan[k].cycles}`, plan[k].cycles === sim[k], `sim says ${sim[k]}`);
}
- return total;
}
-function estimateYear1Cum(phases, commPct, giftLevel) {
- let cum = 0;
- let months = 0;
+console.log("2) hybrid payout/profit constants reconcile with LEVELS:");
+for (const [k, slots] of Object.entries(PHASE_SLOTS)) {
+ const p = HYBRIDS[k];
+ const payout = slots.reduce((s, lv) => s + LEVELS[lv - 1].payout, 0);
+ const camp = slots.reduce((s, lv) => s + LEVELS[lv - 1].camp, 0);
+ check(`${k} payout ${p.payout}`, Math.abs(p.payout - payout) < 0.01, `slots say ${payout.toFixed(2)}`);
+ check(`${k} profit ${p.profit}`, Math.abs(p.profit - (payout - camp)) < 0.01, `slots say ${(payout - camp).toFixed(2)}`);
+}
+
+console.log("3) months honest (cycles x 19d / 30.4):");
+{
+ const phases = getPhaseSequence(0);
+ const PI = phaseInfo(phases);
for (const k of phases) {
- if (k === '3xL7') break;
- const p = HYBRIDS[k];
- const yourCut = p.payout * (commPct / 100);
- const cyclesInYear1 = Math.min(p.cycles, Math.max(0, (12 - months) / (p.months / p.cycles)));
- const actual = Math.floor(cyclesInYear1);
- for (let i = 0; i < actual; i++) cum += yourCut;
- months += p.months;
- if (months >= 12) break;
- }
- return Math.round(cum);
-}
-
-function buildSCENARIOS() {
- const levels = [
- { giftLvl: 1, selfLvl: 0, id: 'gift1', label: 'L1 Gift ($14)', yourCost: 14 },
- { giftLvl: 0, selfLvl: 1, id: 'self1', label: 'Self L1 ($14)', yourCost: 0 },
- { giftLvl: 2, selfLvl: 0, id: 'gift2', label: 'L1-L2 Gift ($98)', yourCost: 98 },
- { giftLvl: 0, selfLvl: 2, id: 'self2', label: 'Self L2 ($98)', yourCost: 0 },
- { giftLvl: 3, selfLvl: 0, id: 'gift3', label: 'L1-L3 Gift ($263)', yourCost: 263},
- { giftLvl: 0, selfLvl: 3, id: 'self3', label: 'Self L3 ($263)', yourCost: 0 },
- { giftLvl: 4, selfLvl: 0, id: 'gift4', label: 'L1-L4 Gift ($593)', yourCost: 593},
- { giftLvl: 0, selfLvl: 4, id: 'self4', label: 'Self L4 ($593)', yourCost: 0 },
- { giftLvl: 5, selfLvl: 0, id: 'gift5', label: 'L1-L5 Gift ($1,253)', yourCost:1253},
- { giftLvl: 0, selfLvl: 5, id: 'self5', label: 'Self L5 ($1,253)', yourCost: 0 },
- ];
- return levels.map(function(s) {
- const eff = Math.max(s.giftLvl, s.selfLvl);
- const phases = getPhaseSequence(eff);
- const t = Math.round(estimateMonths(phases));
- const y = estimateYear1Cum(phases, 10, s.giftLvl);
- const eg = phases.filter(function(k) { return k !== '3xL7'; })
- .reduce(function(sum, k) { return sum + HYBRIDS[k].payout * 0.10 * HYBRIDS[k].cycles; }, 0);
- return { id: s.id, label: s.label, yourCost: s.yourCost, time: t, yr1: Math.round(y), totalEG: Math.round(eg), monthly: 1860 };
- });
-}
-
-const SCENARIOS = buildSCENARIOS();
-
-console.log("=== SCENARIOS ===");
-for (var i = 0; i < SCENARIOS.length; i++) {
- var s = SCENARIOS[i];
- console.log(s.id + ": time=" + s.time + "mo, yr1=$" + s.yr1 + ", totalEG=$" + s.totalEG);
-}
-
-// Verify consistency: rebuild should give same result
-var r = buildSCENARIOS();
-var ok = true;
-for (var i = 0; i < SCENARIOS.length; i++) {
- if (SCENARIOS[i].time !== r[i].time || SCENARIOS[i].yr1 !== r[i].yr1) {
- console.log("MISMATCH at " + SCENARIOS[i].id);
- ok = false;
+ if (k === "3xL7") continue;
+ const want = Math.round(PI[k].cycles * 19 / 30.4 * 10) / 10;
+ check(`${k} months ${PI[k].months}`, PI[k].months === want, `want ${want}`);
}
}
-if (ok) console.log("=== ALL CONSISTENT ===");
\ No newline at end of file
+
+console.log("4) anchor values (hand-derived, reserve model with carryover):");
+{
+ const plan = computePlan(getPhaseSequence(0));
+ check("L1-only from scratch = 21 cycles", plan["L1-only"].cycles === 21, `got ${plan["L1-only"].cycles}`);
+ const plan7 = computePlan(getPhaseSequence(7));
+ check("L7 start -> 3xL7 = 6 cycles (extra lanes camp-only)", plan7["L7"].cycles === 6, `got ${plan7["L7"].cycles}`);
+}
+
+console.log("5) Year-1 includes endgame cycles for funded starts:");
+{
+ const y = estimateYear1Cum(getPhaseSequence(7), 10, 7);
+ // 6 L7 cycles (114d) at $324 + floor(251/19)=13 endgame cycles at $972
+ check(`gift7 year1 = ${y}`, y === Math.round(6 * 324 + 13 * 972), `want ${6 * 324 + 13 * 972}`);
+}
+
+console.log("6) timeline months advance ~19d per cycle (not 1 month per cycle):");
+{
+ const rows = buildMonthlyTimeline(getPhaseSequence(4), 10);
+ const last = rows[rows.length - 1];
+ const cycles = rows.length;
+ check(`last month ${last.m} ≈ cycles*19/30.4 (${Math.ceil(cycles * 19 / 30.4)})`,
+ last.m === Math.ceil(cycles * 19 / 30.4), `rows=${cycles}`);
+}
+
+console.log(failures === 0 ? "\nALL PASS" : `\n${failures} FAILURE(S)`);
+process.exit(failures === 0 ? 0 : 1);