<?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[Tool Verification Notes]]></title><description><![CDATA[Tool Verification Notes]]></description><link>https://tool-verification-notes.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 24 Oct 2026 10:45:24 GMT</lastBuildDate><atom:link href="https://tool-verification-notes.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to Test a Pokémon Type Calculator Without Copying Its Own Logic]]></title><description><![CDATA[Build an independent oracle and exhaustive tests for a modern main-series Pokémon type calculator without copying production logic.]]></description><link>https://tool-verification-notes.hashnode.dev/how-to-test-a-pok-mon-type-calculator-without-copying-its-own-logic</link><guid isPermaLink="true">https://tool-verification-notes.hashnode.dev/how-to-test-a-pok-mon-type-calculator-without-copying-its-own-logic</guid><category><![CDATA[Testing]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[data validation]]></category><category><![CDATA[pokemon]]></category><dc:creator><![CDATA[xianyu]]></dc:creator><pubDate>Thu, 08 Oct 2026 22:48:19 GMT</pubDate><content:encoded><![CDATA[<p>A type-effectiveness calculator can return plausible numbers even when one row, direction, or dual-type combination is wrong. The difficult part is creating a reference genuinely independent from the production implementation.</p>
<p>This guide describes a test-oracle approach for the 18 regular types used by modern main-series Pokémon games. It deliberately keeps the scope narrow. Stellar is a special Tera Type and does not belong in the ordinary 18-by-18 base matrix. Pokémon GO and the trading card game use different systems. Abilities, items, Terastallization, terrain, weather and special move rules can also change what happens in an actual battle.</p>
<h2>Start with direction, because direction is the commonest bug</h2>
<p>A type chart is directional. “Fire is super effective against Grass” does not imply that “Grass is super effective against Fire.” A production application may store rows as attacking types and columns as defending types, or the other way around. A test fixture that uses the same orientation without checking it can reproduce the same mistake.</p>
<p>Write the test contract before writing code:</p>
<blockquote>
<p>Given an attacking type and one or two defending regular types, return the product of the corresponding base defensive multipliers.</p>
</blockquote>
<p>Then add a few unmistakable directional checks. Electric attacking Ground should be <code>0</code>. Ground attacking Electric should be <code>2</code>. Normal attacking Ghost should be <code>0</code>, while Ghost attacking Normal should also be <code>0</code> for a different directional relationship. These checks reveal row/column reversals before a large suite hides the problem in hundreds of results.</p>
<h2>Build an independent oracle</h2>
<p>Do not import the application's chart object into its tests. If the application contains a wrong value, the test will happily confirm it. Instead, encode the reference relationships separately in the test project or generate them from an independently reviewed source file.</p>
<p>One compact representation stores only the non-neutral relationships for each attacking type:</p>
<pre><code class="language-js">const oracle = {
  electric: {
    water: 2,
    flying: 2,
    electric: 0.5,
    grass: 0.5,
    dragon: 0.5,
    ground: 0,
  },
  // Add the remaining attacking types independently.
};

function baseMultiplier(attack, defend) {
  return oracle[attack]?.[defend] ?? 1;
}
</code></pre>
<p>The important word is “independently.” A second object copied from the production file is duplication, not verification. Review the oracle row by row, keep its source and version visible, and make changes explicit in code review.</p>
<h2>Exhaust the single-type matrix</h2>
<p>With 18 regular attacking types and 18 regular defending types, the full single-type matrix contains only 324 cells. That is small enough to check exhaustively on every build.</p>
<pre><code class="language-js">for (const attack of TYPES) {
  for (const defend of TYPES) {
    const expected = baseMultiplier(attack, defend);
    const actual = appMultiplier(attack, [defend]);
    assert.equal(actual, expected, `${attack} -&gt; ${defend}`);
  }
}
</code></pre>
<p>This catches isolated transcription errors, missing immunities, incorrect defaults and direction mistakes. It also gives a precise failure message instead of a vague “calculator is wrong” report.</p>
<h2>Exhaust every unique dual type</h2>
<p>For base type effectiveness, a dual-type defensive result is multiplication. If an attack is <code>2</code> against the first type and <code>2</code> against the second, the result is <code>4</code>. If it is <code>2</code> against one and <code>0.5</code> against the other, the result is neutral <code>1</code>. If either relationship is <code>0</code>, the product is <code>0</code>.</p>
<p>There are 153 unordered pairs of different types. Add the 18 single types and there are 171 single-or-dual defensive combinations. Across 18 attacking types, that creates 3,078 checks—still trivial for a build.</p>
<pre><code class="language-js">function oracleDual(attack, a, b) {
  return baseMultiplier(attack, a) * baseMultiplier(attack, b);
}

for (let i = 0; i &lt; TYPES.length; i++) {
  for (let j = i + 1; j &lt; TYPES.length; j++) {
    const a = TYPES[i];
    const b = TYPES[j];
    for (const attack of TYPES) {
      assert.equal(
        appMultiplier(attack, [a, b]),
        oracleDual(attack, a, b),
        `${attack} -&gt; ${a}/${b}`
      );
    }
  }
}
</code></pre>
<p>This loop intentionally treats <code>Water/Ground</code> and <code>Ground/Water</code> as one defensive combination. Add a separate property test to confirm that reversing the two defending types never changes the result.</p>
<h2>Add metamorphic properties</h2>
<p>An oracle tells you the expected answer. Metamorphic tests verify relationships that must remain true even when you change the input.</p>
<p>Useful properties include:</p>
<ol>
<li><strong>Order independence:</strong> <code>M(attack, A, B) = M(attack, B, A)</code>.</li>
<li><strong>Single-type identity:</strong> adding a neutral relationship does not change the multiplier.</li>
<li><strong>Immunity dominance:</strong> if either defensive relationship is zero, the combined base multiplier is zero.</li>
<li><strong>Cancellation:</strong> <code>2 × 0.5 = 1</code> and <code>0.5 × 2 = 1</code>.</li>
<li><strong>Expected result set:</strong> single-type outputs belong to <code>{0, 0.5, 1, 2}</code>; dual-type outputs belong to <code>{0, 0.25, 0.5, 1, 2, 4}</code>.</li>
</ol>
<p>These properties catch implementation errors that individual examples may miss. An addition bug can still produce plausible values for some combinations, but it will violate cancellation and the expected result set.</p>
<h2>Test classification separately from arithmetic</h2>
<p>Many interfaces group results into labels such as “4× weak,” “2× weak,” “neutral,” “resistant” and “immune.” Test the numeric multiplier first, then test the grouping layer separately.</p>
<p>This prevents a display bug from being confused with a chart bug. A correct value of <code>0.25</code> placed under a <code>0.5×</code> heading is a presentation failure. A wrong value of <code>0.5</code> is a calculation failure. They need different fixes.</p>
<p>Also test that every attacking type appears exactly once across the displayed groups. Missing and duplicated types can survive numeric unit tests if the rendering code filters incorrectly.</p>
<h2>Keep battle mechanics outside the base oracle</h2>
<p>The base matrix should remain boring. Do not quietly add an ability, held item, move exception or Tera state to the same function and then call its output “type effectiveness.” Create a separate rule layer with explicit inputs.</p>
<p>For example, Freeze-Dry has a special interaction with Water-type targets. Flying Press has unusual type-effectiveness handling. Levitate can change a Ground interaction. Those rules do not mean the underlying Ground/Flying or Ice/Water matrix cells should be rewritten.</p>
<p>This separation also explains the scope to users. <a href="https://typeweakness.com/">TypeWeakness</a> presents the 18-regular-type base matrix for modern main-series games and documents that special mechanics may change a battle result. A verification suite should preserve that same boundary rather than claiming to simulate every battle system.</p>
<h2>Make failures useful</h2>
<p>A good failure should show:</p>
<ul>
<li>attacking type;</li>
<li>defending type or pair;</li>
<li>expected multiplier;</li>
<li>actual multiplier;</li>
<li>oracle version or source revision;</li>
<li>whether the failure occurred in arithmetic or display classification.</li>
</ul>
<p>When a game update or data-source change requires review, this information makes the change auditable. Do not automatically overwrite the oracle from the production source to make a failing build green. Investigate which side is wrong.</p>
<h2>Run the small exhaustive suite on every build</h2>
<p>This is one of the rare domains where exhaustive testing is simpler than clever sampling. Three thousand and seventy-eight base checks are tiny. Add validation for input names, duplicate types, unknown values and result-group coverage, and the suite still finishes quickly.</p>
<p>The strongest confidence does not come from a long list of hand-picked Pokémon. It comes from a separately constructed oracle, complete matrix coverage, every unique dual type, and explicit scope boundaries. That structure makes a calculator easier to trust because each conclusion can be traced to a small, testable rule.</p>
]]></content:encoded></item></channel></rss>