<?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[SpinWheelNames]]></title><description><![CDATA[SpinWheelNames]]></description><link>https://heyiamluke.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 12 Nov 2026 10:50:05 GMT</lastBuildDate><atom:link href="https://heyiamluke.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[SvelteKit and Canvas: separating wheel rendering, probability and verification]]></title><description><![CDATA[A spinning wheel combines several different jobs: editing a list, drawing slices, choosing a stopping position, animating the rotation and reporting a…]]></description><link>https://heyiamluke.hashnode.dev/sveltekit-and-canvas-separating-wheel-rendering-probability-and-verification</link><guid isPermaLink="true">https://heyiamluke.hashnode.dev/sveltekit-and-canvas-separating-wheel-rendering-probability-and-verification</guid><category><![CDATA[Svelte]]></category><category><![CDATA[Sveltekit]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[canvas]]></category><dc:creator><![CDATA[Luke]]></dc:creator><pubDate>Sun, 08 Nov 2026 12:40:51 GMT</pubDate><content:encoded><![CDATA[<p>A spinning wheel combines several different jobs: editing a list, drawing slices, choosing a stopping position, animating the rotation and reporting a winner. Treating all of that as one visual effect makes it harder to explain what the result actually means.</p>
<p>I'm Luke, the builder of SpinWheelNames. My app uses SvelteKit and an HTML5 canvas, with the stopping position decided in the browser using <code>crypto.getRandomValues</code>. Here are the boundaries I find useful when thinking about that implementation, the test that exercises it, and the separate presentation needed for a stream overlay.</p>
<h2>1. Distinguish the application boundary from the drawing boundary</h2>
<p>There are two separate architectural questions: where the application runs, and how the wheel gets drawn.</p>
<p>My SvelteKit configuration uses <code>@sveltejs/adapter-node</code>. That does not mean a Node server chooses the winner: the wheel's random stopping position is decided client-side. Deployment and selection are different responsibilities.</p>
<p>Likewise, using Svelte does not mean every animated shape needs to be a DOM element. The wheel itself is drawn on a canvas; the surrounding interface lets people enter names, customise colours and change options.</p>
<p>For me, that is a useful division of labour. Svelte is a fit for an interface that changes with user input, while the browser's drawing surface handles the wheel graphics. Canvas is not a replacement for the application interface, and the framework does not need to replace the drawing API.</p>
<p>If I were reviewing another canvas-heavy app, I would start with those same questions: which work actually needs the server, which state belongs to the interface, and which work is just drawing?</p>
<h2>2. Reason about stopping angles, not just random numbers</h2>
<p>The random source matters, but it is only one part of the argument for fairness.</p>
<p>SpinWheelNames uses the browser's cryptographically secure <code>crypto.getRandomValues</code>, rather than <code>Math.random</code>, to choose the extra rotation. On a computer, the wheel makes between five and ten extra turns; on phones it makes fewer so the animation stays smooth. Wherever it stops, the entry beneath the pointer wins.</p>
<p>The important detail is that the random movement spans a whole number of revolutions. That means a uniform distribution over that span covers every stopping angle equally. It does <strong>not</strong> mean that each spin adds only an integer number of turns: that would keep returning the wheel to the same orientation.</p>
<p>This gives a geometric way to describe the result:</p>
<ul>
<li>With equal-sized slices, each entry occupies the same fraction of the wheel and has the same chance.</li>
<li>With weighted entries, the slice sizes change in proportion to the weights, and the chances follow those sizes.</li>
</ul>
<p>For example, three equal slices each cover one third of the circle. If their weights are instead 1, 1 and 2, their shares are one quarter, one quarter and one half. The larger chance should be visible in the geometry, not hidden behind a wheel that still looks equally divided.</p>
<p>The engineering lesson is to check both the source of randomness and its mapping to the visible outcome. A strong random source alone cannot demonstrate that the angle-to-entry calculation is correct.</p>
<h2>3. Exercise the selection path without waiting for animations</h2>
<p>My <a href="https://spinwheelnames.com/fairness">in-browser fairness test</a> runs 100,000 spins using the same code the real wheel uses to choose the turns and read the slice beneath the pointer. It does not animate them, and every visit runs a fresh test rather than displaying precomputed results.</p>
<p>One detail matters here: each trial starts where the previous spin stopped. Resetting the wheel to a fixed orientation for every trial would test a different sequence from normal repeated use.</p>
<p>The test uses ten equal entries. After 100,000 spins, the expected count for each is 10,000, but exactly equal counts are not the goal. Randomness naturally produces differences.</p>
<p>The chi-squared calculation summarises those differences:</p>
<p><code>χ² = Σ ((observed count − expected count)² / expected count)</code></p>
<p>For ten entries, the test has nine degrees of freedom. The p-value describes how often a fair wheel would produce counts at least as uneven as the observed counts.</p>
<p>A p-value above 0.05 means the test found no evidence of bias at that threshold. It does not prove the implementation is flawless. Below 0.05 is a reason to investigate, not a verdict by itself: even a fair wheel produces such a result in roughly one test out of twenty.</p>
<p>I would rerun an unexpectedly low result and inspect the counts rather than treating one pass or failure as certification. Repeated tests are useful evidence, but no finite random test guarantees that every biased implementation will fail every run.</p>
<p>There is also a boundary to this particular test: it checks equal-entry frequencies, not deliberately weighted configurations. A weighted test needs expected counts based on the weights, not an assumption that every entry should win equally often.</p>
<h2>4. Treat the overlay as a different presentation surface</h2>
<p>A wheel on a normal page and a wheel inside a broadcast scene have different constraints. Capturing a browser tab brings along page furniture that does not belong on stream.</p>
<p>My <a href="https://spinwheelnames.com/obs-overlay">OBS overlay builder</a> produces a separate URL for a transparent 1920×1080 presentation. The setup is:</p>
<ol>
<li>Enter the names in the builder and copy the generated URL.</li>
<li>Add a Browser source in OBS and paste the URL.</li>
<li>Set the source to 1920×1080.</li>
<li>Open the source's <strong>Interact</strong> window, then click <strong>Spin</strong> or press <strong>Space</strong>.</li>
</ol>
<p>That interaction boundary is deliberate: chat can supply entrants through the streamer's usual bot, but there is no built-in chat command that lets viewers trigger spins. Collecting entries and authorising the draw are separate jobs.</p>
<p>Real transparency is the default for OBS and Streamlabs Browser sources. For capture setups that cannot preserve it, the builder also offers green or magenta backgrounds for chroma keying. Magenta is useful when green slices would otherwise disappear under a green key.</p>
<p>For several prizes, <strong>Remove the winner after each spin</strong> removes each selected entry before the next round. Refreshing the Browser source restores the entries. That changes the list between draws, so it is a different process from repeatedly sampling an unchanged list.</p>
<p>The overlay is free, requires no account, and shows no ads or branding on stream. That is specific to the overlay: regular site pages carry Google AdSense ads.</p>
<h2>What I would check in another interactive picker</h2>
<p>These are the questions I would take into a review:</p>
<ul>
<li>Does the probability model match the visible slice sizes?</li>
<li>Does the test exercise the same selection logic and starting-state behaviour as ordinary use?</li>
<li>Are statistical results described as evidence rather than proof?</li>
<li>Are weighted draws and removal between rounds treated as distinct cases?</li>
<li>Is the output surface designed for where people actually display it?</li>
</ul>
<p>SvelteKit and canvas cover different parts of this problem. The useful result is not just a wheel that moves smoothly, but a selection process I can explain, exercise without animation, and present without confusing the controls with the output.</p>
]]></content:encoded></item></channel></rss>