<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[CalculatorQueen Method Notes]]></title><description><![CDATA[CalculatorQueen Method Notes]]></description><link>https://calculatorqueen.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6a918c875532710bfa5bcb3e/416c27a1-be61-4fd1-bb86-15a08879143c.png</url><title>CalculatorQueen Method Notes</title><link>https://calculatorqueen.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 02 Sep 2026 15:08:02 GMT</lastBuildDate><atom:link href="https://calculatorqueen.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Expected Value Is Not a Forecast: Engineering a Transparent Crafting Calculator]]></title><description><![CDATA[Randomized crafting systems invite a deceptively simple question: “Is this recipe worth attempting?” A calculator can produce a number quickly, but that number is only useful if its assumptions remain]]></description><link>https://calculatorqueen.hashnode.dev/expected-value-is-not-a-forecast-engineering-a-transparent-crafting-calculator</link><guid isPermaLink="true">https://calculatorqueen.hashnode.dev/expected-value-is-not-a-forecast-engineering-a-transparent-crafting-calculator</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[probability]]></category><category><![CDATA[algorithms]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[CalculatorQueen]]></dc:creator><pubDate>Fri, 28 Aug 2026 13:38:47 GMT</pubDate><content:encoded><![CDATA[<p>Randomized crafting systems invite a deceptively simple question: “Is this recipe worth attempting?” A calculator can produce a number quickly, but that number is only useful if its assumptions remain visible.</p>
<p>The safest design is not a hidden catalog of items, prices, and success rates. Those values can change by patch, server, market, or community methodology. A more durable calculator treats them as user-supplied evidence and performs a small, auditable expected-value calculation.</p>
<p>This article builds that model, derives its break-even condition, implements it in JavaScript, and explains what the result cannot tell us.</p>
<h2>Define the scenario before writing code</h2>
<p>Suppose one crafting attempt consumes several materials. For material <code>i</code>, let:</p>
<ul>
<li><code>qᵢ</code> be the quantity consumed per attempt;</li>
<li><code>vᵢ</code> be the entered value per unit;</li>
<li><code>c</code> be any additional cost per attempt;</li>
<li><code>a</code> be the number of planned attempts;</li>
<li><code>p</code> be the entered probability of success, from <code>0</code> to <code>1</code>;</li>
<li><code>u</code> be the number of output units produced by one success;</li>
<li><code>V</code> be the entered value of one output unit.</li>
</ul>
<p>All values must use one consistent unit. It can be a game currency, guide points, or an opportunity-cost scale, but mixing units makes the result meaningless.</p>
<p>The input cost of one attempt is:</p>
<pre><code class="language-text">C = Σ(qᵢ × vᵢ) + c
</code></pre>
<p>Across <code>a</code> attempts, total input value is <code>aC</code>. The expected number of successes is <code>ap</code>, so expected gross output value is:</p>
<pre><code class="language-text">E[gross output] = a × p × u × V
</code></pre>
<p>Expected net value follows directly:</p>
<pre><code class="language-text">E[net] = a × (p × u × V - C)
</code></pre>
<p>This is the ordinary probability-weighted mean. OpenStax's discussion of the <a href="https://openstax.org/books/introductory-statistics/pages/4-2-mean-or-expected-value-and-standard-deviation">mean or expected value of a discrete random variable</a> gives the broader statistical definition.</p>
<h2>Derive the break-even probability</h2>
<p>Set expected net value to zero:</p>
<pre><code class="language-text">p × u × V - C = 0
</code></pre>
<p>Solving for <code>p</code> gives:</p>
<pre><code class="language-text">p* = C / (u × V)
</code></pre>
<p><code>p*</code> is the arithmetic break-even success probability. If the entered probability is above <code>p*</code>, the model has positive expected net value; if it is below, the model has negative expected net value.</p>
<p>Notice that the attempt count cancels. Running the same model more times scales expected profit or loss, but it does not change the break-even probability. That distinction is useful in interface design: attempt count describes exposure, while <code>p*</code> describes the decision boundary.</p>
<p>The boundary can also reveal impossible economics. If <code>p* &gt; 1</code>, even a guaranteed success would not recover the entered cost under the current output assumptions. The software should display that result rather than clamp it to 100% and hide the problem.</p>
<h2>A small JavaScript implementation</h2>
<p>The arithmetic is short enough to review in one sitting. Validation is where most of the defensive work belongs.</p>
<pre><code class="language-js">function craftingExpectedValue({
  materials,
  otherCostPerAttempt,
  attempts,
  successChancePercent,
  outputsPerSuccess,
  valuePerOutput,
}) {
  if (!Array.isArray(materials) || materials.length === 0) {
    throw new TypeError("At least one material is required");
  }
  if (!Number.isInteger(attempts) || attempts &lt; 1) {
    throw new RangeError("Attempts must be a positive integer");
  }
  if (!Number.isInteger(outputsPerSuccess) || outputsPerSuccess &lt; 1) {
    throw new RangeError("Outputs per success must be a positive integer");
  }
  if (
    !Number.isFinite(successChancePercent) ||
    successChancePercent &lt; 0 ||
    successChancePercent &gt; 100
  ) {
    throw new RangeError("Success chance must be between 0 and 100");
  }
  if (!Number.isFinite(valuePerOutput) || valuePerOutput &lt;= 0) {
    throw new RangeError("Output value must be positive");
  }
  if (!Number.isFinite(otherCostPerAttempt) || otherCostPerAttempt &lt; 0) {
    throw new RangeError("Other cost cannot be negative");
  }

  const materialCostPerAttempt = materials.reduce((sum, material) =&gt; {
    if (!Number.isInteger(material.quantity) || material.quantity &lt; 1) {
      throw new RangeError("Material quantities must be positive integers");
    }
    if (!Number.isFinite(material.valueEach) || material.valueEach &lt; 0) {
      throw new RangeError("Material values cannot be negative");
    }
    return sum + material.quantity * material.valueEach;
  }, 0);

  const costPerAttempt = materialCostPerAttempt + otherCostPerAttempt;
  if (costPerAttempt &lt;= 0) {
    throw new RangeError("The entered cost per attempt must be positive");
  }

  const p = successChancePercent / 100;
  const expectedSuccesses = attempts * p;
  const expectedFailures = attempts - expectedSuccesses;
  const expectedOutputs = expectedSuccesses * outputsPerSuccess;
  const totalInputValue = attempts * costPerAttempt;
  const expectedGrossValue = expectedOutputs * valuePerOutput;
  const expectedNetValue = expectedGrossValue - totalInputValue;
  const breakEvenChance = costPerAttempt / (outputsPerSuccess * valuePerOutput);

  return {
    costPerAttempt,
    expectedSuccesses,
    expectedFailures,
    expectedOutputs,
    totalInputValue,
    expectedGrossValue,
    expectedNetValue,
    breakEvenChancePercent: breakEvenChance * 100,
    chanceGapPercentagePoints: successChancePercent - breakEvenChance * 100,
  };
}
</code></pre>
<p>Material labels, a source label, an “as of” date, and a scenario note do not change the formula. They still belong in the data model because they preserve provenance. A result without a dated source is difficult to reproduce after a game update.</p>
<h2>Work a synthetic example by hand</h2>
<p>Consider a deliberately fictional recipe:</p>
<ul>
<li>2 units of material A at 10 value units each;</li>
<li>3 units of material B at 20 each;</li>
<li>20 additional value units per attempt;</li>
<li>10 attempts;</li>
<li>40% entered success chance;</li>
<li>2 outputs per success;</li>
<li>80 value units per output.</li>
</ul>
<p>The material cost is <code>2 × 10 + 3 × 20 = 80</code>. Including the additional cost gives <code>C = 100</code> per attempt and <code>1,000</code> across the run.</p>
<p>Expected successes are <code>10 × 0.4 = 4</code>, producing an expected 8 output units. Expected gross value is <code>8 × 80 = 640</code>, so expected net value is <code>640 - 1,000 = -360</code>.</p>
<p>The break-even chance is:</p>
<pre><code class="language-text">100 / (2 × 80) = 0.625 = 62.5%
</code></pre>
<p>The entered chance is therefore 22.5 percentage points below break-even. You can reproduce this scenario in the <a href="https://calculatorqueen.com/calculators/the-forge-calculator">Crafting Expected Value Calculator</a>, which keeps recipe labels, entered values, chance, source date, and assumptions explicit. It does not supply live materials or derive the probability.</p>
<h2>Expected value does not describe the next run</h2>
<p>Four expected successes does not predict exactly four successes. Under the additional assumptions that attempts are independent and have a constant success probability, the success count is binomial. The <a href="https://www.itl.nist.gov/div898/handbook/eda/section3/eda366i.htm">NIST/SEMATECH Binomial Distribution reference</a> gives its probability and variance formulas.</p>
<p>For <code>a</code> attempts with probability <code>p</code>:</p>
<pre><code class="language-text">Var(successes) = a × p × (1 - p)
SD(successes)  = √(a × p × (1 - p))
</code></pre>
<p>If each success has declared value <code>uV</code>, then the standard deviation of gross value is <code>uV</code> times the standard deviation of successes. Two recipes can have equal expected net value but very different variability.</p>
<p>Do not apply this extension automatically. A pity system, changing game state, dependent attempts, multiple outcome tiers, or conditional bonuses violates the simple binomial model. In those cases, record the richer mechanics and choose a model that represents them.</p>
<h2>Test identities, not just examples</h2>
<p>A few invariant-based tests make the implementation harder to break:</p>
<pre><code class="language-js">import assert from "node:assert/strict";

const result = craftingExpectedValue({
  materials: [
    { quantity: 2, valueEach: 10 },
    { quantity: 3, valueEach: 20 },
  ],
  otherCostPerAttempt: 20,
  attempts: 10,
  successChancePercent: 40,
  outputsPerSuccess: 2,
  valuePerOutput: 80,
});

assert.equal(result.costPerAttempt, 100);
assert.equal(result.expectedSuccesses + result.expectedFailures, 10);
assert.equal(result.expectedGrossValue, 640);
assert.equal(result.expectedNetValue, -360);
assert.equal(result.breakEvenChancePercent, 62.5);

const atBreakEven = craftingExpectedValue({
  materials: [{ quantity: 5, valueEach: 8 }],
  otherCostPerAttempt: 10,
  attempts: 4,
  successChancePercent: 50,
  outputsPerSuccess: 1,
  valuePerOutput: 100,
});

assert.equal(atBreakEven.expectedNetValue, 0);
assert.equal(atBreakEven.chanceGapPercentagePoints, 0);
</code></pre>
<p>Also test 0% and 100% probabilities, one attempt, multiple materials, an exact break-even case, a break-even probability above 100%, and invalid negative or fractional quantities.</p>
<h2>Prefer sensitivity analysis to false precision</h2>
<p>The calculation is deterministic; the inputs are not necessarily trustworthy. When the success chance or output value is uncertain, run low, central, and high scenarios. Compare whether the entered chance remains above break-even in all three cases.</p>
<p>That workflow communicates more than a long decimal. It shows which assumption changes the decision and keeps arithmetic confidence separate from data confidence.</p>
<p>A good crafting calculator should be boring in the best way: explicit inputs, reviewable formulas, reproducible tests, and visible limits. Expected value is a conditional average—not a forecast, recommendation, or guarantee.</p>
<h2>References</h2>
<ul>
<li>OpenStax, <em>Introductory Statistics</em>: <a href="https://openstax.org/books/introductory-statistics/pages/4-2-mean-or-expected-value-and-standard-deviation">Mean or Expected Value and Standard Deviation</a></li>
<li>NIST/SEMATECH e-Handbook of Statistical Methods: <a href="https://www.itl.nist.gov/div898/handbook/eda/section3/eda366i.htm">Binomial Distribution</a></li>
</ul>
]]></content:encoded></item></channel></rss>