Guest User

Untitled

a guest
Oct 21st, 2026
28
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // Simplifications versus the shrine are in README.md next to this file.
  2. const UPGRADE_BASE = {
  3.     0: { 1: 0.9999999, 2: 0.98, 3: 0.95, 4: 0.7, 5: 0.6, 6: 0.4, 7: 0.25, 8: 0.15, 9: 0.07, 10: 0.024, 11: 0.14, 12: 0.11 },
  4.     1: { 1: 0.99998, 2: 0.97, 3: 0.94, 4: 0.68, 5: 0.58, 6: 0.38, 7: 0.24, 8: 0.14, 9: 0.066, 10: 0.018, 11: 0.13, 12: 0.10 },
  5.     2: { 1: 0.97, 2: 0.94, 3: 0.92, 4: 0.64, 5: 0.52, 6: 0.32, 7: 0.232, 8: 0.13, 9: 0.062, 10: 0.015, 11: 0.12, 12: 0.09 }
  6. };
  7. const COMPOUND_BASE = {
  8.     0: { 1: 0.99, 2: 0.75, 3: 0.40, 4: 0.25, 5: 0.20, 6: 0.10, 7: 0.08, 8: 0.05, 9: 0.05, 10: 0.05 },
  9.     1: { 1: 0.90, 2: 0.70, 3: 0.40, 4: 0.20, 5: 0.15, 6: 0.08, 7: 0.05, 8: 0.05, 9: 0.05, 10: 0.03 },
  10.     2: { 1: 0.80, 2: 0.60, 3: 0.32, 4: 0.16, 5: 0.10, 6: 0.05, 7: 0.03, 8: 0.03, 9: 0.03, 10: 0.02 }
  11. };
  12. const DEFAULT_GRADES = [9, 10, 11, 12];
  13. const HELMET_GRADES = [ 7, 9, 10, 12 ];
  14. const HELMET_BASE_COST = 3200;
  15. const MAX_FAILSTACK = 18;
  16. const MAX_PRIMESTACK = 30;
  17. const STAT_SCROLL_QTY = [1, 10, 100, 1000, 9999, 9999, 9999];
  18. const CALC_VERSION = 8;
  19.  
  20. function example() {
  21.     const item = { grades: [0,8,10,12], cost: 200000 };
  22.     const targetLevel = 12;
  23.     let costs = {
  24.         priming: 5000000,
  25.         scroll: [1000,40000,1600000,300000000,Infinity],
  26.         offering: [0,5000000,27420000,650000000]
  27.     };
  28.     costs = stacks_calculate(costs);
  29.     const use_lucky_slot = true;
  30.     const use_server_ugrace = false;
  31.     // const overrides = { 8: { offering_grade: 1 } };
  32.     const overrides = null;
  33.     const result = timing("upgrade_calc", () => upgrade_calc(item, targetLevel, { costs, use_lucky_slot, use_server_ugrace, overrides }));
  34.     console.log(result);
  35.  
  36.     // const compoundCosts = { ...costs, scroll: [6400, 240000, 9200000, 1840000000] };
  37.     // const compoundResult = timing("compound_calc", () => compound_calc(
  38.     //     { grades: [3, 5, 6, 7], cost: 1000 },
  39.     //     7,
  40.     //     { costs: compoundCosts }
  41.     // ));
  42.     // console.log(compoundResult);
  43. };
  44.  
  45. function statScrollQty(grade) {
  46.     var n = STAT_SCROLL_QTY[grade];
  47.     return n > 0 ? n : 9999;
  48. }
  49.  
  50. // One stat-scroll click is +1 grace, the same as two +0.5 primes, and it
  51. // spends one Primling plus statScrollQty(grade) Dexterity Scrolls.
  52. function primingSpend(units, itemGrade, costs, canStat) {
  53.     units = units || 0;
  54.     var single = costs.priming;
  55.     var statClicks = 0;
  56.     var normal = units;
  57.     var each = 0;
  58.     var scroll = costs.statScroll;
  59.     if (canStat && units >= 2 && isFinite(single) && isFinite(scroll) && scroll >= 0) {
  60.         each = statScrollQty(itemGrade);
  61.         var pair = single + each * scroll;
  62.         if (pair <= single * 2) {
  63.             statClicks = Math.floor(units / 2);
  64.             normal = units % 2;
  65.         } else {
  66.             each = 0;
  67.         }
  68.     }
  69.     var cost = 0;
  70.     if (units) {
  71.         if (!isFinite(single)) cost = Infinity;
  72.         else cost = statClicks * (single + each * scroll) + normal * single;
  73.     }
  74.     return {
  75.         statClicks: statClicks,
  76.         normalPrimes: normal,
  77.         statScrollEach: statClicks ? each : 0,
  78.         cost: cost
  79.     };
  80. }
  81.  
  82. function upgrade_calc(
  83.     { grades: item_grades, cost: item_cost, stat },
  84.     target_level,
  85.     { costs, use_lucky_slot = true, use_server_ugrace = false, overrides }) {
  86.  
  87.     const igrade = innateIgrade(item_grades);
  88.     const igrace = igraceOf(igrade);
  89.     const max_level = item_grades[3];
  90.     target_level = Math.min(target_level, max_level);
  91.     const item_grace_cap = target_level + 1;
  92.  
  93.     let last = [ { item_grace: igrace, cost: item_cost} ];
  94.     for (let level_to = 1; level_to <= target_level; level_to++) {
  95.         const base_chance = UPGRADE_BASE[igrade][level_to];
  96.         const item_grade = itemGrade(item_grades, level_to - 1);
  97.         const static_options = { level_to, base_chance, item_grade };
  98.         const level_overrides = overrides?.[level_to] || {};
  99.  
  100.         const p1 = pruner();
  101.         for (const parent of last) {
  102.             let minPriming = level_overrides.priming != null
  103.                 ? level_overrides.priming
  104.                 : 0;
  105.             let maxPriming = level_overrides.priming != null
  106.                 ? level_overrides.priming
  107.                 : Math.max(0, Math.ceil(2 * (item_grace_cap - parent.item_grace) - 1e-9));
  108.             for (let priming = minPriming; priming <= maxPriming; priming++) {
  109.                 const spend = primingSpend(priming, item_grade, costs, stat);
  110.                 const itemGrace = Math.min(item_grace_cap, parent.item_grace + priming * 0.5);
  111.                 const cost = parent.cost + spend.cost;
  112.                 p1.get_set(itemGrace, cost, () => ({
  113.                     parent,
  114.                     priming,
  115.                     primingCost: spend.cost,
  116.                     statClicks: spend.statClicks,
  117.                     normalPrimes: spend.normalPrimes,
  118.                     statScrollEach: spend.statScrollEach,
  119.                     cost,
  120.                     item_grace: itemGrace
  121.                 }));
  122.             }
  123.         }
  124.         const primedStates = p1.pareto();
  125.  
  126.         const minScrollGrade = level_overrides.scroll_grade != null ? level_overrides.scroll_grade : item_grade;
  127.         const maxScrollGrade = level_overrides.scroll_grade != null ? level_overrides.scroll_grade : 4;
  128.         const minOfferingGrade = level_overrides.offering_grade != null ? level_overrides.offering_grade : 0;
  129.         const maxOfferingGrade = level_overrides.offering_grade != null ? level_overrides.offering_grade : 3;
  130.         const minFailstack = level_overrides.failstack != null ? level_overrides.failstack : 0;
  131.         const maxFailstack = level_overrides.failstack != null ? level_overrides.failstack : MAX_FAILSTACK;
  132.         const minPrimestack = level_overrides.primestack != null ? level_overrides.primestack : 0;
  133.         const maxPrimestack = level_overrides.primestack != null ? level_overrides.primestack : costs.primestack.length - 1;
  134.  
  135.         const p2 = pruner();
  136.         for (const { parent, priming, primingCost, statClicks, normalPrimes, statScrollEach } of primedStates) {
  137.             for (let scroll_grade = minScrollGrade; scroll_grade <= maxScrollGrade; scroll_grade++) {
  138.                 for (let offering_grade = minOfferingGrade; offering_grade <= maxOfferingGrade; offering_grade++) {
  139.                     for (let failstack = minFailstack; failstack <= maxFailstack; failstack++) {
  140.                         for (let primestack = minPrimestack; primestack <= maxPrimestack; primestack++) {
  141.                             const options = {
  142.                                 scroll_grade, offering_grade, priming, primingCost,
  143.                                 statClicks, normalPrimes, statScrollEach,
  144.                                 failstack, primestack
  145.                             };
  146.                             const result = upgrade(static_options, parent.item_grace, options, { use_lucky_slot, use_server_ugrace });
  147.                             result.item_grace = Math.min(result.item_grace, item_grace_cap);
  148.                             const { cost, breakdown } = calculate_cost(static_options, options, result, parent.cost, costs);
  149.  
  150.                             const best = p2.get_set(result.item_grace, cost, () => ({ parent, ...static_options, ...options, ...result, cost, breakdown }));
  151.                             const lowerBound = breakdown.fixed_cost + breakdown.attempt_cost / result.chance_cap;
  152.                             if (lowerBound >= best.cost) break;
  153.                             if (result.chance >= result.chance_cap) break;
  154.                         }
  155.                     }
  156.                 }
  157.             }
  158.         }
  159.  
  160.         last = p2.pareto();
  161.     }
  162.     last.sort((a, b) => a.cost - b.cost);
  163.    
  164.     let current = last[0];
  165.     const result = [ ]
  166.     while (current) {
  167.         const parent = current.parent;
  168.         delete current.parent;
  169.         result.push(current);
  170.         current = parent;
  171.     }
  172.     return result.reverse();
  173. }
  174.  
  175. function pruner() {
  176.     const bestByGrace = new Map();
  177.     function get_set(item_grace, cost, getVal) {
  178.         const grace10 = Math.round(item_grace * 10);
  179.         let old = bestByGrace.get(grace10);
  180.         if (!old || cost < old.cost) {
  181.             const val = getVal();
  182.             bestByGrace.set(grace10, val);
  183.             return val;
  184.         }
  185.         return old;
  186.     }
  187.     function pareto() {
  188.         const current = [...bestByGrace.values()];
  189.         current.sort((a, b) => b.item_grace - a.item_grace);
  190.         let bestCost = Infinity;
  191.         const frontier = [];
  192.         for (const state of current) {
  193.             if (state.cost < bestCost) {
  194.                 frontier.push(state);
  195.                 bestCost = state.cost;
  196.             }
  197.         }
  198.         return frontier;
  199.     }
  200.     return { get_set, pareto };
  201. }
  202.  
  203. function calculate_cost(static_options, options, result, parent_cost, costs) {
  204.     const { scroll_grade, offering_grade, priming, failstack, primestack } = options;
  205.     const priming_cost = typeof options.primingCost === "number"
  206.         ? options.primingCost
  207.         : (priming ? costs.priming * priming : 0);
  208.     const statClicks = options.statClicks || 0;
  209.     const normalPrimes = options.normalPrimes != null ? options.normalPrimes : (priming || 0);
  210.     const statEach = options.statScrollEach || 0;
  211.     const stat_scroll_cost = statClicks * statEach * (costs.statScroll || 0);
  212.     const primling_cost = (statClicks + normalPrimes) * (isFinite(costs.priming) ? costs.priming : 0);
  213.     const base_cost = parent_cost + priming_cost;
  214.     const scroll_cost = costs.scroll[scroll_grade];
  215.     const offering_cost = costs.offering[offering_grade];
  216.     const failstack_cost = failstack && costs.failstack[static_options.level_to][failstack].cost;
  217.     const primestack_cost = primestack && costs.primestack[primestack].cost;
  218.  
  219.     let attempt_cost = 0;
  220.     let fixed_cost = 0;
  221.     if (scroll_grade == 4) { // special case - item is not destroyed on failure
  222.         attempt_cost = scroll_cost + offering_cost;
  223.         fixed_cost = base_cost + failstack_cost + primestack_cost;
  224.     } else {
  225.         attempt_cost = base_cost + scroll_cost + offering_cost;
  226.         fixed_cost = failstack_cost + primestack_cost;
  227.     }
  228.  
  229.     const cost = fixed_cost + attempt_cost / result.chance;
  230.     const breakdown = {
  231.         parent_cost,
  232.         priming_cost,
  233.         primling_cost,
  234.         stat_scroll_cost,
  235.         stat_clicks: statClicks,
  236.         normal_primes: normalPrimes,
  237.         stat_scroll_each: statEach,
  238.         scroll_cost,
  239.         offering_cost,
  240.         failstack_cost,
  241.         failstack: failstack && costs.failstack[static_options.level_to][failstack],
  242.         primestack_cost,
  243.         primestack: primestack && costs.primestack[primestack],
  244.         fixed_cost,
  245.         attempt_cost,
  246.         chance: result.chance
  247.     }
  248.  
  249.     return { cost, breakdown };
  250. }
  251.  
  252. function upgrade(
  253.     { level_to, base_chance, item_grade },
  254.     item_grace,
  255.     { scroll_grade, offering_grade, priming, failstack, primestack },
  256.     { use_lucky_slot, use_server_ugrace }) {
  257.  
  258.     item_grace += priming * 0.5;
  259.  
  260.     const item_grace_cap = level_to + 1;
  261.     const raw_ugrace = Math.min(3, failstack / 4.5);
  262.     const raw_server_ugrace = use_server_ugrace ? Math.min(6, failstack / 3.0) : 0;
  263.     const raw_ograce = primestack * 0.6 / 3.2;
  264.  
  265.     var chance = base_chance;
  266.     var grace = Math.max(0, Math.min(item_grace_cap, item_grace + raw_ugrace) + raw_server_ugrace + raw_ograce);
  267.     grace = (chance * grace) / level_to + grace / 1000.0;
  268.    
  269.     if (scroll_grade > item_grade && level_to <= 10) {
  270.         chance = chance * 1.2 + 0.01;
  271.         item_grace += 0.4;
  272.     }
  273.  
  274.     if (offering_grade) {
  275.         if (offering_grade > item_grade + 1) {
  276.             chance = chance * 1.7 + grace * 4;
  277.             item_grace += 3;
  278.         } else if (offering_grade > item_grade) {
  279.             chance = chance * 1.5 + grace * 1.2;
  280.             item_grace += 1;
  281.         } else if (offering_grade == item_grade) {
  282.             chance = chance * 1.4 + grace;
  283.             item_grace += 0.4;
  284.         } else if (offering_grade == item_grade - 1) {
  285.             chance = chance * 1.15 + grace / 3.2;
  286.             item_grace += 0.2;
  287.         } else {
  288.             chance = chance * 1.08 + grace / 4;
  289.             item_grace += 0.1;
  290.         }
  291.     } else {
  292.         grace = Math.max(0, grace / 4.8 - 0.4 / ((level_to - 0.999) * (level_to - 0.999)));
  293.         chance += grace;
  294.     }
  295.  
  296.     let chance_cap = (scroll_grade > item_grade && level_to <= 10 || offering_grade > item_grade)
  297.         ? Math.min(base_chance + 0.36, base_chance * 3)
  298.         : Math.min(base_chance + 0.24, base_chance * 2);
  299.     chance_cap = Math.min(chance_cap, 1);
  300.     chance = Math.min(chance, chance_cap);
  301.  
  302.     function adjust_for_lucky_slot(p) {
  303.         if (p <= 0) return 0;
  304.         if (p >= 1) return 1;
  305.         let transformed;
  306.         if (p < 0.0001) transformed = (p * 10000) * ((p + 0.012) / 0.975);
  307.         else if (p < 0.963) transformed = (p + 0.012) / 0.975;
  308.         else transformed = 1;
  309.         return 0.4 * p + 0.6 * transformed;
  310.     }
  311.     if (use_lucky_slot) {
  312.         chance = adjust_for_lucky_slot(chance);
  313.         chance_cap = adjust_for_lucky_slot(chance_cap);
  314.     }
  315.  
  316.     return { chance, item_grace, chance_cap };
  317. }
  318.  
  319. function compound_calc(
  320.     { grades: item_grades, cost: item_cost, igrade },
  321.     target_level,
  322.     { costs, overrides }) {
  323.  
  324.     const innate = igrade != null ? igrade : innateIgrade(item_grades);
  325.     const max_level = Math.min(item_grades[3], 10);
  326.     target_level = Math.min(target_level, max_level);
  327.  
  328.     let last = [ { item_grace: 0, cost: item_cost } ];
  329.     for (let level_to = 1; level_to <= target_level; level_to++) {
  330.         const level = level_to - 1;
  331.         const row = level >= 3 ? itemGrade(item_grades, level - 2) : innate;
  332.         const base_chance = (COMPOUND_BASE[row] || COMPOUND_BASE[2])[level_to];
  333.         const item_grade = itemGrade(item_grades, level);
  334.         const static_options = { level_to, base_chance, item_grade };
  335.         const level_overrides = overrides?.[level_to] || {};
  336.  
  337.         const minScrollGrade = level_overrides.scroll_grade != null ? level_overrides.scroll_grade : item_grade;
  338.         const maxScrollGrade = level_overrides.scroll_grade != null ? level_overrides.scroll_grade : 3;
  339.         const minOfferingGrade = level_overrides.offering_grade != null ? level_overrides.offering_grade : 0;
  340.         const maxOfferingGrade = level_overrides.offering_grade != null ? level_overrides.offering_grade : 3;
  341.         const minPrimestack = level_overrides.primestack != null ? level_overrides.primestack : 0;
  342.         const maxPrimestack = level_overrides.primestack != null ? level_overrides.primestack : costs.primestack.length - 1;
  343.  
  344.         const p2 = pruner();
  345.         for (const parent of last) {
  346.             for (let scroll_grade = minScrollGrade; scroll_grade <= maxScrollGrade; scroll_grade++) {
  347.                 for (let offering_grade = minOfferingGrade; offering_grade <= maxOfferingGrade; offering_grade++) {
  348.                     for (let primestack = minPrimestack; primestack <= maxPrimestack; primestack++) {
  349.                         const options = { scroll_grade, offering_grade, primestack };
  350.                         const result = compound(static_options, parent.item_grace, options);
  351.                         const { cost, breakdown } = calculate_compound_cost(static_options, options, result, parent.cost, costs);
  352.  
  353.                         const best = p2.get_set(result.item_grace, cost, () => ({ parent, ...static_options, ...options, ...result, copies: 3, cost, breakdown }));
  354.                         const lowerBound = breakdown.fixed_cost + breakdown.attempt_cost / result.chance_cap;
  355.                         if (lowerBound >= best.cost) break;
  356.                         if (result.chance >= result.chance_cap) break;
  357.                     }
  358.                 }
  359.             }
  360.         }
  361.         last = p2.pareto();
  362.     }
  363.     last.sort((a, b) => a.cost - b.cost);
  364.  
  365.     let current = last[0];
  366.     const result = [ ]
  367.     while (current) {
  368.         const parent = current.parent;
  369.         delete current.parent;
  370.         result.push(current);
  371.         current = parent;
  372.     }
  373.     return result.reverse();
  374. }
  375.  
  376. function calculate_compound_cost(static_options, { scroll_grade, offering_grade, primestack }, result, parent_cost, costs) {
  377.     const scroll_cost = costs.scroll[scroll_grade];
  378.     const offering_cost = costs.offering[offering_grade];
  379.     const primestack_cost = primestack && costs.primestack[primestack].cost;
  380.     const attempt_cost = 3 * parent_cost + scroll_cost + offering_cost;
  381.     const fixed_cost = primestack_cost;
  382.     const cost = fixed_cost + attempt_cost / result.chance;
  383.     const breakdown = {
  384.         parent_cost,
  385.         scroll_cost,
  386.         offering_cost,
  387.         primestack_cost,
  388.         primestack: primestack && costs.primestack[primestack],
  389.         fixed_cost,
  390.         attempt_cost,
  391.         chance: result.chance
  392.     }
  393.     return { cost, breakdown };
  394. }
  395.  
  396. function compound(
  397.     { level_to, base_chance, item_grade },
  398.     item_grace,
  399.     { scroll_grade, offering_grade, primestack }) {
  400.  
  401.     const level = level_to - 1;
  402.     const ograce = primestack * 0.6;
  403.     const grace_sum = item_grace * 3;
  404.  
  405.     var chance = base_chance;
  406.     var high = false;
  407.  
  408.     item_grace = (offering_grade ? grace_sum : item_grace) / 6.4;
  409.  
  410.     if (scroll_grade > item_grade) {
  411.         chance = chance * 1.1 + 0.001;
  412.         item_grace += 0.4;
  413.         high = scroll_grade - item_grade;
  414.     }
  415.  
  416.     if (offering_grade) {
  417.         var grace = 0.027 * (grace_sum + 0.5 + ograce);
  418.         if (offering_grade > item_grade + 1) {
  419.             chance = chance * 1.64 + grace * 2;
  420.             high = true;
  421.             item_grace += 3;
  422.         } else if (offering_grade > item_grade) {
  423.             chance = chance * 1.48 + grace;
  424.             high = true;
  425.             item_grace += 1;
  426.         } else if (offering_grade == item_grade) {
  427.             chance = chance * 1.36 + Math.min(30 * 0.027, grace);
  428.             item_grace += 0.5;
  429.         } else if (offering_grade == item_grade - 1) {
  430.             chance = chance * 1.15 + Math.min(25 * 0.019, grace) / Math.max(level - 2, 1);
  431.             item_grace += 0.2;
  432.         } else {
  433.             chance = chance * 1.08 + Math.min(15 * 0.015, grace) / Math.max(level - 1, 1);
  434.             item_grace += 0.1;
  435.         }
  436.     } else {
  437.         var raw = 0.007 * (grace_sum + ograce);
  438.         chance = chance + Math.min(25 * 0.007, raw) / Math.max(level - 1, 1);
  439.     }
  440.  
  441.     let chance_cap = Math.min(
  442.         base_chance * (3 + ((high && high * 0.6) || 0)),
  443.         base_chance + 0.2 + ((high && high * 0.05) || 0)
  444.     );
  445.     chance_cap = Math.min(chance_cap, 1);
  446.     chance = Math.min(chance, chance_cap);
  447.  
  448.     return { chance, item_grace, chance_cap };
  449. }
  450.  
  451. //
  452. // Failstack and Primestack Calculation
  453. //
  454.  
  455. function stacks_calculate(costs) {
  456.     const calculated = cache("stacks_calculate", { priming: costs.priming, scroll: costs.scroll, offering: costs.offering }, () => {
  457.         const stackCosts = stacking_precalculate(costs);
  458.         const seed = {
  459.             failstack: stacking_failstack(stackCosts),
  460.             primestack: stacking_primestack(stackCosts)
  461.         };
  462.         const plan = upgrade_calc(
  463.             { grades: HELMET_GRADES, cost: HELMET_BASE_COST },
  464.             HELMET_GRADES[3],
  465.             { costs: { ...costs, ...seed }, use_lucky_slot: true, use_server_ugrace: false }
  466.         );
  467.         // Chances stay on the unlucky matching-scroll click. The plan replaces the helmet that click destroys.
  468.         for (let i = 1; i < stackCosts.length; i++) {
  469.             const grade = itemGrade(HELMET_GRADES, i - 1);
  470.             const copy = plan[i - 1].cost;
  471.             stackCosts[i].lucky_cost = plan[i].cost;
  472.             stackCosts[i].failstack_cost = copy + costs.scroll[grade];
  473.             stackCosts[i].primestack_attempt_cost = copy + costs.scroll[grade] + costs.offering[1];
  474.         }
  475.         return {
  476.             failstack: stacking_failstack(stackCosts),
  477.             primestack: stacking_primestack(stackCosts),
  478.             helmetTime: helmetLevelTimes(plan)
  479.         };
  480.     });
  481.     return { ...costs, ...calculated };
  482. }
  483. function stacking_precalculate(costs) {
  484.     const item_grades = HELMET_GRADES;
  485.     const base_item_cost = HELMET_BASE_COST;
  486.  
  487.     const igrade = innateIgrade(item_grades);
  488.     const igrace = igraceOf(igrade);
  489.     const max_level = item_grades[3];
  490.  
  491.     const results = [ { lucky_cost: base_item_cost } ];
  492.    
  493.     for (let i = 1; i <= max_level; i++) {
  494.         const prev = results[i - 1];
  495.    
  496.         const item_grade = itemGrade(item_grades, i - 1);
  497.         const static_options = { level_to: i, base_chance: UPGRADE_BASE[igrade][i], item_grade };
  498.         const options = { scroll_grade: item_grade, offering_grade: 0, priming: 0, failstack: 0, primestack: 0 };
  499.        
  500.         const lucky_result = upgrade(static_options, igrace, options, { use_lucky_slot: true });
  501.         const lucky_cost = calculate_cost(static_options, options, lucky_result, prev.lucky_cost, costs).cost;
  502.        
  503.         const unlucky_result = upgrade(static_options, igrace, options, { use_lucky_slot: false });
  504.         const failstack_attempt_cost = prev.lucky_cost + costs.scroll[item_grade];
  505.        
  506.         const primestack_result = upgrade(static_options, igrace, { ...options, offering_grade: 1 }, { use_lucky_slot: false });
  507.         const primestack_attempt_cost = prev.lucky_cost + costs.scroll[item_grade] + costs.offering[1];
  508.        
  509.         results.push({ lucky_cost, failstack_chance: unlucky_result.chance, failstack_cost: failstack_attempt_cost, primestack_chance: primestack_result.chance, primestack_attempt_cost });
  510.     }
  511.     return results;
  512. }
  513. // Personal ugrace from one failed upgrade to `levelTo`, no offering.
  514. // Server ugrace is priced as these same amounts. A success clears only index `levelTo`.
  515. function failUgraceGains(levelTo) {
  516.     const gain = new Map();
  517.     const add = (level, n) => {
  518.         if (level < 0) return;
  519.         gain.set(level, (gain.get(level) || 0) + n);
  520.     };
  521.     add(levelTo, 1);
  522.     add(levelTo - 1, 1);
  523.     if (levelTo >= 8 && levelTo <= 15) {
  524.         add(levelTo - 1, 1);
  525.         add(levelTo - 2, 2);
  526.         add(levelTo - 3, 2);
  527.     }
  528.     return gain;
  529. }
  530.  
  531. function streakAttempts(failChance, failures) {
  532.     if (failures <= 0) return 0;
  533.     if (failChance >= 1) return failures;
  534.     if (!(failChance > 0)) return Infinity;
  535.     const p = failChance;
  536.     return (1 - Math.pow(p, failures)) / ((1 - p) * Math.pow(p, failures));
  537. }
  538.  
  539. function splashPlans(sources, maxUGrace) {
  540.     const plans = new Array(maxUGrace + 1).fill(null);
  541.     if (!sources.length) return plans;
  542.     const maxGain = sources.reduce((max, src) => Math.max(max, src.gain), 0);
  543.     const limit = maxUGrace + maxGain;
  544.     const dp = new Array(limit + 1).fill(null);
  545.     dp[0] = { cost: 0, counts: Object.create(null) };
  546.     for (let g = 0; g <= limit; g++) {
  547.         const cur = dp[g];
  548.         if (!cur) continue;
  549.         for (const src of sources) {
  550.             const next = g + src.gain;
  551.             if (next > limit) continue;
  552.             const cost = cur.cost + src.attemptCost / src.q;
  553.             if (!dp[next] || cost < dp[next].cost) {
  554.                 const counts = Object.assign(Object.create(null), cur.counts);
  555.                 counts[src.level] = (counts[src.level] || 0) + 1;
  556.                 dp[next] = { cost, counts };
  557.             }
  558.         }
  559.     }
  560.     let best = null;
  561.     for (let g = limit; g >= 1; g--) {
  562.         if (dp[g] && (!best || dp[g].cost < best.cost)) best = dp[g];
  563.         if (g <= maxUGrace) plans[g] = best;
  564.     }
  565.     return plans;
  566. }
  567.  
  568. function farmsFromCounts(counts, byLevel, attemptsOf) {
  569.     const farms = [];
  570.     for (const key of Object.keys(counts)) {
  571.         const level = Number(key);
  572.         const failures = counts[key];
  573.         const action = byLevel[level];
  574.         farms.push({
  575.             level,
  576.             gain: action.gain,
  577.             failures,
  578.             attempts: attemptsOf(action, failures)
  579.         });
  580.     }
  581.     farms.sort((a, b) => a.level - b.level);
  582.     return farms;
  583. }
  584.  
  585. function finishFailstack(j, farms) {
  586.     let attempts = 0;
  587.     let cost = 0;
  588.     let level = null;
  589.     let most = -1;
  590.     for (const farm of farms) {
  591.         attempts += farm.attempts;
  592.         if (farm.attempts > most) {
  593.             most = farm.attempts;
  594.             level = farm.level;
  595.         }
  596.     }
  597.     return { level, ugrace: j, attempts, cost, farms };
  598. }
  599.  
  600. function priceFailstack(direct, splash, byLevel, j) {
  601.     let farms = null;
  602.     let best = Infinity;
  603.     const splashPlan = splash && splash[j];
  604.     if (splashPlan && Number.isFinite(splashPlan.cost)) {
  605.         best = splashPlan.cost;
  606.         farms = farmsFromCounts(splashPlan.counts, byLevel, (action, failures) => failures / action.q);
  607.     }
  608.     if (direct) {
  609.         const failures = Math.ceil(j / direct.gain);
  610.         const attempts = streakAttempts(direct.q, failures);
  611.         const cost = attempts * direct.attemptCost;
  612.         if (Number.isFinite(cost) && cost < best) {
  613.             best = cost;
  614.             farms = [{ level: direct.level, gain: direct.gain, failures, attempts }];
  615.         }
  616.     }
  617.     if (!farms) {
  618.         return { level: null, ugrace: j, attempts: Infinity, cost: Infinity, farms: [] };
  619.     }
  620.     const priced = finishFailstack(j, farms);
  621.     priced.cost = best;
  622.     return priced;
  623. }
  624.  
  625. function stacking_failstack(stackCosts, maxUGrace = MAX_FAILSTACK) {
  626.     const farmLevels = [];
  627.     for (let level = 1; level < stackCosts.length; level++) {
  628.         const row = stackCosts[level];
  629.         const q = 1 - row.failstack_chance;
  630.         if (!(q > 0) || !Number.isFinite(row.failstack_cost)) continue;
  631.         farmLevels.push({
  632.             level,
  633.             q,
  634.             attemptCost: row.failstack_cost,
  635.             gains: failUgraceGains(level)
  636.         });
  637.     }
  638.  
  639.     const results = [[]];
  640.     for (let target = 1; target < stackCosts.length; target++) {
  641.         const splash = [];
  642.         const byLevel = Object.create(null);
  643.         let direct = null;
  644.         for (const farm of farmLevels) {
  645.             const gain = farm.gains.get(target) || 0;
  646.             if (!gain) continue;
  647.             const action = { level: farm.level, gain, q: farm.q, attemptCost: farm.attemptCost };
  648.             byLevel[farm.level] = action;
  649.             if (farm.level === target) direct = action;
  650.             else splash.push(action);
  651.         }
  652.         const splashTable = splashPlans(splash, maxUGrace);
  653.         const level_results = [{ level: target, ugrace: 0, attempts: 0, cost: 0, farms: [] }];
  654.         for (let j = 1; j <= maxUGrace; j++) {
  655.             level_results.push(priceFailstack(direct, splashTable, byLevel, j));
  656.         }
  657.         results.push(level_results);
  658.     }
  659.     return results;
  660. }
  661. function stacking_primestack(stackCosts, maxOGrace = MAX_PRIMESTACK) {
  662.     const step = 0.6;
  663.  
  664.     let actions = stackCosts
  665.         .map((x, level) => x && ({
  666.             level,
  667.             chance: x.primestack_chance,
  668.             attemptCost: x.primestack_attempt_cost
  669.         }))
  670.         .filter(x => x && x.chance < 1 && Number.isFinite(x.attemptCost));
  671.  
  672.     // Remove levels that are both more expensive and more likely to succeed.
  673.     actions = actions.filter(a =>
  674.         !actions.some(b =>
  675.             b !== a &&
  676.             b.attemptCost <= a.attemptCost &&
  677.             b.chance <= a.chance &&
  678.             (b.attemptCost < a.attemptCost || b.chance < a.chance)
  679.         )
  680.     );
  681.  
  682.     function expectedAttempts(target, p) {
  683.         if (target <= 0) return 0;
  684.  
  685.         // Finer internal resolution because success produces grace * 0.25.
  686.         const resolution = 0.05;
  687.         const n = Math.ceil(target / resolution);
  688.         const v = new Float64Array(n);
  689.  
  690.         const q = 1 - p;
  691.         const failures = Math.ceil(target / step);
  692.         const initial = (1 - Math.pow(q, failures)) / (p * Math.pow(q, failures));
  693.         v.fill(initial);
  694.  
  695.         function get(g) {
  696.             if (g >= target) return 0;
  697.  
  698.             const x = g / resolution;
  699.             const lo = Math.min(Math.floor(x), n - 1);
  700.             const hi = Math.min(lo + 1, n - 1);
  701.             const f = x - lo;
  702.  
  703.             return v[lo] * (1 - f) + v[hi] * f;
  704.         }
  705.  
  706.         for (let iteration = 0; iteration < 300; iteration++) {
  707.             let maxChange = 0;
  708.  
  709.             for (let i = n - 1; i >= 0; i--) {
  710.                 const g = i * resolution;
  711.                 const old = v[i];
  712.  
  713.                 const next =
  714.                     1 +
  715.                     p * get(g * 0.25) +
  716.                     (1 - p) * get(g + step);
  717.  
  718.                 v[i] = next;
  719.  
  720.                 const change = Math.abs(next - old);
  721.                 if (change > maxChange) maxChange = change;
  722.             }
  723.  
  724.             if (maxChange < 1e-5) break;
  725.         }
  726.  
  727.         return v[0];
  728.     }
  729.  
  730.     const count = Math.floor(maxOGrace / step);
  731.     const result = [{
  732.         ograce: 0,
  733.         level: null,
  734.         attempts: 0,
  735.         cost: 0
  736.     }];
  737.  
  738.     for (let i = 1; i <= count; i++) {
  739.         const ograce = i * step;
  740.         let best = null;
  741.  
  742.         for (const action of actions) {
  743.             const attempts = expectedAttempts(ograce, action.chance);
  744.             const cost = attempts * action.attemptCost;
  745.  
  746.             if (!best || cost < best.cost) {
  747.                 best = {
  748.                     ograce,
  749.                     level: action.level,
  750.                     attempts,
  751.                     cost
  752.                 };
  753.             }
  754.         }
  755.  
  756.         result.push(best);
  757.     }
  758.  
  759.     return result;
  760. }
  761.  
  762.  
  763. //
  764. // Utils
  765. //
  766.  
  767. // Mass Production++ is on for every click, so the shrine wait is divided by 10.
  768. function shrineClickMs(mode, levelTo, igrade) {
  769.     if (mode === "compound") return 1000;
  770.     const tmult = igrade >= 2 ? 2 : igrade === 1 ? 1.5 : 1;
  771.     return 500 * levelTo * Math.sqrt(levelTo) * tmult / 10;
  772. }
  773.  
  774. // Expected shrine time to hold one helmet at each level. plan[0] is +0.
  775. function helmetLevelTimes(plan) {
  776.     const times = [0];
  777.     for (let n = 1; n < plan.length; n++) {
  778.         const step = plan[n];
  779.         const click = shrineClickMs("upgrade", step.level_to, 0);
  780.         const p = step.chance;
  781.         const prev = times[n - 1];
  782.         if (!(p > 0) || !isFinite(p) || !isFinite(click) || !isFinite(prev)) {
  783.             times[n] = Infinity;
  784.             continue;
  785.         }
  786.         const clicks = 1 / p;
  787.         times[n] = step.scroll_grade === 4 ? prev + click * clicks : (prev + click) * clicks;
  788.     }
  789.     return times;
  790. }
  791.  
  792. function itemGrade(grades, level) {
  793.     grades = grades || DEFAULT_GRADES;
  794.     level = level || 0;
  795.     if (level >= grades[3]) return 4;
  796.     if (level >= grades[2]) return 3;
  797.     if (level >= grades[1]) return 2;
  798.     if (level >= grades[0]) return 1;
  799.     return 0;
  800. }
  801. function innateIgrade(grades) {
  802.     return itemGrade(grades, 0);
  803. }
  804. function igraceOf(igrade) {
  805.     if (igrade === 0) return 1;
  806.     if (igrade === 1) return -1;
  807.     if (igrade === 2) return -2;
  808.     return 0;
  809. }
  810.  
  811. function cache(name, key, cbValue) {
  812.     const cacheKey = "upgrade_calc_" + name;
  813.     const keyString = JSON.stringify(key);
  814.     const version = CALC_VERSION;
  815.  
  816.     try {
  817.         const cached = JSON.parse(localStorage.getItem(cacheKey));
  818.         if (cached?.key === keyString && cached?.version === version) return cached.result;
  819.     } catch {}
  820.  
  821.     const result = timing(name, () => cbValue());
  822.     try {
  823.         localStorage.setItem(cacheKey, JSON.stringify({ key: keyString, version, result }));
  824.     } catch {}
  825.  
  826.     return result;
  827. }
  828.  
  829. function timing(name, cb) {
  830.     const start = performance.now();
  831.     const result = cb();
  832.     console.log(`${name} took ${performance.now() - start}ms`);
  833.     return result;
  834. }
Advertisement
Add Comment
Please, Sign In to add comment