<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: cw chen</title>
    <description>The latest articles on DEV Community by cw chen (@cw_chen_458c005f0bd38c2c8).</description>
    <link>https://dev.to/cw_chen_458c005f0bd38c2c8</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4190227%2F6f044784-7778-49f5-9ef4-ea1ae92a262f.png</url>
      <title>DEV Community: cw chen</title>
      <link>https://dev.to/cw_chen_458c005f0bd38c2c8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cw_chen_458c005f0bd38c2c8"/>
    <language>en</language>
    <item>
      <title>What the Gamepad API actually tells you about stick drift (with a working verdict</title>
      <dc:creator>cw chen</dc:creator>
      <pubDate>Sat, 31 Oct 2026 06:24:53 +0000</pubDate>
      <link>https://dev.to/cw_chen_458c005f0bd38c2c8/what-the-gamepad-api-actually-tells-you-about-stick-drift-with-a-working-verdict-3oe1</link>
      <guid>https://dev.to/cw_chen_458c005f0bd38c2c8/what-the-gamepad-api-actually-tells-you-about-stick-drift-with-a-working-verdict-3oe1</guid>
      <description>&lt;p&gt;What the Gamepad API actually tells you about stick drift (with a working verdict algorithm)&lt;/p&gt;

&lt;p&gt;A player files a bug: "my controller drifts." You open the browser, call &lt;code&gt;navigator.getGamepads()&lt;/code&gt;, and see a resting value of &lt;code&gt;0.03&lt;/code&gt;. Is that a defective stick — or electrical noise?&lt;/p&gt;

&lt;p&gt;A single reading can't answer that. Here's how to build a verdict that can, and the traps the Gamepad API sets along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The API gives you samples, not facts
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Gamepad&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getGamepads&lt;/span&gt;&lt;span class="p"&gt;?.()&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;pads&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things bite everyone eventually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No data before user interaction.&lt;/strong&gt; Browsers only populate gamepad state after a button press or stick move (by design — same policy as audio). Your tester must prompt "press a button first" or every axis reads &lt;code&gt;0&lt;/code&gt; and you'll ship a false "healthy" verdict.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's a polling API.&lt;/strong&gt; There's no drift event; you call &lt;code&gt;getGamepads()&lt;/code&gt; every frame inside &lt;code&gt;requestAnimationFrame&lt;/code&gt; and diff the values yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You read what the OS reports, not the potentiometer.&lt;/strong&gt; Deadzone compensation may already be applied somewhere in the driver stack. That's a behavioral measurement, not factory calibration — fine for triage, wrong for QA-grade claims.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. One sample lies; a window doesn't
&lt;/h2&gt;

&lt;p&gt;At rest, a healthy stick wiggles around zero — typically &lt;code&gt;0.02–0.04&lt;/code&gt; of electrical noise that averages out. A drifting stick &lt;strong&gt;stays&lt;/strong&gt; offset and creeps upward. The distinction isn't the magnitude of one reading, it's the shape of the distribution over a window.&lt;/p&gt;

&lt;p&gt;So accumulate instead of judging point-in-time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;StickStats&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxAbsX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxAbsY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sumAbsX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sumAbsY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;accumulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StickStats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;StickStats&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxAbsX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxAbsX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="na"&gt;maxAbsY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxAbsY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="na"&gt;sumAbsX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sumAbsX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;sumAbsY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sumAbsY&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then classify the window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Verdict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;insufficient&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bad&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;judgeDrift&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StickStats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Verdict&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;MIN_SAMPLES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;insufficient&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// don't guess with 5 samples&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;meanAbs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sumAbsX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sumAbsY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;meanAbs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.005&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxAbsX&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// stick never touched?&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;meanAbs&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bad&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;meanAbs&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is (a simplified version of) the actual engine we ship — &lt;code&gt;judgeDrift&lt;/code&gt; deliberately returns &lt;code&gt;'insufficient'&lt;/code&gt; and &lt;code&gt;'idle'&lt;/code&gt; as first-class verdicts. Those two strings are what separate an honest tester from a random-number generator:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;'insufficient'&lt;/code&gt;: not enough samples yet. Showing a verdict at 5 samples is how testers "detect" drift that isn't there.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;'idle'&lt;/code&gt;: the stats suggest the stick was never moved during calibration, so the baseline is meaningless.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Pick the threshold from user experience, not from the datasheet
&lt;/h2&gt;

&lt;p&gt;Our defaults: ~0.05 sustained offset starts to be perceptible in-game; 0.08+ is noticeable in most titles; 0.15+ is severe. These are behavioral thresholds — derived from what players report feeling, not from potentiometer specs. Calibrate your own against real reports; the number that matters is "resting offset at which players complain," and that's measurable from support tickets.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The check resting drift can't do: roundness
&lt;/h2&gt;

&lt;p&gt;Some faults — travel loss, center jumps — never show at rest. Have the user trace two slow full circles and fit the samples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;1.00&lt;/code&gt; = perfect circle&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;gt; 1.3&lt;/code&gt; = travel loss or a center jump somewhere in the lap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the number that catches the controller which "feels fine at rest but won't full-sprint in game."&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Ship the honesty
&lt;/h2&gt;

&lt;p&gt;Everything above runs locally, nothing uploads, and the full verdict logic (drift, key chatter, double-click faults, dead zones) is open source, MIT:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/chencw6578-commits/gearlint-verdicts" rel="noopener noreferrer"&gt;github.com/chencw6578-commits/gearlint-verdicts&lt;/a&gt;&lt;/strong&gt;​ — &lt;code&gt;npm install gearlint-verdicts&lt;/code&gt; if you want the un-simplified engine with all 58 tests.&lt;/p&gt;

&lt;p&gt;If you'd rather see it running than read code: &lt;a href="https://www.gearlint.com/controller-test" rel="noopener noreferrer"&gt;GearLint's controller test&lt;/a&gt; shows the raw numbers next to the verdict, including the roundness check.&lt;/p&gt;

&lt;p&gt;The principle we build the whole site on: &lt;strong&gt;show the raw number, say what the method can't measure, never present a guess as a measurement.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you've built input handling on top of the Gamepad API, I'd genuinely like to hear how you handle the noise-vs-drift distinction — thresholds are a judgment call and mine aren't gospel.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>tutorial</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Is your controller drifting, or is it just the game's deadzone?</title>
      <dc:creator>cw chen</dc:creator>
      <pubDate>Sat, 17 Oct 2026 09:48:17 +0000</pubDate>
      <link>https://dev.to/cw_chen_458c005f0bd38c2c8/is-your-controller-drifting-or-is-it-just-the-games-deadzone-2l84</link>
      <guid>https://dev.to/cw_chen_458c005f0bd38c2c8/is-your-controller-drifting-or-is-it-just-the-games-deadzone-2l84</guid>
      <description>&lt;p&gt;A controller that "drifts" in one game and feels fine in another usually isn't haunted — the two games apply different software deadzones on top of the raw stick values. A competitive shooter with a 0.02 deadzone exposes every bit of hardware looseness; an adventure game with a 0.15 default hides it completely.&lt;/p&gt;

&lt;p&gt;So before you take the controller apart, read the raw numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to actually check for drift:&lt;/strong&gt;​&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open a raw-value tester (e.g. &lt;a href="https://www.gearlint.com/controller-test" rel="noopener noreferrer"&gt;GearLint's controller test&lt;/a&gt;) and press a button first — browsers only expose gamepad data after user interaction, by design.&lt;/li&gt;
&lt;li&gt;Don't touch the stick and watch the resting value. 0.02–0.04 is normal electrical noise. Drift is a value that stays above ~0.05 and &lt;em&gt;creeps upward&lt;/em&gt; — noise wiggles around zero and averages out.&lt;/li&gt;
&lt;li&gt;As a rule of thumb: persistently above 0.08 on a resting stick will be noticeable in most games. Above 0.15 is severe.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The check most people skip:&lt;/strong&gt;​ circle calibration. Rotate the stick slowly two full laps and watch the roundness number. 1.00 is a perfect circle; above 1.3 flags travel loss or center jumps — wear that resting drift can't show.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest limitation:&lt;/strong&gt;​ a browser reads what the OS reports, not the potentiometer itself. It's a behavioral measurement, not factory calibration. For deciding whether to fix, replace, or return — that's enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus for warranty claims:&lt;/strong&gt;​ a brand-new controller reading over 0.08 at rest is the seller's problem. Screenshot the reading, keep it dated. GearLint's result cards have a one-click copy button that formats the report for exactly this.&lt;/p&gt;

&lt;p&gt;The same philosophy runs through the rest of the tests on the site (keyboard chatter, rollover, mouse double-click, CPS) — show the raw number, say what the method can't measure, never present a guess as a measurement.&lt;/p&gt;

&lt;p&gt;Everything is free, runs locally in the browser, and nothing is uploaded: &lt;strong&gt;&lt;a href="https://www.gearlint.com" rel="noopener noreferrer"&gt;GearLint&lt;/a&gt;&lt;/strong&gt;​&lt;/p&gt;

&lt;p&gt;Curious how the verdict is actually computed in code? I wrote a developer follow-up: &lt;a href="https://dev.to/cw_chen_458c005f0bd38c2c8/what-the-gamepad-api-actually-tells-you-about-stick-drift-with-a-working-verdict-3oe1"&gt;What the Gamepad API actually tells you about stick drift&lt;/a&gt; — the engine itself is open source (MIT): &lt;a href="https://github.com/chencw6578-commits/gearlint-verdicts" rel="noopener noreferrer"&gt;gearlint-verdicts&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>gaming</category>
      <category>tools</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
