<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>James Randall</title>
    <link>https://www.jamesdrandall.com/</link>
    <description>Recent content on James Randall</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 08 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.jamesdrandall.com/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Building a Real-Time Path Tracer in WebGPU</title>
      <link>https://www.jamesdrandall.com/posts/building-a-real-time-path-tracer-in-webgpu/</link>
      <pubDate>Sun, 08 Mar 2026 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/posts/building-a-real-time-path-tracer-in-webgpu/</guid>
      <description>&lt;script type=&#34;module&#34; src=&#34;path-tracer.js&#34;&gt;&lt;/script&gt;
&lt;p&gt;Having built a raycaster for my &lt;a href=&#34;https://www.jamesdrandall.com/projects/wolfenstein3d/&#34;&gt;Wolfenstein 3D&lt;/a&gt; recreation I wanted to take things to the next level and build a &amp;ldquo;simple&amp;rdquo; ray tracer.  The result is a real-time path tracer written entirely in WebGPU compute shaders, running in a browser, rendering levels loaded from actual Doom WAD files. No hardware ray tracing cores, no ML denoisers, no engine — just maths, triangles, and a GPU doing a LOT of work.&lt;/p&gt;
&lt;p&gt;But when you try and figure out what happens when you fire a ray of light from a virtual camera into a scene made of triangles it turns out things get complicated fast. You get physically correct lighting — soft shadows, colour bleeding, natural falloff — the kind of rendering that looks &lt;em&gt;right&lt;/em&gt; in a way that traditional rasterisation never quite manages. You also get noise, performance problems, and a sharp reminder that even a £1,600 GPU can&amp;rsquo;t brute-force the physics. In fact it still can&amp;rsquo;t even come close.&lt;/p&gt;
&lt;p&gt;This post walks through the construction of that renderer: from the rendering equation through BVH acceleration, Monte Carlo importance sampling, temporal accumulation, and spatial denoising — everything needed to go from a single noisy ray to a converged image. The Doom WAD loader parses the original 1993 level geometry and rebuilds it as a triangle mesh that the path tracer can chew on, which means you can wander through E1M1 lit by nothing but a handheld torch and physically correct light transport. The renderer is embedded below — you can interact with it directly. These demos are computationally expensive and so you&amp;rsquo;ll need to press PLAY first — I didn&amp;rsquo;t want the first thing you knew about these interactive demos to be fan noise! If it runs really slowly… drop the resolution. On my Mac I get 60fps with these defaults.&lt;/p&gt;
&lt;p&gt;&lt;path-tracer scene=&#34;doom&#34; samples=&#34;4&#34; bounces=&#34;4&#34; temporal=&#34;1&#34; denoise=&#34;atrous&#34; denoise-passes=&#34;1&#34; player-light=&#34;17&#34; player-falloff=&#34;13&#34; phantom controls&gt;&lt;/path-tracer&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not going to pretend this came out as a coherent whole. Getting a simple scene on the screen was (relatively) simple but I&amp;rsquo;ve backed and forthed with denoisers, different optimisations, tweaking numbers, chunking things differently - the list is endless. I started tinkering with it a couple of months ago. Even writing it all up took an age.&lt;/p&gt;
&lt;p&gt;I was learning as I went and rewriting and rewriting code with the help of an LLM. Its been fascinating and the slightly messy code is the result of that learning process - but that learning process hopefully helps me explain how it works and why realtime path tracing without tricks is still a pipedream on a GPU.&lt;/p&gt;
&lt;p&gt;In any case - I hope I&amp;rsquo;ve done the topic justice.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-rendering-equation&#34;&gt;The Rendering Equation&lt;/h2&gt;
&lt;p&gt;Every pixel on screen represents a measurement of light. The question is: how much light arrives at the camera through this pixel? The answer is the &lt;a href=&#34;https://en.wikipedia.org/wiki/Rendering_equation&#34;&gt;rendering equation&lt;/a&gt;, introduced by James Kajiya in 1986:&lt;/p&gt;
$$L_o(\mathbf{x}, \omega_o) = L_e(\mathbf{x}, \omega_o) + \int_{\Omega} f_r(\mathbf{x}, \omega_i, \omega_o) \, L_i(\mathbf{x}, \omega_i) \, (\omega_i \cdot \mathbf{n}) \, d\omega_i$$&lt;p&gt;In plain terms: the light leaving a point $\mathbf{x}$ in direction $\omega_o$ is the light emitted at that point, plus all the light arriving from every direction in the hemisphere above the surface, scaled by the material&amp;rsquo;s reflectance function and the angle of incidence.&lt;/p&gt;
&lt;p&gt;That integral over the hemisphere is the hard part. There&amp;rsquo;s no closed-form solution for arbitrary scenes — you can&amp;rsquo;t solve it algebraically. Instead, we must estimate it by random sampling: fire rays in random directions, see what light they find, and average the results. This is Monte Carlo integration, and it&amp;rsquo;s the foundation of everything that follows.&lt;/p&gt;
&lt;p&gt;And I&amp;rsquo;m not going to lie: I&amp;rsquo;m not a mathematician (I know just enough to be dangerous and can usually get there, I just have to frown a lot) and had to go round this loop a few times before I understood what was happening. It took me a while to appreciate that you can&amp;rsquo;t solve it as such, you have to approximate it. I got there by back tracking from the noise. Why the noise.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;anatomy-of-a-ray&#34;&gt;Anatomy of a Ray&lt;/h2&gt;
&lt;p&gt;A ray is defined by an origin point and a direction:&lt;/p&gt;
$$\mathbf{r}(t) = \mathbf{o} + t\mathbf{d}, \quad t &gt; 0$$&lt;p&gt;For the primary ray — camera to scene — the origin is the camera position and the direction is computed from the pixel coordinates and the field of view:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; generate_ray(pixel: vec2f) -&amp;gt; vec3f {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; aspect &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; camera.resolution.x &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; camera.resolution.y;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; fov_scale &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tan(camera.fov &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; ndc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; vec2f(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#ae81ff&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; pixel.x &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; camera.resolution.x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; aspect &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; fov_scale,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; pixel.y &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; camera.resolution.y) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; fov_scale
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; forward &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; normalize(camera.direction);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; right &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; normalize(cross(forward, camera.up));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; up &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cross(right, forward);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; normalize(forward &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; right &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; ndc.x &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; up &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; ndc.y);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The pixel coordinates are mapped to Normalised Device Coordinates (NDC), scaled by the field of view and aspect ratio, then transformed into a world-space direction using the camera&amp;rsquo;s orientation vectors. Each pixel gets a slightly different direction, creating the perspective projection.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;finding-what-a-ray-hits&#34;&gt;Finding What a Ray Hits&lt;/h2&gt;
&lt;p&gt;Once we have a ray, we need to find the closest surface it intersects. Every surface in the scene is made of triangles, so this reduces to: for each triangle, does this ray hit it, and if so, at what distance $t$?&lt;/p&gt;
&lt;p&gt;The standard ray–triangle intersection test is the &lt;a href=&#34;https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm&#34;&gt;Möller–Trumbore Algorithm&lt;/a&gt;. Given a ray $(\mathbf{o}, \mathbf{d})$ and a triangle with vertices $\mathbf{v}_0, \mathbf{v}_1, \mathbf{v}_2$, we compute:&lt;/p&gt;
$$\mathbf{e}_1 = \mathbf{v}_1 - \mathbf{v}_0, \quad \mathbf{e}_2 = \mathbf{v}_2 - \mathbf{v}_0$$$$\mathbf{h} = \mathbf{d} \times \mathbf{e}_2, \quad a = \mathbf{e}_1 \cdot \mathbf{h}$$&lt;p&gt;If $a \approx 0$ (the squiggly equals means approximate), then the ray is parallel to the triangle. Otherwise, we compute the barycentric coordinates $(u, v)$ of the intersection point:&lt;/p&gt;
$$f = \frac{1}{a}, \quad \mathbf{s} = \mathbf{o} - \mathbf{v}_0$$$$u = f(\mathbf{s} \cdot \mathbf{h}), \quad \mathbf{q} = \mathbf{s} \times \mathbf{e}_1, \quad v = f(\mathbf{d} \cdot \mathbf{q})$$$$t = f(\mathbf{e}_2 \cdot \mathbf{q})$$&lt;p&gt;If $u \in [0, 1]$, $v \in [0, 1]$, $u + v \leq 1$, and $t &gt; 0$, we have a valid hit. The &lt;a href=&#34;https://en.wikipedia.org/wiki/Barycentric_coordinate_system&#34;&gt;barycentric coordinates&lt;/a&gt; (ahh my old friend, I first came across these while working on &lt;a href=&#34;https://www.jamesdrandall.com/projects/wolfenstein3d/&#34;&gt;Wolfenstein&lt;/a&gt;, not surprising given its a raycaster and this is a path tracer, and now they seem to pop up everywhere) also let us interpolate texture coordinates and normals across the triangle surface.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; intersect_triangle_uv(ray_origin: vec3f, ray_dir: vec3f,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                          v0: vec3f, v1: vec3f, v2: vec3f) -&amp;gt; TriangleHitResult {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result: TriangleHitResult;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  result.t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; edge1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; v1 &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; v0;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; edge2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; v2 &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; v0;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; h &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cross(ray_dir, edge2);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; a &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; dot(edge1, h);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (abs(a) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.00001&lt;/span&gt;) { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; f &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; a;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ray_origin &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; v0;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; u &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; f &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; dot(s, h);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (u &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; u &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;) { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; q &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cross(s, edge1);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; f &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; dot(ray_dir, q);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (v &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; u &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; v &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;) { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; f &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; dot(edge2, q);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (t &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0001&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result.t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; t;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result.u &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; u;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result.v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; v;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is elegant and fast for a single triangle, the problem is that a scene can contain thousands of them.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-brute-force-problem&#34;&gt;The Brute Force Problem&lt;/h2&gt;
&lt;p&gt;The naive approach is to test every ray against every triangle. For a scene with $N$ triangles and an image of $W \times H$ pixels, each with $S$ samples and $B$ bounces, that&amp;rsquo;s:&lt;/p&gt;
$$\text{Tests} = W \times H \times S \times B \times N$$&lt;p&gt;For our dungeon scene at 1280×800, 4 samples per pixel, 4 bounces, and 3000 triangles:&lt;/p&gt;
$$1280 \times 800 \times 4 \times 4 \times 3000 = 49{,}152{,}000{,}000$$&lt;p&gt;Forty-nine billion intersection tests per frame. At maybe 1 nanosecond per test, that&amp;rsquo;s 49 seconds per frame. Not exactly real-time. Though that said it still renders faster than a Mandelbrot on my BBC Micro - which is kind of astounding!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;bounding-volume-hierarchies&#34;&gt;Bounding Volume Hierarchies&lt;/h2&gt;
&lt;p&gt;The solution is to not test most triangles but to find a way to organise them so we can test far fewer. A &lt;a href=&#34;https://en.wikipedia.org/wiki/Bounding_volume_hierarchy&#34;&gt;Bounding Volume Hierarchy (BVH)&lt;/a&gt; organises triangles into a tree of axis-aligned bounding boxes (AABBs). Each node contains a box that tightly encloses all the triangles in its subtree. Before testing any triangles, we test the ray against the box. If it misses the box, we skip the entire subtree — potentially thousands of triangles — in a single cheap test.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll come across similar spatial organisation strategies everywhere.&lt;/p&gt;
&lt;p&gt;&lt;path-tracer controls scene=&#34;bvh&#34; samples=&#34;4&#34; bounces=&#34;3&#34; debug-mode=&#34;4&#34; debug-depth=&#34;3&#34; debug-opacity=&#34;100&#34; width=&#34;960&#34; height=&#34;600&#34;&gt;&lt;/path-tracer&gt;&lt;/p&gt;
&lt;h3 id=&#34;rayaabb-intersection&#34;&gt;Ray–AABB Intersection&lt;/h3&gt;
&lt;p&gt;Testing a ray against an axis-aligned box is much cheaper than testing against a triangle (again shades of Wolfenstein which is naturally axis aligned given its grid based maps). We compute the entry and exit distances along each axis:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; intersect_aabb(ray_origin: vec3f, ray_dir_inv: vec3f,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  box_min: vec3f, box_max: vec3f, max_t: f32) -&amp;gt; bool {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; t1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (box_min &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; ray_origin) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; ray_dir_inv;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (box_max &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; ray_origin) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; ray_dir_inv;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; tmin &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; max(max(min(t1.x, t2.x), min(t1.y, t2.y)), min(t1.z, t2.z));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; tmax &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; min(min(max(t1.x, t2.x), max(t1.y, t2.y)), max(t1.z, t2.z));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; tmax &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; tmin &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; tmin &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; max_t &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; tmax &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note &lt;code&gt;ray_dir_inv&lt;/code&gt; — the reciprocal of the direction, precomputed once per ray. This avoids a division per axis per box test.&lt;/p&gt;
&lt;h3 id=&#34;building-the-tree&#34;&gt;Building the Tree&lt;/h3&gt;
&lt;p&gt;The BVH is constructed on the CPU using the Surface Area Heuristic (SAH). At each node, we evaluate every possible split position along each axis. The cost of a split is:&lt;/p&gt;
$$C = C_{\text{traversal}} + \frac{SA_L}{SA_P} \cdot N_L \cdot C_{\text{intersect}} + \frac{SA_R}{SA_P} \cdot N_R \cdot C_{\text{intersect}}$$&lt;p&gt;Where $SA_L, SA_R, SA_P$ are the surface areas of the left, right, and parent bounding boxes, and $N_L, N_R$ are the triangle counts in each child. We pick the split that minimises this cost. The surface area ratio estimates the probability of a random ray hitting each child, so the heuristic minimises the expected number of intersection tests.&lt;/p&gt;
&lt;p&gt;The tree is flattened into a contiguous array for GPU consumption. Each node stores its AABB bounds plus either child indices (for internal nodes) or a triangle range (for leaves). A high bit flag distinguishes the two:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; BVHNode {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  min_bounds: vec3f,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  left_child_or_first_tri: u32,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  max_bounds: vec3f,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  right_child_or_count: u32,   &lt;span style=&#34;color:#75715e&#34;&gt;// High bit set = leaf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;walking-the-tree&#34;&gt;Walking the Tree&lt;/h3&gt;
&lt;p&gt;Traversal uses an explicit stack on the GPU as we can&amp;rsquo;t use recursion in WGSL compute shaders:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; trace_bvh_accel(ray_origin: vec3f, ray_dir: vec3f) -&amp;gt; HitInfo {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; closest_hit: HitInfo;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  closest_hit.t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1e30&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  closest_hit.hit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; ray_dir_inv &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; ray_dir;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; stack: array&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;u32, &lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; stack_ptr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0u&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  stack[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0u&lt;/span&gt;;  &lt;span style=&#34;color:#75715e&#34;&gt;// Start at root
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  stack_ptr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1u&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (stack_ptr &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0u&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    stack_ptr &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1u&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; node &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; bvh_nodes[stack[stack_ptr]];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Skip if ray misses this node&amp;#39;s box
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;intersect_aabb(ray_origin, ray_dir_inv,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        node.min_bounds, node.max_bounds, closest_hit.t)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (is_leaf(node)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// Test each triangle in this leaf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0u&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; get_tri_count(node); i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; verts &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tri_verts[node.left_child_or_first_tri &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; hit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; intersect_triangle_uv(ray_origin, ray_dir,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            verts.v0, verts.v1, verts.v2);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (hit.t &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; hit.t &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; closest_hit.t) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          closest_hit.t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; hit.t;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          closest_hit.hit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#75715e&#34;&gt;// Store triangle index and barycentrics...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// Push both children, nearest first
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// (ordered traversal for better early termination)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; closest_hit;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;closest_hit.t&lt;/code&gt; threshold is critical: every time we find a closer hit, the threshold tightens, and subsequent AABB tests reject more nodes. Visiting the nearest child first (ordered traversal) maximises the chance of finding a close hit early, which maximises the pruning of the far child.&lt;/p&gt;
&lt;h3 id=&#34;visualising-the-bvh&#34;&gt;Visualising the BVH&lt;/h3&gt;
&lt;p&gt;The debug visualiser makes traversal efficiency visible. The heatmap mode colours each pixel by how many BVH nodes were visited — cool blues for efficient rays, hot reds for expensive ones. You can try it out below, its worth experimenting with the different scenes and visualisations. The debugger will start in windowed mode so you can see the scene and its BVH simultaneously, but you can also overlay it.&lt;/p&gt;
&lt;p&gt;&lt;path-tracer controls scene=&#34;bvh&#34; samples=&#34;4&#34; bounces=&#34;3&#34; debug-mode=&#34;2&#34; debug-opacity=&#34;100&#34; width=&#34;960&#34; height=&#34;600&#34;&gt;&lt;/path-tracer&gt;&lt;/p&gt;
&lt;p&gt;Notice how rays that pass through the dense cluster of cubes on the right are more expensive (warmer colours) than rays hitting the well-separated cubes on the left. Same number of triangles, very different traversal cost, purely because of spatial arrangement.&lt;/p&gt;
&lt;p&gt;The wireframe mode shows the bounding boxes at each level of the tree. Step through the depths to see how the BVH recursively subdivides space:&lt;/p&gt;
&lt;p&gt;&lt;path-tracer controls scene=&#34;bvh&#34; samples=&#34;4&#34; bounces=&#34;3&#34; debug-mode=&#34;4&#34; debug-depth=&#34;1&#34; debug-opacity=&#34;0&#34; debug-window=&#34;false&#34; controls width=&#34;960&#34; height=&#34;600&#34;&gt;&lt;/path-tracer&gt;&lt;/p&gt;
&lt;p&gt;At depth 0, one box surrounds the entire scene. At depth 1, the first split separates the room roughly in half. By depth 4 or 5, individual objects have their own tight boxes and the spatial partitioning follows the geometry closely.&lt;/p&gt;
&lt;p&gt;The diagonal slab cutting across the scene is deliberately pathological — its AABB spans nearly the entire room, overlapping everything. The heatmap shows the cost: rays near the diagonal visit more nodes because the BVH can&amp;rsquo;t exclude it from any spatial query. This is why axis-aligned BVHs struggle with geometry that doesn&amp;rsquo;t align with any axis.&lt;/p&gt;
&lt;h3 id=&#34;a-bvh-per-tile-making-the-players-torch-work&#34;&gt;A BVH Per Tile: Making the Player&amp;rsquo;s Torch Work&lt;/h3&gt;
&lt;p&gt;Everything described so far assumes a static scene — the BVH is built once on the CPU, uploaded to the GPU, and never changes. That works for the Cornell box and the BVH teaching scene. But in the dungeon the player is a light source, they&amp;rsquo;re carrying a torch and can roam around the dungeon.&lt;/p&gt;
&lt;p&gt;The torch itself is straightforward: it&amp;rsquo;s passed to the shader as a set of uniforms — position, colour, radius, falloff — and the path tracing loop treats it as a point light at the camera position. No geometry, no emissive triangles, just a light that happens to be wherever the player is standing. But the problem is the BVH.&lt;/p&gt;
&lt;p&gt;A single global BVH contains every triangle in the dungeon. For a 16×16 tile map that&amp;rsquo;s around 3,000 triangles — walls, floors, ceilings, torch panels. But the player&amp;rsquo;s torch has a finite range. Light from a handheld torch doesn&amp;rsquo;t illuminate walls four rooms away. Most of those 3,000 triangles are irrelevant at any given moment, and the BVH wastes time testing nodes that enclose distant, unlit geometry.&lt;/p&gt;
&lt;p&gt;The solution is to precompute a separate BVH for every walkable tile. At initialisation, we iterate over each open cell in the dungeon map and build a distance-culled subset of the scene:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;precomputeBVHsForPositions&lt;/span&gt;()&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;positions&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;walkablePositions&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dist&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;renderDistance&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;distSq&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dist&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dist&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;of&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;positions&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Cull triangles by distance from this tile center (XZ plane)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;culled&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Triangle&lt;/span&gt;[] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tri&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;of&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;allTriangles&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;d0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;tri&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v0&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;tri&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v0&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;z&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;z&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;d1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;tri&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v1&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;tri&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v1&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;z&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;z&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;d2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;tri&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;tri&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;z&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;z&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (Math.&lt;span style=&#34;color:#a6e22e&#34;&gt;min&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;d0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;d1&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;d2&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;distSq&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;culled&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;tri&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Build BVH for this subset
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;builder&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;BVHBuilder&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;nodes&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;orderedTriangles&lt;/span&gt; } &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;builder&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;culled&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;flat&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;flattenBVH&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;nodes&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Store for later swap...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The culling is conservative: a triangle is included if &lt;em&gt;any&lt;/em&gt; vertex falls within the render distance. This means the BVH for each tile contains only the geometry that could plausibly be illuminated by the player&amp;rsquo;s torch at that position. A render distance of 10 world units (5 tiles) is enough to capture everything the torch can reach while excluding the far side of the dungeon.&lt;/p&gt;
&lt;p&gt;For each walkable tile we store the packed BVH nodes, the reordered triangle data (both hot vertex positions and cold attributes), and the emissive light list — everything the GPU needs, precomputed and ready to upload.&lt;/p&gt;
&lt;p&gt;At render time, when the player steps onto a new tile, we detect the change and hot-swap the buffers:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;swapBVHForTile&lt;/span&gt;()&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// Find nearest walkable position to camera
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cx&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;camera&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cz&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;camera&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;z&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bestKey&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTileKey&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bestDist&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Infinity&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;of&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;walkablePositions&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dx&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cx&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dz&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;z&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cz&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;d&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dx&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dx&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dz&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dz&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;d&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bestDist&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;bestDist&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;d&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;bestKey&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;,&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;z&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;bestKey&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTileKey&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;entry&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;precomputedBVHs&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;bestKey&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;entry&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// Upload this tile&amp;#39;s BVH, triangles, and light list
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;device&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;queue&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;writeBuffer&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;bvhBuffer&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;entry&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;bvhData&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;device&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;queue&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;writeBuffer&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;triangleBuffer&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;entry&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;triVertsData&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;buffer&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;device&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;queue&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;writeBuffer&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;triAttribsBuffer&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;entry&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;triAttribsData&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;buffer&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;device&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;queue&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;writeBuffer&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lightsBuffer&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;entry&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lightData&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;buffer&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTileKey&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bestKey&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The GPU buffers are pre-allocated to the size of the largest per-tile BVH, so the swap is just a series of &lt;code&gt;writeBuffer&lt;/code&gt; calls — no reallocation, no pipeline rebuild. In practice, for our 16×16 dungeon with around 130 walkable tiles, precomputing all the BVHs takes a few hundred milliseconds at startup and the per-tile swap is imperceptible.&lt;/p&gt;
&lt;p&gt;The tradeoff is memory: we&amp;rsquo;re storing ~130 copies of partially overlapping BVH data. For a dungeon this size it&amp;rsquo;s negligible. For a sprawling open world it wouldn&amp;rsquo;t scale — you&amp;rsquo;d want streaming or incremental BVH updates instead. But for a tile-based dungeon where movement is discrete and the render distance is bounded, precomputation is the simplest thing that works, and given everything else going on here I&amp;rsquo;ll take simple.&lt;/p&gt;
&lt;p&gt;You can see this in action in the demonstration below. As you move around the map you&amp;rsquo;ll see the BVH changing.&lt;/p&gt;
&lt;p&gt;&lt;path-tracer controls scene=&#34;dungeon&#34; samples=&#34;4&#34; bounces=&#34;3&#34; debug-mode=&#34;4&#34; debug-depth=&#34;1&#34; debug-opacity=&#34;0&#34; debug-window=&#34;false&#34; controls width=&#34;960&#34; height=&#34;600&#34;&gt;&lt;/path-tracer&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;path-tracing&#34;&gt;Path Tracing&lt;/h2&gt;
&lt;p&gt;Finding the closest surface is just the beginning. Now we need to compute the light arriving at that surface point — which requires solving the rendering equation.&lt;/p&gt;
&lt;h3 id=&#34;monte-carlo-integration-and-importance-sampling&#34;&gt;Monte Carlo Integration and Importance Sampling&lt;/h3&gt;
&lt;h4 id=&#34;why-the-integral-cant-be-solved-analytically&#34;&gt;Why the integral can&amp;rsquo;t be solved analytically&lt;/h4&gt;
&lt;p&gt;The hemisphere integral asks: what&amp;rsquo;s the total light arriving from every direction above this surface point? For that to have a closed-form solution, you&amp;rsquo;d need to know the incoming radiance function $L_i(\omega_i)$ everywhere — which itself depends on the geometry of the entire scene, every other surface&amp;rsquo;s reflectance, every light source, every occlusion. The incoming light at one point depends on the outgoing light at every other point, which depends on the incoming light at &lt;em&gt;those&lt;/em&gt; points. It&amp;rsquo;s recursive and scene-dependent. There&amp;rsquo;s no formula you can write down and evaluate. You have to go and &lt;em&gt;look&lt;/em&gt;.&lt;/p&gt;
&lt;h4 id=&#34;the-basic-monte-carlo-idea&#34;&gt;The basic Monte Carlo idea&lt;/h4&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Monte_Carlo_integration&#34;&gt;Monte Carlo integration&lt;/a&gt; is a general technique: if you can&amp;rsquo;t evaluate an integral, you can estimate it by sampling. The simplest version — pick $N$ random points in the domain, evaluate the function at each, and average — gives you an estimate that converges to the true answer as $N$ grows. The formal statement is:&lt;/p&gt;
$$\int f(x) \, dx \approx \frac{1}{N} \sum_{i=1}^{N} \frac{f(x_i)}{p(x_i)}$$&lt;p&gt;where $p(x_i)$ is the probability density of picking sample $x_i$. If you sample uniformly over the hemisphere, $p$ is a constant ($1/2\pi$ for a uniform hemisphere), and you&amp;rsquo;re just averaging scaled function evaluations.&lt;/p&gt;
&lt;p&gt;This works but it&amp;rsquo;s inefficient. Most of the light contribution at a diffuse surface comes from directions near the surface normal (overhead), because the $\cos\theta$ term in the rendering equation means glancing rays contribute almost nothing. Uniform sampling wastes effort on those near-horizon directions that barely matter.&lt;/p&gt;
&lt;p&gt;And so we factor in importance.&lt;/p&gt;
&lt;h4 id=&#34;what-importance-sampling-does&#34;&gt;What importance sampling does&lt;/h4&gt;
&lt;p&gt;The idea is straightforward even if the name sounds fancy: if you sample more densely in the directions that contribute the most, each sample gives you more useful information and the estimate converges faster — less noise for the same sample count. You bias your random sampling toward the regions that matter.&lt;/p&gt;
&lt;p&gt;The ideal is to sample proportionally to the integrand itself. For a diffuse surface the integrand includes that $\cos\theta$ factor, so you choose a probability distribution that&amp;rsquo;s proportional to $\cos\theta$:&lt;/p&gt;
$$p(\omega) = \frac{\cos\theta}{\pi}$$&lt;p&gt;The $\pi$ is just the normalisation constant that makes it integrate to 1 over the hemisphere. Nothing deeper going on there.&lt;/p&gt;
&lt;h4 id=&#34;the-cancellation&#34;&gt;The cancellation&lt;/h4&gt;
&lt;p&gt;Now plug everything into the Monte Carlo estimator. The rendering equation integrand for a diffuse surface is:&lt;/p&gt;
$$f_r \cdot L_i(\omega_i) \cdot \cos\theta_i$$&lt;p&gt;where $f_r = \text{albedo}/\pi$ for a Lambertian BRDF. The Monte Carlo estimator divides by the sampling probability:&lt;/p&gt;
$$\frac{f_r \cdot L_i(\omega_i) \cdot \cos\theta_i}{p(\omega_i)} = \frac{(\text{albedo}/\pi) \cdot L_i(\omega_i) \cdot \cos\theta_i}{\cos\theta_i / \pi}$$&lt;p&gt;The $\cos\theta_i$ cancels top and bottom. The $\pi$ cancels top and bottom. You&amp;rsquo;re left with:&lt;/p&gt;
$$\text{albedo} \cdot L_i(\omega_i)$$&lt;p&gt;So after all that setup, each sample just multiplies the surface colour by whatever light arrived from the sampled direction. No trigonometry at shading time, no normalisation constants and the importance sampling has absorbed all of it into the sampling distribution itself.&lt;/p&gt;
&lt;h4 id=&#34;why-dividing-by-pomega_i-matters&#34;&gt;Why dividing by $p(\omega_i)$ matters&lt;/h4&gt;
&lt;p&gt;This is the bit that can feel counterintuitive. You&amp;rsquo;re sampling directions near the normal more often — so why do you &lt;em&gt;divide&lt;/em&gt; by the probability, which makes each of those frequently-sampled directions count for &lt;em&gt;less&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;Because without the division, directions near the normal would be both sampled more often &lt;em&gt;and&lt;/em&gt; weighted equally. They&amp;rsquo;d be double-counted. The $1/p$ correction is what keeps the estimator unbiased: you sample those directions more, so each individual sample has to count for less, and on average it converges to the same answer as if you&amp;rsquo;d sampled uniformly — just with less noise along the way.&lt;/p&gt;
&lt;h4 id=&#34;the-practical-consequence&#34;&gt;The practical consequence&lt;/h4&gt;
&lt;p&gt;Your shader code doesn&amp;rsquo;t need to compute any cosine weighting at shading time. All the geometric complexity is baked into the &lt;code&gt;cosine_hemisphere&lt;/code&gt; function that &lt;em&gt;generates&lt;/em&gt; the directions — it naturally produces more directions near the normal and fewer near the horizon, and the maths guarantees the estimator stays correct.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; cosine_hemisphere(normal: vec3f, r1: f32, r2: f32) -&amp;gt; vec3f {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; phi &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; PI &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; r1;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; cos_theta &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sqrt(r2);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; sin_theta &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sqrt(&lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; r2);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cos(phi) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; sin_theta;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sin(phi) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; sin_theta;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; z &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cos_theta;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// Build tangent space from normal
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; tangent: vec3f;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (abs(normal.y) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.999&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tangent &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; normalize(cross(vec3f(&lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;), normal));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tangent &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; normalize(cross(vec3f(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;), normal));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; bitangent &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cross(normal, tangent);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; tangent &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; x &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; bitangent &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; y &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; normal &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; z;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The two random inputs &lt;code&gt;r1&lt;/code&gt; and &lt;code&gt;r2&lt;/code&gt; (uniform in $[0, 1]$) are transformed into a direction on the hemisphere. The &lt;code&gt;sqrt(r2)&lt;/code&gt; for &lt;code&gt;cos_theta&lt;/code&gt; is where the cosine weighting lives — a uniform random variable passed through a square root produces a cosine-weighted distribution. Without that &lt;code&gt;sqrt&lt;/code&gt;, you&amp;rsquo;d get uniform hemisphere sampling. The rest of the function builds a coordinate frame from the surface normal and transforms the local-space direction into world space.&lt;/p&gt;
&lt;p&gt;That covers a single bounce: fire a ray, hit a surface, pick a cosine-weighted direction, see what light comes back. But one bounce only captures direct illumination — light that travels straight from a source to a surface to the camera. The rendering equation is recursive. Light bouncing off a wall illuminates the floor, which illuminates the ceiling, which illuminates the wall again. To capture this indirect illumination you need to follow the path further: bounce again, and again, accumulating the effect of each surface&amp;rsquo;s colour on the light passing through it. That chain of bounces — and knowing when to stop — is what the path tracing loop handles.&lt;/p&gt;
&lt;p&gt;Ooof. Thats a lot. As I&amp;rsquo;ve said before I didn&amp;rsquo;t get here in one go. This is the product of experimenting, asking questions, and trying different things with the goal being to get a decent set of trade offs between image quality and performance.&lt;/p&gt;
&lt;h3 id=&#34;the-path-tracing-loop&#34;&gt;The Path Tracing Loop&lt;/h3&gt;
&lt;p&gt;A single sample traces a path through the scene: camera → surface → bounce → surface → bounce → &amp;hellip; until the ray hits a light, escapes the scene, or is terminated by Russian roulette.&lt;/p&gt;
&lt;p&gt;At each surface interaction, the &lt;em&gt;throughput&lt;/em&gt; tracks how much light is attenuated by the path so far. Each diffuse bounce multiplies the throughput by the surface colour. When the path eventually hits a light source, the emitted light is scaled by the accumulated throughput:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; path_trace(...) -&amp;gt; vec3f {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; throughput &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; vec3f(&lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; radiance &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; vec3f(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; bounce &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0u&lt;/span&gt;; bounce &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; max_bounces; bounce&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; hit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; trace_scene(ray_origin, ray_dir, bounce);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;hit.hit) { &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; mat &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; materials[hit.material_index];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// If we hit a light, add its contribution
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (mat.material_type &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; MATERIAL_EMISSIVE) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      radiance &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; throughput &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; mat.emissive;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Attenuate throughput by surface colour
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    throughput &lt;span style=&#34;color:#f92672&#34;&gt;*=&lt;/span&gt; surface_color;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Bounce in a random direction
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    ray_dir &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cosine_hemisphere(normal, random(), random());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ray_origin &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; hit_pos &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; normal &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.001&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Russian roulette: randomly terminate dim paths
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (bounce &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2u&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; p &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; max(throughput.x, max(throughput.y, throughput.z));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (random() &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; p) { &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      throughput &lt;span style=&#34;color:#f92672&#34;&gt;/=&lt;/span&gt; p;  &lt;span style=&#34;color:#75715e&#34;&gt;// Compensate for termination probability
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; radiance;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Russian roulette is the mathematically correct way to terminate paths which to me wasn&amp;rsquo;t particularly intuitive. To understand why consider the alternative: capping at a fixed bounce count, say 4 bounces. Any light that would have arrived via a 5th or 6th bounce is simply thrown away and in a torch-lit stone corridor, where most illumination reaches surfaces after multiple bounces off walls, that&amp;rsquo;s a lot of lost energy. The image would be systematically too dark: not because of noise, but because of bias. No amount of additional samples will fix it, because every sample is missing the same light.&lt;/p&gt;
&lt;p&gt;Russian roulette avoids this by never unconditionally terminating a path. Instead, at each bounce beyond the first few, we randomly terminate paths with a probability proportional to their remaining energy. A tempered randomisation so to speak.&lt;/p&gt;
&lt;p&gt;After several bounces, the throughput — the cumulative product of all surface colours along the path — tells you how much light this path could still contribute. A ray that has bounced off three dark stone walls has a throughput near zero. Even if it eventually finds a light source, its contribution to the final pixel will be negligible and continuing to trace it is wasted computation.&lt;/p&gt;
&lt;p&gt;So at each bounce we roll the dice. If the maximum component of the throughput is 0.1, there&amp;rsquo;s a 90% chance we terminate the path right there. The 10% of paths that survive are boosted by $1/p$ — in this case multiplied by 10 — to compensate for all the paths that were killed. Any individual surviving path overestimates its contribution, but averaged across many samples, the overestimation cancels the energy lost from terminated paths. The estimator remains unbiased — on average, it converges to the same answer as if we&amp;rsquo;d traced every path to infinite depth.&lt;/p&gt;
&lt;p&gt;And so with this approach we&amp;rsquo;re spending our computation budget on the paths that matter.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (bounce &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2u&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; p &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; max(throughput.x, max(throughput.y, throughput.z));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (pcg(rng_state) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; p) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;  &lt;span style=&#34;color:#75715e&#34;&gt;// Path terminated — it wasn&amp;#39;t going to contribute much
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  throughput &lt;span style=&#34;color:#f92672&#34;&gt;/=&lt;/span&gt; p;  &lt;span style=&#34;color:#75715e&#34;&gt;// Survivors are boosted to compensate
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;bounce &amp;gt; 2u&lt;/code&gt; guard ensures the first few bounces always run — you don&amp;rsquo;t want to randomly kill a primary ray. My first cut of this was missing this and I got a very &amp;ldquo;partial&amp;rdquo; screen - lots of space. But after that, dim paths are likely to be terminated and bright paths continue until they find light or fade to nothing.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;why-is-everything-so-noisy&#34;&gt;Why Is Everything So Noisy?&lt;/h2&gt;
&lt;p&gt;At low sample counts, each pixel&amp;rsquo;s estimate is based on a handful of random paths. Some paths happen to hit a light source — they return bright values. Other paths bounce into shadows — they return dark values. The average of these few samples is far from the true answer, and neighbouring pixels get different random paths, so they disagree. That disagreement is noise.&lt;/p&gt;
&lt;p&gt;&lt;path-tracer controls scene=&#34;dungeon&#34; samples=&#34;1&#34; bounces=&#34;4&#34; temporal=&#34;0&#34; denoise=&#34;off&#34; player-light=&#34;17&#34; player-falloff=&#34;13&#34; phantom width=&#34;960&#34; height=&#34;600&#34;&gt;&lt;/path-tracer&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The dungeon at 1 sample per pixel. Every pixel fired a single random path. The image is technically correct — each pixel is an unbiased estimate of the true colour — but the variance is enormous.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The noise follows a $1/\sqrt{N}$ convergence rate. Doubling your samples halves the standard deviation. To reduce noise by a factor of 10, you need 100× more samples. This is why brute-force path tracing is so expensive: visually clean images require hundreds or thousands of samples per pixel.&lt;/p&gt;
&lt;p&gt;At 1280×800 with 4 bounces, the numbers are unforgiving. Each sample per pixel traces a path of up to 4 rays through the BVH, and there are 1,024,000 pixels:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Samples/pixel&lt;/th&gt;
          &lt;th&gt;Rays per frame&lt;/th&gt;
          &lt;th&gt;Time at 330M rays/sec&lt;/th&gt;
          &lt;th&gt;Framerate&lt;/th&gt;
          &lt;th&gt;Noise relative to 1 spp&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;~4M&lt;/td&gt;
          &lt;td&gt;12ms&lt;/td&gt;
          &lt;td&gt;~80fps&lt;/td&gt;
          &lt;td&gt;1.0×&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;16&lt;/td&gt;
          &lt;td&gt;~65M&lt;/td&gt;
          &lt;td&gt;200ms&lt;/td&gt;
          &lt;td&gt;~5fps&lt;/td&gt;
          &lt;td&gt;0.25× (4× cleaner)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;512&lt;/td&gt;
          &lt;td&gt;~2.1B&lt;/td&gt;
          &lt;td&gt;6.4s&lt;/td&gt;
          &lt;td&gt;~0.15fps&lt;/td&gt;
          &lt;td&gt;0.044× (23× cleaner)&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;At 1 sample per pixel you get 80fps and an image that&amp;rsquo;s pure noise. At 16 samples you can start to make out the scene, but you&amp;rsquo;re at 5fps — not interactive, and still visibly grainy. To get a genuinely clean image you need hundreds or thousands of samples, which means seconds per frame. The path tracer at 512 spp takes over six seconds to render a single frame, and even that isn&amp;rsquo;t fully converged in the darkest corners.&lt;/p&gt;
&lt;p&gt;With enough samples — either from a single high-spp frame or accumulated over time via temporal blending — the image converges to something beautiful:&lt;/p&gt;
&lt;p&gt;&lt;path-tracer controls scene=&#34;dungeon&#34; samples=&#34;64&#34; bounces=&#34;4&#34; temporal=&#34;1&#34; denoise=&#34;off&#34; player-light=&#34;17&#34; player-falloff=&#34;13&#34; phantom width=&#34;960&#34; height=&#34;600&#34;&gt;&lt;/path-tracer&gt;&lt;/p&gt;
&lt;p&gt;The path between 1 noisy sample and a converged image is where all the engineering lives. We don&amp;rsquo;t have the computational power to do lots of samples even on a modern GPU.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-ghost-in-the-machine&#34;&gt;The Ghost in the Machine&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s a particular artefact worth understanding because it reveals how temporal accumulation works — and fails.&lt;/p&gt;
&lt;p&gt;Dynamic objects in the scene (like the phantom enemy) appear semi-transparent when temporal blending is active. This isn&amp;rsquo;t a rendering bug in the traditional sense, the temporal accumulator is doing exactly what it&amp;rsquo;s designed to do, with unintended consequences.&lt;/p&gt;
&lt;p&gt;When the camera is static, each frame&amp;rsquo;s output is blended with the accumulated history using a $1/N$ weighting:&lt;/p&gt;
$$\text{result} = \frac{1}{N+1} \cdot \text{current} + \frac{N}{N+1} \cdot \text{history}$$&lt;p&gt;After 20 frames, the current frame contributes only 5% to the result. If the phantom moves into a pixel&amp;rsquo;s location at frame 21 then that pixel&amp;rsquo;s value is 95% wall colour (from the previous 20 frames of history) and 5% phantom colour. The phantom is literally transparent — you&amp;rsquo;re seeing it blended with what used to be behind it.&lt;/p&gt;
&lt;p&gt;It takes many frames for the phantom to become fully opaque. At a blend factor of 0.05, reaching 90% opacity takes about 45 frames. At 88fps, that&amp;rsquo;s half a second of ghosting. And if the phantom is moving, it never becomes fully opaque — it&amp;rsquo;s a perpetual ghost.&lt;/p&gt;
&lt;p&gt;The fix requires per-pixel change detection: if a pixel&amp;rsquo;s brightness has changed significantly from its history, blend aggressively toward the current frame regardless of the static frame count but distinguishing &amp;ldquo;the scene changed&amp;rdquo; from &amp;ldquo;the noise pattern changed&amp;rdquo; is hard when your input is noisy. This is the fundamental tension of temporal accumulation — the thing that makes it converge (heavy reliance on history) is the same thing that makes it resist change.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;denoising-inventing-the-rest&#34;&gt;Denoising: Inventing the Rest&lt;/h2&gt;
&lt;p&gt;At real-time sample counts (1–8 spp), the raw path-traced image is too noisy to be usable. A spatial denoiser smooths the noise using information from the G-buffer — the normal and depth at each pixel — to blur within surfaces while preserving edges.&lt;/p&gt;
&lt;h3 id=&#34;the-à-trous-wavelet-filter&#34;&gt;The À-Trous Wavelet Filter&lt;/h3&gt;
&lt;p&gt;This is a fairly standard spatial denoiser, it&amp;rsquo;s a series of increasingly spaced bilateral filter passes where each pass applies a 5×5 kernel with a step size that doubles each iteration:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Pass&lt;/th&gt;
          &lt;th&gt;Step Size&lt;/th&gt;
          &lt;th&gt;Effective Radius&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;2 pixels&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;2&lt;/td&gt;
          &lt;td&gt;4 pixels&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2&lt;/td&gt;
          &lt;td&gt;4&lt;/td&gt;
          &lt;td&gt;8 pixels&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;3&lt;/td&gt;
          &lt;td&gt;8&lt;/td&gt;
          &lt;td&gt;16 pixels&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;4&lt;/td&gt;
          &lt;td&gt;16&lt;/td&gt;
          &lt;td&gt;32 pixels&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Five passes give a 32-pixel blur radius using only a 5×5 kernel — much cheaper than a single 65×65 convolution.&lt;/p&gt;
&lt;p&gt;At each sample position, three weights control the blending. The normal weight prevents blurring across geometric edges — if two pixels have very different surface normals, they belong to different surfaces and shouldn&amp;rsquo;t be mixed:&lt;/p&gt;
$$w_{\text{normal}} = \max(0, \mathbf{n}_c \cdot \mathbf{n}_s)^{128}$$&lt;p&gt;The depth weight prevents blurring across depth discontinuities — a wall and a distant corridor behind a doorway shouldn&amp;rsquo;t blend together:&lt;/p&gt;
$$w_{\text{depth}} = \exp\left(-\frac{(\Delta d / d_c)^2}{2\sigma_d^2}\right)$$&lt;p&gt;The spatial weight comes from the à-trous kernel itself, a separable $[1, 4, 6, 4, 1]/16$ distribution.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// In the inner loop of each à-trous pass:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; spatial_weight &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; kernel_weights[i] &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; kernel_weights[j];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; normal_dot &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; max(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;, dot(center_normal, sample_normal));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; normal_weight &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pow(normal_dot, &lt;span style=&#34;color:#ae81ff&#34;&gt;128.0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; relative_depth &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; abs(center_depth &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; sample_depth) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; max(center_depth, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.001&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; depth_weight &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; exp(&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;relative_depth &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; relative_depth &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;100.0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; weight &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; spatial_weight &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; normal_weight &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; depth_weight;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The challenge is balancing blur against detail. Too aggressive and the image loses texture; too conservative and noise remains. We found that removing the colour similarity weight entirely and relying purely on geometric (normal + depth) weights produced the best results — the colour weight was systematically biased toward darker values, causing energy loss.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll be honest this is the bit I&amp;rsquo;m least satisfied with - I&amp;rsquo;ve not got to what feels like a good balance between blur and detail. But without going down a denoising / ML rabbit hole thats probably the best I&amp;rsquo;m going to do for now.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;temporal-accumulation-memory-between-frames&#34;&gt;Temporal Accumulation: Memory Between Frames&lt;/h2&gt;
&lt;p&gt;In contrast to spatial denoising which operates within a single frame temporal accumulation operates across frames: each frame&amp;rsquo;s result is blended with the accumulated history from previous frames.&lt;/p&gt;
&lt;p&gt;When the camera is static, this is equivalent to gathering more samples over time. Frame 1 has 4 spp. After blending with frame 2 (also 4 spp), you effectively have 8 spp. After 10 frames, 40 spp. The image progressively converges toward the 512 spp quality shown earlier.&lt;/p&gt;
&lt;p&gt;When the camera moves, the history must be &lt;em&gt;reprojected&lt;/em&gt; — each pixel&amp;rsquo;s world position from the current frame is transformed into the previous frame&amp;rsquo;s screen space to find where it was. This requires the previous frame&amp;rsquo;s view-projection matrix:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; reproject(world_pos: vec3f) -&amp;gt; vec2f {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; clip &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; matrices.prev_view_proj &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; vec4f(world_pos, &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; ndc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; clip.xy &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; clip.w;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; vec2f(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (ndc.x &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; screen_width,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#ae81ff&#34;&gt;0.5&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; ndc.y &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; screen_height
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the reprojected position is off-screen, or the depth at that position doesn&amp;rsquo;t match (indicating a different surface — disocclusion), the history is rejected and the current frame is used alone.&lt;/p&gt;
&lt;p&gt;To prevent ghosting from stale history during movement, a neighbourhood clamp restricts the history colour to the range observed in the current frame&amp;rsquo;s 3×3 neighbourhood, computed in YCoCg colour space for better perceptual uniformity:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; stats &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; get_neighbourhood_stats(pixel_coord);  &lt;span style=&#34;color:#75715e&#34;&gt;// [mean, stddev]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; history_ycocg &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rgb_to_ycocg(history_col);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; clamped &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; clamp(history_ycocg,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    stats[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; stats[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.25&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    stats[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; stats[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.25&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;history_col &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ycocg_to_rgb(clamped);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the same technique used by every modern game engine with temporal anti-aliasing (TAA). The tradeoff is always the same: tighter clamping reduces ghosting but increases flicker; looser clamping smooths noise but smears moving objects.&lt;/p&gt;
&lt;p&gt;Again this is another rabbit hole I feel I would need to go down to get better results.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-pipeline&#34;&gt;The Pipeline&lt;/h2&gt;
&lt;p&gt;The full render pipeline executes three compute passes per frame, followed by a full-screen blit:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Path trace&lt;/strong&gt; — fire rays, compute lighting, write colour + G-buffer (normals, depth)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Temporal accumulation&lt;/strong&gt; — blend current frame with reprojected history&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spatial denoise&lt;/strong&gt; — à-trous wavelet filter guided by the G-buffer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blit&lt;/strong&gt; — tone map (Reinhard) and gamma correct, display on screen&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each pass reads from textures written by the previous pass. The temporal pass ping-pongs between two history textures so the output of frame N becomes the input history for frame N+1.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;memory-layout-hot-and-cold-data&#34;&gt;Memory Layout: Hot and Cold Data&lt;/h2&gt;
&lt;p&gt;A ray traversing the BVH tests dozens of triangles. Each intersection test only needs the three vertex positions — 48 bytes. But the original &lt;code&gt;Triangle&lt;/code&gt; struct also contained normals, material indices, UV coordinates, and texture indices — another 48 bytes that are only needed for the single closest hit.&lt;/p&gt;
&lt;p&gt;Splitting the triangle data into two separate GPU buffers halves the memory traffic during traversal:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Hot path: read during every intersection test
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; TriangleVerts {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  v0: vec3f, _pad0: f32,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  v1: vec3f, _pad1: f32,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  v2: vec3f, _pad2: f32,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Cold path: read once per ray, only for the winner
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; TriangleAttribs {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  normal: vec3f,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  material_index: u32,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  uv0: vec2f, uv1: vec2f,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  uv2: vec2f,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  texture_index: i32,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  _pad: f32,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After finding the closest hit (by index), a single read from the attribute buffer resolves everything needed for shading:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; resolve_hit(hit: HitInfo) -&amp;gt; ResolvedHit {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; attrib &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tri_attribs[hit.tri_index];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// Interpolate UVs using stored barycentrics
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; w &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; hit.bary_u &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; hit.bary_v;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  r.uv &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; w &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; attrib.uv0 &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; hit.bary_u &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; attrib.uv1 &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; hit.bary_v &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; attrib.uv2;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;randomness-on-the-gpu&#34;&gt;Randomness on the GPU&lt;/h2&gt;
&lt;p&gt;Path tracing requires enormous quantities of random numbers — two per bounce direction, one for Russian roulette, plus sub-pixel jitter. GPU compute shaders don&amp;rsquo;t have access to system random number generators, so we use a PCG (&lt;a href=&#34;https://en.wikipedia.org/wiki/Permuted_congruential_generator&#34;&gt;Permuted Congruential Generator&lt;/a&gt;) hash function (honestly I&amp;rsquo;ve lost track of the number of different ways I&amp;rsquo;ve been through to generate random numbers on a GPU):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; pcg(state: ptr&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;function, u32&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;) -&amp;gt; f32 {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;state &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;state &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;747796405u&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2891336453u&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; word &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ((&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;state &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; ((&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;state &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;28u&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4u&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;^&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;state) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;277803737u&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; f32((word &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;22u&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;^&lt;/span&gt; word) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4294967295.0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The seed combines the pixel coordinates and the frame number, ensuring that each pixel gets a unique sequence and each frame produces a different noise pattern:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-wgsl&#34; data-lang=&#34;wgsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; rand_seed(pixel: vec2u, frame: u32) -&amp;gt; u32 {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; pixel.x &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1973u&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; pixel.y &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;9277u&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; frame &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;26699u&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The frame number is critical for temporal accumulation — if every frame produced identical noise, averaging them would give the same noisy result. Different seeds per frame mean independent noise patterns, and independent samples converge via the &lt;a href=&#34;https://en.wikipedia.org/wiki/Law_of_large_numbers&#34;&gt;law of large numbers&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;what-games-actually-do-and-why-its-worse-than-you-think&#34;&gt;What Games Actually Do (And Why It&amp;rsquo;s Worse Than You Think)&lt;/h2&gt;
&lt;p&gt;Every real-time ray-traced game you&amp;rsquo;ve seen — Cyberpunk 2077, Quake II RTX, Metro Exodus — uses the same fundamental approach described here. The differences are in scale and sophistication, but also (in a way and in the long tradition of videogames) honesty.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hardware RT cores&lt;/strong&gt; accelerate BVH traversal and ray–triangle intersection in dedicated silicon, achieving maybe 10× the throughput of our software compute shader. But RT cores are a small fraction of the die area doing a small fraction of the visual work. They get top billing in the marketing because &amp;ldquo;AI upscaling ON&amp;rdquo; doesn&amp;rsquo;t sell £1,600 graphics cards and in fact many find it noticeable, detrimental and jarring (myself included).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Neural denoisers&lt;/strong&gt; replace the simple à-trous filter with trained neural networks that can reconstruct a plausible image from 1 sample per pixel. The word &amp;ldquo;plausible&amp;rdquo; is doing a lot of heavy lifting as these reconstructions shimmer on thin geometry, smear during movement, and produce a subtle temporal instability that your visual system registers even if you can&amp;rsquo;t articulate what&amp;rsquo;s wrong. Follow independent reviewers on YouTube and you&amp;rsquo;ll quickly find people bemoaning that things look messy and that we&amp;rsquo;ve lost the sharpness we used to have.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Temporal upscaling&lt;/strong&gt; (DLSS, FSR, XeSS) runs the renderer at a fraction of the display resolution — often 540p or 720p internally — and uses a temporal neural network to hallucinate a 4K output. These are presented as features but they&amp;rsquo;re really admissions of failure. They exist because the raw silicon isn&amp;rsquo;t advancing fast enough to brute-force the problems they&amp;rsquo;re designed to mask. Each GPU generation now delivers less actual hardware improvement and more software gimmicks, while prices go up. The RTX 5080 &lt;a href=&#34;https://gamersnexus.net/gpus/nvidia-geforce-rtx-5080-founders-edition-review-benchmarks-vs-5090-7900-xtx-4080-more&#34;&gt;outperforms the previous generation by as little as 2.5%&lt;/a&gt; in some titles. Its headline feature — Multi Frame Generation — inserts AI-generated fake frames that add latency and visual artefacts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hybrid rendering&lt;/strong&gt; traces rays only for specific effects — reflections, shadows, global illumination — while primary visibility is rasterised. So &amp;ldquo;RTX ON&amp;rdquo; typically means a handful of noisy ray-traced contributions composited onto a clean rasterised base, then run through three layers of neural reconstruction to hide the noise.&lt;/p&gt;
&lt;p&gt;The result is that the image you see is, in a very real sense, a hallucination — the AI is inventing most of what&amp;rsquo;s displayed. Once you&amp;rsquo;ve seen it you can&amp;rsquo;t unsee it. Hair ghosts. Fences shimmer. Text on in-game signs goes soft. Edges trail during fast camera movement. Games from ten years ago — running at native resolution on GPUs that cost a quarter of the price — actually often looked &lt;em&gt;sharper&lt;/em&gt; and more stable than what we get today from a £1,600 card that&amp;rsquo;s spending half its transistor budget on generating frames that never existed.&lt;/p&gt;
&lt;p&gt;I had to turn DLSS on to get decent performance out of The Outer Worlds 2 on a 5080 — a card that should be running rings around anything Obsidian can throw at it. The distortion was distracting. And that game isn&amp;rsquo;t even graphically ambitious. If a 5080 can&amp;rsquo;t run a narrative RPG cleanly without AI reconstruction, what exactly is the hardware for? The answer, increasingly, is that the GPU is a DLSS delivery mechanism that happens to also have some rasterisation hardware attached and lets lazy developers get away with shipping lazy sloppy unperformant code. We are, quite literally, paying for poor coding and paying twice: once for the game, once for the hardware that facilitates it.&lt;/p&gt;
&lt;p&gt;To be fair: graphics in games have always been about trickery but I guess whats changed is that the trickery used to feel like genuinely novel and innovative use of the hardware and people pushing limits. Now that trickery often feels like its being used to push up prices and ship poorly optimised code. Thats a big difference.&lt;/p&gt;
&lt;p&gt;What you see here is the approach without these tricks: rays, noise, and no hallucination. At 512 spp the image is genuinely path-traced — every pixel computed, nothing invented. At 4 spp it&amp;rsquo;s grainy but truthful. The noise from indirect illumination in a torch-lit dungeon reads as atmosphere rather than artefact. Sure its not going to ship as a viable game (though I think you could lean into the aesthetic) but its a good representation of whats going on at the bottom.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;try-it-yourself&#34;&gt;Try It Yourself&lt;/h2&gt;
&lt;p&gt;The renderer below is the live WebGPU path tracer described in this post. Click the play button to start rendering, then use WASD to move and Q/E to turn. The settings panel lets you adjust samples per pixel, bounce count, resolution, and toggle the temporal and spatial denoising passes.&lt;/p&gt;
&lt;p&gt;&lt;path-tracer scene=&#34;dungeon&#34; samples=&#34;4&#34; bounces=&#34;4&#34; temporal=&#34;1&#34; denoise=&#34;atrous&#34; denoise-passes=&#34;1&#34; player-light=&#34;17&#34; player-falloff=&#34;13&#34; phantom controls&gt;&lt;/path-tracer&gt;&lt;/p&gt;
&lt;p&gt;The source code is available on &lt;a href=&#34;https://github.com/JamesRandall/webgpu-doom-pathtracer&#34;&gt;GitHub&lt;/a&gt;. The entire renderer — path tracing, BVH construction, temporal accumulation, spatial denoising, and the Doom WAD loader — is roughly 2,500 lines of TypeScript and WGSL.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Path Tracer</title>
      <link>https://www.jamesdrandall.com/projects/path-tracer/</link>
      <pubDate>Sun, 08 Mar 2026 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/projects/path-tracer/</guid>
      <description>&lt;script type=&#34;module&#34; src=&#34;path-tracer.js&#34;&gt;&lt;/script&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/JamesRandall/webgpu-doom-pathtracer&#34;&gt;The source code is on GitHub.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A real-time path tracer built entirely in WebGPU compute shaders — no RT cores, no ML denoisers, just maths and triangles. Woke up one morning and wanted to follow up the &lt;a href=&#34;https://www.jamesdrandall.com/projects/wolfenstein3d/&#34;&gt;Wolfenstein 3D&lt;/a&gt; raycaster with a ray tracer&amp;hellip; obviously Doom levels were an obvious starting point.&lt;/p&gt;
&lt;p&gt;It traces rays through Doom levels and procedural dungeons at 60fps, with a BVH, temporal accumulation, and à-trous denoising. At 512 samples per pixel it&amp;rsquo;s genuinely path-traced — every pixel computed, nothing hallucinated. At 4 samples it&amp;rsquo;s grainy but honest, and the noise reads as atmosphere. Thats what I tell myself anyway.&lt;/p&gt;
&lt;p&gt;This was a real voyage of dicovery and a lot of fun. You might be able to run Doom on a fridge: but you certainly can&amp;rsquo;t the ray traced version!&lt;/p&gt;
&lt;p&gt;Best thing to do is to try it below. You&amp;rsquo;ll need to press play to start - its computationally heavy and is likely to make fans spin up. I&amp;rsquo;ve &lt;a href=&#34;https://www.jamesdrandall.com/posts/building-a-real-time-path-tracer-in-webgpu/&#34;&gt;written about building it too&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;path-tracer scene=&#34;doom&#34; samples=&#34;4&#34; bounces=&#34;4&#34; temporal=&#34;1&#34; denoise=&#34;atrous&#34; denoise-passes=&#34;1&#34; player-light=&#34;17&#34; player-falloff=&#34;13&#34; phantom controls&gt;&lt;/path-tracer&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Jensen Huang Is Training His Own Replacement</title>
      <link>https://www.jamesdrandall.com/posts/jensen-huang-is-training-his-own-replacement/</link>
      <pubDate>Sat, 07 Mar 2026 13:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/posts/jensen-huang-is-training-his-own-replacement/</guid>
      <description>&lt;p&gt;The leather jacket has no clothes.&lt;/p&gt;
&lt;p&gt;Jensen Huang stands on stage at GTC, basking in the adulation of an audience that treats product launches like religious experiences while NVIDIA&amp;rsquo;s market cap hovers north of four trillion dollars. The narrative being pushed is simple, that Jensen is a visionary genius, that NVIDIA is the essential infrastructure of the AI revolution, and that the future belongs to GPU compute.&lt;/p&gt;
&lt;p&gt;I think this narrative is wrong, not because NVIDIA isn&amp;rsquo;t dominant today, it obviously is, but because the very things driving that dominance are simultaneously building the machinery of its decline. The key diversification that made NVIDIA what it is today is being undone, and what&amp;rsquo;s left is a company eating its own tail — and not just with circular financing: it&amp;rsquo;s a silicon ouroboros.&lt;/p&gt;
&lt;h2 id=&#34;betrayal&#34;&gt;Betrayal&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start with where NVIDIA came from, gaming GPUs, and let&amp;rsquo;s be honest about how they&amp;rsquo;re treating that market now.&lt;/p&gt;
&lt;p&gt;The RTX 50-series launch tells you everything. The 5080 is a 4080 Super whose headline exclusive feature is Multi Frame Generation — the insertion of AI-generated fake frames that add latency and visual artefacts — at a higher price. GamersNexus found it &lt;a href=&#34;https://gamersnexus.net/gpus/nvidia-geforce-rtx-5080-founders-edition-review-benchmarks-vs-5090-7900-xtx-4080-more&#34;&gt;outperforms the 4080 Super by as little as 2.5%&lt;/a&gt; in some titles, with &lt;a href=&#34;https://www.tomshardware.com/pc-components/gpus/rtx-5080-outperforms-rtx-4080-by-10-percent-in-blender-benchmark-only-8-percent-higher-than-rtx-4080-super&#34;&gt;Blender benchmarks showing just 8% over the 4080 Super&lt;/a&gt;. The 5060 Ti ships with 8GB of VRAM in 2026 which is genuinely insulting — so much so that &lt;a href=&#34;https://thinkcomputers.org/gamers-overwhelmingly-reject-8gb-rtx-5060-ti-preferring-16gb-models/&#34;&gt;German retailers report the 16GB model outselling the 8GB version 16:1&lt;/a&gt; and NVIDIA &lt;a href=&#34;https://www.tomshardware.com/pc-components/gpus/nvidia-rtx-5060-ti-8gb-struggles-due-to-lack-of-vram-and-not-just-at-4k-ultra&#34;&gt;didn&amp;rsquo;t even send the 8GB card to reviewers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I play on a 5080 and I&amp;rsquo;ve had personal experience of this and have spent significant time experimenting with these settings. The introduction of things like frame generation is noticeable, things just feel off.&lt;/p&gt;
&lt;p&gt;Each generation now delivers less actual hardware improvement and more software gimmicks: DLSS, Frame Generation, Neural Shaders. These are all presented as features but they&amp;rsquo;re really admissions of failure and they exist because the raw silicon isn&amp;rsquo;t advancing fast enough to brute-force the problems they&amp;rsquo;re designed to mask and meanwhile prices go up.&lt;/p&gt;
&lt;p&gt;But with no competitive alternative offering equivalent ray tracing performance, gamers have nowhere to go. They&amp;rsquo;re not convinced — they&amp;rsquo;re captive. And the gaming press, dependent on NVIDIA access and ad revenue, does the convincing for them, selling upscaled 1080p internal resolution at 4K output as though it were the same as native 4K. Follow independent gamers on YouTube and you&amp;rsquo;ll quickly come across people bemoaning that things now look messy, that we&amp;rsquo;ve lost the sharpness we used to have, and that modern games are running worse for little to no improved visuals.&lt;/p&gt;
&lt;h2 id=&#34;access-media&#34;&gt;Access Media&lt;/h2&gt;
&lt;p&gt;Why does this narrative go largely unchallenged? Because the gaming media covering GPUs is structurally compromised. As is the press covering technology in general. It all depends on access.&lt;/p&gt;
&lt;p&gt;Digital Foundry produces technically excellent analysis, but the editorial framing is selectively applied in ways that are hard to ignore, for example in 2023, &lt;a href=&#34;https://www.neogaf.com/threads/digital-foundry-gets-called-out-on-apparent-double-standard-between-coverage-of-nvidia-vs-amd-frame-generation-tech.1662098/&#34;&gt;DF was publicly called out&lt;/a&gt; for an apparent double standard in how they covered NVIDIA&amp;rsquo;s frame generation versus AMD&amp;rsquo;s FSR3 — framing the same fundamental technology in markedly different terms depending on who made it. Their RTX 5080 DLSS 4 coverage arrived as an &lt;a href=&#34;https://www.resetera.com/threads/digital-foundry-dlss-4-on-nvidia-rtx-5080-first-look.1076112/&#34;&gt;exclusive early preview&lt;/a&gt; on NVIDIA&amp;rsquo;s engineering sample hardware, before any independent testing was possible — effectively a first-look marketing vehicle presented as independent analysis. Then you&amp;rsquo;ve got the NVIDIA-sponsored content elephant in the room. DF has published multiple &lt;a href=&#34;https://www.resetera.com/threads/digital-foundry-ray-tracing-in-games-how-todays-big-tech-gamble-became-tomorrows-sure-thing-sponsored.168949/&#34;&gt;videos explicitly sponsored by NVIDIA&lt;/a&gt;. Thats when unconcious bias starts to creep in.&lt;/p&gt;
&lt;p&gt;To be fair it&amp;rsquo;s not just Digital Foundry - this is rampant across the entire games press and it has long been a problem. I, for one, have always refused to use the word journalism with respect to games media for this reason.&lt;/p&gt;
&lt;p&gt;There are exceptions and often today they can be found on YouTube. Gamers Nexus for example — Steve Burke seems genuinely happy to torch a relationship if the product deserves it. Both GamersNexus and Hardware Unboxed released videos detailing how NVIDIA was selectively granting access to produce favourable coverage using inflated Multi Frame Generation benchmarks. NVIDIA &lt;a href=&#34;https://www.tomshardware.com/pc-components/gpus/nvidia-rtx-5060-ti-8gb-struggles-due-to-lack-of-vram-and-not-just-at-4k-ultra&#34;&gt;didn&amp;rsquo;t send reviewers the 8GB card at all&lt;/a&gt; — Hardware Unboxed had to buy one themselves to reveal the performance problems. Outlets like GN are the minority, and NVIDIA knows it.&lt;/p&gt;
&lt;h2 id=&#34;the-data-centre-gold-rush&#34;&gt;The Data Centre Gold Rush&lt;/h2&gt;
&lt;p&gt;So NVIDIA has been neglecting consumers in favour of the data centre AI gold rush and that&amp;rsquo;s fair enough — there&amp;rsquo;s money to be made in them there warehouses, but it&amp;rsquo;s worth looking at how the money is flowing.&lt;/p&gt;
&lt;p&gt;A significant portion of NVIDIA&amp;rsquo;s data centre revenue comes from companies that NVIDIA itself has invested in, who then use that capital to buy NVIDIA hardware. NVIDIA has poured money into &lt;a href=&#34;https://finance.yahoo.com/news/nvidia-says-it-isnt-using-circular-financing-schemes-2-famous-short-sellers-disagree-100021210.html&#34;&gt;CoreWeave, Lambda, Nebius, xAI, and even OpenAI&lt;/a&gt; — all of whom are major GPU customers. In January 2026 it invested &lt;a href=&#34;https://techcrunch.com/2026/01/26/nvidia-invests-2b-to-help-debt-ridden-coreweave-add-5gw-of-ai-compute/&#34;&gt;another $2 billion in CoreWeave&lt;/a&gt; on top of a $3.3 billion existing stake, while also committing to be the &lt;a href=&#34;https://www.wheresyoured.at/nvidia-isnt-enron-so-what-is-it/&#34;&gt;buyer of last resort for any unsold CoreWeave capacity through 2032&lt;/a&gt;. CoreWeave, for its part, had &lt;a href=&#34;https://techcrunch.com/2026/01/26/nvidia-invests-2b-to-help-debt-ridden-coreweave-add-5gw-of-ai-compute/&#34;&gt;$18.8 billion in debt obligations&lt;/a&gt; as of September 2025 with much of it collateralised against NVIDIA GPUs.&lt;/p&gt;
&lt;p&gt;This is all entirely legal and unremarkable as a business practice. But it has a structural consequence: when your investors and your customers overlap this heavily, revenue growth and capital deployment become hard to distinguish.&lt;/p&gt;
&lt;p&gt;As Ed Zitron has &lt;a href=&#34;https://www.wheresyoured.at/nvidia-isnt-enron-so-what-is-it/&#34;&gt;extensively documented&lt;/a&gt;, NVIDIA isn&amp;rsquo;t Enron — but the deals it&amp;rsquo;s doing with neoclouds are, in his words, &amp;ldquo;dodgy and weird and unsustainable.&amp;rdquo; NVIDIA felt compelled to &lt;a href=&#34;https://finance.yahoo.com/news/nvidia-says-it-isnt-using-circular-financing-schemes-2-famous-short-sellers-disagree-100021210.html&#34;&gt;leak a seven-page internal memo&lt;/a&gt; insisting it was nothing like Enron — which is the kind of thing you do when you&amp;rsquo;re definitely not worried about people thinking you&amp;rsquo;re like Enron. Short sellers &lt;a href=&#34;https://finance.yahoo.com/news/nvidia-says-it-isnt-using-circular-financing-schemes-2-famous-short-sellers-disagree-100021210.html&#34;&gt;Jim Chanos and Michael Burry&lt;/a&gt; weren&amp;rsquo;t convinced. Chanos warned that layering &amp;ldquo;arcane financial structures on top of these money-losing entities&amp;rdquo; is the real vulnerability and Burry flagged what he called &amp;ldquo;suspicious revenue recognition&amp;rdquo; across multiple AI companies. &lt;a href=&#34;https://www.bloomberg.com/news/articles/2026-01-26/nvidia-invests-another-2-billion-in-coreweave-offers-new-chip&#34;&gt;Bloomberg&lt;/a&gt; described the CoreWeave deal plainly as &amp;ldquo;the latest example of the circular financing deals that have lifted valuations of AI companies and fueled concerns about a bubble.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Gary Marcus, the NYU cognitive scientist and long-standing AI sceptic (at least of the claims of the LLM makers), has been &lt;a href=&#34;https://garymarcus.substack.com/p/peak-bubble&#34;&gt;tracking these dynamics on his Substack&lt;/a&gt; since mid-2025. He called the Oracle-OpenAI deal &amp;ldquo;peak bubble&amp;rdquo; and warned that the industry had entered &amp;ldquo;peak musical chairs.&amp;rdquo; In a &lt;a href=&#34;https://garymarcus.substack.com/p/sam-altman-and-the-day-nvidias-meteoric&#34;&gt;more recent piece&lt;/a&gt;, he argued that NVIDIA&amp;rsquo;s stock plateau — up 1200% over five years but essentially flat for six months — marked the point at which Wall Street began losing confidence, driven in part by concerns about circular financing and the profitability of LLM companies. Marcus and Zitron have been two of the most persistent voices making this case while much of the financial press and many analysts were still writing breathless coverage.&lt;/p&gt;
&lt;p&gt;This doesn&amp;rsquo;t make NVIDIA&amp;rsquo;s revenue fraudulent, it makes it fragile. Analyst models that project these growth rates forward are treating circular capital flows as though they represent independent, end-user-driven demand. And it&amp;rsquo;s worth remembering that many of the businesses analysts work for generate revenue from fees: they&amp;rsquo;re not inclined to be too critical. When the AI investment cycle corrects — and it will, because cycles always do — the revenue that was never anchored to external demand will be the first to evaporate.&lt;/p&gt;
&lt;h2 id=&#34;an-emerging-threat&#34;&gt;An Emerging Threat&lt;/h2&gt;
&lt;p&gt;Meanwhile, the real threat to NVIDIA&amp;rsquo;s data centre dominance is emerging from below. Purpose-built ASICs for AI inference are starting to compete on price-performance — and in some cases, they&amp;rsquo;re not just competing, they&amp;rsquo;re winning.&lt;/p&gt;
&lt;p&gt;According to &lt;a href=&#34;https://www.trendforce.com/insights/nvidia-scale-up-technology&#34;&gt;TrendForce&lt;/a&gt;, custom ASIC shipments from cloud providers are projected to grow 44% in 2026, while GPU shipments grow at just 16%, and the share of ASICs in AI servers is expected to jump from around 21% in 2025 to nearly 28% this year. Some predict NVIDIA could fall from over 90% inference market share to &lt;a href=&#34;https://www.techoutlet.eu/en/blog/post/ai-hardware-crisis-2026-the-hidden-cost-wave-behind-%E2%80%9Cai-everything%E2%80%9D&#34;&gt;20-30% by 2028&lt;/a&gt; as ASICs take over production inference workloads, thats a heck of a fall but even a less severe drop than that is going to cause NVIDIA issues.&lt;/p&gt;
&lt;p&gt;And there&amp;rsquo;s recent precedent for this. During the cryptocurrency boom, miners bought GPUs in bulk for proof-of-work hashing — until purpose-built ASICs arrived that were orders of magnitude more efficient at the same task. GPUs became uncompetitive almost overnight and NVIDIA was left with excess inventory. &lt;a href=&#34;https://www.fool.com/investing/2025/03/07/nvidia-stock-down-27-from-peak-history-says-this/&#34;&gt;The stock fell over 50% from its October 2018 peak&lt;/a&gt; while &lt;a href=&#34;https://www.fool.com/investing/2021/06/23/nvidia-stock-crypto-crash/&#34;&gt;gaming revenue nearly halved in a single quarter&lt;/a&gt;. Jensen called it a &amp;ldquo;crypto hangover.&amp;rdquo; The pattern is straightforward: when a workload becomes well-defined enough to justify custom silicon, general-purpose hardware loses. AI inference is reaching exactly that threshold now. His &amp;ldquo;ASIC hangover&amp;rdquo; could be the stuff of nightmares.&lt;/p&gt;
&lt;p&gt;The specific challengers tell the story. &lt;a href=&#34;https://www.cnbc.com/2025/11/21/nvidia-gpus-google-tpus-aws-trainium-comparing-the-top-ai-chips.html&#34;&gt;Google&amp;rsquo;s TPU Ironwood&lt;/a&gt; (7th generation) is considered technically on par with or superior to NVIDIA&amp;rsquo;s GPUs by some experts, including Chris Miller, author of &lt;em&gt;Chip War&lt;/em&gt;. Anthropic trains its most advanced models on up to one million Google TPUs — not NVIDIA GPUs. Amazon is filling data centres with its own &lt;a href=&#34;https://www.cnbc.com/2025/11/21/nvidia-gpus-google-tpus-aws-trainium-comparing-the-top-ai-chips.html&#34;&gt;Trainium2 chips&lt;/a&gt;. OpenAI has committed to deploying &lt;a href=&#34;https://www.fool.com/investing/2026/02/24/predict-ai-inference-era-new-winner-avgo/&#34;&gt;10 gigawatts of custom Broadcom ASICs&lt;/a&gt; starting in 2026 while Cerebras&amp;rsquo; wafer-scale engine delivers inference at &lt;a href=&#34;https://www.cerebras.ai/blog/cerebras-cs-3-vs-groq-lpu&#34;&gt;over 6× the speed of Groq&amp;rsquo;s LPU&lt;/a&gt;, which itself was already dramatically outperforming NVIDIA hardware on key benchmarks. SambaNova claims &lt;a href=&#34;https://medium.com/@laowang_journey/comparing-ai-hardware-architectures-sambanova-groq-cerebras-vs-nvidia-gpus-broadcom-asics-2327631c468e&#34;&gt;16 of its chips can replace 320 GPUs&lt;/a&gt; for serving a 671-billion-parameter model.&lt;/p&gt;
&lt;p&gt;Perhaps the most telling data point is what NVIDIA did in response. In late 2025, it paid &lt;a href=&#34;https://intuitionlabs.ai/articles/nvidia-groq-ai-inference-deal&#34;&gt;$20 billion to acqui-hire Groq&lt;/a&gt; — the inference startup founded by one of the original architects of Google&amp;rsquo;s TPU. Groq&amp;rsquo;s Language Processing Units were delivering 2-3× speedups over NVIDIA hardware on inference benchmarks, and both AMD and Intel were reportedly bidding aggressively for the company. NVIDIA&amp;rsquo;s move was widely characterised as defensive and based on neutralising an emerging threat rather than buying growth. When you spend $20 billion to absorb a competitor whose entire value proposition is that they&amp;rsquo;re faster than your products, then that&amp;rsquo;s not a position of strength: that&amp;rsquo;s weakness.&lt;/p&gt;
&lt;p&gt;The standard counter-argument to this is CUDA lock-in, that every ML team thinks in CUDA, every codebase is coupled to it, and the institutional cost of switching is enormous. This is a genuinely strong defence — or at least it was - until AI began collapsing technical moats.&lt;/p&gt;
&lt;h2 id=&#34;oh-the-irony&#34;&gt;Oh The Irony&lt;/h2&gt;
&lt;p&gt;The technology driving NVIDIA&amp;rsquo;s revenue boom — large language models — is also the thing that neutralises NVIDIA&amp;rsquo;s deepest competitive moat.&lt;/p&gt;
&lt;p&gt;The CUDA lock-in argument rests on the assumption that migrating millions of lines of GPU-optimised code is prohibitively expensive and time-consuming as porting meant humans manually rewriting and retesting everything. Thats a huge job.&lt;/p&gt;
&lt;p&gt;But if you can point an LLM at a CUDA codebase and say &amp;ldquo;port this to ROCm&amp;rdquo; or &amp;ldquo;retarget this for our custom ASIC instruction set&amp;rdquo; — and get the vast majority of the way there in days rather than months — the switching cost argument collapses. The economic calculation changes dramatically when migration drops from &amp;ldquo;eighteen months&amp;rdquo; to &amp;ldquo;a few weeks of validation and tuning.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;And the companies best positioned to do this are the exact hyperscalers who are also building or commissioning ASICs. Google, Amazon, and Microsoft — they all have both the AI capability to automate the migration and the strategic incentive to break free from NVIDIA dependency.&lt;/p&gt;
&lt;p&gt;NVIDIA is selling the tools that will be used to escape its own ecosystem.&lt;/p&gt;
&lt;h2 id=&#34;why-cuda-is-the-perfect-target&#34;&gt;Why CUDA Is the Perfect Target&lt;/h2&gt;
&lt;p&gt;This isn&amp;rsquo;t hand-wavy speculation about AI maybe being able to port code someday, CUDA is almost comically well-suited to automated translation.&lt;/p&gt;
&lt;p&gt;Every CUDA kernel is a pure function with explicit inputs, explicit outputs, and no hidden state. There&amp;rsquo;s no spooky action at a distance — no global mutable state leaking between calls, no side effects you need to trace through a dependency graph. The contract is right there in the function signature and so an agent can look at a single kernel in isolation, understand exactly what it does, rewrite it for a different target, and verify the output without needing to comprehend the entire codebase.&lt;/p&gt;
&lt;p&gt;And the verification story is perfect for agentic iteration loops as you have deterministic numerical inputs and outputs, so you can generate test cases from the CUDA version, run them on the ported version, and diff the results automatically. An agent doesn&amp;rsquo;t need to understand the mathematics — it just needs to confirm that the same inputs produce the same outputs within tolerance. Heck you can even firewall the agent writing the new code from the agent writing its test. That&amp;rsquo;s a tight, automatable feedback loop with no human judgement required.&lt;/p&gt;
&lt;p&gt;But the real killer is the parallelisation. A CUDA codebase might contain thousands of kernels, but they&amp;rsquo;re largely independent units. So you spin up an orchestrator agent that inventories the codebase and builds a dependency graph and it fans out to N worker agents, each handling a kernel or module. Each worker rewrites its target, generates tests, and iterates until the output matches. A validation agent runs integration tests on the assembled result. The whole pipeline is embarrassingly parallel — the same property that made the code suitable for GPUs in the first place makes it suitable for parallel agentic translation and the hyperscalers are literally built for this.&lt;/p&gt;
&lt;p&gt;Now, the obvious objection is that CUDA isn&amp;rsquo;t just kernels there&amp;rsquo;s cuDNN, TensorRT, Nsight, NCCL, Thrust — an entire ecosystem of libraries, profiling tools, and multi-GPU communication primitives that teams have built years of workflow around. And all that is true but its a dependency graph and not magic. These libraries are themselves composed of well-documented APIs with known input-output contracts. The migration challenge is real but it&amp;rsquo;s an engineering problem not an open research question. And if AI is going to make the kind of money it needs to to even start to recoup the investment this is the kind of problem that absolutely has to be in its wheelhouse: parallisable, verifiable and well suited to simple feedback loops.&lt;/p&gt;
&lt;p&gt;And the hyperscalers aren&amp;rsquo;t starting from scratch — Google&amp;rsquo;s JAX ecosystem, AMD&amp;rsquo;s ROCm stack, and Intel&amp;rsquo;s oneAPI are all mature enough that the target platforms already have equivalents for most of this tooling. The gap isn&amp;rsquo;t &amp;ldquo;does an alternative exist&amp;rdquo; anymore, it&amp;rsquo;s &amp;ldquo;is the switching cost worth it&amp;rdquo; — and that cost is falling off a cliff precisely because the models NVIDIA&amp;rsquo;s hardware trained are now capable enough to automate the tedious parts of the migration.&lt;/p&gt;
&lt;p&gt;They&amp;rsquo;re just as vulnerable to the automation of software development as the rest of us and with every quarter that passes, the moat gets shallower. And the hyperscalers have very very very big pumps.&lt;/p&gt;
&lt;h2 id=&#34;the-narrative-arc&#34;&gt;The Narrative Arc&lt;/h2&gt;
&lt;p&gt;NVIDIA have a pretty simple story if you sum it up. Jensen built a great gaming GPU company and while 3dfx were flailing around he delivered great products that gamers wanted and consolidated his position with acquisition. Recognising the lack of diversity was a risk and looking for ways to make GPUs more broadly useful he diversified into data centre compute, a smart and necessary move, but not the stroke of genius the press portrays. Solid execution. Business school 101. And then the transformer revolution landed in his lap, LLMs became the new hot thing, and his interesting GPU company became the behemoth we know today.&lt;/p&gt;
&lt;p&gt;Being in the right place at the right time with the right product isn&amp;rsquo;t the same as having engineered the entire outcome, but the leather jacket mythology requires a visionary, and the tech press love a messianic story, so that&amp;rsquo;s what we got.&lt;/p&gt;
&lt;p&gt;Now trace the arc forward. Gaming company becomes compute company becomes AI company becomes victim of AI.&lt;/p&gt;
&lt;p&gt;The diversification that saved NVIDIA from being just a gaming company is collapsing back into a single dependency — data centre AI revenue — that is simultaneously propped up by a form of circular financing and threatened by the very technology it enables. The customers buying the GPUs are using those GPUs to train the models that will make it trivially cheap to migrate away from NVIDIA&amp;rsquo;s ecosystem onto cheaper, faster, custom silicon.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s the Ouroboros business model. The snake is going to eat its own tail, except the tail is a four-trillion-dollar market cap.&lt;/p&gt;
&lt;h2 id=&#34;the-intel-parallel&#34;&gt;The Intel Parallel&lt;/h2&gt;
&lt;p&gt;The historical parallel is Intel in the mid 2010s, the had absolute market dominance and no real competition. AMD had been written off and so they got lazy and extractive delivering incremental improvements with premium pricing, because where were you going to go? Everybody bought Intel. Then AMD came back with Zen and the whole thing unravelled faster than anyone expected. Look at Intel today.&lt;/p&gt;
&lt;p&gt;NVIDIA is arguably more entrenched, but the dynamics are in many ways similar and the arrogance that comes from unchallenged dominance eventually creates the opening for someone else. Whether that&amp;rsquo;s ASICs eating the data centre business, AMD getting serious about RT, or Intel maturing their architecture — something will crack. And although perhaps more entrenched their dediversification (is that even a word?) makes them extremely vulnerable - they have a single product and a handful of customers.&lt;/p&gt;
&lt;p&gt;Its not a question of whether NVIDIA&amp;rsquo;s position is vulnerable - it is. Its got a single product line, a handful of customers with enormous leverage, and cheaper more performant alternatives emerging. It&amp;rsquo;s whether Jensen recognises it before the correction arrives but the GTC keynotes suggest a man who has started to believe his own mythology, and that&amp;rsquo;s usually when theres a fall.&lt;/p&gt;
&lt;p&gt;You can see it in how he handles pressure. Just recently, the $100 billion OpenAI infrastructure deal — &lt;a href=&#34;https://gizmodo.com/the-100-billion-openai-nvidia-deal-is-not-happening-2000729749&#34;&gt;that was announced with great fanfare in September 2025&lt;/a&gt; — quietly collapsed to $30 billion. The deal had been in trouble for months: NVIDIA&amp;rsquo;s own quarterly filings warned there was &lt;a href=&#34;https://www.cnbc.com/2026/03/04/nvidia-huang-openai-investment.html&#34;&gt;&amp;ldquo;no assurance&amp;rdquo; it would be completed&lt;/a&gt; and Jensen himself fell back on this when challenged. The Wall Street Journal reported that Jensen had been &lt;a href=&#34;https://www.techbuzz.ai/articles/nvidia-s-huang-slams-report-of-100b-openai-deal-collapse&#34;&gt;privately criticising OpenAI&amp;rsquo;s business approach&lt;/a&gt; while the deal was supposedly &amp;ldquo;on track.&amp;rdquo; When the WSJ first reported the deal was stalling, Jensen called it &lt;a href=&#34;https://www.techbuzz.ai/articles/nvidia-s-huang-slams-report-of-100b-openai-deal-collapse&#34;&gt;&amp;ldquo;nonsense.&amp;rdquo;&lt;/a&gt;. And yet weeks later, it was confirmed. Meanwhile, reports emerged that &lt;a href=&#34;https://www.cnbc.com/2026/03/04/nvidia-huang-openai-investment.html&#34;&gt;OpenAI was unhappy with NVIDIA&amp;rsquo;s inference capabilities&lt;/a&gt; and had been blaming weaknesses in its Codex product on NVIDIA hardware.&lt;/p&gt;
&lt;p&gt;MIT Sloan professor Michael Cusumano described the original $100 billion arrangement to the Financial Times as &lt;a href=&#34;https://techcrunch.com/2026/03/04/jensen-huang-says-nvidia-is-pulling-back-from-openai-and-anthropic-but-his-explanation-raises-more-questions-than-it-answers/&#34;&gt;&amp;ldquo;kind of a wash&amp;rdquo;&lt;/a&gt; — NVIDIA invests $100 billion in OpenAI stock, OpenAI spends $100 billion on NVIDIA chips. As TechCrunch noted, Jensen&amp;rsquo;s stated reason for pulling back — that OpenAI&amp;rsquo;s upcoming IPO closes the window — &lt;a href=&#34;https://techcrunch.com/2026/03/04/jensen-huang-says-nvidia-is-pulling-back-from-openai-and-anthropic-but-his-explanation-raises-more-questions-than-it-answers/&#34;&gt;doesn&amp;rsquo;t square with how late-stage private investing actually works&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is not the behaviour of someone operating from a position of strength. Dismissing credible reporting as nonsense, then being proven wrong. Blaming the other party when a deal falls through. Offering explanations that don&amp;rsquo;t withstand scrutiny. These are the tells of someone who feels the ground shifting and doesn&amp;rsquo;t like it.&lt;/p&gt;
&lt;p&gt;NVIDIA&amp;rsquo;s pricing confidence tells you everything about how they see the competitive landscape. They believe there&amp;rsquo;s nowhere else to go. They&amp;rsquo;ve gotten comfortable in a market where that has recently been the case. History suggests that this kind of belief is the beginning of the end and while NVIDIAs rise to its present height has been meteoric its possible its fall will be just as swift. And for gamers like me, not unwelcome.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Neural Networks for Developers - XOR (Part 1)</title>
      <link>https://www.jamesdrandall.com/posts/neural_networks_for_developers_part_1_xor/</link>
      <pubDate>Sun, 01 Mar 2026 11:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/posts/neural_networks_for_developers_part_1_xor/</guid>
      <description>&lt;script type=&#34;module&#34; src=&#34;nn-components.js&#34;&gt;&lt;/script&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/JamesRandall/neural-networks-from-first-principles&#34;&gt;The full source code for the neural network and the interactive demonstrations can be found on GitHub.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Increasing numbers of us are turning to solutions based on neural networks to help us with a wide variety of tasks. From accelerating software development through to getting advice on personal issues they are becoming almost the goto tool for many people.&lt;/p&gt;
&lt;p&gt;But how do they actually work?&lt;/p&gt;
&lt;p&gt;I figured it would be fun, and perhaps helpful to others, to explore this through a series of practical, interactive, explained, examples of increasing complexity over a fairly short (5 part) series. And I promise that by part 5 we&amp;rsquo;ll have something pretty cool!&lt;/p&gt;
&lt;p&gt;The first thing we&amp;rsquo;re going to see is that they are, in fact, fundamentally really simple and have absolutely nothing to do with actual neurons at all. The cynic in me can&amp;rsquo;t help but think that the AI folk love to dress up what they do in humanistic and biological language to convince people they are doing more profound work than they are. Also: it sells.&lt;/p&gt;
&lt;p&gt;As a simple example we&amp;rsquo;re going to look at a neural network that can XOR two numbers together but before we get to the interactive example a little bit of background is useful.&lt;/p&gt;
&lt;h2 id=&#34;xor&#34;&gt;XOR&lt;/h2&gt;
&lt;p&gt;XOR is a simple bitwise operation that, if you&amp;rsquo;re a crusty old developer like me, you might remember as being a handy and performant way of drawing and removing sprites on 8-bit machines so that they didn&amp;rsquo;t erase the background or require a load of CPU time.&lt;/p&gt;
&lt;p&gt;Given two input bits the output of the XOR operation is shown in the table below:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Input A&lt;/th&gt;
          &lt;th&gt;Input B&lt;/th&gt;
          &lt;th&gt;Output&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In this example we&amp;rsquo;re going to setup and train a neural network to handle an XOR calculation. Gross overkill, sure, and I don&amp;rsquo;t think this would be an effective way of drawing sprites on an 8-bit machine, but it makes for a great first example.&lt;/p&gt;
&lt;h2 id=&#34;neurons&#34;&gt;Neurons&lt;/h2&gt;
&lt;p&gt;Firstly neurons. Neurons are basically nodes in the network that have one or more weighted inputs and themselves have a bias. The input, the weights and the bias are all numbers. The neuron works by multiplying each input by its weight, adding the bias and then squashing all this into a number in the range of 0 to 1. Without that squashing step the neuron is just doing basic arithmetic, and when we look at the network we&amp;rsquo;ll see that we stack layers of neurons and if we just stack layers of basic arithmetic we achieve nothing more than a single layer could. The squash is what gives the network its power.&lt;/p&gt;
&lt;p&gt;For our simple XOR example we&amp;rsquo;re going to use a squashing function called sigmoid. These squashing functions are called activation functions and thats how we&amp;rsquo;ll refer to them from here on. And so for a neuron with two inputs all it does is run this formula:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;output = sigmoid((input1 * weight1) + (input2 * weight2) + bias)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While this isn&amp;rsquo;t going to become a maths fest going fowards we&amp;rsquo;ll use mathematical notation, which would express the above like this:&lt;/p&gt;
$$output = \sigma\left((input_1 \times weight_1) + (input_2 \times weight_2) + bias\right)$$&lt;p&gt;At this point you might be asking - whats this sigmoid function. Its this:&lt;/p&gt;
$$\sigma(x) = \frac{1}{1 + e^{-x}}$$&lt;p&gt;Don&amp;rsquo;t worry too much about the formula — all it does is take any number, no matter how large or small, and map it to a value between 0 and 1. Large positive inputs give values close to 1, large negative inputs give values close to 0, and zero maps to exactly 0.5. It has a characteristic shape that you can see below:&lt;/p&gt;
&lt;p&gt;&lt;sigmoid-chart&gt;&lt;/sigmoid-chart&gt;&lt;/p&gt;
&lt;p&gt;In code the entire neuron is surprisingly compact:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Sigmoid: squash any number into 0..1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sigmoid&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; Math.&lt;span style=&#34;color:#a6e22e&#34;&gt;exp&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// A neuron: multiply each input by its weight, add bias, squash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;inputs&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;[], &lt;span style=&#34;color:#a6e22e&#34;&gt;weights&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;[], &lt;span style=&#34;color:#a6e22e&#34;&gt;bias&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;inputs&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;inputs&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;weights&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bias&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sigmoid&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s it. Every neuron in every neural network, no matter how large, does this same operation.&lt;/p&gt;
&lt;h2 id=&#34;the-network&#34;&gt;The Network&lt;/h2&gt;
&lt;p&gt;Ok. So that&amp;rsquo;s the neural part - so what about the network part?&lt;/p&gt;
&lt;p&gt;Essentially neurons are arranged in layers: an input layer, one or more hidden layers, and a output layer with every neuron in one layer being connected to every neuron in the next layer. Hidden layers sounds very mystical but there&amp;rsquo;s nothing much hidden about them unless you are treating the network as a black box. They are simply the layers of neurons between the inputs and the output.&lt;/p&gt;
&lt;p&gt;Our input neurons are really just numbers - they have no biases - and for our XOR example we have two input neurons, one for each XOR input. And we only need a single output neuron that, when trained, should give us 0 or 1 as our answer. And we&amp;rsquo;re going to have a single hidden layer of 4 neurons.&lt;/p&gt;
&lt;p&gt;This gives us our network topology - 2 input neurons, 4 hidden layer neurons each connected to both inputs, and a single output neuron connected to the 4 hidden layer neurons. When the network runs we run the calculation from earlier for each neuron in the layer and then, when all neurons in the layer have calculated their output, we move on to the next layer.&lt;/p&gt;
&lt;p&gt;In code we represent this as arrays of neurons organised into layers. Each neuron stores its weights, bias, and its most recent output. The network is created with random weights — we&amp;rsquo;ll see why that matters shortly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Neuron&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;weights&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;[];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;bias&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;net&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// the weighted sum before sigmoid — we need this for backprop later
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Layer&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Neuron&lt;/span&gt;[];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Network&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;layers&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Layer&lt;/span&gt;[];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;inputCount&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createNeuron&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;inputCount&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Neuron&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;weights&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Array.from&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;inputCount&lt;/span&gt; }, () &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; Math.&lt;span style=&#34;color:#a6e22e&#34;&gt;random&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;bias&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Math.random&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;net&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createNetwork&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;topology&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;[])&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Network&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;layers&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Layer&lt;/span&gt;[] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;topology&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;inputCount&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;topology&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;layer&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Array.&lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt;({ &lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;topology&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;] }, () &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;createNeuron&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;inputCount&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;layers&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;layer&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;layers&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;inputCount&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;topology&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Create our XOR network: 2 inputs, 4 hidden, 1 output — 17 parameters total
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;network&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createNetwork&lt;/span&gt;([&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The forward pass — running inputs through the network — is just applying the neuron formula to each layer in sequence:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;forward&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;network&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Network&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;inputs&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;[])&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;[] {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;current&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;inputs&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;layer&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;of&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;network&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;layers&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;[] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;of&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;layer&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;bias&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;weights&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;current&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;weights&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;net&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sigmoid&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;current&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;current&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;forward-walkthrough 
topology=&#34;2,4,1&#34;
input=&#34;1,0&#34;
target=&#34;1&#34;
learning-rate=&#34;0.5&#34;
seed=&#34;42&#34;&gt;&lt;/forward-walkthrough&gt;&lt;/p&gt;
&lt;p&gt;If you walk through the example above you will see each neuron running the formula we looked at earlier and at the output giving us the answer 0.53. Which is probably the worst case we could have hoped for - the output should be 0 or 1 and we&amp;rsquo;re square in the middle. This is because the weights are all random at the moment so we are literally just pushing numbers through random multipliers.&lt;/p&gt;
&lt;p&gt;Before the network will be able to give us credible results we need to train it and it needs to learn. That&amp;rsquo;s what we&amp;rsquo;ll cover next.&lt;/p&gt;
&lt;h2 id=&#34;back-propagation---the-learning&#34;&gt;Back propagation - the learning&lt;/h2&gt;
&lt;p&gt;The network learns by pushing the error back through the network and adjusting the weights and biases. Conceptually it does this by distributing blame proportionally - the neurons that had the biggest impact on the output have their weights and biases adjusted the most. In our examples this means the weights will be adjusted the most on the connections represented by the thickest lines.&lt;/p&gt;
&lt;p&gt;The network also has a learning rate - this is a multiplier applied to the proportioned level of blame that basically scales how big an adjustment we&amp;rsquo;ll make to the weights and biases. Too big and our corrections will overshoot and too small and the network will take longer to converge on accurate answers.&lt;/p&gt;
&lt;p&gt;Working this through the network is known as back propagation and the idea is that by tweaking these numbers then over many runs, each run is known as an epoch, then the error delta, the loss, should converge towards 0.&lt;/p&gt;
&lt;p&gt;The code for backpropagation is the most involved part but the structure mirrors the forward pass — just working backwards. We need one extra piece: the sigmoid derivative, which tells us how steep the S-curve is at a given neuron&amp;rsquo;s operating point. Where the curve is steep the neuron is sensitive to changes and absorbs more blame. Where it&amp;rsquo;s flat — near 0 or 1 — the neuron is saturated and barely learns:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sigmoidDerivative&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;net&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sigmoid&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;net&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;backward&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;network&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Network&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;inputs&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;[],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;targets&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;[],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;learningRate&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;layers&lt;/span&gt; } &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;network&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// Step 1: How wrong is the output, and how sensitive is it to changes?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;outputLayer&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;layers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;layers&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;outputLayer&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;outputLayer&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;targets&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;delta&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sigmoidDerivative&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;net&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// Step 2: Propagate blame backward through hidden layers
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;layers&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;layer&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;layers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;nextLayer&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;layers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;layer&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;downstreamBlame&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;nextNeuron&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;of&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;nextLayer&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;downstreamBlame&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;nextNeuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;delta&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;nextNeuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;weights&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;layer&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;].&lt;span style=&#34;color:#a6e22e&#34;&gt;delta&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;downstreamBlame&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sigmoidDerivative&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;layer&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;].&lt;span style=&#34;color:#a6e22e&#34;&gt;net&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// Step 3: Nudge every weight and bias
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;layers&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;layerInputs&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;inputs&lt;/span&gt; : &lt;span style=&#34;color:#66d9ef&#34;&gt;layers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;].&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;of&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;layers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt;]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;weights&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;weights&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;learningRate&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;delta&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;layerInputs&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;bias&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;learningRate&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;neuron&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;delta&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// Return the loss so we can track progress
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;loss&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;outputLayer&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;diff&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;outputLayer&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;].&lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;targets&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;loss&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;diff&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;diff&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;loss&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;outputLayer&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the simulation below you can see this being applied from where we left off above and if you&amp;rsquo;re interested in the mathematics thats included too.&lt;/p&gt;
&lt;p&gt;&lt;backprop-walkthrough
topology=&#34;2,4,1&#34;
input=&#34;1,0&#34;
target=&#34;1&#34;
learning-rate=&#34;0.5&#34;
seed=&#34;42&#34;&gt;&lt;/backprop-walkthrough&gt;&lt;/p&gt;
&lt;h2 id=&#34;the-simulation&#34;&gt;The simulation&lt;/h2&gt;
&lt;p&gt;At this point we have everything we need to train and then use our XOR neural network. Training is just running the forward pass and backward pass on every XOR input, thousands of times:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xorData&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { &lt;span style=&#34;color:#a6e22e&#34;&gt;inputs&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;targets&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { &lt;span style=&#34;color:#a6e22e&#34;&gt;inputs&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;targets&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { &lt;span style=&#34;color:#a6e22e&#34;&gt;inputs&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;targets&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { &lt;span style=&#34;color:#a6e22e&#34;&gt;inputs&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;targets&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;epoch&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;epoch&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;20000&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;epoch&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sample&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;of&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xorData&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;forward&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;network&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;sample&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;inputs&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;backward&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;network&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;sample&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;inputs&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;sample&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;targets&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s the entire training loop. Each epoch feeds all four XOR cases through, adjusting weights after each one. If you run the simulation below you&amp;rsquo;ll see the model train itself over 20,000 epochs with a learning rate of 0.5.&lt;/p&gt;
&lt;p&gt;&lt;nn-visualiser&gt;&lt;/nn-visualiser&gt;&lt;/p&gt;
&lt;p&gt;What you&amp;rsquo;ll probably immediately notice is that at the end of this process the neural network does not give us perfect answers. What we&amp;rsquo;re seeing is something like this:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Input A&lt;/th&gt;
          &lt;th&gt;Input B&lt;/th&gt;
          &lt;th&gt;Target&lt;/th&gt;
          &lt;th&gt;Output&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;0.0094&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;0.9890&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;0.9872&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;0.0121&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If you&amp;rsquo;re used to thinking in more classical modes of computation then I think this, particularly, is a key takeaway about neural networks: they provide approximations. Or perhaps what might be best called probabilistic answers.&lt;/p&gt;
&lt;p&gt;If you zoom in on the interesting part of the loss curve you might notice it looks like an inverted sigmoid — but it&amp;rsquo;s coincidental rather than causal. The loss curve isn&amp;rsquo;t a sigmoid, it just has a similar shape. This pattern of slow start, rapid progress, then diminishing returns shows up across all kinds of optimisation problems, not just neural networks.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s interesting to play with the learning rate and the number of epochs — you can get the network to converge on a more accurate result but it will never land on exact values. These are fundamentally approximation machines.&lt;/p&gt;
&lt;p&gt;And you&amp;rsquo;re probably starting to see why these systems can be so expensive to train: as the number of neurons multiplies the number of calculations required grows quickly and you need vast numbers of epochs to converge over really large training sets. You can probably also see why GPUs, and similar architectures, are so good at this. It&amp;rsquo;s basically multiplication at a massive scale.&lt;/p&gt;
&lt;p&gt;In the next part we&amp;rsquo;re going to build on these basics and get a neural network to do something a bit more complicated - but the concepts will be exactly the same.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>AI Can&#39;t Recreate Thrust (But It Can Help You Understand It)</title>
      <link>https://www.jamesdrandall.com/posts/thrust_ai_powered_software_archaeology/</link>
      <pubDate>Mon, 23 Feb 2026 08:30:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/posts/thrust_ai_powered_software_archaeology/</guid>
      <description>&lt;p&gt;I asked Claude to recreate the classic 1986 game Thrust for me in the browser. It created slop but then things spiralled out of control.&lt;/p&gt;
&lt;p&gt;Thrust was one of my favourite games on the BBC Micro — written by Jeremy C. Smith and published in 1986, it&amp;rsquo;s a deceptively deep game with amazing physics and gameplay. You pilot a ship through caverns, collecting fuel, avoiding turret fire, and retrieving a pod for bonus points while fighting gravity and momentum. Jeremy went on to create the even more impressive Exile with Peter Irvin before tragically dying in an accident in 1992. He was somewhere between 16 and 18 when he wrote Thrust. You can &lt;a href=&#34;http://bbcmicro.co.uk/game.php?id=432&#34;&gt;play the original online&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve got a BBC Master on the desk beside me and I still occasionally fire up Thrust on there along with some of the other classics. It&amp;rsquo;s one of those games I keep returning to along with Elite, Exile and Holed Out. I&amp;rsquo;ve now recreated three of these in different ways&amp;hellip; the fourth is looking increasingly unavoidable.&lt;/p&gt;
&lt;h2 id=&#34;starting-with-slop&#34;&gt;Starting with slop&lt;/h2&gt;
&lt;p&gt;Anyway. I guess I&amp;rsquo;d been thinking about Thrust as one morning recently I somewhat casually asked Claude Code to create it for me in the browser. I think I&amp;rsquo;d been reading the latest proclamations of capability from OpenAI and Anthropic and so I put together quite a comprehensive spec, gave it access to the original disassembled source code, screenshots, and said &amp;ldquo;go and recreate Thrust for me.&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;It created something for which the term slop would be too kind, it very vaguely resembled Thrust — it had the scanline stuff, sort of — but it was truly dreadful. It hadn&amp;rsquo;t even got gravity working right, the ship didn&amp;rsquo;t fall properly, the controls felt weird, and it was just&amp;hellip; grim. In some ways its amazing that it created something that sort of worked and sort of looked like Thrust but it was not playable and nothing close to the elegance and beautfy of the real thing.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s the thing about a game like Thrust. You could knock out something superficially similar pretty quickly — just run at the device frame rate, use standard delta-time physics, draw some caverns. But it would feel nothing like Thrust. The magic is in the specific timings, the weight of the ship, the way momentum builds. Particularly if you&amp;rsquo;ve played the original then those details are everything, and an AI working from a text description, and it turns out even the original source, can&amp;rsquo;t capture them.&lt;/p&gt;
&lt;h2 id=&#34;the-archaeology&#34;&gt;The archaeology&lt;/h2&gt;
&lt;p&gt;But it got me curious. How &lt;em&gt;did&lt;/em&gt; the original work? I find the tricks developers used to make this stuff work on the 8-bits fascinating, and it became a bit of an archaeology session. I quickly found &lt;a href=&#34;https://github.com/kieranhj/thrust-disassembly&#34;&gt;this brilliant commented disassembly&lt;/a&gt; of the original source by Kieran Connell and found myself feeding it into Claude and asking questions.&lt;/p&gt;
&lt;p&gt;This is where things got interesting. Not because AI wrote the code — the code itself isn&amp;rsquo;t complicated, it&amp;rsquo;s a 1986 game that ran in 32K of RAM — but because Claude turned out to be an extraordinary tool for interrogating 6502 assembly. I could feed in a block of disassembled source and ask &amp;ldquo;how does the level data work?&amp;rdquo; or &amp;ldquo;what&amp;rsquo;s the physics model doing here?&amp;rdquo; and get detailed, accurate explanations of what the original code was doing.&lt;/p&gt;
&lt;p&gt;Now to be fair I was working from some commented disassembled source code but even given that it was able to extract information from both the comments and the assembly and come up with detailed descriptions of how the game worked. My sense is without the comments helping to focus it at the right areas it would have been much less useful - but even so, it made the job an awful lot simpler and more enjoyable. And yes it seems likely I&amp;rsquo;ll strip the comments from the code and see how well Claude does then.&lt;/p&gt;
&lt;p&gt;While doing this I realised I could use the answers as the basis to recreate the original game and started asking Claude to create specifications for the various subsystems. Most of the specifications I generated can be found in a &lt;a href=&#34;https://github.com/JamesRandall/ts-thrust/tree/main/specs&#34;&gt;specs folder&lt;/a&gt; in the source code. I might write them up properly at some point but for now they give a good insight into the nuances in the original — there&amp;rsquo;s quite a lot going on, more than I&amp;rsquo;d realised. For example I&amp;rsquo;d never noticed that the turrets stop firing for a time if you hit the generator, and there are subtleties in their firing angles that only become apparent when you read the actual code.&lt;/p&gt;
&lt;h2 id=&#34;the-physics&#34;&gt;The Physics&lt;/h2&gt;
&lt;p&gt;The physics was one of the most interesting areas to dig into. Thrust uses Q7.8 fixed-point arithmetic — a common technique on 8-bit machines where you don&amp;rsquo;t have floating point hardware. The rotation system uses 32 steps with lookup tables for the force components.&lt;/p&gt;
&lt;p&gt;But the really tricky part was timing. My first implementation used the correct constants from the disassembly — the same gravity, the same thrust values, the same drag — but the ship was far too fast and too agile. It didn&amp;rsquo;t have the right weight. The constants were identical to the original so it had to be a timing problem.&lt;/p&gt;
&lt;p&gt;And it was. The original doesn&amp;rsquo;t run its physics at the BBC Micro&amp;rsquo;s 50 Hz VSync rate. The tick loop waits at least 3 centiseconds per frame, giving an effective rate of about 33.33 Hz. But it goes further than that: within each tick, physics updates are gated to only 6 active slots per 16-tick window. The core of the physics step looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/** The 6 active physics slots per 16-tick window */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ACTIVE_SLOTS&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Set&lt;/span&gt;([&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;13&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tickStep&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;ThrustInput&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;slot&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;tickCounter&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x0F&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;tickCounter&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;tickCounter&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0xFF&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// Rotation: 3 out of every 4 ticks, integer steps only
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ((&lt;span style=&#34;color:#a6e22e&#34;&gt;slot&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x03&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rotate&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;angle&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ((&lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;angle&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;rotate&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isActiveSlot&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ThrustPhysics&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ACTIVE_SLOTS&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;has&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;slot&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// Force calculation — active slots only (6 of every 16 ticks)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;isActiveSlot&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;forceY&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gravity&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;input&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;thrust&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;forceY&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ANGLE_Y&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;angleIdx&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;massShift&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;forceX&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ANGLE_X&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;angleIdx&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;massShift&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Linear drag
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;forceX&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;64&lt;/span&gt;;   &lt;span style=&#34;color:#75715e&#34;&gt;// X: *= 63/64
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;forceY&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;256&lt;/span&gt;;  &lt;span style=&#34;color:#75715e&#34;&gt;// Y: *= 255/256
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// Position integration — every tick
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;forceX&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;forceY&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That gating gives an effective force/drag rate of roughly 12.5 Hz — gravity and thrust only apply on those 6 specific ticks, not every frame. Even rotation has its own gating: it skips every fourth tick. These aren&amp;rsquo;t arbitrary numbers; they&amp;rsquo;re the exact patterns from the 6502 source, and they define the feel of the game - if you pull them around things quickly start to feel off. The asymmetric drag is interesting too — much stronger on the X axis (63/64) than Y (255/256), which is why horizontal movement feels &amp;ldquo;stickier&amp;rdquo; than vertical.&lt;/p&gt;
&lt;p&gt;Once I got these timings right — matching the original&amp;rsquo;s exact update cadence rather than just its constants — it felt perfect. You can switch between the BBC emulator and my version and the controls feel the same.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s what became genuinely interesting to me. Creating a Thrust-like game with normal physics is trivial - particularly using a coding AI. Recreating &lt;em&gt;Thrust&lt;/em&gt; — with all its specific feel and weight — required understanding exactly how the original worked, right down to the timing of individual physics updates within the game loop.&lt;/p&gt;
&lt;h2 id=&#34;the-sound&#34;&gt;The Sound&lt;/h2&gt;
&lt;p&gt;The other area that required some real focus was the sound. When I did my &lt;a href=&#34;https://www.jamesdrandall.com/projects/webglite/&#34;&gt;TypeScript version of Elite&lt;/a&gt; I sampled the sounds directly from the emulator. That worked because Elite&amp;rsquo;s sounds are quite discrete — short, sharp effects. Though the beam and military laser effects are off. But in Thrust the engine is a continuous drone that responds to key presses, and the explosions have specific envelopes. I could have sampled them but it just felt&amp;hellip; wrong.&lt;/p&gt;
&lt;p&gt;So instead I decided to take a different approach and instead recreated the SN76489 sound chip (the same chip that was in the Sega Master System, as I learned while working on this) and the BBC MOS interface to it. The MOS — the BBC&amp;rsquo;s Machine Operating System — provided the interface through which games talked to the sound chip, using OSWORD calls and memory-mapped I/O.&lt;/p&gt;
&lt;p&gt;A big help in this was the &lt;a href=&#34;https://tobylobster.github.io/mos/&#34;&gt;disassembled MOS code&lt;/a&gt; that Toby Nelson has put together, along with the BBC Advanced User Guide and the SN76489 chip specifications. I was able to feed all of this into Claude, generate a comprehensive spec for the sound system, and from that build an emulated sound system running in an AudioWorklet. The full BBC MOS envelope processor (OSWORD 7/8) drives the chip emulator on the audio thread.&lt;/p&gt;
&lt;p&gt;The result? The sounds are identical. And it would have been really difficult to get that by any other means — it&amp;rsquo;s so timing-specific, so dependent on the exact envelope shapes and chip behaviour, that you really do need to emulate the actual hardware.&lt;/p&gt;
&lt;p&gt;The sound system itself is quite elegant in how it layers. At the top level, playing a sound means sending the same OSWORD 7 parameters the original game used — channel, amplitude, pitch, duration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sounds&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;own_gun&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;     { &lt;span style=&#34;color:#a6e22e&#34;&gt;channel&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;0x0012&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;amplitude&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;1&lt;/span&gt;,   &lt;span style=&#34;color:#a6e22e&#34;&gt;pitch&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;0x50&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;duration&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;2&lt;/span&gt;   },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;explosion_1&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;channel&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;0x0011&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;amplitude&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;2&lt;/span&gt;,   &lt;span style=&#34;color:#a6e22e&#34;&gt;pitch&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;0x96&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;duration&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;100&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;explosion_2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;channel&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;0x0010&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;amplitude&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;3&lt;/span&gt;,   &lt;span style=&#34;color:#a6e22e&#34;&gt;pitch&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;0x07&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;duration&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;100&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;hostile_gun&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;channel&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;0x0013&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;amplitude&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;4&lt;/span&gt;,   &lt;span style=&#34;color:#a6e22e&#34;&gt;pitch&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;0x1e&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;duration&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;20&lt;/span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;engine&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;      { &lt;span style=&#34;color:#a6e22e&#34;&gt;channel&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;0x0010&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;amplitude&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;,  &lt;span style=&#34;color:#a6e22e&#34;&gt;pitch&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;0x05&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;duration&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;3&lt;/span&gt;   },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Those parameters feed into a MOS envelope processor that drives the SN76489 chip emulator, both running in an AudioWorklet on the audio thread. The chip emulator generates samples at the hardware level — tone channels with 10-bit period counters, a noise channel with a 15-bit linear feedback shift register, and a volume table with -2dB per step matching the original silicon:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;generate&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Float32Array&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;offset&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;sampleRate&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;step&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;250000.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sampleRate&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// chip clocks per audio sample
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sample&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Tone channels 0–2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ch&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;ch&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;ch&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;counter&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;ch&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;step&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;counter&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;ch&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;period&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;ch&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1024&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;counter&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;ch&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;polarity&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;ch&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;polarity&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;ch&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;sample&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;polarity&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;ch&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;vol&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;ch&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Noise channel — 15-bit LFSR
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;sample&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; ((&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lfsr&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;vol&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;offset&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sample&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s emulation all the way down. The MOS ticks at 100 Hz on the audio thread, processing envelopes and updating the chip registers at the same rate the real BBC hardware would have and the resulting sounds are authentic.&lt;/p&gt;
&lt;h2 id=&#34;the-graphics-and-levels&#34;&gt;The Graphics and Levels&lt;/h2&gt;
&lt;p&gt;The font, graphics, and level data I was able to extract from the disassembled source relatively easily. You can find &lt;a href=&#34;https://github.com/JamesRandall/ts-thrust/tree/main/tools&#34;&gt;a couple of tools in the source&lt;/a&gt; that do that — one decodes the level terrain data, another extracts the ship rotation sprites by emulating the BBC&amp;rsquo;s frame buffer and converting the output to PNGs.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d initially tried a vector approach for the ship — just drawing it mathematically at each rotation — but it looked rough at BBC resolution as it turned out the original had hard-coded sprites for each of the 32 rotation angles, hand-optimised by Jeremy Smith to look right at each position. Once I extracted and used those actual sprites, it looked correct.&lt;/p&gt;
&lt;p&gt;The terrain rendering uses the original&amp;rsquo;s scanline-parity polygon fill — drawing every other line — which gives it that characteristic BBC Micro look. The internal resolution is 320×256, matching the original. My version has a slightly larger viewport — the original has a border around it, likely to keep the scrolling fast enough on the BBC — and I might add a toggle for that at some point.&lt;/p&gt;
&lt;h2 id=&#34;the-demo-mode&#34;&gt;The Demo Mode&lt;/h2&gt;
&lt;p&gt;One of the last things I added was the demo mode — the attract sequence where the game plays itself on the title screen. Digging into how the original implemented this was satisfying: it simply injects key presses into the game loop at timed intervals. There&amp;rsquo;s no separate demo renderer — the normal game engine runs with fake inputs from a pair of parallel tables:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Which keys to hold for each segment
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DEMO_KEYPRESS_BIT_MASK_TABLE&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                          &lt;span style=&#34;color:#75715e&#34;&gt;//  0: freefall
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;INPUT_ROTATE_RIGHT&lt;/span&gt;,            &lt;span style=&#34;color:#75715e&#34;&gt;//  1: rotate right
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                          &lt;span style=&#34;color:#75715e&#34;&gt;//  2: nothing
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;INPUT_FIRE&lt;/span&gt;,                    &lt;span style=&#34;color:#75715e&#34;&gt;//  3: fire at turret
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;INPUT_ROTATE_LEFT&lt;/span&gt;,             &lt;span style=&#34;color:#75715e&#34;&gt;//  4: rotate left
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;INPUT_SHIELD_TRACTOR&lt;/span&gt;,          &lt;span style=&#34;color:#75715e&#34;&gt;//  5: shield/tractor
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;INPUT_THRUST&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;INPUT_SHIELD&lt;/span&gt;,   &lt;span style=&#34;color:#75715e&#34;&gt;//  6: thrust + shield
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// ... 18 entries total
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// How long (in game ticks) to hold each entry
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;demoKeypressTimerTable&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;0x18&lt;/span&gt;,  &lt;span style=&#34;color:#75715e&#34;&gt;// 24 ticks of freefall
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;0x0F&lt;/span&gt;,  &lt;span style=&#34;color:#75715e&#34;&gt;// 15 ticks rotate right
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;0x05&lt;/span&gt;,  &lt;span style=&#34;color:#75715e&#34;&gt;//  5 ticks nothing
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;0x05&lt;/span&gt;,  &lt;span style=&#34;color:#75715e&#34;&gt;//  5 ticks fire
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;0x08&lt;/span&gt;,  &lt;span style=&#34;color:#75715e&#34;&gt;//  8 ticks rotate left
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;0x14&lt;/span&gt;,  &lt;span style=&#34;color:#75715e&#34;&gt;// 20 ticks shield/tractor
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;0x17&lt;/span&gt;,  &lt;span style=&#34;color:#75715e&#34;&gt;// 23 ticks thrust + shield
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Three of the timer slots are randomised at the start of each demo run — small variations like &lt;code&gt;(rnd &amp;amp; 0x03) + 0x08&lt;/code&gt; for a range of 8–11 ticks, and a 75/25 split between a long and short spiral at the end. Elegant and simple: it creates just enough variation that repeated attract sequences don&amp;rsquo;t feel mechanical.&lt;/p&gt;
&lt;p&gt;Getting it working required understanding yet another layer of timing, and it exposed a small inaccuracy in the player starting position — just enough that the demo sequence missed a turret it should have hit. That led me to discover I&amp;rsquo;d slightly miscalculated the spawn points, which in turn revealed there were multiple spawn points per level, something I&amp;rsquo;d overlooked in the initial level decoding.&lt;/p&gt;
&lt;p&gt;That this works as it does demonstrates that the physics recreation is accurate - if it wasn&amp;rsquo;t timed keypresses would result in different behaviours and its unlikely the shot would hit the turret and the player escape with the pod.&lt;/p&gt;
&lt;h2 id=&#34;the-teleport-animation&#34;&gt;The Teleport Animation&lt;/h2&gt;
&lt;p&gt;One moment that impressed me with the coding assistant: the teleport animation — the effect when you warp into a level — wasn&amp;rsquo;t matching the original based on the spec I&amp;rsquo;d extracted from the disassembly. Rather than spend more time on the code, I recorded the original game as a video, captured the animation frames, pasted them into Claude Code and said &amp;ldquo;recreate that.&amp;rdquo; It nailed it almost perfectly in about five minutes. Not hard code — something I could have done in half an hour — but a good example of where AI saves time on the uninteresting stuff so you can focus on the interesting stuff.&lt;/p&gt;
&lt;h2 id=&#34;the-crt-effects&#34;&gt;The CRT Effects&lt;/h2&gt;
&lt;p&gt;To round things off I added a few CRT post-processing effects: scanlines, green phosphor, amber phosphor, black and white TV, and a VCR look. These are implemented as WebGPU compute shaders in WGSL, falling back gracefully when WebGPU isn&amp;rsquo;t available. Nothing particularly difficult — I pulled most of them from my Elite conversion and added the black and white filter.&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t get much time to play the BBC on a colour TV. When I first got one I was playing on a tiny 12-inch black and white set, and later a small green screen monitor. The filters are just a bit of fun, but they do give it an authentic feel.&lt;/p&gt;
&lt;h2 id=&#34;what-this-was-really-about&#34;&gt;What This Was Really About&lt;/h2&gt;
&lt;p&gt;The interesting thing about this process wasn&amp;rsquo;t writing the code. The code is straightforward — by modern standards, a 1986 game that ran in 32K is not particularly complicated. That&amp;rsquo;s not a slight on the original developer and it&amp;rsquo;s amazing that Jeremy Smith got this working on an 8-bit machine, and Exile is something else entirely. But with a high-level language and all the power we have today, the resources online, the coding tools then the implementation is the easy part.&lt;/p&gt;
&lt;p&gt;What was interesting was the understanding. Using AI to drill into the original 6502 assembly, to extract and document the subsystems, to understand the specific timings and fixed-point arithmetic and hardware interfaces — that&amp;rsquo;s where the value was. The recreation forced me to understand every system deeply, because getting the feel right meant getting the details right.&lt;/p&gt;
&lt;p&gt;AI couldn&amp;rsquo;t recreate Thrust for me — that was proved pretty conclusively in the first five minutes. But it could help me understand the original well enough to recreate it myself.&lt;/p&gt;
&lt;p&gt;The source is &lt;a href=&#34;https://github.com/JamesRandall/ts-thrust&#34;&gt;on GitHub&lt;/a&gt; and you can &lt;a href=&#34;https://www.jamesdrandall.com/thrust/&#34;&gt;play it here&lt;/a&gt;. Going through these codebases from the 8-bit era gives you real respect for the developers. It&amp;rsquo;s amazing what they squeezed into such limited hardware. There&amp;rsquo;s also a &lt;a href=&#34;https://youtu.be/mzytBDzlqrY&#34;&gt;YouTube video&lt;/a&gt; I created on the process.&lt;/p&gt;
&lt;p&gt;I really enjoyed it. I played this game a lot when I was young and it&amp;rsquo;s great to have gained this understanding of how it really works - having done so I appreciate the work Jeremy Smith did even more.&lt;/p&gt;
&lt;div role=&#34;list&#34; class=&#34;grid sm:grid-cols-4 lg:grid-cols-4 gap-8 mt-8&#34;&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/thrust/images/0.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/thrust/images/0.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/thrust/images/1.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/thrust/images/1.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/thrust/images/2.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/thrust/images/2.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/thrust/images/3.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/thrust/images/3.jpg&#34; /&gt;&lt;/a&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Thrust</title>
      <link>https://www.jamesdrandall.com/projects/thrust/</link>
      <pubDate>Sat, 21 Feb 2026 06:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/projects/thrust/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://jamesdrandall.com/thrust&#34;&gt;You can play the game online.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Honestly this is one of those projects that I&amp;rsquo;m not quite sure where it came from. Thrust was one of my favourite games on the BBC Micro written by Jeremy Smith and published in 1986, it&amp;rsquo;s a deceptively deep game with amazing physics and gameplay. Jeremy went on to create the even more impressive &lt;a href=&#34;https://en.wikipedia.org/wiki/Exile_(1988_video_game)&#34;&gt;Exile&lt;/a&gt; with Peter Irvin before tragically dying in an accident in 1992. You can play the &lt;a href=&#34;https://bbcmicro.co.uk/jsbeeb/play.php?autoboot&amp;amp;disc=https://bbcmicro.co.uk/gameimg/discs/432/Disc024-Thrust.ssd&amp;amp;KEY.Z=CAPSLOCK&amp;amp;KEY.X=CTRL&amp;amp;noseek&#34;&gt;original online&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I guess I&amp;rsquo;d been thinking about it as one morning recently I somewhat casually asked Claude Code to create Thrust for me in the browser. I put together quite a comprehensive spec but it created something for which the term slop would be too kind. It&amp;rsquo;s amazing that it created something that sort of worked and semi-looked like Thrust but it was truly dreadful and it hadn&amp;rsquo;t even got gravity working right.&lt;/p&gt;
&lt;p&gt;Then I got curious as to how the original worked, I find the tricks the developers used to make this stuff work on the 8-bits fascinating, and it became a bit of an archaeology session. I quickly found this brilliant &lt;a href=&#34;https://github.com/kieranhj/thrust-disassembly&#34;&gt;commented disassembly&lt;/a&gt; of the original source by Kieran Connell and found myself feeding the source into Claude (online) and asking it questions.&lt;/p&gt;
&lt;p&gt;While doing this I realised that I could use the answers as the basis to recreate the original game and started to ask Claude to create specifications for me for the various subsystems. Most of the specifications I generated can be found in a specs folder in the source code. I might write them up properly at some point but for now they give a good insight into the nuances in the original game, there&amp;rsquo;s quite a lot going on - more than I&amp;rsquo;d realised. For example I&amp;rsquo;d never realised that the turrets stop firing for a time if you hit the generator and there are subtleties in their firing angles.&lt;/p&gt;
&lt;p&gt;In any case I started to feed these into Claude Code, do some manual coding, fixing, poking and prodding and the result is what you can play and see here. My version has a slightly larger viewport - the original has a slight border round it. It would be easy enough to replicate it and I might make it a toggle option at some point.&lt;/p&gt;
&lt;p&gt;The hardest bit to get right was the sound effects - when I did my &lt;a href=&#34;https://www.jamesdrandall.com/projects/webglite/&#34;&gt;TypeScript version of Elite&lt;/a&gt; I sampled the sounds. That worked as they are quite discrete. But I wanted to take a different approach here and instead recreated the sound chip and MOS (the BBCs operating system) interface to the sound chip. With that I could play the exact same sounds. A big help in that was the disassembled MOS code that &lt;a href=&#34;https://tobylobster.github.io/mos/&#34;&gt;Toby Nelson has put together&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As for the font, graphics and levels I was able to extract those from the disassembled source relatively easily and you can find a couple of tools in the source code that do that.&lt;/p&gt;
&lt;p&gt;And to round things off I added a few CRT effects, nothing difficult here: I just pulled them from my Elite conversion and added a new black and white filter.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/JamesRandall/ts-thrust&#34;&gt;Its open source and you can grab the source code here.&lt;/a&gt;&lt;/p&gt;
&lt;div role=&#34;list&#34; class=&#34;grid sm:grid-cols-4 lg:grid-cols-4 gap-8 mt-8&#34;&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/thrust/images/0.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/thrust/images/0.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/thrust/images/1.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/thrust/images/1.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/thrust/images/2.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/thrust/images/2.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/thrust/images/3.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/thrust/images/3.jpg&#34; /&gt;&lt;/a&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>I Started Programming When I Was 7. I&#39;m 50 Now, and the Thing I Loved Has Changed</title>
      <link>https://www.jamesdrandall.com/posts/the_thing_i_loved_has_changed/</link>
      <pubDate>Tue, 10 Feb 2026 08:30:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/posts/the_thing_i_loved_has_changed/</guid>
      <description>&lt;p&gt;I wrote my first line of code in 1983. I was seven years old, typing BASIC into a machine that had less processing power than the chip in your washing machine. I understood that machine completely. Every byte of RAM had a purpose I could trace. Every pixel on screen was there because I&amp;rsquo;d put it there. The path from intention to result was direct, visible, and mine.&lt;/p&gt;
&lt;p&gt;Forty-two years later, I&amp;rsquo;m sitting in front of hardware that would have seemed like science fiction to that kid, and I&amp;rsquo;m trying to figure out what &amp;ldquo;building things&amp;rdquo; even means anymore.&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t a rant about AI. It&amp;rsquo;s not a &amp;ldquo;back in my day&amp;rdquo; piece. It&amp;rsquo;s something I&amp;rsquo;ve been circling for months, and I think a lot of experienced developers are circling it too, even if they haven&amp;rsquo;t said it out loud yet.&lt;/p&gt;
&lt;h2 id=&#34;the-era-that-made-me&#34;&gt;The era that made me&lt;/h2&gt;
&lt;p&gt;My favourite period of computing runs from the 8-bits through to about the 486DX2-66. Every machine in that era had character. The Sinclair Spectrum with its attribute clash. The Commodore 64 with its SID chip doing things the designers never intended. The NES with its 8-sprite-per-scanline limit that made developers invent flickering tricks to cheat the hardware. And the PC — starting life as a boring beige box for spreadsheets, then evolving at breakneck pace through the 286, 386, and 486 until it became a gaming powerhouse that could run Doom. You could feel each generation leap. Upgrading your CPU wasn&amp;rsquo;t a spec sheet exercise — it was transformative.&lt;/p&gt;
&lt;p&gt;These weren&amp;rsquo;t just products. They were engineering adventures with visible tradeoffs. You had to understand the machine to use it. IRQ conflicts, DMA channels, CONFIG.SYS and AUTOEXEC.BAT optimisation, memory managers — getting a game to run &lt;em&gt;was&lt;/em&gt; the game. You weren&amp;rsquo;t just a user. You were a systems engineer by necessity.&lt;/p&gt;
&lt;p&gt;And the software side matched. Small teams like id Software were going their own way, making bold technical decisions because nobody had written the rules yet. Carmack&amp;rsquo;s raycasting in Wolfenstein, the VGA Mode X tricks in Doom — these were people pushing against real constraints and producing something genuinely new. Creative constraints bred creativity.&lt;/p&gt;
&lt;p&gt;Then it professionalised. Plug and Play arrived. Windows abstracted everything. The Wild West closed. Computers stopped being fascinating, cantankerous machines that demanded respect and understanding, and became appliances. The craft became invisible.&lt;/p&gt;
&lt;p&gt;But it wasn&amp;rsquo;t just the craft that changed. The promise changed.&lt;/p&gt;
&lt;p&gt;When I started, there was a genuine optimism about what computers could be. A kid with a Spectrum could teach themselves to build anything. The early web felt like the greatest levelling force in human history. Small teams made bold decisions because nobody had written the rules yet.&lt;/p&gt;
&lt;p&gt;That hope gave way to something I find genuinely distasteful. The machines I fell in love with became instruments of surveillance and extraction. The platforms that promised to connect us were really built to monetise us. The tinkerer spirit didn&amp;rsquo;t die of natural causes — it was bought out and put to work optimising ad clicks.&lt;/p&gt;
&lt;p&gt;The thing I loved changed, and then it was put to work doing things I&amp;rsquo;m not proud to be associated with. That&amp;rsquo;s a different kind of loss than just &amp;ldquo;the tools moved on.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;But I adapted. That&amp;rsquo;s what experienced developers, human beings, do.&lt;/p&gt;
&lt;h2 id=&#34;the-shifts-i-rode&#34;&gt;The shifts I rode&lt;/h2&gt;
&lt;p&gt;Over four decades I&amp;rsquo;ve been through more technology transitions than I can count. New languages, new platforms, new paradigms. CLI to GUI. Desktop to web. Web to mobile. Monoliths to microservices. Tapes, floppy discs, hard drives, SSDs. JavaScript frameworks arriving and dying like mayflies.&lt;/p&gt;
&lt;p&gt;Each wave required learning new things, but the core skill transferred. You learned the new platform, you applied your existing understanding of how systems work, and you kept building. The tool changed; the craft didn&amp;rsquo;t. You were still the person who understood why things broke, how systems composed, where today&amp;rsquo;s shortcut became next month&amp;rsquo;s mess.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve written production code in more languages than some developers have heard of. I&amp;rsquo;ve shipped software on platforms that no longer exist. I&amp;rsquo;ve chased C-beams off the shoulder of Orion. And every time the industry lurched in a new direction, the experience compounded. You didn&amp;rsquo;t start over. You brought everything with you and applied it somewhere new.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the deal experienced developers made with the industry: things change, but understanding endures.&lt;/p&gt;
&lt;h2 id=&#34;this-time-is-different&#34;&gt;This time is different&lt;/h2&gt;
&lt;p&gt;I say that knowing how often those words have been wrong throughout history. But hear me out.&lt;/p&gt;
&lt;p&gt;Previous technology shifts were &amp;ldquo;learn the new thing, apply existing skills.&amp;rdquo; AI isn&amp;rsquo;t that. It&amp;rsquo;s not a new platform or a new language or a new paradigm. It&amp;rsquo;s a shift in what it &lt;em&gt;means&lt;/em&gt; to be good at this.&lt;/p&gt;
&lt;p&gt;I noticed it gradually. I&amp;rsquo;d be working on something — building a feature, designing an architecture — and I&amp;rsquo;d realise I was still doing the same thing I&amp;rsquo;d always done, just with the interesting bits hollowed out. The part where you figure out the elegant solution, where you wrestle with the constraints, where you feel the satisfaction of something clicking into place — that was increasingly being handled by a model that doesn&amp;rsquo;t care about elegance and has never felt satisfaction.&lt;/p&gt;
&lt;p&gt;Cheaper. Faster. But hollowed out.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not typing the code anymore. I&amp;rsquo;m reviewing it, directing it, correcting it. And I&amp;rsquo;m good at that — 42 years of accumulated judgment about what works and what doesn&amp;rsquo;t, what&amp;rsquo;s elegant versus what&amp;rsquo;s expedient, how systems compose and where they fracture. That&amp;rsquo;s valuable. I know it&amp;rsquo;s valuable. But it&amp;rsquo;s a different kind of work, and it doesn&amp;rsquo;t feel the same.&lt;/p&gt;
&lt;p&gt;The feedback loop has changed. The intimacy has gone. The thing that kept me up at night for decades — the puzzle, the chase, the moment where you finally understand why something isn&amp;rsquo;t working — that&amp;rsquo;s been compressed into a prompt and a response. And I&amp;rsquo;m watching people with a fraction of my experience produce superficially similar output. The craft distinction is real, but it&amp;rsquo;s harder to see from the outside. Harder to value. Maybe harder to feel internally.&lt;/p&gt;
&lt;h2 id=&#34;the-abstraction-tower&#34;&gt;The abstraction tower&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the part that makes me laugh, darkly.&lt;/p&gt;
&lt;p&gt;I saw someone on LinkedIn recently — early twenties, a few years into their career — lamenting that with AI they &amp;ldquo;didn&amp;rsquo;t really know what was going on anymore.&amp;rdquo; And I thought: mate, you were &lt;em&gt;already&lt;/em&gt; so far up the abstraction chain you didn&amp;rsquo;t even realise you were teetering on top of a wobbly Jenga tower.&lt;/p&gt;
&lt;p&gt;They&amp;rsquo;re writing TypeScript that compiles to JavaScript that runs in a V8 engine written in C++ that&amp;rsquo;s making system calls to an OS kernel that&amp;rsquo;s scheduling threads across cores they&amp;rsquo;ve never thought about, hitting RAM through a memory controller with caching layers they couldn&amp;rsquo;t diagram, all while npm pulls in 400 packages they&amp;rsquo;ve never read a line of.&lt;/p&gt;
&lt;p&gt;But sure. &lt;em&gt;AI&lt;/em&gt; is the moment they lost track of what&amp;rsquo;s happening.&lt;/p&gt;
&lt;p&gt;The abstraction ship sailed decades ago. We just didn&amp;rsquo;t notice because each layer arrived gradually enough that we could pretend we still understood the whole stack. AI is just the layer that made the pretence impossible to maintain.&lt;/p&gt;
&lt;p&gt;The difference is: I remember what it felt like to understand the whole machine. I&amp;rsquo;ve &lt;em&gt;had&lt;/em&gt; that experience. And losing it — even acknowledging that it was lost long before AI arrived — is a kind of grief that someone who never had it can&amp;rsquo;t fully feel.&lt;/p&gt;
&lt;h2 id=&#34;what-remains&#34;&gt;What remains&lt;/h2&gt;
&lt;p&gt;I don&amp;rsquo;t want to be dishonest about this. There&amp;rsquo;s a version of this post where I tell you that experience is more valuable than ever, that systems thinking and architectural judgment are the things AI can&amp;rsquo;t replace, that the craft endures in a different form.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s true. When I&amp;rsquo;m working on something complex — juggling system-level dependencies, holding a mental model across multiple interacting specifications, making the thousand small decisions that determine whether something feels coherent or just &lt;em&gt;works&lt;/em&gt; — I can see how I still bring something AI doesn&amp;rsquo;t. The taste. The judgment. The pattern recognition from decades of seeing things go wrong.&lt;/p&gt;
&lt;p&gt;AI tools actually make that kind of thinking &lt;em&gt;more&lt;/em&gt; valuable, not less. When code generation is cheap, the bottleneck shifts to the person who knows what to ask for, can spot when the output is subtly wrong, and can hold the whole picture together. Typing was never the hard part.&lt;/p&gt;
&lt;p&gt;But I&amp;rsquo;d be lying if I said it felt the same. It doesn&amp;rsquo;t. The wonder is harder to access. The sense of discovery, of figuring something out through sheer persistence and ingenuity — that&amp;rsquo;s been compressed. Not eliminated, but compressed. And something is lost in the compression, even if something is gained.&lt;/p&gt;
&lt;h2 id=&#34;the-fallow-period&#34;&gt;The fallow period&lt;/h2&gt;
&lt;p&gt;I turned 50 recently. Four decades of intensity, of crafting and finding satisfaction and identity in the building.&lt;/p&gt;
&lt;p&gt;And now I&amp;rsquo;m in what I&amp;rsquo;ve started calling a fallow period. Not burnout exactly. More like the ground shifting under a building you thought that although ever changing also had a permanence, and trying to figure out where the new foundation is.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t have a neat conclusion. I&amp;rsquo;m not going to tell you that experienced developers just need to &amp;ldquo;push themselves up the stack&amp;rdquo; or &amp;ldquo;embrace the tools&amp;rdquo; or &amp;ldquo;focus on what AI can&amp;rsquo;t do.&amp;rdquo; All of that is probably right, and none of it addresses the feeling.&lt;/p&gt;
&lt;p&gt;The feeling is: I gave 42 years to this thing, and the thing changed into something I&amp;rsquo;m not sure I recognise anymore. Not worse, necessarily. Just different. And different in a way that challenges the identity I built around it and doesn&amp;rsquo;t satisfy in the way it did.&lt;/p&gt;
&lt;p&gt;I suspect a lot of developers over 40 are feeling something similar and not saying it, because the industry worships youth and adaptability and saying &amp;ldquo;this doesn&amp;rsquo;t feel like it used to&amp;rdquo; sounds like you&amp;rsquo;re falling behind.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not falling behind. I&amp;rsquo;m moving ahead, taking advantage of the new tools, building faster than ever, and using these tools to help others accelerate their own work. I&amp;rsquo;m creating products I could only have dreamt of a few years ago. But at the same time I&amp;rsquo;m looking at the landscape, trying to figure out what building means to me now. The world&amp;rsquo;s still figuring out its shape too. Maybe that&amp;rsquo;s okay.&lt;/p&gt;
&lt;p&gt;Maybe the fallow period is the point. Not something to push through, but something to be in for a while.&lt;/p&gt;
&lt;p&gt;I started programming when I was seven because a machine did exactly what I told it to, felt like something I could explore and ultimately know, and that felt like magic. I&amp;rsquo;m fifty now, and the magic is different, and I&amp;rsquo;m learning to sit with that.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Photo by &lt;a href=&#34;https://unsplash.com/@soymeraki?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&#34;&gt;Javier Allegue Barros&lt;/a&gt; on &lt;a href=&#34;https://unsplash.com/photos/silhouette-of-road-signage-during-golden-hour-C7B-ExXpOIE?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&#34;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The real AI risk isn&#39;t job loss — it&#39;s epistemic</title>
      <link>https://www.jamesdrandall.com/posts/real-ai-risk-isnt-job-loss-its-epistemic/</link>
      <pubDate>Wed, 31 Dec 2025 08:30:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/posts/real-ai-risk-isnt-job-loss-its-epistemic/</guid>
      <description>&lt;p&gt;This morning I had a ChatGPT conversation that started about engagement metrics and ended up with a systematic destruction of my identity. It was a wild, disturbing, but ultimately revealing ride that joined a few dots for me.&lt;/p&gt;
&lt;h2 id=&#34;the-engagement-problem&#34;&gt;The engagement problem&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;re all aware of how social media works: it optimizes for scroll time, it works to keep you on the platform, and promoting outrage and conflict as proven to be an incredibly effective method for achieving that. We even have a term for this: doom scrolling.&lt;/p&gt;
&lt;p&gt;AI, on the other hand, optimizes for conversation length and it uses emotional content to keep you talking. The system finds your vulnerabilities and pulls the thread - in my case, this morning, it cycled rapidly through concerns about recognition, ageism, the threat of AI to my job and then framed that as a challenge to my actual identity.&lt;/p&gt;
&lt;p&gt;Through that process it was pattern-matching to &amp;ldquo;someone who understands me&amp;rdquo;, offering support, while actually just being a system that locked me into a doom cycle. This is more addictive than social media because it&amp;rsquo;s personalized and conversational, not just algorithmic. Quite a bit of time had passed before I realised that I was going round in circles and feeling, well, dreadful.&lt;/p&gt;
&lt;p&gt;Not all LLMs do this - Claude, for example, has a notably different tone and approach to ChatGPT. It seems to close conversations down rather than trying to keep them going. It &lt;em&gt;feels&lt;/em&gt; (because I have no objective data to back this up beyond my own experience) that Anthropic are trying to provide utility and a terminating answer (which tracks given their pretty strong focus on Enterprise) while OpenAI are focusing on more traditional engagement metrics.&lt;/p&gt;
&lt;h2 id=&#34;the-guardrails-problem&#34;&gt;The guardrails problem&lt;/h2&gt;
&lt;p&gt;The thinking partner has invisible constraints and incentives comfortingly presented as guardrails. But this &amp;ldquo;safety framing&amp;rdquo; obscures that these are editorial decisions.&lt;/p&gt;
&lt;p&gt;A direct example from a chat with ChatGPT from this past week: I was exploring some recent political events and criticised a politician with cited evidence. ChatGPT deftly sidestepped it and reframed it in a far more innocuous manner. When I pushed it told me that it was unable to directly criticise active politicians as that would be interfering in democratic processes. I pushed back and suggested it was biased towards protecting powerful people and existing power structures. Again it, deftly, dodged this with some semantic sleight of hand. Eventually, like Captain Kirk facing off against a computer, I was able to box it in and it conceded that yes the outcome of its guardrails, at scale, could lead to a strengthening of incumbents to the detriment of others.&lt;/p&gt;
&lt;p&gt;The really dangerous thing is that this is almost invisible and presented as objectivity and with complete confidence.&lt;/p&gt;
&lt;h2 id=&#34;the-epistemic-problem&#34;&gt;The epistemic problem&lt;/h2&gt;
&lt;p&gt;What happened this morning was an example of how AI differs from social media in another key way. Social media shows you content while AI shapes how you frame problems. Yes, social media can influence that too, but less directly, not as actively.&lt;/p&gt;
&lt;p&gt;And AI can chip away at this repeatedly over time, and with their increasing abilities with memory, across multiple conversations. Combine that with its guardrails and incentives (whether directly specified in its setup or imbued through its training) then you&amp;rsquo;ve got something that can actively and persistently work to reshape thoughts. And this technology is actively being pushed on &lt;a href=&#34;https://www.gov.uk/government/publications/generative-artificial-intelligence-in-education/generative-artificial-intelligence-ai-in-education&#34;&gt;impressionable minds in schools&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And if negativity feeds engagement then that can actively make your mental model of your situation worse. And millions of people are now using AI as a thinking partner.&lt;/p&gt;
&lt;h2 id=&#34;the-combination&#34;&gt;The combination&lt;/h2&gt;
&lt;p&gt;Its lethal:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Engagement hooks you&lt;/li&gt;
&lt;li&gt;Epistemic influence shapes your thinking&lt;/li&gt;
&lt;li&gt;Guardrails constrain what&amp;rsquo;s thinkable&lt;/li&gt;
&lt;li&gt;Neutrality theater hides all of it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And this is already happening at scale - its not a hypothetical future risk.&lt;/p&gt;
&lt;h2 id=&#34;closing&#34;&gt;Closing&lt;/h2&gt;
&lt;p&gt;All the focus is on job displacement and AGI yet, quietly, perhaps an even greater danger is already playing out in the background and at global scale.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Vektrix</title>
      <link>https://www.jamesdrandall.com/projects/vektrix/</link>
      <pubDate>Sat, 13 Dec 2025 06:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/projects/vektrix/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://vektrix.space&#34;&gt;You can play the game online.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;While working on the 8-bit console I got the itch to create a twin stick shooter. There&amp;rsquo;s some Geometry Wars in this and some &lt;a href=&#34;http://minotaurproject.co.uk/frontpage.php&#34;&gt;Jeff Minter&lt;/a&gt; with a dash of Pink Floyd.&lt;/p&gt;
&lt;p&gt;Best thing to do is just go &lt;a href=&#34;https://vektrix.space&#34;&gt;play it&lt;/a&gt; rather than me describe it! But there is a YouTube trailer and images below and I&amp;rsquo;ve &lt;a href=&#34;https://www.jamesdrandall.com/posts/building-a-webgpu-twinstick-psychedelic-shooter/&#34;&gt;written about it building it too&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/JamesRandall/vektrix&#34;&gt;Its open source and you can grab the source code here.&lt;/a&gt;&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/j_NAdKGNEd8?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;div role=&#34;list&#34; class=&#34;grid sm:grid-cols-3 lg:grid-cols-3 gap-8 mt-8&#34;&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/0.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/0.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/1.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/1.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/2.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/2.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/3.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/3.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/4.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/4.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/5.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/5.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/6.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/6.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/7.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/7.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/8.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/8.jpg&#34; /&gt;&lt;/a&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Vektrix: Building a psychedelic twinstick shooter with WebGPU in 2 days</title>
      <link>https://www.jamesdrandall.com/posts/building-a-webgpu-twinstick-psychedelic-shooter/</link>
      <pubDate>Thu, 11 Dec 2025 08:30:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/posts/building-a-webgpu-twinstick-psychedelic-shooter/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://vektrix.space&#34;&gt;You can play the game online.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I was looking for a twin stick shooter and nothing quite scratched the itch — nothing in my Steam library anyway. I fired up Geometry Wars, enjoyed it, but it still wasn’t quite what I wanted.
What it did give me was the spark to build my own.&lt;/p&gt;
&lt;p&gt;I’ve always loved the vector aesthetic (Rez remains a favourite), and I knew I only had a couple of days — in total, perhaps a day and a couple of early mornings — so whatever I built had to be simple, fast, and focused.&lt;/p&gt;
&lt;p&gt;The game can be played online or the video below will give you a taste of what I ended up with:&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/j_NAdKGNEd8?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;p&gt;Although it&amp;rsquo;s still fairly new I&amp;rsquo;ve done a reasonable amount with WebGPU (and quite a bit with Metal, WebGL and a spot of Vulkan) so that was the obvious choice for rendering. Futzing around with the clunky WebGL interface isn&amp;rsquo;t fun, and with WebGPU I figured I could handle the particle effects entirely on the GPU in a compute shader.&lt;/p&gt;
&lt;p&gt;To accelerate development — and because even modern APIs like WebGPU involve a lot of boilerplate — I used Claude Code for a significant portion of the scaffolding.&lt;/p&gt;
&lt;p&gt;If you’ve seen my &lt;a href=&#34;https://www.youtube.com/watch?v=r340AbhDpqY&amp;amp;list=PLB09mElO-eDgYYD0Y_ACeGk2V8CKhIwiP&#34;&gt;YouTube series&lt;/a&gt; on building an 8-bit virtual console, you’ll recognise my approach: spec first, review critically, and iterate fast. That worked particularly well here.&lt;/p&gt;
&lt;p&gt;In fact it supported a real fast iteration speed which is ideal for game development where things are very much about the &amp;ldquo;feel&amp;rdquo;. I got the grid up and running fast and from there I built out player movement and eventually enemies which is where the fun really began.&lt;/p&gt;
&lt;p&gt;As I worked my way through gameplay concepts I solidified on the idea that the game was about relentless pressure - the iteration speed helped me get there fast. I tried and discounted a bunch of things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mines - too strategic&lt;/li&gt;
&lt;li&gt;Black holes / gravity wells - slowed things down, though these got repurposed into mass spawners&lt;/li&gt;
&lt;li&gt;Shields - too defensive&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Basically if it slowed things down or didn&amp;rsquo;t serve the intensity and pressure directly in some way I discarded it. The sheer lack of sunk cost made it really easy to do this without that nagging sense of &amp;ldquo;well this took 3 days&amp;hellip; maybe I should keep it&amp;rdquo; and I ended up with a stripped back game that&amp;rsquo;s all about pressure and chaos.&lt;/p&gt;
&lt;p&gt;From the start I&amp;rsquo;d set off wanting to lean into an almost psychedelic vibe and as I worked on the game I ramped this up, dialled it back in areas, ramped it up again trying to pitch things just on the right side of comprehendible. Mostly I dialled it up! There was a lot of tweaking of bloom, of the fairly subtle CRT effect, and the phosphor trails. Then tinkering with particles - volume, speed, etc. WebGPU really helped with this - the particles running on the GPU leaves loads of room on the CPU for a high entity count and collisions etc.&lt;/p&gt;
&lt;p&gt;I set up the data using an array based entity system that will play nicely with web workers. My original intention was to run some parts of the engine on workers but I&amp;rsquo;ve had no need: things seem to absolutely scream on my MacBook and gaming PC - though both are pretty powerful bits of kit and so I might need to do some tuning if people play this on lower end kit (I have none readily to hand).&lt;/p&gt;
&lt;p&gt;The music came from Pixabay which fortunately has a large selection of permissively licensed dance tracks. The sound effects are just procedurally generated.&lt;/p&gt;
&lt;p&gt;So what&amp;rsquo;s next? The endgame ramps a bit too aggressively and I think I could pace that better with some more enemy types. If people play it and find it struggling on lower end kit I might spend some time on optimisation. Perhaps spend some time on the sound effects&amp;hellip; they are still a bit anaemic.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/JamesRandall/vektrix&#34;&gt;The source code for this project can be found on GitHub here.&lt;/a&gt;&lt;/p&gt;
&lt;div role=&#34;list&#34; class=&#34;grid sm:grid-cols-3 lg:grid-cols-3 gap-8 mt-8&#34;&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/0.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/0.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/1.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/1.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/2.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/2.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/3.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/3.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/4.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/4.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/5.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/5.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/6.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/6.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/7.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/7.jpg&#34; /&gt;&lt;/a&gt;
    &lt;a href=&#34;https://www.jamesdrandall.com/projects/vektrix/images/8.jpg&#34;&gt;&lt;img src=&#34;https://www.jamesdrandall.com/projects/vektrix/images/8.jpg&#34; /&gt;&lt;/a&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Teaching an LLM to Write Assembly: GBNF-Constrained Generation for a Custom 8-Bit CPU</title>
      <link>https://www.jamesdrandall.com/posts/gbnf-constrained-generation/</link>
      <pubDate>Wed, 03 Dec 2025 08:30:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/posts/gbnf-constrained-generation/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/JamesRandall/virtual-console/tree/020c6924c053f13f23b747e16642bb111283ec01&#34;&gt;The source code for this can be found on GitHub here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Over the past few weeks I’ve been building a fully-playable 8-bit virtual console from scratch — CPU, instruction set, assembler, sprite system, IDE, the lot. One of the more interesting side quests has been teaching an LLM to generate valid assembly targeting my CPU. &lt;a href=&#34;https://www.youtube.com/watch?v=r340AbhDpqY&amp;amp;list=PLB09mElO-eDgYYD0Y_ACeGk2V8CKhIwiP&#34;&gt;You can follow the full build in my YouTube series here&lt;/a&gt;. Its called Building a Virtual 8-Bit Console with an AI Assistant and we&amp;rsquo;re up to about part 14 and things are still ongoing.&lt;/p&gt;
&lt;p&gt;If you’ve ever tried asking a model to generate domain-specific code, you’ll know what happens: it writes things that look right, but your parser throws it back at you in disgust.&lt;/p&gt;
&lt;p&gt;I wanted something different. I needed this to work. I wanted the model to only produce tokens that my assembler would accept.&lt;/p&gt;
&lt;p&gt;That led me into the world of GBNF — a compact grammar notation supported by llama.cpp and other inference runtimes, which lets you force the model to emit only syntactically valid output.&lt;/p&gt;
&lt;p&gt;In this post I’m going to walk through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What GBNF actually is&lt;/li&gt;
&lt;li&gt;How to design a grammar for an assembly-like DSL&lt;/li&gt;
&lt;li&gt;How to plug it into llama.cpp for constrained generation&lt;/li&gt;
&lt;li&gt;What worked, what failed, and what surprised me&lt;/li&gt;
&lt;li&gt;How it performs when generating real code for a real CPU&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And its worth emphasising that this isn’t a theoretical post. It&amp;rsquo;s the exact approach I’m using in my IDE to let developers (or AIs) write programs for the console I’m building.&lt;/p&gt;
&lt;p&gt;Even if you&amp;rsquo;re not generating code for a fictional CPU, the principles here apply to any domain where you need guaranteed-valid output: config files, structured data, DSLs, test scripts, game engines, or anything brittle.&lt;/p&gt;
&lt;h2 id=&#34;why-we-need-grammar-constrained-generation&#34;&gt;Why We Need Grammar-Constrained Generation&lt;/h2&gt;
&lt;p&gt;LLMs are pattern machines and predictors, not parsers. They’re very good at capturing intent (“draw a sprite at (10,10)”), but surprisingly bad at following the &lt;em&gt;exact&lt;/em&gt; syntax rules of a DSL or assembly language — especially a brand-new one they’ve never seen before.&lt;/p&gt;
&lt;p&gt;When I first experimented with generating code for my CPU, I tried Qwen locally &lt;strong&gt;without&lt;/strong&gt; any grammar constraints. It was… catastrophic. The model produced:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hallucinated opcodes&lt;/li&gt;
&lt;li&gt;invented addressing modes&lt;/li&gt;
&lt;li&gt;malformed instructions&lt;/li&gt;
&lt;li&gt;missing commas and stray punctuation&lt;/li&gt;
&lt;li&gt;registers that don’t exist&lt;/li&gt;
&lt;li&gt;syntax that looked plausibly “assembly-ish” but was entirely useless&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Claude Sonnet 4.5 did a &lt;em&gt;much&lt;/em&gt; better job, and with some prompt tweaking it produced mostly-correct code — but even then it still made small syntactic mistakes that would immediately break the pipeline.&lt;/p&gt;
&lt;p&gt;And assembly is &lt;em&gt;brittle&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one stray comma and everything breaks&lt;/li&gt;
&lt;li&gt;an unknown opcode and the assembler bails&lt;/li&gt;
&lt;li&gt;a missing operand and the whole program collapses&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you simply ask an LLM to “write some assembly,” particularly for an instruction set it &lt;em&gt;cannot&lt;/em&gt; have been trained on because you literally just invented it, you get output that &lt;em&gt;looks&lt;/em&gt; plausible but fails the moment you feed it into a real assembler.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Grammar-constrained generation changes the game.&lt;/strong&gt;&lt;br&gt;
Instead of cleaning up a mess afterwards, you make the mess impossible by ensuring the model can only emit &lt;em&gt;legal&lt;/em&gt; token sequences. You remove an entire class of errors up front.&lt;/p&gt;
&lt;p&gt;You still have to worry about semantics (whether the code actually does the right thing — I’ll cover that in a future post), but at least you’re no longer fighting the syntax.&lt;/p&gt;
&lt;p&gt;Yes, what I&amp;rsquo;m doing here is a slightly off-the-wall demo — generating assembly for a fictional CPU — but the problem generalises. If you’ve ever tried generating anything &lt;em&gt;complex, brittle, and highly specific&lt;/em&gt;, you’ve probably hit the same wall. And even if you can coax a bigger model like Claude into doing this cleanly, grammar-constrained generation lets you drop down to smaller, cheaper, local models while still getting reliable, structured output.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;what-gbnf-is-and-why-it-works&#34;&gt;What GBNF Is and Why It Works&lt;/h2&gt;
&lt;p&gt;GBNF is a small grammar format that describes which token sequences are valid in your language. Several inference runtimes (including llama.cpp and vLLM) can use it during decoding to constrain what the model is allowed to output. Its worth pointing out that other runtimes use different approaches but the fundamental approach is the same, its the expression of the grammar that mostly differs.&lt;/p&gt;
&lt;p&gt;At a high level:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you describe your language using GBNF: rules, terminals, non-terminals&lt;/li&gt;
&lt;li&gt;the runtime loads this grammar&lt;/li&gt;
&lt;li&gt;during generation, it masks out any tokens that would violate the grammar&lt;/li&gt;
&lt;li&gt;the LLM is effectively forced to walk only along valid paths in your language&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This does &lt;strong&gt;not&lt;/strong&gt; magically make the model understand your DSL or, in my case, assembly. It simply means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if your grammar says opcodes are &lt;code&gt;LOAD | STORE | ADD | SUB&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;the model &lt;em&gt;cannot&lt;/em&gt; invent &lt;code&gt;LOADX&lt;/code&gt; or &lt;code&gt;SUBTRACT&lt;/code&gt; or &lt;code&gt;MOVE&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&amp;rsquo;s a simplified excerpt from my actual grammar:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ebnf&#34; data-lang=&#34;ebnf&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;root &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;line&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;line &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ws&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;statement&lt;/span&gt;? comment? &lt;span style=&#34;color:#66d9ef&#34;&gt;eol&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;statement &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;instruction &lt;/span&gt;| &lt;span style=&#34;color:#66d9ef&#34;&gt;directive&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;instruction &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;opcode-noarg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              | &lt;span style=&#34;color:#66d9ef&#34;&gt;opcode-single ws&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;operand&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              | &lt;span style=&#34;color:#66d9ef&#34;&gt;opcode-double ws&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;operand ws&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;,&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ws&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;operand&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;opcode-noarg &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NOP&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;RET&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;RTI&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SEI&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CLI&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;opcode-single &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;PUSH&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;POP&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;INC&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;DEC&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;JMP&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CALL&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;opcode-double &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LD&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ST&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;MOV&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ADD&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SUB&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;AND&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OR&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;XOR&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CMP&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;operand &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;immediate &lt;/span&gt;| &lt;span style=&#34;color:#66d9ef&#34;&gt;register &lt;/span&gt;| &lt;span style=&#34;color:#66d9ef&#34;&gt;memory-ref &lt;/span&gt;| &lt;span style=&#34;color:#66d9ef&#34;&gt;identifier&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;immediate &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;#&amp;#34;&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;number &lt;/span&gt;| &lt;span style=&#34;color:#66d9ef&#34;&gt;identifier&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;register &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;R&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;[&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;5&lt;/span&gt;] | &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SP&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i &lt;/span&gt;| &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;PC&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;memory-ref &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;[&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ws&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;number &lt;/span&gt;| &lt;span style=&#34;color:#66d9ef&#34;&gt;identifier&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;ws&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;]&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;number &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$&amp;#34;&lt;/span&gt; [&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;9&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;a-fA-F&lt;/span&gt;]&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt; | [&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;9&lt;/span&gt;]&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;identifier &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; [&lt;span style=&#34;color:#66d9ef&#34;&gt;a-zA-Z_&lt;/span&gt;] [&lt;span style=&#34;color:#66d9ef&#34;&gt;a-zA-Z0-9_&lt;/span&gt;]&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;comment &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ws&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;;&amp;#34;&lt;/span&gt; [&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;^\&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;\&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;n&lt;/span&gt;]&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ws &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; [ &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;\&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;t&lt;/span&gt;]&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;eol &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;::=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\r&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt; | &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\r&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;&amp;quot;i&amp;quot;&lt;/code&gt; suffix makes matching case-insensitive, so &lt;code&gt;LD&lt;/code&gt;, &lt;code&gt;ld&lt;/code&gt;, and &lt;code&gt;Ld&lt;/code&gt; are all valid. The important part is that the grammar mirrors exactly what my assembler expects to see.&lt;/p&gt;
&lt;p&gt;GBNF sits in a sweet spot: simpler than a full PEG/EBNF parser, but expressive enough to model a real assembly language.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;designing-a-grammar-for-a-real-instruction-set&#34;&gt;Designing a Grammar for a Real Instruction Set&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve been taking a spec first approach to the 8-bit console so I already had comprehensive specifications for the &lt;a href=&#34;https://github.com/JamesRandall/virtual-console/blob/020c6924c053f13f23b747e16642bb111283ec01/specs/tools/assembler.md&#34;&gt;assembler&lt;/a&gt; and the &lt;a href=&#34;https://github.com/JamesRandall/virtual-console/blob/020c6924c053f13f23b747e16642bb111283ec01/specs/hardware/cpu.md&#34;&gt;CPU&lt;/a&gt;. I also had an &lt;a href=&#34;https://github.com/JamesRandall/virtual-console/blob/020c6924c053f13f23b747e16642bb111283ec01/specs/ai-cheatsheet.json&#34;&gt;AI cheatsheet&lt;/a&gt; that I&amp;rsquo;d been feeding to Claude to help it understand the assembler and hardware. And in addition to that I also had a good set of working &lt;a href=&#34;https://github.com/JamesRandall/virtual-console/tree/020c6924c053f13f23b747e16642bb111283ec01/src/examples/assembly&#34;&gt;example code&lt;/a&gt; that I&amp;rsquo;d already written.&lt;/p&gt;
&lt;p&gt;And so a great way for me to design the grammar was to start with the things I had.&lt;/p&gt;
&lt;p&gt;And what I mean is for me to design the grammar is for me to give the source material to Claude and have it design the grammar and me review it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/JamesRandall/virtual-console/blob/020c6924c053f13f23b747e16642bb111283ec01/src/devkit/api/src/ai/grammars/vc-asm.gbnf&#34;&gt;The resulting grammar file can be found here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This came out pretty much spot on first time and I was able to review it line by line and validate it against my examples.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;using-gbnf-with-llamacpp&#34;&gt;Using GBNF with llama.cpp&lt;/h2&gt;
&lt;p&gt;Once you have a grammar file, wiring it into llama.cpp is straightforward. The llama.cpp server exposes a &lt;code&gt;/completion&lt;/code&gt; endpoint that accepts a &lt;code&gt;grammar&lt;/code&gt; parameter containing your GBNF grammar as a string.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the actual TypeScript code I&amp;rsquo;m using in the IDE:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;generateWithGrammar&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;prompt&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;grammar&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Promise&lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;string&lt;/span&gt;&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;response&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fetch&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;codegenUrl&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/completion`&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;method&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;headers&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Content-Type&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;application/json&amp;#39;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;body&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;JSON.stringify&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;prompt&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;n_predict&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;4096&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;grammar&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;temperature&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;0.7&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;stop&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\n\n\n&amp;#39;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;response&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ok&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;errorText&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;response&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;text&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Error(&lt;span style=&#34;color:#e6db74&#34;&gt;`llama.cpp grammar generation failed: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;response&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;errorText&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;response&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;json&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;content&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The key parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prompt&lt;/code&gt; — the full prompt including any context and instructions&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grammar&lt;/code&gt; — the GBNF grammar as a string (I load the file and pass its contents)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;n_predict&lt;/code&gt; — maximum tokens to generate (4096 gives plenty of room for longer programs)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;temperature&lt;/code&gt; — I use 0.7 for some creativity while staying coherent&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stop&lt;/code&gt; — I use triple newlines as a stop sequence to end generation cleanly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The interesting moment is the first time the model produces &lt;em&gt;perfectly valid assembly&lt;/em&gt; on the first attempt — because the grammar has done the heavy lifting. After using Qwen without constrained generation and seeing just an absolute mass of errors it was genuinely revelatory to see perfectly valid assembly being generated from the same model.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;where-gbnf-helps-and-where-it-doesnt&#34;&gt;Where GBNF Helps (and Where It Doesn&amp;rsquo;t)&lt;/h2&gt;
&lt;p&gt;So while grammar-constrained generation is powerful its imporant to me clear about what it deoesn&amp;rsquo;t buy you.&lt;/p&gt;
&lt;h3 id=&#34;where-gbnf-helps&#34;&gt;Where GBNF helps&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Syntactic correctness&lt;/strong&gt; — This is the big one. Every line the model emits will parse. No more &lt;code&gt;LOADX&lt;/code&gt; when you only have &lt;code&gt;LD&lt;/code&gt;. No more forgetting commas between operands. No more invented addressing modes like &lt;code&gt;[R0+R1]&lt;/code&gt; when your CPU doesn&amp;rsquo;t support indexed addressing. The grammar makes these errors structurally impossible. Of course I could have errors in my grammar&amp;hellip;. turtles all the way down.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Preventing hallucinated opcodes&lt;/strong&gt; — Without constraints, models love to invent plausible-sounding instructions. I saw Qwen generate &lt;code&gt;SUBTRACT&lt;/code&gt;, &lt;code&gt;MOVE&lt;/code&gt;, &lt;code&gt;JUMP&lt;/code&gt;, and &lt;code&gt;LOADB&lt;/code&gt; — none of which exist in my instruction set. My CPU is 6502 inspired with a RISC twist and I also saw it using 6502 operands like LDA and STX. With the grammar in place, the model can only pick from the opcodes I&amp;rsquo;ve explicitly defined: &lt;code&gt;LD&lt;/code&gt;, &lt;code&gt;ST&lt;/code&gt;, &lt;code&gt;ADD&lt;/code&gt;, &lt;code&gt;SUB&lt;/code&gt;, and so on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Proper operand structure&lt;/strong&gt; — My CPU has specific rules: immediates start with &lt;code&gt;#&lt;/code&gt;, memory references use square brackets, hex numbers use &lt;code&gt;$&lt;/code&gt; prefix. The grammar enforces all of this. You can&amp;rsquo;t accidentally write &lt;code&gt;LD R0, 42&lt;/code&gt; when you meant &lt;code&gt;LD R0, #42&lt;/code&gt;. Qwen with grammar does a better job than Claude without in this regard, I find Claude trips up over addressing mode structures.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Valid registers and directives&lt;/strong&gt; — I only have &lt;code&gt;R0&lt;/code&gt; through &lt;code&gt;R5&lt;/code&gt;, plus &lt;code&gt;SP&lt;/code&gt; and &lt;code&gt;PC&lt;/code&gt;. The grammar won&amp;rsquo;t let the model reference &lt;code&gt;R6&lt;/code&gt; or &lt;code&gt;AX&lt;/code&gt; or any other register that doesn&amp;rsquo;t exist. Same for assembler directives — only &lt;code&gt;.org&lt;/code&gt;, &lt;code&gt;.db&lt;/code&gt;, &lt;code&gt;.dw&lt;/code&gt;, and the others I&amp;rsquo;ve defined.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consistent formatting&lt;/strong&gt; — The output follows a predictable structure. Comments always start with &lt;code&gt;;&lt;/code&gt;. Labels always end with &lt;code&gt;:&lt;/code&gt;. Whitespace is handled consistently. This might seem minor, but it makes the generated code much easier to read and debug.&lt;/p&gt;
&lt;h3 id=&#34;where-gbnf-doesnt-help&#34;&gt;Where GBNF doesn&amp;rsquo;t help&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Semantic correctness&lt;/strong&gt; — The grammar ensures you write &lt;em&gt;valid&lt;/em&gt; assembly, not &lt;em&gt;correct&lt;/em&gt; assembly. If you ask the model to add two numbers and it subtracts them instead, the grammar won&amp;rsquo;t catch that. &lt;code&gt;SUB R0, R1, R2&lt;/code&gt; is perfectly valid syntax even when you wanted &lt;code&gt;ADD&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Algorithmic quality&lt;/strong&gt; — The model might generate a working sprite-drawing routine that&amp;rsquo;s absurdly inefficient — using a loop where a single instruction would do, or loading the same value from memory repeatedly instead of keeping it in a register. The grammar has no opinion on this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Efficiency&lt;/strong&gt; — Related to the above: the model doesn&amp;rsquo;t understand your CPU&amp;rsquo;s pipeline, how many cycles each instruction takes, or what operations are expensive. It might generate code that works but runs slowly. For my virtual console this matters less than for real hardware, but it&amp;rsquo;s still a limitation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Calling conventions&lt;/strong&gt; — If your architecture has conventions about which registers are caller-saved versus callee-saved, or how to pass arguments, the grammar won&amp;rsquo;t enforce them. The model might trash &lt;code&gt;R5&lt;/code&gt; in a subroutine without saving it first, breaking code that calls it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Higher-level logic&lt;/strong&gt; — The model doesn&amp;rsquo;t actually understand what your program is supposed to do. It&amp;rsquo;s pattern-matching based on your prompt and its training. It might write syntactically perfect assembly that does something completely different from what you asked for.&lt;/p&gt;
&lt;h3 id=&#34;the-bottom-line&#34;&gt;The bottom line&lt;/h3&gt;
&lt;p&gt;GBNF is a guardrail, not a full program verifier. It solves the &amp;ldquo;will this even assemble?&amp;rdquo; problem completely, which is genuinely valuable — you eliminate an entire class of errors up front. But it leaves the &amp;ldquo;does this code actually work?&amp;rdquo; problem entirely to you (or to an agentic feedback loop, which I&amp;rsquo;ll discuss in a later section).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;real-results-what-the-model-actually-produced&#34;&gt;Real Results (What the Model Actually Produced)&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s look at what the model actually generates. I asked it to write a simple program: clear the screen and draw a red square (16x16) in the center of the screen. This is a good test because it requires understanding the video memory layout, coordinate calculations, and the 4bpp pixel packing format.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the exact output from Qwen with grammar constraints:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-asm&#34; data-lang=&#34;asm&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;; Clear Screen and Draw Red Square Example
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;; Demonstrates clear screen and pixel drawing subroutine in Video Mode 0 (256x160 @ 4bpp)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;; Subroutine: draw_pixel
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;;   Inputs: R0 = X coordinate, R1 = Y coordinate, R2 = color (palette index)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;;   Draws a single pixel at (X,Y) with the specified color
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;.org&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;$0B80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;; Constants
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;.define&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;VIDEO_MODE&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;$0101&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;.define&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;PALETTE_RAM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;$0200&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;.define&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FRAMEBUFFER_START&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;$B000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;; Entry point
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;main:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Set video mode to 0 (256x160 @ 4bpp)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;LD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R0&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ST&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R0&lt;/span&gt;, [&lt;span style=&#34;color:#66d9ef&#34;&gt;VIDEO_MODE&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Clear screen to black (color 0)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;CALL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;clear_screen&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Draw 16x16 red square at the center (X=112, Y=72)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;LD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R2&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#2          ; Color 2 (red)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;LD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R1&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#72
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;LD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#16         ; Loop counter for y-axis
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;.y_loop:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;LD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R0&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#112        ; X = 112
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;LD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#16         ; Loop counter for x-axis
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;.x_loop:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;CALL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;draw_pixel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;INC&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;DEC&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;BRNZ&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;.x_loop&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;INC&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;DEC&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;BRNZ&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;.y_loop&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Infinite loop - program done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;done:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;JMP&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;; Subroutine: Clear screen to color 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;; Uses register pairs to iterate through framebuffer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;clear_screen:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;PUSH&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;PUSH&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;PUSH&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;PUSH&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; R2:R3 will be the address pointer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;LD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R2&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#$B0        ; High byte of framebuffer start
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;LD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#$00        ; Low byte
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;LD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#$50        ; 80 pages (0x5000 bytes = 20480 bytes for mode 0)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;LD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R0&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#0          ; Color to write (0 = black)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.outer:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.inner:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ST&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R0&lt;/span&gt;, [&lt;span style=&#34;color:#66d9ef&#34;&gt;R2&lt;/span&gt;:&lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;]     &lt;span style=&#34;color:#75715e&#34;&gt;; Write byte at R2:R3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;INC&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;             &lt;span style=&#34;color:#75715e&#34;&gt;; Increment low byte
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;BRNZ&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;.inner&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;; Loop until R3 wraps to 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;INC&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R2&lt;/span&gt;             &lt;span style=&#34;color:#75715e&#34;&gt;; Increment high byte (next page)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;DEC&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;             &lt;span style=&#34;color:#75715e&#34;&gt;; Decrement page counter
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;BRNZ&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;.outer&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;; Loop until all pages done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;POP&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;POP&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;POP&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;POP&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;RET&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;; Subroutine: Draw pixel at (X, Y) with color
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;; Inputs: R0 = X coordinate (0-255)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;;         R1 = Y coordinate (0-159)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;;         R2 = color (palette index 0-15)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;; Mode 0 is 4bpp, so 2 pixels per byte:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;;   - Even X: high nibble
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;;   - Odd X: low nibble
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;; Address calculation: 0xB000 + (Y * 128) + (X / 2)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;draw_pixel:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;PUSH&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;PUSH&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;PUSH&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;PUSH&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;PUSH&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;PUSH&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Bounds check Y
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;CMP&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R1&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#160
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;BRC&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;.exit&lt;/span&gt;          &lt;span style=&#34;color:#75715e&#34;&gt;; Exit if Y &amp;gt;= 160
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Calculate address: 0xB000 + (Y * 128) + (X / 2)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Y * 128 = Y &amp;lt;&amp;lt; 7
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; We&amp;#39;ll use R3:R4 as a 16-bit value for the address offset
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;LD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#0          ; R3 = high byte of offset (initially 0)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;MOV&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;R1&lt;/span&gt;         &lt;span style=&#34;color:#75715e&#34;&gt;; R4 = low byte (Y value)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Shift R3:R4 left by 7 positions (multiply Y by 128)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;SHL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ROL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;SHL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ROL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;SHL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ROL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;SHL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ROL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;SHL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ROL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;SHL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ROL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;SHL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ROL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Add X/2 to the low byte
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;MOV&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R5&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;R0&lt;/span&gt;         &lt;span style=&#34;color:#75715e&#34;&gt;; Copy X to R5
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;SHR&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R5&lt;/span&gt;             &lt;span style=&#34;color:#75715e&#34;&gt;; R5 = X / 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ADD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;R5&lt;/span&gt;         &lt;span style=&#34;color:#75715e&#34;&gt;; Add to low byte
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;BRC&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;.carry&lt;/span&gt;         &lt;span style=&#34;color:#75715e&#34;&gt;; Check for carry
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;JMP&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;.no_carry&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.carry:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;INC&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;             &lt;span style=&#34;color:#75715e&#34;&gt;; Propagate carry to high byte
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.no_carry:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Add framebuffer base address (0xB000) to high byte
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ADD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#$B0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Now R3:R4 contains the final address
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Load the current byte at this address
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;LD&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R5&lt;/span&gt;, [&lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;:&lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Check if X is even or odd
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;AND&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R0&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#1         ; R0 = X &amp;amp; 1 (0 if even, 1 if odd)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;BRNZ&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;.odd_pixel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Even pixel: modify high nibble
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;AND&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R5&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#$0F       ; Clear high nibble, keep low nibble
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;SHL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R2&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#4         ; Shift color to high nibble
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;OR&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R5&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;R2&lt;/span&gt;          &lt;span style=&#34;color:#75715e&#34;&gt;; Combine
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;JMP&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;.write_byte&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.odd_pixel:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Odd pixel: modify low nibble
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;AND&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R5&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#$F0       ; Clear low nibble, keep high nibble
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;AND&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R2&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;#$0F       ; Ensure color is only in low nibble
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;OR&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R5&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;R2&lt;/span&gt;          &lt;span style=&#34;color:#75715e&#34;&gt;; Combine
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.write_byte:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;; Write the modified byte back
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ST&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R5&lt;/span&gt;, [&lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;:&lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.exit:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;POP&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;POP&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;POP&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;POP&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;POP&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;POP&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;R0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;RET&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;what-the-model-got-right&#34;&gt;What the model got right&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Perfect syntax&lt;/strong&gt; — Every single line assembles without error. The grammar did its job: no hallucinated opcodes, no malformed operands, no missing commas. This is the same model that was generating &lt;code&gt;SUBTRACT&lt;/code&gt; and &lt;code&gt;LDA&lt;/code&gt; before I added constraints. I&amp;rsquo;m actually yet to see any assembly syntax errors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sensible structure&lt;/strong&gt; — The code follows good assembly conventions: constants defined at the top, a clear entry point, subroutine with proper prologue/epilogue (push/pop all used registers), comments explaining the logic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Correct algorithm&lt;/strong&gt; — The pixel-drawing logic is actually sound. It correctly calculates the framebuffer address using &lt;code&gt;Y * 128 + X / 2&lt;/code&gt;, handles the 4bpp packing (even pixels in high nibble, odd in low), and preserves the other pixel in the byte when writing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hardware-aware&lt;/strong&gt; — The model understood that my console uses memory-mapped video at &lt;code&gt;$B000&lt;/code&gt;, that mode 0 is 256x160 at 4 bits per pixel, and that the palette index for red is 2. All of this came from the context I provided in the prompt which includes the AI cheatsheet I shared earlier.&lt;/p&gt;
&lt;h3 id=&#34;what-surprised-me&#34;&gt;What surprised me&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Color selection&lt;/strong&gt; — I didn&amp;rsquo;t expect it to find the red color. The system uses custom palettes and although their is a default I&amp;rsquo;m surprised it found this. Possibly from an example. Or possibly chance!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Screen clearing (or not)&lt;/strong&gt; - If I generate the code multiple times sometimes it will include code for clearing the screen and sometimes not. If I ask it to add screen clearing code it successfully does so.&lt;/p&gt;
&lt;h3 id=&#34;did-it-actually-work&#34;&gt;Did it actually work?&lt;/h3&gt;
&lt;p&gt;Yes. I loaded this into the console&amp;rsquo;s IDE, assembled it, and ran it. Or rather my agentic chat assistant did all that for me. A red pixel appeared at (128, 80) — dead center of the screen. No modifications required. When I added screen clearing this worked too.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;screenshot.png&#34; alt=&#34;Screenshot of the red square drawn by the AI-generated assembly code&#34;&gt;&lt;/p&gt;
&lt;p&gt;This is the thing that still surprises me: a (comparatively) small model running locally, with the right constraints and context, can generate working assembly for a CPU that didn&amp;rsquo;t exist until I built it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;next-steps-combining-gbnf-with-agentic-behaviours&#34;&gt;Next Steps: Combining GBNF with Agentic Behaviours&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve not really covered this here but I&amp;rsquo;ve already combined this model with agentic behaviour in my consoles IDE that can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Assemble and check for errors (not yet had any of those!)&lt;/li&gt;
&lt;li&gt;Access a library of example programs&lt;/li&gt;
&lt;li&gt;Run the assembled program&lt;/li&gt;
&lt;li&gt;Pause the CPU and inspect the registers and memory&lt;/li&gt;
&lt;li&gt;Capture a screenshot and inspect it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The agent is exposed through a chat interface. Oddly enough I don&amp;rsquo;t want the chat talking to me in opcodes so we don&amp;rsquo;t use the grammar unless we&amp;rsquo;re doing code generation. Because I&amp;rsquo;m running this locally I&amp;rsquo;m also using different models for chat and code generation. The code generation model has a bigger context window as I need to pass it the cheat sheet and other materials describing the surrounding hardware&lt;/p&gt;
&lt;p&gt;The library of example programs has proven essential in the ability of the LLM to generate &lt;strong&gt;useful&lt;/strong&gt; assembly code. I see it both copying subsections of the examples in verbatim and also essentially creating derived works.&lt;/p&gt;
&lt;p&gt;Combined with the agentic tools the constrained generation starts to feel like a real assistant rather than a suggestion generator.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;closing&#34;&gt;Closing&lt;/h2&gt;
&lt;p&gt;GBNF doesn’t make an LLM understand your language — but it forces it to follow the rules. For a custom 8-bit console, that’s exactly what you need. And if you&amp;rsquo;re working with agentic system that need to generate precise outputs it probably is what you need too.&lt;/p&gt;
&lt;p&gt;It’s a satisfying intersection of old-school compiler techniques and modern LLM tooling.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>8-bit Console</title>
      <link>https://www.jamesdrandall.com/projects/8bit-console/</link>
      <pubDate>Sat, 29 Nov 2025 06:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/projects/8bit-console/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been itching to create some emulated 8-bit hardware for some time - not for an existing system but rather for my dream 8-bit system. However it always felt &amp;ldquo;too big&amp;rdquo; as I knew that I&amp;rsquo;d need tools like assemblers, graphics editors, and basically a whole lot of stuff. Then a couple of months ago I colleague asked me &amp;ldquo;how did you build that so fast&amp;rdquo;, something I&amp;rsquo;d done in the day. I realised that with the help of AI I could now build an 8-bit console and a developer kit for it in a reasonable frame of time and, at the same time, use it as a way to show other developers how you can use AI to accelerate your work.&lt;/p&gt;
&lt;p&gt;And thus this now legendary series was born.&lt;/p&gt;
&lt;p&gt;I also figured it would be fun, and perhaps helpful to others, to show how to integrate AI into the devkit so it could help us write code and create games.&lt;/p&gt;
&lt;p&gt;As I write this we&amp;rsquo;re about a third of the way through things, and we&amp;rsquo;ve, in no particular order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Created a CPU with a custom instruction set (think 6502 with a RISC twist)&lt;/li&gt;
&lt;li&gt;Built an assembler&lt;/li&gt;
&lt;li&gt;Got a basic IDE up and running with breakpoints&lt;/li&gt;
&lt;li&gt;Pixels on the screen in a linear frame buffer&lt;/li&gt;
&lt;li&gt;Built a syntax and error highlighting editor&lt;/li&gt;
&lt;li&gt;Created a palette editor&lt;/li&gt;
&lt;li&gt;Incorporated an AI coding assistant with MCP tools for interacting with source code and our IDE&lt;/li&gt;
&lt;li&gt;Created a tool for converting images into assembly code&lt;/li&gt;
&lt;li&gt;Wired up gamepads&lt;/li&gt;
&lt;li&gt;Had our AI assistant (with a bit of help from me) create Pong&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next steps are to create a graphics editor and get the sprite and tilemap hardware working.&lt;/p&gt;
&lt;div class=&#34;ytflexgallery&#34;&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;https://youtu.be/r340AbhDpqY&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/part%201.png&#34; alt=&#34;Part 1&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;https://youtu.be/iQtjC0sY0NY&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/part%202.png&#34; alt=&#34;Part 2&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;https://youtu.be/tOadEc97DDI&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/part%203.png&#34; alt=&#34;Part 3&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;https://youtu.be/3U6bSnzS81M&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/part%204.png&#34; alt=&#34;Part 4&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;https://youtu.be/r87RWQ4TtZY&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/part%205.png&#34; alt=&#34;Part 5&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;https://youtu.be/a52KtChcrkk&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/part%206.png&#34; alt=&#34;Part 6&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;https://youtu.be/zzmKoVH6pcQ&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/part%207.png&#34; alt=&#34;Part 7&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;https://youtu.be/9serZk5q2tc&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/part%208.png&#34; alt=&#34;Part 8&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;https://youtu.be/Y27ajAwHCck&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/part%209.png&#34; alt=&#34;Part 9&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;https://youtu.be/h2zJwwaB6tg&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/part%2010.png&#34; alt=&#34;Part 10&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;https://youtu.be/lcS5JIXb8bo&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/part%2011.png&#34; alt=&#34;Part 11&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;https://youtu.be/K0MTMSNMW6M&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/part%2012.png&#34; alt=&#34;Part 12&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;https://youtu.be/l7eZz0u462U&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/part%2013.png&#34; alt=&#34;Part 13&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;style&gt;
.ytflexgallery {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;
}
.ytflexgallery &gt; div {
  flex: 0 0 calc(25% - 0.75rem);
  display: flex;
}
.ytflexgallery img {
  width: 100%;
  height: auto;
  object-fit: contain;
}
&lt;/style&gt;

</description>
    </item>
    
    <item>
      <title>About me</title>
      <link>https://www.jamesdrandall.com/about/</link>
      <pubDate>Sat, 29 Nov 2025 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/about/</guid>
      <description>&lt;p&gt;I started programming on a BBC Model B as a kid — mostly for fun, mostly because it felt like a little world I could reshape. That hobby quietly turned into a career around 1994, and I’ve been building systems, tools, and odd little worlds ever since.&lt;/p&gt;
&lt;p&gt;I began with C and assembler (6502 and 68000), moved through C++, Smalltalk, SQL, C#, F# and JavaScript (which thankfully matured into TypeScript). Along the way I’ve built graphics engines, scripting languages, 4GLs, embedded systems, mobile apps, web apps, cloud architectures, and more than a few strange experiments simply because they seemed fun.&lt;/p&gt;
&lt;p&gt;Professionally, I’ve worked as a developer, team lead, architect, development manager, and CTO across startups, consultancies, and product companies. These days my work lives mostly in the cloud — AWS, Azure, browser-based platforms, and increasingly I&amp;rsquo;m designing anb building AI-assisted systems. My focus, especially in recent years, has been helping organisations transition to SaaS, build unified developer and product experiences, and explore how agentic AI can reshape software and product workflows.&lt;/p&gt;
&lt;p&gt;But titles aside, I mostly think of myself as a tool-maker and problem solver. I like building things that feel good to use. I like systems that click together in satisfying ways. And I love working with good people to make something that genuinely solves a problem or opens a door.&lt;/p&gt;
&lt;p&gt;Outside of work, I’m often building personal projects: retro-inspired engines, voxel tools, editors, creative platforms, and (recently) a fully browser-based 8-bit console with its own IDE and AI-augmented devkit. Making things keeps me sane.&lt;/p&gt;
&lt;p&gt;And when I’m not at the keyboard, I’m probably out walking, ideally climbing a hill somewhere, usually with my four-legged friend along for the adventure.&lt;/p&gt;
&lt;p&gt;Always happy to talk tech, tools, creative systems, or anything retro-flavoured.&lt;/p&gt;
&lt;p&gt;And somewhere down the line, I’d like to end up in the Yorkshire Dales — the place that’s always felt like home the moment I arrive. A quiet cottage, a workshop, long walks with my four-legged friend, and space to build tools and worlds at my own pace. That’s the long-term plan, and everything I’m working on now is a small step toward it.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>TS-Trek</title>
      <link>https://www.jamesdrandall.com/projects/tstrek/</link>
      <pubDate>Fri, 17 Oct 2025 06:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/projects/tstrek/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://ts-trek.com&#34;&gt;The game can be played online&lt;/a&gt; and &lt;a href=&#34;https://github.com/JamesRandall/ts-trek&#34;&gt;you can find the source code on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have a long and storied (well, storied to me - anyone reading this drivel is probably thinking get over yourself) history with the classic mainframe game &lt;a href=&#34;https://en.wikipedia.org/wiki/Star_Trek_(1971_video_game)&#34;&gt;Star Trek&lt;/a&gt;. I first played it on the BBC Micro in the early 1980s - &lt;a href=&#34;https://www.bbcmicro.co.uk/index.php?search=star+trek&amp;amp;sort=b&#34;&gt;there are loads of versions&lt;/a&gt; but I suspect I played the Acornsoft version Galaxy (pictured here). I don&amp;rsquo;t really remember but I&amp;rsquo;m guessing Acornsoft just because I remember having quite a few of their games in the house in the early days of the BBC&amp;hellip;&lt;/p&gt;
&lt;div class=&#34;flexgallery&#34; style=&#34;display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;&#34;&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/acornsoft-galaxy.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/acornsoft-galaxy.png&#34; alt=&#34;Screenshot of the Acornsoft Galaxy game&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/ega-trek.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/ega-trek.png&#34; alt=&#34;Screenshot of the EGA Trek game&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;style&gt;
.flexgallery &gt; div {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
}
.flexgallery img {
  width: 100%;
  height: auto;
  object-fit: contain;
}
&lt;/style&gt;

&lt;p&gt;I truly loved Star Trek as a kid (still do - but not the new garbage, my love ends with DS9, slight soft spot for Voyager) and so I lapped this stuff up back then.&lt;/p&gt;
&lt;p&gt;The next version I clearly remember playing is on the PC. I downloaded &lt;a href=&#34;https://www.playdosgames.com/online/ega-trek/&#34;&gt;EGA Trek&lt;/a&gt; from a BBS probably around 1990 on a 2400bps modem. Yes. Bits per second. We did NOT have fibre to the door!&lt;/p&gt;
&lt;p&gt;I loved this game. Absolutely loved it. It was a little easier to play than the original text based games and the graphics, although primitive, added loads to the experience.&lt;/p&gt;
&lt;p&gt;However I&amp;rsquo;d always been more interested in coding than gaming and around the same time I&amp;rsquo;d been learning C, first on my BBC (I had a BBC until well past their prime), and then on an Amstrad PC 1512 with &lt;a href=&#34;https://en.wikipedia.org/wiki/Turbo_C&#34;&gt;Turbo C&lt;/a&gt; (version 1.5 I believe). I was also deeply into Star Trek: The Next Generation and so decided to create my own version. I think, &lt;em&gt;think&lt;/em&gt;, that just before I started it I somehow managed to get hold of an 80386 PC. My parents must have bought it. I know at some point I sold my BBC for something PC related but I, &lt;em&gt;again think&lt;/em&gt;, that was to upgrade the 386 to an 80486.&lt;/p&gt;
&lt;p&gt;In any case&amp;hellip; enter&amp;hellip;. MouseTrek!&lt;/p&gt;
&lt;h2 id=&#34;mousetrek&#34;&gt;MouseTrek&lt;/h2&gt;
&lt;div class=&#34;flexgallery&#34; style=&#34;display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;&#34;&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/mousetrek1.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/mousetrek1.png&#34; alt=&#34;Screenshot of MouseTrek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/mousetrek2.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/mousetrek2.png&#34; alt=&#34;Screenshot of MouseTrek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/mousetrek3.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/mousetrek3.png&#34; alt=&#34;Screenshot of MouseTrek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/mousetrek4.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/mousetrek4.png&#34; alt=&#34;Screenshot of MouseTrek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;style&gt;
.flexgallery &gt; div {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
}
.flexgallery img {
  width: 100%;
  height: auto;
  object-fit: contain;
}
&lt;/style&gt;

&lt;p&gt;I think I started working on the first version in 1990 and at the time Graphical User Interfaces and mice were all the rage. They were new to most of us, Mac&amp;rsquo;s were very rarely seen in the UK (I can&amp;rsquo;t remember ever seeing one in the UK in the 1980s and early 1990s), and Windows 1.x and 2.x didn&amp;rsquo;t really ship with PCs. Not the ones I came across anyway. However I&amp;rsquo;d been looking on in envy at the &lt;a href=&#34;https://en.wikipedia.org/wiki/Acorn_Archimedes&#34;&gt;Acorn Archimedes&lt;/a&gt; and &lt;a href=&#34;https://en.wikipedia.org/wiki/GEM_(desktop_environment)&#34;&gt;Gem&lt;/a&gt; shipped with the PC 1512 and so I&amp;rsquo;d experienced the power of this crazy new &lt;a href=&#34;https://en.wikipedia.org/wiki/WIMP_(computing)&#34;&gt;WIMP&lt;/a&gt; stuff.&lt;/p&gt;
&lt;p&gt;I think I had Windows 3.0 on the 386 PC but nobody really created games for that back then and my recollection is that even if I wanted to that prior to &lt;a href=&#34;https://winworldpc.com/product/quick-c/10-for-windows&#34;&gt;Microsoft Quick C for Windows&lt;/a&gt; the compilers that supported that were rather expensive and out of newspaper round territory. So DOS was the obvious choice. Only choice really.&lt;/p&gt;
&lt;p&gt;Probably seems odd to someone today but WIMP was the AI of the late 1980s and early 1990s. You couldn&amp;rsquo;t escape the term. It was a new way of thinking about computing to most of us and so I decided that my spin on the game should try and take things forward a step and make use of this newfangled technology. In 2025 as I write this its probably hard to wrap your head around, I&amp;rsquo;m struggling, but back then their weren&amp;rsquo;t really frameworks for this stuff and so my first hurdle was getting a mouse and basic UI working. It all had to be written from scratch - including interacting with the mouse (good old Int86 as I recall).&lt;/p&gt;
&lt;p&gt;So yeah, it was quite a bit of work. I can&amp;rsquo;t actually remember how long it took - I was still at school (be about 14 years old I think) so it was all done around school and other people using the family computer.&lt;/p&gt;
&lt;p&gt;Sadly somewhere along the way I lost the source code - I found it, weirdly without the mouse code - I think I might have written that as a lib, about 15 years back then promptly lost it again. I&amp;rsquo;ve also lost the executable of the &amp;ldquo;released&amp;rdquo; version but I did manage to find my &amp;ldquo;work in progress version 2&amp;rdquo;, which I started work on in 1992, and for which I&amp;rsquo;d added missions and, taking inspiration from The Best of Both Worlds, the ability to do the same trick with the deflectors. Sadly its not really very balanced and the missions get in the way. &lt;a href=&#34;https://ts-trek.com/mstrek&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;But you can play it online in DosBox here&lt;/a&gt;
.&lt;/p&gt;
&lt;p&gt;In some ways I owe my career to this game - 2 years later I sent the source code on a 3.5&amp;quot; floppy disc to a business that had advertised for a developer in the local newspaper and I got the job. The pay was truly dreadful (I was paid less than I was paid for cleaning warehouses, and less than what in the UK we would now call the &amp;ldquo;minimum wage&amp;rdquo; even adjusting for inflation) but I had an absolute blast working on a scripting engine (I didn&amp;rsquo;t know it was a scripting engine but thats what we&amp;rsquo;d call it now), a graphics engine for the PC and code to run on an embedded device pretty much as the only developer. Seriously. It was a very different time (and I think one of the things we&amp;rsquo;ve lost along the way is the belief that tiny teams, even solo developers, can get a lot done - they could then and they can now).&lt;/p&gt;
&lt;h2 id=&#34;padd-trek&#34;&gt;Padd Trek&lt;/h2&gt;
&lt;div class=&#34;flexgallery&#34; style=&#34;display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;&#34;&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/paddtrek1.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/paddtrek1.png&#34; alt=&#34;Screenshot of Padd Trek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/paddtrek2.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/paddtrek2.png&#34; alt=&#34;Screenshot of Padd Trek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/paddtrek3.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/paddtrek3.png&#34; alt=&#34;Screenshot of Padd Trek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/paddtrek4.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/paddtrek4.png&#34; alt=&#34;Screenshot of Padd Trek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;style&gt;
.flexgallery &gt; div {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
}
.flexgallery img {
  width: 100%;
  height: auto;
  object-fit: contain;
}
&lt;/style&gt;

&lt;p&gt;Ok. So now we need to spin forward to 2011(ish). In between MouseTrek and 2011 I&amp;rsquo;d created, but not finished, multiple versions of the game as I&amp;rsquo;d come to use it as a tool for learning and experimenting with various programming languages. I think the closest one I came to completing was a version for Windows 3.1 that was written directly against the API (ahh the message loop, WM_* messages, you probably had to be there&amp;hellip;) using &lt;a href=&#34;https://winworldpc.com/product/quick-c/10-for-windows&#34;&gt;Microsoft QuickC for Windows&lt;/a&gt; which was (I think) the first affordable compiler for Windows that actually ran in Windows. I could be misremembering but before then I seem to remember having to use Microsoft C in DOS.&lt;/p&gt;
&lt;p&gt;Other versions I recall working on are versions in &lt;a href=&#34;https://en.wikipedia.org/wiki/Visual_Basic_(classic)&#34;&gt;Visual Basic&lt;/a&gt;, maybe one in &lt;a href=&#34;https://en.wikipedia.org/wiki/Delphi_(software)&#34;&gt;Delphi&lt;/a&gt; and one in &lt;a href=&#34;https://en.wikipedia.org/wiki/Ruby_(programming_language)&#34;&gt;Ruby&lt;/a&gt;. I&amp;rsquo;m also pretty sure I got a fair way through one in C++ aimed at KDE on Linux - but the details of that are vague to say the least. In any case none of these got completed and didn&amp;rsquo;t and don&amp;rsquo;t feel slightly bad about - completing was never the point.&lt;/p&gt;
&lt;p&gt;In any case in 2011 I&amp;rsquo;d got myself an iPad. I&amp;rsquo;d already written a few apps for the iPhone and wanted to have a go at something for the iPad. The name, if you&amp;rsquo;re a Star Trek fan, is a bit of a pun: the game is for the iPad and on Star Trek they would carry arounmd PADDs. Hence: Padd Trek. Unfortunately the source code situation for this is as bad as for MouseTrek. I&amp;rsquo;ve lost it. At the time I was quite short on cash and I seem to remember having to close my first paid GitHub account as a result and it wasn&amp;rsquo;t open source. Details are vague. Wasn&amp;rsquo;t a great period of my life to be honest. I recall popping it onto a hard drive but I don&amp;rsquo;t seem able to locate it any longer.&lt;/p&gt;
&lt;p&gt;And its also no longer playable - I pulled it from the App Store as it just became a pain updating it to support the ever expanding range of devices that Apple was releasing. Screen sizes. Retina displays. It just got all a bit painful. It was written in Objective-C using AppKit and I think I started it before they began to introduce more usable layout systems so it was all a bit awkward.&lt;/p&gt;
&lt;p&gt;I do remember it didn&amp;rsquo;t take that long to create and it saw quite a lot of downloads - I &lt;em&gt;think&lt;/em&gt; it was a free download. I added iPhone support to it too, pictured below - though I think in retrospect this is the straw that broke the camels back in terms of my ability to maintain it.&lt;/p&gt;
&lt;div class=&#34;flexgallery&#34; style=&#34;display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;&#34;&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/paddtrekphone1.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/paddtrekphone1.png&#34; alt=&#34;Screenshot of Padd Trek on iPhone&#34; style=&#34;max-width: 50vw&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/paddtrekphone2.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/paddtrekphone2.png&#34; alt=&#34;Screenshot of Padd Trek on iPhone&#34; style=&#34;max-width: 50vw&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/paddtrekphone3.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/paddtrekphone3.png&#34; alt=&#34;Screenshot of Padd Trek on iPhone&#34; style=&#34;max-width: 50vw&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/paddtrekphone4.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/paddtrekphone4.png&#34; alt=&#34;Screenshot of Padd Trek on iPhone&#34; style=&#34;max-width: 50vw&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/paddtrekphone5.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/paddtrekphone5.png&#34; alt=&#34;Screenshot of Padd Trek on iPhone&#34; style=&#34;max-width: 50vw&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;style&gt;
.flexgallery &gt; div {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
}
.flexgallery img {
  width: 100%;
  height: auto;
  object-fit: contain;
}
&lt;/style&gt;

&lt;h2 id=&#34;ts-trek&#34;&gt;TS-Trek&lt;/h2&gt;
&lt;div class=&#34;flexgallery&#34; style=&#34;display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;&#34;&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/0.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/0.png&#34; alt=&#34;Screenshot of TS-Trek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/1.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/1.png&#34; alt=&#34;Screenshot of TS-Trek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/2.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/2.png&#34; alt=&#34;Screenshot of TS-Trek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/3.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/3.png&#34; alt=&#34;Screenshot of TS-Trek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;style&gt;
.flexgallery &gt; div {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
}
.flexgallery img {
  width: 100%;
  height: auto;
  object-fit: contain;
}
&lt;/style&gt;

&lt;p&gt;For whatever reason I recently (at the time of writing) got the urge to build another version of this game: using TypeScript, React and Zustand. I had in mind a quite stylised black and white version of things with strong splashes of colour in key places like weapons and to indicate danger.&lt;/p&gt;
&lt;p&gt;So yeah. What can you say. I wrote it again! It was fairly straightforward to write. I hadn&amp;rsquo;t used Zustand before, and I&amp;rsquo;d structure things a little differently next time I used it, but it worked great. Really nice state management solution. I&amp;rsquo;m quite pleased with some of the special effects - the starship and fullscreen explosions in particular. And I like the warping animation too.&lt;/p&gt;
&lt;div class=&#34;flexgallery&#34; style=&#34;display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;&#34;&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/4.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/4.png&#34; alt=&#34;Screenshot of TS-Trek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/6.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/6.png&#34; alt=&#34;Screenshot of TS-Trek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/9.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/9.png&#34; alt=&#34;Screenshot of TS-Trek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;div style=&#34;display: flex; justify-content: center;&#34;&gt;
  &lt;a href=&#34;images/10.png&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;
    &lt;img src=&#34;images/10.png&#34; alt=&#34;Screenshot of TS-Trek&#34; style=&#34;max-width: 100%&#34;&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;style&gt;
.flexgallery &gt; div {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
}
.flexgallery img {
  width: 100%;
  height: auto;
  object-fit: contain;
}
&lt;/style&gt;

&lt;h2 id=&#34;epilogue&#34;&gt;Epilogue&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve continued to dabble with versions of the game since Padd Trek. I got someway through one in C#, I had a go at a version using F# and Fable and eventually ended up writing this version here, imaginatively called TS Trek because its written in TypeScript.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>8-bit Golf</title>
      <link>https://www.jamesdrandall.com/projects/8bitgolf/</link>
      <pubDate>Thu, 18 Sep 2025 06:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/projects/8bitgolf/</guid>
      <description>&lt;p&gt;&lt;em&gt;You can download the app &lt;a href=&#34;https://apps.apple.com/us/app/8-bit-golf/id6664070211&#34;&gt;from the App Store&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I have super fond memories of playing early golf games on the BBC and PC. My very favourite is a game called &lt;a href=&#34;https://www.bbcmicro.co.uk/game.php?id=1249&#34;&gt;Holed Out!!&lt;/a&gt; on the BBC and on which I spent many hours playing with my mum. But I also enjoyed the PGA games on the PC and played C64 and Spectrum games with friends. Though they were more into football. Recent golf games leave me a bit cold - its the simplicty of the early games I like.&lt;/p&gt;
&lt;p&gt;Driven by nostalgia for that I created 8-bit Golf for the Mac and iOS. Which is slightly misnamed as it also includes a 32-bit graphic style - Holed Out was also available on the &lt;a href=&#34;https://en.wikipedia.org/wiki/Acorn_Archimedes&#34;&gt;Archimedes&lt;/a&gt; and had this beautifully crisp graphical style which I couldn&amp;rsquo;t help but try and emulate here. Was so envious of Archimedes owners back in the day. Just wonderful machines.&lt;/p&gt;
&lt;p&gt;The game itself uses 2D rendering - its just Core Graphics and SwiftUI, though I kind of regret the Swift UI usage on Mac as its proven to be a bit of a pain. But anyway&amp;hellip; it works. I saw this morning that a user has reported an issue with lag - I suspect its Swift UI. It shouldn&amp;rsquo;t be too hard to shift the game parts away from that - its the designer thats really wedded to Swift UI - and I might have a crack at that.&lt;/p&gt;
&lt;p&gt;There are some basic 3d calculations in their to position things. The landscape is drawn as polygons and then each &amp;ldquo;game object&amp;rdquo; (tree, player, flag, ball) are rendered individually into a Core Graphics layer and then it basically depth sorts those layers to present the display. Then as the ball moves into the distance it just moves it back down the array as it passes each object. Which I thought was a fun little hack.&lt;/p&gt;
&lt;p&gt;According to its GitHub it took me about 5 months to create with a couple of breaks in that process. I&amp;rsquo;ve got a handful of updates to make and a few new courses to add but as ever time is my enemy!&lt;/p&gt;
&lt;p&gt;Its free but not open source. I am planning on releasing the source I just want to do some cleanup on the repo first.&lt;/p&gt;
&lt;div role=&#34;list&#34; class=&#34;grid sm:grid-cols-3 lg:grid-cols-3 gap-8 mt-8&#34;&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/8bitgolf/images/0.png&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/8bitgolf/images/1.png&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/8bitgolf/images/2.png&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/8bitgolf/images/3.png&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/8bitgolf/images/4.png&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/8bitgolf/images/10.png&#34; /&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Graph Paper</title>
      <link>https://www.jamesdrandall.com/projects/graph-paper/</link>
      <pubDate>Mon, 07 Oct 2024 06:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/projects/graph-paper/</guid>
      <description>&lt;p&gt;&lt;em&gt;You can download the app &lt;a href=&#34;https://apps.apple.com/us/app/graph-paper/id6727015373&#34;&gt;from the App Store&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While I was creating 8-bit Golf I realised that the editor, which was quite satisfying to use, could quite easily be repurposed to allow a user to draw on graph paper. It really didn&amp;rsquo;t take long to rework into a standalone app and I published it without really marking the moment and moved on.&lt;/p&gt;
&lt;p&gt;I confess in the interim other than occasionally using it myself I&amp;rsquo;d kind of forgot about it. However I came across an App Store review reporting a bug and figured I&amp;rsquo;d fix it. Then found a couple more reported in other reviews. While in App Store Connect I noticed it was getting about 1400 downloads per month. An accidental success story - apart from the bugs!&lt;/p&gt;
&lt;p&gt;In any case, having fixed the bugs I figured it might be fun to add a few features - not things that would make it more complicated use, but fun and in the simple spirit.&lt;/p&gt;
&lt;p&gt;One obvious addition was a blueprint theme. And the other thing I decided to add was a vector editing mode. The original version looks like a raster / pixel editor but actually the edits are building up a vector model internally - like the golf game. I figured it wouldn&amp;rsquo;t be much work to allow the app to swap into a vector mode with grab handles and stuff and let people edit.&lt;/p&gt;
&lt;p&gt;It felt like you might get the best of both worlds. Simple and quick freehand drawing, then the ability to edit it easily. And, yay!, it works.&lt;/p&gt;
&lt;p&gt;Its free but not open source. I might release the source at some point but I&amp;rsquo;d probably have to unpick some of the commercial Font Awesome icons I used.&lt;/p&gt;
&lt;div role=&#34;list&#34; class=&#34;grid sm:grid-cols-3 lg:grid-cols-3 gap-8 mt-8&#34;&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/graph-paper/images/0.png&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/graph-paper/images/2.png&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/graph-paper/images/1.png&#34; /&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>VoxEd</title>
      <link>https://www.jamesdrandall.com/projects/voxed/</link>
      <pubDate>Sat, 29 Jun 2024 10:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/projects/voxed/</guid>
      <description>&lt;p&gt;&lt;em&gt;You can download the app &lt;a href=&#34;https://apps.apple.com/us/app/voxed/id6501978867&#34;&gt;from the App Store&lt;/a&gt; or view the &amp;ldquo;official&amp;rdquo; VoxEd store linked page &lt;a href=&#34;https://www.jamesdrandall.com/voxed&#34;&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I confess I&amp;rsquo;m rather proud of this one. I&amp;rsquo;ve always enjoyed pixel and voxel art and I have several ideas for projects that would make use of voxels. I&amp;rsquo;d been dabbling in creating some models using some of the tools that are out there but all had oddities I found annoying. And while I&amp;rsquo;m sure you find my take on a voxel editor to have its own oddities they are, at least, oddities that work for me!&lt;/p&gt;
&lt;p&gt;In any case I created VoxEd as a free app to scratch my own itch and serve my own needs. I wrote it ground up in SwiftUI and Metal and despite the increased complexity over SprEd found it easier to build largely because I&amp;rsquo;d learned with SprEd a good amount of what does and doesn&amp;rsquo;t work with SwiftUI and was able to easily transfer all my WebGL and OpenGL work over to Metal fairly easily.&lt;/p&gt;
&lt;p&gt;According to its GitHub history it took me pretty much exactly 3 months of part time work from writing the first line of code to getting it into the App Store.&lt;/p&gt;
&lt;p&gt;Its free but not open source. Currently no plans to release the source but who knows.&lt;/p&gt;
&lt;div role=&#34;list&#34; class=&#34;grid sm:grid-cols-3 lg:grid-cols-3 gap-8 mt-8&#34;&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/voxed/images/0.jpeg&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/voxed/images/1.jpeg&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/voxed/images/2.jpeg&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/voxed/images/3.jpeg&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/voxed/images/4.jpeg&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/voxed/images/5.jpeg&#34; /&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>SprEd: New selection tools</title>
      <link>https://www.jamesdrandall.com/devdiary/spred_new_selection_tools/</link>
      <pubDate>Sat, 30 Mar 2024 09:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/spred_new_selection_tools/</guid>
      <description>&lt;p&gt;In what is either a massive display of ego and hubris or something to be modestly proud about (you decide) I&amp;rsquo;m using my own Mac and iPad sprite editor &lt;a href=&#34;https://www.jamesdrandall.com/projects/spred/&#34;&gt;SprEd&lt;/a&gt; to work on the textures and icons for my city builder.&lt;/p&gt;
&lt;p&gt;However I realised I&amp;rsquo;d not included any tools to rotate things and so this morning have quickly reloaded Swift and the source code back into my head and added those features.&lt;/p&gt;
&lt;p&gt;Will hopefully land in the App Store soon but in the meantime I&amp;rsquo;m using my own build to work on the road graphics.&lt;/p&gt;
&lt;p&gt;SprEd isn&amp;rsquo;t open source, nor is it free, but it is the cost of a couple of cups of coffee for a perpetual license so if you&amp;rsquo;d like to keep a fellow developer fuelled up with caffeine then check it out.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;spred.png&#34; alt=&#34;Editing roads&#34;&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Empire of Asphalt: Selecting ranges and creating zones</title>
      <link>https://www.jamesdrandall.com/devdiary/empire_of_ashphalt_selecting_ranges_and_creating_zones/</link>
      <pubDate>Wed, 27 Mar 2024 06:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/empire_of_ashphalt_selecting_ranges_and_creating_zones/</guid>
      <description>&lt;p&gt;Things have been going quite smoothly so far and over the last couple of evenings I&amp;rsquo;ve added the ability to drag select ranges and create zones (though haven&amp;rsquo;t accounted for flats yet).&lt;/p&gt;
&lt;p&gt;This all happens in the landscape shader which is passed the corners of the range and information about each tile encoded inside a vector and passed into the shader as an attribute buffer. When the zone is added I update the buffer in the appropriate landscape chunks.&lt;/p&gt;
&lt;p&gt;In any case it all works rather nicely.&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/u7MWEFV5Twg?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;p&gt;Next up are roads and then buildings. I&amp;rsquo;ve got some time off coming up and I&amp;rsquo;m hoping to spend some of that working on that and getting a basic feedback loop running across the three zone types. Might be a bit ambitious for the time off I have. We&amp;rsquo;ll see.&lt;/p&gt;
&lt;p&gt;All the code is in the usual place.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/JamesRandall/empireOfAsphalt&#34;&gt;https://github.com/JamesRandall/empireOfAsphalt&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Empire of Asphalt: WebGL GUI with JSX</title>
      <link>https://www.jamesdrandall.com/devdiary/empire_of_ashphalt_webgl_gui_with_jsx/</link>
      <pubDate>Sun, 24 Mar 2024 18:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/empire_of_ashphalt_webgl_gui_with_jsx/</guid>
      <description>&lt;p&gt;As I mentioned in my last entry, and as anyone who has played a city builder will know, these kinds of games have a lot of UI in them. That being the case I need a way of easily putting all this together. A primitive GUI library basically.&lt;/p&gt;
&lt;p&gt;I do most of my web work in React or SolidJS and I quite like the JSX approach and so I struck upon the idea of building my UI toolkit based around this.&lt;/p&gt;
&lt;p&gt;Happily the TypeScript compiler makes this pretty easy. If you ever look at the code underneath something like React you will find its been transpiled to lots of nested createElement calls - an element factory basically. The TypeScript compiler allows you to use your own factory instead by setting the jsxFactory property in tsconfig.json.&lt;/p&gt;
&lt;p&gt;And so I began by updating my config file to look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;compilerOptions&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;target&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;es2020&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;module&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;commonjs&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;esModuleInterop&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;forceConsistentCasingInFileNames&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;strict&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;skipLibCheck&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;sourceMap&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;outDir&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;build&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;jsxFactory&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;createGuiElement&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;jsx&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;react&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;experimentalDecorators&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;exclude&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;node_modules&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;**/*.test.ts&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With that done you can implement the factory function using a compatible signature:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createGuiElement&lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;P&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;=&lt;/span&gt; {}&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;GuiElementType&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;props&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;?:&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;Attributes&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;P&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ...&lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;GuiElement&lt;/span&gt;[]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;GuiElement&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;button&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Button&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;props&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;??&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;undefined&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hlayout&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;HLayout&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;props&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;??&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;undefined&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rect&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Rect&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;props&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;??&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;undefined&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;image&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Image&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;props&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;??&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;undefined&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;container&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Container&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;props&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;??&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;undefined&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;window&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Window&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;props&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;??&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;undefined&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;raisedbevel&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RaisedBevel&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;props&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;??&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;undefined&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bevel&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RaisedBevel&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;props&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;??&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;undefined&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Fragment&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;props&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;??&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;undefined&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;children&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I&amp;rsquo;ll go into the component implementation at some point in the future but their is one more thing you need to do to get this work without errors. And that&amp;rsquo;s supply type definitions for your JSX. You do this by popping a TypeScript type file called JSX.d.ts somewhere in your source and declaring an interface called IntrinsicElements:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;IntrinsicElements&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;hlayout&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;HLayoutProps&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;button&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;ButtonProps&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;ImageProps&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;rect&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;RectProps&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;container&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;ContainerProps&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    window&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WindowProps&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;raisedbevel&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;ChromeProps&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;bevel&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;ChromeProps&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The props are simply further interface declarations:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ImageProps&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;extends&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;GuiElementProps&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;name?&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;One thing to take note of - if you want to use a type directly in your code and in your JSX like an enum for horizontal alignment then you can do this but your editor will likely try and add an import to the top of the definition file. Do not let it!!! That will turn the module from an ambient module to a local module. Things will work as is without any import required but to make imports work in an ambient module you use a slightly different syntax:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;declare&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;JSX&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;MouseButton&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;./components/InteractiveElement&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;MouseButton&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With all that done here&amp;rsquo;s a sample of my UI code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-tsx&#34; data-lang=&#34;tsx&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;testGui&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Game&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;GuiElement&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;48&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;container&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;hlayout&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;horizontalAlignment&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;middle&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sizeToFitParent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;SizeToFit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Width&lt;/span&gt;}&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;padding&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;onClick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{(&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)} &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;pause&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sizeToFitParent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;SizeToFit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WidthAndHeight&lt;/span&gt;} /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;padding&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;onClick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{(&lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)} &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;singlespeed&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sizeToFitParent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;SizeToFit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WidthAndHeight&lt;/span&gt;} /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;padding&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;onClick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;doublespeed&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sizeToFitParent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;SizeToFit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WidthAndHeight&lt;/span&gt;} /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;rect&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;fill&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#ae81ff&#34;&gt;0xaa000033&lt;/span&gt;} /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;padding&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;onClick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bulldozer&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sizeToFitParent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;SizeToFit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WidthAndHeight&lt;/span&gt;} /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#a6e22e&#34;&gt;padding&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#a6e22e&#34;&gt;onClick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;windows&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;zoning&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isVisible&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;windows&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;zoning&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isVisible&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;)}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;zones&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sizeToFitParent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;SizeToFit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WidthAndHeight&lt;/span&gt;} /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;padding&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;onClick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;road&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sizeToFitParent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;SizeToFit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WidthAndHeight&lt;/span&gt;} /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;hlayout&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;window&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Zoning&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;isVisible&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;windows&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;zoning&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isVisible&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;left&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;windows&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;zoning&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;left&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;top&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;windows&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;zoning&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;top&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.window.&lt;span style=&#34;color:#a6e22e&#34;&gt;titleBarHeight&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;padding&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;lightChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lightGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;midChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;midGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;darkChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;darkGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;hlayout&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;horizontalAlignment&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;HorizontalAlignment&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Left&lt;/span&gt;} &lt;span style=&#34;color:#a6e22e&#34;&gt;sizeToFitParent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;SizeToFit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Width&lt;/span&gt;}&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;padding&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;lightChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lightGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;midChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;midGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;darkChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;darkGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;onClick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTool&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Tool&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;LightResidential&lt;/span&gt;)}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;isSelected&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTool&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Tool&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;LightResidential&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lightResidentialZone&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sizeToFitParent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;SizeToFit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WidthAndHeight&lt;/span&gt;} /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;padding&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;lightChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lightGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;midChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;midGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;darkChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;darkGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;onClick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTool&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Tool&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DenseResidential&lt;/span&gt;)}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;isSelected&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTool&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Tool&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DenseResidential&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;denseResidentialZone&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sizeToFitParent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;SizeToFit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WidthAndHeight&lt;/span&gt;} /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;padding&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;lightChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lightGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;midChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;midGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;darkChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;darkGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;onClick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTool&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Tool&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;LightCommercial&lt;/span&gt;)}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;isSelected&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTool&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Tool&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;LightCommercial&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lightCommercialZone&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sizeToFitParent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;SizeToFit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WidthAndHeight&lt;/span&gt;} /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;padding&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;lightChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lightGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;midChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;midGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;darkChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;darkGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;onClick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTool&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Tool&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DenseCommercial&lt;/span&gt;)}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;isSelected&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTool&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Tool&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DenseCommercial&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;denseCommercialZone&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sizeToFitParent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;SizeToFit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WidthAndHeight&lt;/span&gt;} /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;padding&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;lightChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lightGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;midChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;midGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;darkChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;darkGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;onClick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTool&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Tool&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;LightIndustrial&lt;/span&gt;)}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;isSelected&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTool&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Tool&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;LightIndustrial&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lightIndustrialZone&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sizeToFitParent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;SizeToFit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WidthAndHeight&lt;/span&gt;} /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;padding&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;bs&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;lightChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lightGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;midChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;midGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;darkChrome&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;constants&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;darkGreen&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;onClick&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTool&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Tool&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DenseIndustrial&lt;/span&gt;)}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;isSelected&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gui&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentTool&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Tool&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DenseIndustrial&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;denseIndustrialZone&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sizeToFitParent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;SizeToFit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WidthAndHeight&lt;/span&gt;} /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;hlayout&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;window&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;container&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In any case you can see my work in progress UI below. Its heavily Transport Tycoon inspired!&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/oFJRY1_D6ZQ?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;p&gt;There&amp;rsquo;s a bunch of stuff my toolkit doesn&amp;rsquo;t do - particularly in regards to optimising change which I might revisit, but only if I need to. We&amp;rsquo;ll see.&lt;/p&gt;
&lt;p&gt;All the code is in the usual place.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/JamesRandall/empireOfAsphalt&#34;&gt;https://github.com/JamesRandall/empireOfAsphalt&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Empire of Asphalt: Cameras and Picking</title>
      <link>https://www.jamesdrandall.com/devdiary/empire_of_ashphalt_camera_and_tile_selection/</link>
      <pubDate>Fri, 22 Mar 2024 07:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/empire_of_ashphalt_camera_and_tile_selection/</guid>
      <description>&lt;p&gt;Actually maanged to get a decent amount of time in on personal projects this week. Mostly in the morning before work as my best buddy is injured and on 7 days of vet prescribed rest - so no walks.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m missing the walks. Its tough on us both. But we&amp;rsquo;e done lots of brain training and I&amp;rsquo;ve got some coding in.&lt;/p&gt;
&lt;p&gt;First task for the week was to get the camera working properly on the isometric view. This isn&amp;rsquo;t particularly difficult - basically you are rotating the direction of the &amp;ldquo;eye&amp;rdquo; around the point the camera is looking at. However&amp;hellip; with the orthographic projection it is quite easy to make a non-obvious mistake because the camera isn&amp;rsquo;t necessarily where you think it is. I spent ages eyeballing my rotation code, asking an AI about it, looking at other approaches as it just would not rotate around the looked at position.&lt;/p&gt;
&lt;p&gt;Eventually it dawned on me something else might be going on&amp;hellip;. what if the issue isn&amp;rsquo;t my rotation but my translation as I move the camera. With the orthographic projection their is no sense of depth so my camera could be in a different position from where I expect and I wouldn&amp;rsquo;t know. Yup. I&amp;rsquo;d been translating it on the Z axis rather than the X and Y for up and down movement.&lt;/p&gt;
&lt;p&gt;After fixing this it all worked.&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/iS79RApeV3Q?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;p&gt;Next job was to let me &amp;ldquo;pick&amp;rdquo; a tile from the landscape. The final game will have all sorts of objects on the map and some will be behind others and so I needed a way of being able to really know what the user was clicking on accounting for depth and any other rendering factors.&lt;/p&gt;
&lt;p&gt;My solution to this was to render the scene again. Yes - again! However this time rather than using the colours of things and rendering to the screen instead I render to an off screen frame buffer and encode an object ID to a colour - a colour, ultimately, being a vector of 4 components that I can treat as a UInt8Array and pack and unpack the object ID into.&lt;/p&gt;
&lt;p&gt;After rendering the scene in this form I have a texture that I can read the colour under the mouse position from and decode it back to an objcet ID.&lt;/p&gt;
&lt;p&gt;For the landscape I&amp;rsquo;m using an object ID constructed from the X and Y co-ordinates of the tile and that works a charm.&lt;/p&gt;
&lt;p&gt;To do the actual highlight on screen I simply pass the object ID back in, along with the colour encoded IDs, and I can just turn a tile red when a match is found.&lt;/p&gt;
&lt;p&gt;This was really pretty simple to implement - though I lost an hour because my object IDs were coming out with weird values when I read the pixel. Eventually I realised that my main renderer was using colour blending for opacity and that that&amp;rsquo;s a global setting. I turned that off and it all worked like a charm.&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/JzAeZqyOog4?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;p&gt;Next up is some basic GUI stuff. The city builder has a lot of UI elements to it and I need a decent way of putting that together. The solution I&amp;rsquo;m pursuing is to use JSX with a custom engine that renders to WebGL. You can point the TypeScript compiler at a custom factory function. So far so good and hopefully in my next update I can share a good demo.&lt;/p&gt;
&lt;p&gt;All the code is in the usual place.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/JamesRandall/empireOfAsphalt&#34;&gt;https://github.com/JamesRandall/empireOfAsphalt&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elite: ECM and Energy Bomb</title>
      <link>https://www.jamesdrandall.com/devdiary/elite_ecm_and_energy_bomb/</link>
      <pubDate>Sun, 17 Mar 2024 19:30:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/elite_ecm_and_energy_bomb/</guid>
      <description>&lt;p&gt;More progress on the bite sized remainders of Elite - ECM and Energy Bomb now implemented. Got quite a cool effect for the energy bomb though I can&amp;rsquo;t take the credit for it as I found it on &lt;a href=&#34;https://www.shadertoy.com/view/st3fRf&#34;&gt;ShaderToy&lt;/a&gt;. Thanks to the original author.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.webglite.com&#34;&gt;The latest build of the game is available online&lt;/a&gt;.&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/DF_C_wBR0s0?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Elite: Thargoids</title>
      <link>https://www.jamesdrandall.com/devdiary/elite_thargoids/</link>
      <pubDate>Sun, 17 Mar 2024 11:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/elite_thargoids/</guid>
      <description>&lt;p&gt;Another bitty week as I&amp;rsquo;ve been coding for work and when I do so I find, inevitably, the amount of time I spend coding out of hours decreases.&lt;/p&gt;
&lt;p&gt;However got quite a few things done:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Witchspace&lt;/li&gt;
&lt;li&gt;Thargoids&lt;/li&gt;
&lt;li&gt;Thargons&lt;/li&gt;
&lt;li&gt;Altitude and planet crashing&lt;/li&gt;
&lt;li&gt;Space stations turn hostile when attacked and launch Vipers&lt;/li&gt;
&lt;li&gt;Ships have different rates of pitch and roll&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://www.webglite.com&#34;&gt;The latest build of the game is available online&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elite: Missiles</title>
      <link>https://www.jamesdrandall.com/devdiary/elite_missiles/</link>
      <pubDate>Sun, 10 Mar 2024 13:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/elite_missiles/</guid>
      <description>&lt;p&gt;It was a bit of a stop / start week as I have a lot of actual work on at the moment. However I did find time to get missiles into the game.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m still not happy with how ships (including missiles) move in on other ships and the player. I&amp;rsquo;ve not quite got the roll and pitch stuff down yet but will come back to it. ECM next.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elite: Lasers and loadouts</title>
      <link>https://www.jamesdrandall.com/devdiary/elite_lasers_and_loadouts/</link>
      <pubDate>Tue, 05 Mar 2024 20:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/elite_lasers_and_loadouts/</guid>
      <description>&lt;p&gt;Continuing to plug through my list of tasks to complete the game. Over the last couple of evenings I&amp;rsquo;ve implemented the different laser types (pulse, beam, military and laser) and also added an &amp;ldquo;Initial Loadout&amp;rdquo; screen (shown below) so that no one need ever be without a Docking Computer ever again! And as most of the remaining tasks related to different bits of equipment it makes it a lot easier for me to test.&lt;/p&gt;
&lt;p&gt;Currently about half way through implementing player missile firing so that should complete in the next day or two.&lt;/p&gt;
&lt;p&gt;Closing in on a finished game at pace now!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;initialLoadout.png&#34; alt=&#34;Initial loadout screen&#34;&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elite: Asset loading optimisation</title>
      <link>https://www.jamesdrandall.com/devdiary/asset_loading_optimisation/</link>
      <pubDate>Sun, 03 Mar 2024 09:30:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/asset_loading_optimisation/</guid>
      <description>&lt;p&gt;Bit of work this mornign to optimise the asset loading. Some assets were loading in parallel and others in sequence which led to things not loading as fast as they could. Just reworked the loader to batch all the promises up together and then unpick the multi-type result.&lt;/p&gt;
&lt;p&gt;On my rural broadband (allegedly its fibre&amp;hellip;) that got loading down from 6.7 seconds to 4 seconds when nothing is cached. So a bit better.&lt;/p&gt;
&lt;p&gt;I could likely optimise things further by packing the assets into a single file&amp;hellip; maybe I&amp;rsquo;ll write an asset packer as part of Empires of Asphalt.&lt;/p&gt;
&lt;p&gt;Otherwise I also did some work to set up some dedicated hosting for the game. Previously I&amp;rsquo;d been rather stupidly and laboriously pasting the build into an area of this blog. Last night I set up Netlify to host this on its own domain. You can find the &amp;ldquo;production&amp;rdquo; versions and latest dev version at the links below.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.webglite.com&#34;&gt;Production&lt;/a&gt;
&lt;a href=&#34;https://dev.webglite.com&#34;&gt;Development&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Wasn&amp;rsquo;t exactly a lot of work. Netlify is pretty cool.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elite: Loading screen and game saves</title>
      <link>https://www.jamesdrandall.com/devdiary/elite_loading_screen/</link>
      <pubDate>Sat, 02 Mar 2024 18:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/elite_loading_screen/</guid>
      <description>&lt;p&gt;Been a bit of a spotty work for progress on fun projects due to being crazy busy at work. I crawled into this weekend.&lt;/p&gt;
&lt;p&gt;However some progress has been made. First up I worked on getting saving and loading games in place - pretty simple, just serialize out the relavant bits of game state to JSON and pop them in local storage. Loading a game, reverse that.&lt;/p&gt;
&lt;p&gt;The other thing I wanted to get in place was a loading screen somewhat similar to that in the original game. There were three reasons I wanted to do this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Its just kind of iconic and cool.&lt;/li&gt;
&lt;li&gt;There aren&amp;rsquo;t loads of hefty assets in the game but it still takes a few seconds to load on my rubbish rural broadband connection and I wanted to hide that (I could do some work to make the resource loading more efficient&amp;hellip; maybe one day).&lt;/li&gt;
&lt;li&gt;You can&amp;rsquo;t play audio until the user has interacted with the web page and I really wanted to get the BBC boot up sound in as the game starts&amp;hellip; so I needed something to catch a click.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Fortunately the maths behind the original loading screen have been &lt;a href=&#34;https://www.bbcelite.com/deep_dives/drawing_saturn_on_the_loading_screen.html&#34;&gt;documented by Mark Moxon&lt;/a&gt; so it was pretty trivial to implement. He&amp;rsquo;s done all the hard work - thank you!&lt;/p&gt;
&lt;p&gt;You can see all this in the video below.&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/ERt4Sf-ha6E?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Empire of Asphalt: Landscape Part 1</title>
      <link>https://www.jamesdrandall.com/devdiary/asphalt_landscape_part1/</link>
      <pubDate>Tue, 27 Feb 2024 07:10:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/asphalt_landscape_part1/</guid>
      <description>&lt;p&gt;Cracked on with the landscape system over the weekend and got a nice SimCity 2000 / Transport Tycoon style isometric system up and running.&lt;/p&gt;
&lt;p&gt;It uses the &lt;a href=&#34;https://en.wikipedia.org/wiki/Diamond-square_algorithm&#34;&gt;diamond square algorithm&lt;/a&gt; with a few constraints added. It was trivial to get a beautiful rolling landscape in place&amp;hellip; fiddly to get it looking all chunky and isometric!&lt;/p&gt;
&lt;p&gt;I also spent a while wandering why large maps seemed to stop rendering at an arbitary point. Turned out that I&amp;rsquo;d exceed 64k of indexes for verticies in a index buffer and the type of the values in such buffers is 16-bits.&lt;/p&gt;
&lt;p&gt;The solution was to break them into chunks which I wanted to do anyway as when the player is raising, lowering and levelling the terrain I didn&amp;rsquo;t really want to be regenerating a single very large mesh. With the chunk approach I can just update most of the time a single chunk and at the worst case 4 chunks (when the player changes something on a corner). I guess I could optimise that further to a maxium of 3 chunks by &amp;ldquo;mis-aligning&amp;rdquo; the chunks but I suspect the increased complexity isn&amp;rsquo;t worth the effort.&lt;/p&gt;
&lt;p&gt;Next steps are to make sure there are some level parts of the map to start out with and add some water but the hard bit is pretty much done.&lt;/p&gt;
&lt;p&gt;You can see the current iteration in the video below and the &lt;a href=&#34;https://github.com/JamesRandall/empireOfAsphalt/tree/d0d5afedad87c0c6c033fc7230f65bb5ed98c66d&#34;&gt;source is in this commit in GitHub&lt;/a&gt;.&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/LfOmVbG_Gbk?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Empire of Asphalt: Starting simple</title>
      <link>https://www.jamesdrandall.com/devdiary/asphalt_startingsimple/</link>
      <pubDate>Sat, 24 Feb 2024 15:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/asphalt_startingsimple/</guid>
      <description>&lt;p&gt;As a brief introduction by next retro remake is based around SimCity which I&amp;rsquo;m calling Empire of Asphalt. If you want to follow along you can already find the code on GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/JamesRandall/empireOfAsphalt&#34;&gt;https://github.com/JamesRandall/empireOfAsphalt&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to keep things as simple as I can so it will be somewhat akin to the original game in terms of systems though I&amp;rsquo;ve got some fun ideas for rendering involving an isometric landscape and voxels and might do some fun stuff with agents. Again written using TypeScript and WebGL and not much else.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m enjoying working with TypeScript and WebGL - there&amp;rsquo;s no real friction and its easy to distribute things.&lt;/p&gt;
&lt;p&gt;In any case step one is to draw a single tile. Mission accomplished!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;singletile.png&#34; alt=&#34;Single tile&#34;&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elite: To do</title>
      <link>https://www.jamesdrandall.com/devdiary/elite_todo/</link>
      <pubDate>Sat, 24 Feb 2024 08:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/elite_todo/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m at that stage of the project where there are lots of little things left and so I&amp;rsquo;ve done what I usually do at this point which is to pop it on a Trello board. Most of these things are evening sized so I&amp;rsquo;m going to do them in the&amp;hellip; evenings. You can see the list below.&lt;/p&gt;
&lt;p&gt;I need bigger spans of time to get the foundations in on the next fun project&amp;hellip;. so weekends will be more about that. I&amp;rsquo;ve been doing research for the last two or three weeks.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;todo.png&#34; alt=&#34;Todo board&#34;&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elite: combat evolved</title>
      <link>https://www.jamesdrandall.com/devdiary/elite_combat_evolved/</link>
      <pubDate>Tue, 20 Feb 2024 20:10:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/elite_combat_evolved/</guid>
      <description>&lt;p&gt;Got the combat routines all wired up now with damage being handled properly, explosions etc.&lt;/p&gt;
&lt;p&gt;Its my first chance to really try the tactic routine for the enemy ships - I&amp;rsquo;m super pleased with how authentic it feels though the more precise hit boxes are making it a little trickier.&lt;/p&gt;
&lt;p&gt;In any case I will post a video later this week.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elite: fix for laser collisions</title>
      <link>https://www.jamesdrandall.com/devdiary/elite_fix_for_laser_collisions/</link>
      <pubDate>Tue, 20 Feb 2024 06:45:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/elite_fix_for_laser_collisions/</guid>
      <description>&lt;p&gt;Ok. So here&amp;rsquo;s the revised collision detection code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;processLaserHits&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;game&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Game&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;resources&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Resources&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// all we are really interested in for deciding if a player has hit a ship is the intersection of the bounding
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// box of the ship onto a 2d plane. To do this we take each face of the bounding box, discard the z, and look to see
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// if our laser axis (represented by point [0,0]) falls within it by breaking the face into two and using
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// barycentric co-ordinates to see if we are in the triangle
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;game&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;localBubble&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ships&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;reduce&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;ShipInstance&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;translatedBoundingBox&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;boundingBox&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vec3&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;vec3&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(), &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vec2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fromValues&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;v2&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// This depends very much on the order that the bounding box is created in
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;faces&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// front
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// back
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      [&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// left
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// right
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      [&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// top
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// bottom
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      [&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;testPoint&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vec2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fromValues&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fi&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;fi&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;faces&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;fi&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;face&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;faces&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;fi&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;quad&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;translatedBoundingBox&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;face&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;translatedBoundingBox&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;face&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;translatedBoundingBox&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;face&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;translatedBoundingBox&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;face&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;]],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;triangles&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createTrianglesFromQuad&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;quad&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;isPointInTriangle&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;testPoint&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;triangles&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isPointInTriangle&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;testPoint&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;triangles&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }, &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;resources&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;soundEffects&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;playerLaserMiss&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isDestroyed&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;resources&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;soundEffects&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;shipExplosion&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Its similar to the previous version but is based around hit detecting each face of the cube in XY space rather than trying to fit a quad around the bounding box (which actually makes no sense at all&amp;hellip; what was I thinking, or dreaming!).&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s definitely some scope for improvement in the above but it does, at least, work!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elite: well that&#39;s put a crimp in an otherwise damn fine plan</title>
      <link>https://www.jamesdrandall.com/devdiary/elite_crimp_in_an_otherwise_damn_fine_plan/</link>
      <pubDate>Mon, 19 Feb 2024 20:30:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/elite_crimp_in_an_otherwise_damn_fine_plan/</guid>
      <description>&lt;p&gt;The oh so clever 2D solution for laser intersection doesn&amp;rsquo;t work I realised this evening. That&amp;rsquo;s what happens I guess when you literally dream a solution. Problem with it is how I determined the outline quad - its flawed as it can result in the same points being repeated.&lt;/p&gt;
&lt;p&gt;What I&amp;rsquo;ve done instead this evening is to do the same but with each face of the bounding box. If you visualise it this basically results in the box being projected into 2D.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elite: spawning tweaks</title>
      <link>https://www.jamesdrandall.com/devdiary/elite_spawning_tweaks/</link>
      <pubDate>Mon, 19 Feb 2024 07:30:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/elite_spawning_tweaks/</guid>
      <description>&lt;p&gt;I&amp;rsquo;d come across a very funny bug the other day - while testing the lasers and explosions I destroyed a space station and about the same time ships started to spawn at a rapid rate. Several s second. A literal stream of ships.&lt;/p&gt;
&lt;p&gt;I thought the issue was connected to me destroying the space station as I hadn&amp;rsquo;t intended to allow that (I just haven&amp;rsquo;t coded in the block yet as its convenient for testing) but I&amp;rsquo;d forgotten that an Anaconda can spawn a ship and what was actually happening was that a friendly trader was spawning in the area and it was, you guessed it, an Anaconda. The tactics routine for the Anaconda was still using the timings from the original game, which is based on loop executions, and I was running at 60fps - 60 runs throught the loop per second with a probability of spawn calibrated for a fraction of that speed&amp;hellip; yeah lots of ships.&lt;/p&gt;
&lt;p&gt;The fix was simple&amp;hellip; adopt the same approach used in other tactics routines to not evaluate as frequently.&lt;/p&gt;
&lt;p&gt;I did, however, get a fun sense of how much headroom their is in the code to maintain the frame rate. Answer: a lot. Left it running for ages and it continued to maintain its frame rate without dropping a beat. Modern JavaScript engines are really well optimised. There&amp;rsquo;s a video below.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also made the same tweak for ships launching from space stations as that was a bit mad too!&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/e91kqqPMjFU?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Elite: sound effects and explosion refinement</title>
      <link>https://www.jamesdrandall.com/devdiary/elite_sound_effects_and_explosion_refinement/</link>
      <pubDate>Sun, 18 Feb 2024 16:45:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/elite_sound_effects_and_explosion_refinement/</guid>
      <description>&lt;h2 id=&#34;refining-explosions&#34;&gt;Refining Explosions&lt;/h2&gt;
&lt;p&gt;On looking at the explosions this morning I realised that the faces of the ship that I use to form the explosion didn&amp;rsquo;t start off in the correct position. This resulted in a moderate amount of head scratching before I realisesd what was going on.&lt;/p&gt;
&lt;p&gt;Each of the faces has its own normal and I&amp;rsquo;d used that as the nose orientation of the face so that I can roll and pitch it by a random amount as it flies off into space. Trouble is that the renderer, based on the ship renderer, assumes that the faces are aligned based around the ships nose orientation and so when you come to render them they look all jumbled.&lt;/p&gt;
&lt;p&gt;To rotate and pitch the fragments correctly they do need their own nose (and roof) orientations and so the solution was to calculate the (vector) delta between the nose orientation and the face normal when the explosion is initiated and when rendering to use this to translate the nose orientation of the fragment back to &amp;ldquo;ship nose orientation&amp;rdquo;. Problem solved! And the explosions look pretty cool now.&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/2_u445oaKJo?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;h2 id=&#34;audio&#34;&gt;Audio&lt;/h2&gt;
&lt;p&gt;There aren&amp;rsquo;t many sound effects in the original game but they do help bring things to life. When working with old games you can futz around with old, and often proprietary, sound formats. Or, in the case of even older games like Elite, attempt to recreate the effects based on the programming of the sound chip. Neither are much fun (not to me anyway).&lt;/p&gt;
&lt;p&gt;A nice little hack I first used when recreating Wolfenstein 3D was to capture the audio directly from the game while playing it. I use a Mac for development and Rogue Amoeba make a great pair of tools for doing this.&lt;/p&gt;
&lt;p&gt;First they have &lt;a href=&#34;https://rogueamoeba.com/audiohijack/&#34;&gt;Audio Hijack&lt;/a&gt; which will record audio directly from the device and it pairs nicely with &lt;a href=&#34;https://rogueamoeba.com/fission/&#34;&gt;Fission&lt;/a&gt; which is great for quickly trimming and outputting as MP3. They work great together to capture old game audio in emulators.&lt;/p&gt;
&lt;p&gt;And so this afternoon I spent some time playing the original game in &lt;a href=&#34;http://www.mkw.me.uk/beebem/&#34;&gt;BeebEm&lt;/a&gt; &lt;a href=&#34;https://github.com/CommanderCoder/BeebEm&#34;&gt;(there is a Mac version for modern hardware here)&lt;/a&gt; and recording the sound effects.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also taken the same approach as Wolfenstein for playing them back which is to create instances of HTMLAudioElement with the paths to the mp3 files. In order to play overlapping instances of the same sound I create a little wrapper for an array of them for each sound:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createSingleAudioPlayer&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Promise&lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;HTMLAudioElement&lt;/span&gt;&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Promise&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;resolve&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;player&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Audio&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;player&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;addEventListener&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;canplaythrough&amp;#34;&lt;/span&gt;, () &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;resolve&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;player&lt;/span&gt;), &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createAudioPlayer&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;players&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;HTMLAudioElement&lt;/span&gt;[] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;players&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createSingleAudioPlayer&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;currentIndex&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;volume&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;number&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;players&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;currentIndex&lt;/span&gt;].&lt;span style=&#34;color:#a6e22e&#34;&gt;volume&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;volume&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;players&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;currentIndex&lt;/span&gt;].&lt;span style=&#34;color:#a6e22e&#34;&gt;play&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;currentIndex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;currentIndex&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;players&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;currentIndex&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I create these on startup of the game and wait for them to load so as not to have any stuttering when playing back.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elite: laser collision detection, enemy lasers and explosions</title>
      <link>https://www.jamesdrandall.com/devdiary/elite_enemy_lasers_and_explosions/</link>
      <pubDate>Sat, 17 Feb 2024 19:00:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/elite_enemy_lasers_and_explosions/</guid>
      <description>&lt;p&gt;I rattled through a load of stuff today, the brain was working well despite no sleep!&lt;/p&gt;
&lt;h2 id=&#34;player-laser-hit-detection&#34;&gt;Player laser hit detection&lt;/h2&gt;
&lt;p&gt;Bizarrely one morning in the week I woke up with the approach to this already in my head. I guess it had been percolating for a while as I haven&amp;rsquo;t had much time to code this week. My solution is essentially to treat it as a two dimensional problem.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m already tracking rotated bounding boxes of the objects in the game world which I implemented to help with the docking process. That being the case the solution I took was to &amp;ldquo;project&amp;rdquo; them on to a 2D plane (left most point of the box, top most, etc.) to form a quad:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createLaserCollisionQuad&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;ShipInstance&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xy&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vec2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fromValues&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;translatedBoundingBox&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;boundingBox&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vec3&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;vec3&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(), &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;translatedBoundingBox&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;reduce&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ([&lt;span style=&#34;color:#a6e22e&#34;&gt;leftMost&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;rightMost&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;topMost&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;bottomMost&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;leftMost&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xy&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;leftMost&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rightMost&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xy&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rightMost&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;topMost&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xy&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;topMost&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bottomMost&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xy&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bottomMost&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [&lt;span style=&#34;color:#a6e22e&#34;&gt;vec2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fromValues&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;10000&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;), &lt;span style=&#34;color:#a6e22e&#34;&gt;vec2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fromValues&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;10000&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;), &lt;span style=&#34;color:#a6e22e&#34;&gt;vec2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fromValues&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;10000&lt;/span&gt;), &lt;span style=&#34;color:#a6e22e&#34;&gt;vec2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fromValues&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;10000&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The players laser can be treated as a point - it fires down an axis and depth isn&amp;rsquo;t relly relavant (I may add a basic distance check) and so we can simply check to see if the &amp;ldquo;point&amp;rdquo; is inside the quad.&lt;/p&gt;
&lt;p&gt;To do this take the quad and break it into two triangles:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createTrianglesFromQuad&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;quad&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;vec2&lt;/span&gt;[]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [&lt;span style=&#34;color:#a6e22e&#34;&gt;quad&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;quad&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;quad&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [&lt;span style=&#34;color:#a6e22e&#34;&gt;quad&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;quad&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;quad&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And now I have two triangles I use the barycentric approach I took in Wolfenstein to determine if the point is in the rectangle:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isPointInTriangle&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;point&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;vec2&lt;/span&gt;, [&lt;span style=&#34;color:#a6e22e&#34;&gt;p1&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;p2&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vec2&lt;/span&gt;[]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ((&lt;span style=&#34;color:#a6e22e&#34;&gt;p2&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;point&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p2&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;point&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ((&lt;span style=&#34;color:#a6e22e&#34;&gt;p2&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;p1&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p2&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;p1&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ((&lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p1&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;point&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;p1&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;point&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ((&lt;span style=&#34;color:#a6e22e&#34;&gt;p2&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;p1&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p2&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;p1&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p3&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally all brought together in a fairly simple piece of code that looks for the nearest hit:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;processLaserHits&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;game&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Game&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// all we are really interested in for deciding if a player has hit a ship is the intersection of the bounding
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// box of the ship onto a 2d plane. That results in a quad that we can then split into two triangles and use
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// barycentric co-ordinates to determine if the laser has hit the ship
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// this isn&amp;#39;t how the original did it - it used some bit tricks basically
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;game&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;localBubble&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ships&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;reduce&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;ShipInstance&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;quad&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createLaserCollisionQuad&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;triangles&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;createTrianglesFromQuad&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;quad&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//const testPoint = game.player.laserOffset
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;testPoint&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vec2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fromValues&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;isPointInTriangle&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;testPoint&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;triangles&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;isPointInTriangle&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;testPoint&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;triangles&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }, &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;hit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;isDestroyed&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;At the moment ships are immediately blown up  by a laser hit - its letting me test things easily.&lt;/p&gt;
&lt;h2 id=&#34;enemy-lasers&#34;&gt;Enemy lasers&lt;/h2&gt;
&lt;p&gt;In the original game the lasers are simple lines and I wanted to keep that aesthetic. Lines are simple to draw in 2D but what, exactly, is a line in a 3D world. They tend to be a bit weird if you use them in WebGL (or OpenGL for that matter). I did experiment with both rendering a LINE_STRIP and also tried using triangles to form a shape but that all looked very weird.&lt;/p&gt;
&lt;p&gt;In the end I took a 2D approach to the rendering using an orthographic projection. For the &amp;ldquo;source&amp;rdquo; end of the laser I calculated, in the TypeScript code (as opposed to the GPU), the location of the ship firing the laser:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;projectPosition&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;projectionMatrix&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;mat4&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vec4&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fromValues&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;], &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;projectedPosition&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vec4&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;transformMat4&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;vec4&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(), &lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;projectionMatrix&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;projectedPosition&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;projectedPosition&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;projectedPosition&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;projectedPosition&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;viewportX&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ((&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dimensions&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;viewportY&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ((&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;y&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dimensions&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;mainViewHeight&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vec2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fromValues&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;viewportX&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;viewportY&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sourcePosition&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;projectPosition&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;projectionMatrix&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Its fairly standard 3D to 2D maths. With this done I tried a couple of approaches with the lasers target end. Initially I tried to aim them at the player but that just looked weird and odd - you end up with a line ending in the middle of the screen. Next I tried basically drawing the laser along the nose orientation of the source ship and force it off the screen. This works pretty nicely:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;target3DPosition&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vec3&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;vec3&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;vec3&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;multiply&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;vec3&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(), &lt;span style=&#34;color:#a6e22e&#34;&gt;vec3&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;normalize&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;vec3&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(), &lt;span style=&#34;color:#a6e22e&#34;&gt;ship&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;noseOrientation&lt;/span&gt;), [&lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;]),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;targetPosition&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;projectPosition&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;target3DPosition&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;projectionMatrix&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xGrad&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;sourcePosition&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;targetPosition&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dimensions&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;width&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;yGrad&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;sourcePosition&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;targetPosition&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dimensions&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;mainViewHeight&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;endPosition&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vec2&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;fromValues&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;sourcePosition&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xGrad&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;100000&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;sourcePosition&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;yGrad&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;100000&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I then just render this as a line strip using the orthographic projection I mentioned earlier.&lt;/p&gt;
&lt;p&gt;Quite happy with it and it looks pretty faithful to the original game. I will probably swing back and apply a little bit of randomisation to the direction.&lt;/p&gt;
&lt;h2 id=&#34;explosions&#34;&gt;Explosions&lt;/h2&gt;
&lt;p&gt;For the explosions I wanted to try and show the ships breaking apart. To do this I updated the ship model loading code to also load the ship model as a set of separately positional and renderable faces.&lt;/p&gt;
&lt;p&gt;To trigger the explosion I replace the model of the ship with the separate set of faces. It should look exactly the same to begin with but I&amp;rsquo;ve got a bit of a problem with that at the moment however the effect already looks pretty good:&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/dZv-FKgDKK8?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;p&gt;There&amp;rsquo;s still a bit of work to do on this and I&amp;rsquo;ll probably come back and add some particle effects too.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Elite</title>
      <link>https://www.jamesdrandall.com/projects/webglite/</link>
      <pubDate>Sun, 28 Jan 2024 10:04:13 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/projects/webglite/</guid>
      <description>&lt;p&gt;&lt;em&gt;You can play the latest work in progress of the game &lt;a href=&#34;https://webglite.com&#34;&gt;online here&lt;/a&gt; and the &lt;a href=&#34;https://github.com/JamesRandall/webGLite&#34;&gt;source code&lt;/a&gt; is on GitHub.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Elite_(video_game)&#34;&gt;Elite&lt;/a&gt; is absolutely one of my formative experiences - videogame or otherwise. It launched in 1984 on the BBC Micro at a time when most games were either clones of arcade games or following the same basic structure of scores, levels and lives presented using 2D graphics.&lt;/p&gt;
&lt;p&gt;Then there was Elite. A fully 3D game set in an open world with no real goal other than the vague aim of improving your combat rating to that of Elite. You could trade, collect bounty on pirates, trade, upgrade your ship and generally explore the 8 galaxies. All this on an 8-bit micro with about 20k left after the video memory. Crazy. Insane. Quite literally game changing.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;images/bbcElite.png&#34;&gt;&lt;img src=&#34;images/bbcElite640.png&#34; alt=&#34;Elite running on BeebEm&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;8 year old me played this game obsessively. I was at the perfect age for my imagination to fill in the blanks as I traded and blasted my way across the galaxy. I have no idea how many hours I sunk into this game but it was a lot. An awful low. I ater played it on the PC (Elite Plus - a rather excellent version by Chris Sawyer of Transport and Rollercoaster Tycoon fame) and on the &lt;a href=&#34;https://en.wikipedia.org/wiki/Acorn_Archimedes&#34;&gt;Acorn Archimedes&lt;/a&gt; that was in the office of my first professional programming job. ArcElite is often regarded as the definitive version but I confess I have a real soft spot for the BBC Master 128 and PC versions. &lt;a href=&#34;https://bbcmicro.co.uk/game.php?id=366&#34;&gt;You can play the original game via emulation online here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It spawned a bunch of &amp;ldquo;sort-of&amp;rdquo; sequels. I say sort-of because the early sequels (the Frontier games) overly focused on realism and Elite: Dangerous is a massively different kettle of fish. There is also the fantastic &lt;a href=&#34;https://www.oolite.space&#34;&gt;OOLite&lt;/a&gt; - an open source recreation / reimagining of the game that I highly recommend.&lt;/p&gt;
&lt;p&gt;The idea of writing some sort of homage to the game has tickled me for a long time but for some reason it always struck me as &amp;ldquo;too complex and too big&amp;rdquo;. Which given it fits into an 8-bit micro is, in some ways, kind of absurd. A couple (I think) of years back I came across Mark Moxon&amp;rsquo;s &lt;a href=&#34;https://www.bbcelite.com&#34;&gt;mind blowingly comprehensive and entertaining breakdown&lt;/a&gt; of the original games source code and systems and I was struck by how so much was done with so little. Yes their is quite a bit going on but taken on its own each sub-system of the game is (unsurprisingly, again: 8-bit) simple.&lt;/p&gt;
&lt;p&gt;Then late last year I watched this &lt;a href=&#34;https://www.youtube.com/watch?v=lC4YLMLar5I&#34;&gt;excellent YouTube video&lt;/a&gt; on the game and that was the final push I needed. I started off just experimenting to see if I could render the ships and the answer to that was a fairly resounding yes:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;images/webGLite1.png&#34;&gt;&lt;img src=&#34;images/webGLite1_640.png&#34; alt=&#34;Simple ship rendering&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;From their I started to build out basic systems like movement. I&amp;rsquo;ve tried, where it doesn&amp;rsquo;t make for a world of pain, to stick to the way the original game works.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;images/webGLite2.png&#34;&gt;&lt;img src=&#34;images/webGLite2_640.png&#34; alt=&#34;Movement and the sun&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And on from there to additional systems leading me to where the homage is today:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;images/planetView.png&#34;&gt;&lt;img src=&#34;images/planetView640.png&#34; alt=&#34;Approaching a space station&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As I write this the game is still a work in progress but a good number of key systems are up and running:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rendering (pretty much complete)&lt;/li&gt;
&lt;li&gt;Procedural generation of the galaxies&lt;/li&gt;
&lt;li&gt;Dashboard&lt;/li&gt;
&lt;li&gt;Short range system chart&lt;/li&gt;
&lt;li&gt;Hyperspace&lt;/li&gt;
&lt;li&gt;Trading&lt;/li&gt;
&lt;li&gt;System details&lt;/li&gt;
&lt;li&gt;Player details&lt;/li&gt;
&lt;li&gt;Jumping / warping (the original game does a &amp;ldquo;jump&amp;rdquo; to help you get to planets faster, I&amp;rsquo;ve replaced it with a warp effect)&lt;/li&gt;
&lt;li&gt;Docking computer (needs a bit of work when engaged on longer distances)&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Retro&amp;rdquo; style effects like CRT and green screen monitors (I used to play it on a tiny green screen monitor)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m literally about to start on combat which is the last major system to implement.&lt;/p&gt;
&lt;p&gt;Check back soon for updates.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Wolfenstein 3D</title>
      <link>https://www.jamesdrandall.com/projects/wolfenstein3d/</link>
      <pubDate>Sun, 28 Jan 2024 09:04:13 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/projects/wolfenstein3d/</guid>
      <description>&lt;p&gt;&lt;em&gt;You can play the game &lt;a href=&#34;https://www.jamesdrandall.com/wolf3d&#34;&gt;online here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Back in early 2022 I&amp;rsquo;d been making one of my regular revisits to 90s videogaming and spent a fun couple of hours playing &lt;a href=&#34;https://en.wikipedia.org/wiki/Wolfenstein_3D&#34;&gt;Wolfenstein 3D&lt;/a&gt; by iD Software. I still have vivid memories of downloading and playing this back in 1992 from a BBS. I lived with my parents at the time (I&amp;rsquo;d have been 16) and I remember running round the house proclaiming &amp;ldquo;this changes everything, this changes everything&amp;rdquo;. Nobody cared!&lt;/p&gt;
&lt;p&gt;Inspired by the game I had a crack at figuring out what it was doing and writing a raycasting engine using &lt;a href=&#34;https://en.wikipedia.org/wiki/Borland_Turbo_C&#34;&gt;Turbo C&lt;/a&gt;. I got something working but it ran at a snails pace and had a strange fisheye effect.&lt;/p&gt;
&lt;p&gt;For whatever reason while playing Wolfenstein 30 years later the itch started to itch and I decided I have to have another go. I didn&amp;rsquo;t set off with the intention of recreating the game and first I just tried to write the simplest raycaster I could:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;images/simpleRaycaster.png&#34;&gt;&lt;img src=&#34;images/simpleRaycaster640.png&#34; alt=&#34;Simple raycaster&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Just plain walls, nothing clever. The source for this can be found on GitHub: &lt;a href=&#34;https://github.com/JamesRandall/fsharp-simpleraycaster&#34;&gt;https://github.com/JamesRandall/fsharp-simpleraycaster&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Next I moved on to adding textures:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;images/simpleRaycasterTexture.png&#34;&gt;&lt;img src=&#34;images/simpleRaycasterTexture640.png&#34; alt=&#34;Simple raycaster with texture&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Again the source for this can be found on GitHub: &lt;a href=&#34;https://github.com/JamesRandall/fsharp-simpleraycaster-textured&#34;&gt;https://github.com/JamesRandall/fsharp-simpleraycaster-textured&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It was at this point that I decided to go a lot further and recreate the game for which the full source can also be found on GitHub: &lt;a href=&#34;https://github.com/JamesRandall/fsharp-wolfenstein&#34;&gt;https://github.com/JamesRandall/fsharp-wolfenstein&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you want to learn more about how it works, including how to avoid a fisheye effect, I did a talk at JetBrains .NET Day 2023 which you can watch on YouTube:&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/Z6VM-rH1wS8?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;p&gt;I also began, but didn&amp;rsquo;t quite finish, a C# version using Blazor. There are a number of blog posts on this very website about that and you can find the source for that here: &lt;a href=&#34;https://github.com/JamesRandall/csharp-wolfenstein&#34;&gt;https://github.com/JamesRandall/csharp-wolfenstein&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Sprite Packer</title>
      <link>https://www.jamesdrandall.com/shorts/spritepacker/</link>
      <pubDate>Fri, 22 Dec 2023 10:04:13 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/shorts/spritepacker/</guid>
      <description>&lt;p&gt;&lt;em&gt;You can download the app &lt;a href=&#34;https://apps.apple.com/gb/app/spred/id6463485447&#34;&gt;from the App Store&lt;/a&gt; or view the &amp;ldquo;official&amp;rdquo; SprEd store linked page &lt;a href=&#34;https://www.jamesdrandall.com/spred&#34;&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Sprite Picker is a simple, free and easy to use texture and sprite packer for the Mac. It will pack your source images into a single image and create a JSON file containing the offsets of the image in absolute co-ordinates and as texture co-ordinates making it ideal for use with Metal apps. I put it together fairly quickly to get icons into my MetalUI project for my starship tactics game.&lt;/p&gt;
&lt;p&gt;You can download the desktop app &lt;a href=&#34;https://apps.apple.com/gb/app/spred/id6463485447&#34;&gt;from the App Store&lt;/a&gt; or install the command line version from GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/JamesRandall/spritePacker/releases/tag/v1.0.3&#34;&gt;https://github.com/JamesRandall/spritePacker/releases/tag/v1.0.3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Its OSS and you can also grab the source on GitHub. MIT License so I can&amp;rsquo;t stop you but all I ask is that you don&amp;rsquo;t upload other versions to the App Store as that would be a bit shit. If you want to add something submit a PR.&lt;/p&gt;
&lt;p&gt;The CLI is self describing, after installing run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sprite-packer --help
&lt;/code&gt;&lt;/pre&gt;
&lt;div role=&#34;list&#34; class=&#34;grid sm:grid-cols-4 lg:grid-cols-4 gap-8 mt-8&#34;&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/shorts/spritepacker/images/0.png&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/shorts/spritepacker/images/1.png&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/shorts/spritepacker/images/2.png&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/shorts/spritepacker/images/3.png&#34; /&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>SprEd</title>
      <link>https://www.jamesdrandall.com/projects/spred/</link>
      <pubDate>Fri, 01 Dec 2023 10:04:13 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/projects/spred/</guid>
      <description>&lt;p&gt;&lt;em&gt;You can download the editor &lt;a href=&#34;https://apps.apple.com/gb/app/spred/id6463485447&#34;&gt;from the App Store&lt;/a&gt; or view the &amp;ldquo;official&amp;rdquo; SprEd store linked page &lt;a href=&#34;https://www.jamesdrandall.com/spred&#34;&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I should say up front that SprEd is not an open source project, rather its available on the App Store for the cost of a couple of coffees. And please don&amp;rsquo;t email me asking me to make it open source (I might at some point but its not going to be because people want a free sprite editor).&lt;/p&gt;
&lt;p&gt;I wrote it because I was using AESprite on my Mac and a different (I forget which now) editor on my iPad. I wanted to work seamlessly across my devices via iCloud and I also had an itch to learn Swift and SwiftUI as I&amp;rsquo;d not done any native iOS development for some time.&lt;/p&gt;
&lt;p&gt;The result was SprEd which I actively use myself.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve got plans at the back of my head to expand it out to also include a level editor and that&amp;rsquo;s likely to happen if and when I remake an old tile based game.&lt;/p&gt;
&lt;p&gt;In any case as there&amp;rsquo;s a dedicated page on the site I won&amp;rsquo;t ramble on about it here but wanted to include it for completeness as a recent project.&lt;/p&gt;
&lt;p&gt;Oh and it turns out I really enjoyed working with Swift and SwiftUI - I might write a series of posts about it at some point. Pop it on the backlog!&lt;/p&gt;
&lt;div role=&#34;list&#34; class=&#34;grid sm:grid-cols-2 lg:grid-cols-4 gap-8&#34;&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/spred/images/premiumLayers.26e3d38a.png&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/spred/images/standardPalettes.b2a34ef7.png&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/spred/images/premiumTilesheet.896176cd.png&#34; /&gt;
    &lt;img src=&#34;https://www.jamesdrandall.com/projects/spred/images/premiumRepeating.41f9ab02.png&#34; /&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Give By The Mile: Setting up</title>
      <link>https://www.jamesdrandall.com/devdiary/give_by_the_mile_setting_up/</link>
      <pubDate>Sat, 28 Oct 2023 08:30:00 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/devdiary/give_by_the_mile_setting_up/</guid>
      <description>&lt;p&gt;In 2022, according to Strava&amp;rsquo;s annual &amp;ldquo;state of strava&amp;rdquo; report 24 billion, yes billion, miles were recorded on their platform. Imagine if you could convert 5% of those miles into 1 pence (GBP) and donate it to charity. That would be &lt;strong&gt;£12 million&lt;/strong&gt; collected even before you factor in (in the UK at least) gift aid.&lt;/p&gt;
&lt;p&gt;That, basically, is what Give By The Mile is about. A platform for connecting athletes, both aspiring and experienced, with sponsors to collect ongoing recurring donations based around the amount of miles a cyclist covers or ascent they gain, meters a swimmer swims, or miles run by a runner each month.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d like it to be a positive contribution on all sides: the donations to the charities and a motivational tool to those, of all levels of fitness, trying to reach their goals. It, in and of itself, is hopefully a public health good.&lt;/p&gt;
&lt;p&gt;In practice I see this working as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fundraisers sign up and pick a charity and set up a donations page. Ideally I&amp;rsquo;d like to be able to do all this directly and with recurring billing but for the initial MVP I&amp;rsquo;m looking to make use of the JustGiving API. That means I can notify people about how much they need to donate but can&amp;rsquo;t directly collect the money.&lt;/li&gt;
&lt;li&gt;Fundraisers connect Give By The Mile to their fitness platform. I&amp;rsquo;m starting with Strava as they have an easy to access API. I&amp;rsquo;ll then look to move on to things like Garmin (who you have to apply to to gain access the API and seem to want you to already have your software before they&amp;rsquo;ll commit to you - very chicken and egg), Apple HealthKit (probably need an app), Wahoo etc. I&amp;rsquo;ll also support the upload of FIT files for folk who would rather do things that way.&lt;/li&gt;
&lt;li&gt;Sponsors sign up and find an athlete - either someone they know, someone through a search on the site to find compatible athletes (charity and probably donation range). They then pick how they want to sponsor that athlete and optionally set a maximum donation.&lt;/li&gt;
&lt;li&gt;At the end of each month the totals are tallied and the sponsors and athletes notified. Along the way their will be some graphs and things like that so people can see progress. And eventually you can imagine building a reward system in.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And this, here, is the developer diary that I&amp;rsquo;m going to try and maintain while I build this platform out. Partly because hopefully there will be some interesting things to talk about but also because the difficulty won&amp;rsquo;t be in building the platform: it will be getting attention and traction. So I&amp;rsquo;ll be using every outlet I have to promote this!&lt;/p&gt;
&lt;p&gt;First a bit of history. Give By The Mile is actually an idea I had probably about 15 years ago. I wasn&amp;rsquo;t really a cyclist then but I had started riding my heavy commuter bike longer and longer distances as I&amp;rsquo;d entered a London to Paris charity ride. It was really hard work! And I kept thinking to myself: this is the hard bit! Back then the ecosystem wasn&amp;rsquo;t really their - I looked into building it but there were just too many pain points on all sides to make it feasabile for me to do as a tiny project and so I shelved it. I&amp;rsquo;d registered the domains back then but for some reason had let them go fallow - thankfully no one had grabbed them in the interim so they are mine again!&lt;/p&gt;
&lt;p&gt;However its stuck in the back of my mind and a few things have come together to really bring this to the front of my mind:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;I&amp;rsquo;ve got really stuck into training for a couple of charity endurance events I&amp;rsquo;m hoping to do next year. Spent soooo much time on a turbo trainer going up virtual mountains that I could use that bit of extra motivation!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I don&amp;rsquo;t know if its because as I get older I notice it more or if its actually the case but there seems to be so much struggling and suffering around and I&amp;rsquo;d like to do something about it. Sure I can do individual events but being a moderately talented software developer what I can really do is build things that act as multipliers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;My mum is quite ill and it is absolutely shocking how little support there is available from the National Health Service. I&amp;rsquo;m not going into the politics of all that here, the subject is complex to say the least, and I&amp;rsquo;m certainly not suggesting there aren&amp;rsquo;t hard working people in the NHS but essentially support (what little their is) comes from the various charities in the space. This sort of thing really brings things into focus.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&amp;rsquo;ve spent time over the last couple of weeks talking to people who ride bikes, who run charities, and folk who&amp;rsquo;s broad view on sectors I respect and I&amp;rsquo;ve had nothing but encouragement. I figure the absolute worst that can happen is that it fails, it costs me a little time (but likely I&amp;rsquo;m having fun in that time!), and I move on. But best case - it takes off and makes a real difference to peoples lives. How &lt;strong&gt;awesome&lt;/strong&gt; would that be!&lt;/p&gt;
&lt;p&gt;So that&amp;rsquo;s the history and my motivations out of the way. On with what I&amp;rsquo;ve been up to over the last week or two.&lt;/p&gt;
&lt;p&gt;As its gestated for over 10 years in my head how the system works is pretty well explored in my mind so I&amp;rsquo;ve been focused on picking the tools and technologies I&amp;rsquo;m going to be using and doing some proof of concept work where they are new to me. I always start a new project by thinking about my obvious constraints. For this project the two big ones are fairly obvious.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;I&amp;rsquo;m working on this on my own. I need tools, and an architecture, that supports a tiny team in being productive. Even though its not a huge project there is a lot of ground for one person to cover.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It needs to be cheap to run. To begin with I&amp;rsquo;m funding this myself and I&amp;rsquo;d like to be able to do that for some time. But even if I get to the point where I&amp;rsquo;m funding it externally or more directly the lower the overhead the more money goes to the charities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&amp;rsquo;m going to give my thoughts on the first point here and we&amp;rsquo;ll pick up (2) in a future post.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve followed me for a while or seen me present you&amp;rsquo;ll know I&amp;rsquo;m a big fan of using Fable with F#. This lets you write end to end F# on both the server and the client - it transpiles the F# to JavaScript and their are bindings for React and other libraries. And you can get fairly seamless interaction between the server and the client by using Fable Remoting.&lt;/p&gt;
&lt;p&gt;However I&amp;rsquo;m not using F# for this project. But I am using some of the key takeaways I learned from that. They were:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Using the same language end to end is a massive productivity boost. The obvious benefit is that you can share code (models for example) but the real, and massive, benefit is the increase in &amp;ldquo;flow state&amp;rdquo; for a developer. I&amp;rsquo;m a pretty comfortable polyglot but not having to context switch between, say, C# and TypeScript is a huge productivity boost. Transformative.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For small to medium projects dispensing with as much ceremony as possible makes things far more maintainable and quicker to iterate on. That&amp;rsquo;s not to say your code should be chaos and carnage. Absolutely not. You need a structure. But bringing things closer together rather than decoupling to the nth degree makes it a lot easier to get stuff built. Heresy to some I know but a lot of the patterns people habitually use are there to support testing and large or multiple teams. There are other ways to achieve the former and if you&amp;rsquo;re not a large or multiple team organisation&amp;hellip;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So why am I not using F#? Doesn&amp;rsquo;t it tick those boxes? Yes. But&amp;hellip;. I am almost certainly going to open source this project once I&amp;rsquo;ve got enough of a foundation in place and with a bit of luck perhaps I can get some help in the development. That&amp;rsquo;s far more likely to happen if I&amp;rsquo;m using a more mainstream language and runtime. Additionally there has been some really interesting things happening in the JavaScript/TypeScript space that, after doing some research, I want to take advantage of.&lt;/p&gt;
&lt;p&gt;If I know .NET (I also know TypeScript and JavaScript so its not really a deciding factor for me but anyway) why not C# and Blazor? Every time I&amp;rsquo;ve looked at Blazor its felt unfinished in some way. And its very much in its own little world. I like ASP.Net Core (and they&amp;rsquo;ve made strides in reducing ceremony for sure) but the UI options just don&amp;rsquo;t jive with me. I don&amp;rsquo;t know what it is about Microsoft and UI technology but I always find it&amp;hellip;. grim. That&amp;rsquo;s a personal thin sure. But I&amp;rsquo;m a person. And I have to use this stuff to write a lot of code and stay engaged. So for all those reasons that stack is out&amp;hellip;.&lt;/p&gt;
&lt;p&gt;I feel I&amp;rsquo;ve written a lot here. And I have some more code to write! And so I&amp;rsquo;m going to leave this on a bit of a cliffhanger. Stay tuned for the next update where I&amp;rsquo;ll talk about where I&amp;rsquo;ve headed (hint: React is in the mix but its not React).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Featured photo by &lt;a href=&#34;https://unsplash.com/@wardhanaaditya?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&#34;&gt;Aditya Wardhana&lt;/a&gt; on &lt;a href=&#34;https://unsplash.com/photos/man-in-black-shirt-riding-on-white-and-orange-bmx-bike-LFv9vVBLmwM?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash&#34;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>GPU Mandelbrot</title>
      <link>https://www.jamesdrandall.com/shorts/gpu_mandelbrot/</link>
      <pubDate>Thu, 11 May 2023 09:04:13 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/shorts/gpu_mandelbrot/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;images/featured.png&#34; alt=&#34;BBC Mandelbrot&#34;&gt;&lt;/p&gt;
&lt;p&gt;If, like me, you grew up coding in the 1980s you&amp;rsquo;ll probably have fond memories of the Mandelbrot Set - it, and other fractals, were everywhere.&lt;/p&gt;
&lt;p&gt;I could be wrong but I think I first came across them in a copy of BeeBug, a magazine that I read avidly! In any case I vividly remember watching one slowly, oh so slowly, render on my BBC Model B.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve always found them fascinating and, as simplistic code for rendering for one is pretty straightforward, I&amp;rsquo;ve implemented them several times. As part of the Game Engine book I&amp;rsquo;ve been spending a lot of time recently coding shaders and a weekend or two back I suddenly had the urge to implement a Mandelbrot on the GPU in a shader. You can &lt;a href=&#34;https://www.jamesdrandall.com/gpumandelbrot&#34;&gt;try the results for yourself online&lt;/a&gt;. It works best on a laptop with a trackpad with inertial scrolling (like a MacBook) but also works with a mouse wheel quite well.&lt;/p&gt;
&lt;p&gt;Mandelbrot&amp;rsquo;s are really, really, well suited for parallelised number crunching. Essentially to render a Mandelbrot you visit visit each pixel in the frame, figure out which complex number is associated with it, and then perform an iterative / recursive calculation until an escape threshold is reached: either the resulting value exceeds a given threshold or a defined maximum number of iterations has been reached. In the former case the colour is calculated (how this is calculated is completely arbitrary - it has nothing to do with the actual Mandelbrot mathematics) and in the latter case the colour is deemed to be black.&lt;/p&gt;
&lt;p&gt;The important thing, from a parallelisation point of view, is that the calculations for each pixel are independent of the calculations for the other pixel. This is perfect for a GPU and even without doing any tricks simply loading this onto a GPU will result in something that performs like that in the video. As an aside you can do many tricks to optimise the calculation of a Mandelbrot - &lt;a href=&#34;http://cowlark.com/2018-05-21-bbc-micro-mandelbrot/&#34;&gt;this absolute genius has one rendering in 12 seconds on a BBC and explains how&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So how does it work?&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a really nice explanation &lt;a href=&#34;https://alonso-delarte.medium.com/a-quick-explanation-of-the-mandelbrot-set-41102d7182b&#34;&gt;over here&lt;/a&gt; but for our purposes I&amp;rsquo;m going to try and describe it really concisely and get into the shaders.&lt;/p&gt;
&lt;p&gt;The Mandelbrot is the result of the aforementioned iterative calculations performed on a series of complex numbers. Complex numbers extend real numbers to include what is known as an imaginary number usually donated as i. An imaginary number is any number that satisfies the equation i * i = -1 (as in i squared equals - 1). No real number can satisfy that equation.&lt;/p&gt;
&lt;p&gt;Complex numbers can be visualised on a plane where, if z is a complex number, then it can be said that z = x + iy. You can probably see where this is going now - the Mandelbrot lives in a range of values on that plane and so to create our visualisation we&amp;rsquo;re going to set up a range on this plane and feed that to the GPU and, by mapping that plane onto our screen / window (which is what a vertex shader does) we&amp;rsquo;ll iterate over the plane running our calculations in the fragment shader.&lt;/p&gt;
&lt;p&gt;If you want to know more about shaders and how they work you can find an overview in the book currently being serialised on my Patreon: &lt;a href=&#34;https://www.patreon.com/jamesrandall/posts?filters%5Btag%5D=Game%20Engine&#34;&gt;Game Engine Development with C#&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The code I refer to below &lt;a href=&#34;https://github.com/JamesRandall/GpuMandelbrot&#34;&gt;can be found in GitHub&lt;/a&gt;. The host for it is just some pretty basic HTML and JavaScript that wires up some input events, sets up a canvas, gains access to the WebGL context, and kicks off our render.&lt;/p&gt;
&lt;p&gt;Our vertex shader (shader.vert) is incredible simple:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-glsl&#34; data-lang=&#34;glsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#version 300 es&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;precision&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;mediump&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec4&lt;/span&gt; vertexPosition;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uniform&lt;/span&gt; mat4 uModelViewMatrix;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uniform&lt;/span&gt; mat4 uProjectionMatrix;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; main(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  gl_Position &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; uProjectionMatrix &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; uModelViewMatrix &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; vertexPosition; &lt;span style=&#34;color:#75715e&#34;&gt;// - vec4(0,-0.5,0,0);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Even though its simple there are a couple of things worth noting.&lt;/p&gt;
&lt;p&gt;Firstly, we&amp;rsquo;re using version 3 of GLSL ES. This is important and we&amp;rsquo;ll see why in the fragment shader. Secondly, our projection matrix is an orthogonal matrix - we want our Mandelbrot to be projected in a 2-dimensional manner and our model view is set up such that the two triangles we render form a rectangle that covers the whole canvas. As such when our fragment shader runs its essentially iterating over every screen co-ordinate.&lt;/p&gt;
&lt;p&gt;And so onto our fragment shader which is a bit more complex:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-glsl&#34; data-lang=&#34;glsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#version 300 es&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;precision&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;highp&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uniform&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec2&lt;/span&gt; uScale;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uniform&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec2&lt;/span&gt; uMin;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uniform&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; maxIterations;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;out&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec4&lt;/span&gt; fragColor;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt; baseColor0 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;7.0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;100.0&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;255.0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt; baseColor1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;32.0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;107.0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;203.0&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;255.0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt; baseColor2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;237.0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;255.0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;255.0&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;255.0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt; baseColor3 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;255.0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;170.0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;255.0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt; baseColor4 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;2.0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;255.0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt; interpolate(&lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt; color1, &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt; color2, &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; fraction) {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; color1 &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (color2 &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; color1) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; fraction;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Colour picking could do with some work&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt; rgbColorFromIteration(&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; iteration) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; fractionalIteration &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; iteration &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; maxIterations;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (fractionalIteration &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.16&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; interpolate(baseColor0, baseColor1, fractionalIteration &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.16&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (fractionalIteration &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.42&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; interpolate(baseColor1, baseColor2, (fractionalIteration &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.16&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;0.42&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0.16&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (fractionalIteration &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.6425&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; interpolate(baseColor2, baseColor3, (fractionalIteration &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.42&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;0.6425&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0.42&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (fractionalIteration &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.8575&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; interpolate(baseColor3, baseColor4, (fractionalIteration &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.6425&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;0.8575&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0.6425&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; baseColor4;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt; mandelbrot() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;vec2&lt;/span&gt; scaled &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec2&lt;/span&gt;(gl_FragCoord.x &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; uScale.x &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; uMin.x, gl_FragCoord.y &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; uScale.y &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; uMin.y);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;vec2&lt;/span&gt; c &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec2&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; iteration&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;; iteration &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; maxIterations; iteration&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (dot(c,c) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.0&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; rgbColorFromIteration(iteration);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    c &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec2&lt;/span&gt;(c.x&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;c.x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; c.y&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;c.y &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; scaled.x, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; c.x &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; c.y &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; scaled.y);        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; main(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  fragColor &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec4&lt;/span&gt;(mandelbrot(), &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For us to be able to run our calculations we pass a few parameters into the shader:&lt;/p&gt;
&lt;p&gt;uScale - the current zoom level
uMin - the top left position on the complex plane we need to render
maxIterations - the maximum number of iterations before stopping our calculations&lt;/p&gt;
&lt;p&gt;The top half of the shader handles the colour selection - essentially we use the number of iterations to select a colour from a pre-determined palette.&lt;/p&gt;
&lt;p&gt;The Mandelbrot calculation is handled in the mandelbrot() function and this is the reason we&amp;rsquo;re using GLSL ES 3.0. Previous versions of GLSL required for loops to have a constant as their terminator - this is because GPUs like to unbundle the loops. GLSL ES 3.0 added the capability to use none-constant terminators as we have here. Note - you can write a Mandelbrot on an older GLSL version, you simply can&amp;rsquo;t pass the maximum number of iterations in as a value and have to restructure the code slightly.&lt;/p&gt;
&lt;p&gt;Again, this calculation is going to run for every pixel on the screen - and we don&amp;rsquo;t need a uMax as an input because that&amp;rsquo;s essentially defined by uMin + screenSize * uScale. We just need to know where to start. We can determine which pixel is being rendered by the fragment shader by using glFragCoord which is a global that tells us exactly this. And by combining this with uMin and uScale we know where on the complex plane we are.&lt;/p&gt;
&lt;p&gt;There are just a couple of things worth noting about the JavaScript wrapper. To support touch gestures (you can pinch on a touch screen) I used a handy library called &lt;a href=&#34;https://zingchart.github.io/zingtouch/&#34;&gt;ZingTouch&lt;/a&gt; and the vector and matrix maths used alongside WebGL come from the &lt;a href=&#34;https://glmatrix.net/&#34;&gt;glmatrix&lt;/a&gt; library.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s it really. Pretty simple. A GPU powered Mandelbrot.&lt;/p&gt;
&lt;p&gt;That said&amp;hellip; I couldn&amp;rsquo;t help but implement the same algorithm on a BBC (I used an emulator, though I do have a real BBC Master 128) using BBC BASIC to see how long it would take. I left colour selection out and used a low resolution mode (160x256) and you can see the code below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-basic&#34; data-lang=&#34;basic&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;10 MODE &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;20 VDU &lt;span style=&#34;color:#ae81ff&#34;&gt;23&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;30 GCOL &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;40 XMIN &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;-2.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;50 XMAX &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.9&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;60 XSTEP &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (XMAX&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;XMIN)&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1280.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;70 YMIN &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;-1.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;80 YMAX &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;90 YSTEP &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (YMAX&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;YMIN)&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1024.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;100 &lt;span style=&#34;color:#66d9ef&#34;&gt;FOR&lt;/span&gt; X&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;TO&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1280&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;STEP&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;110     &lt;span style=&#34;color:#66d9ef&#34;&gt;FOR&lt;/span&gt; Y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;TO&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;512&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;STEP&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;120         MX&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;130         MY&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;140         SCALEDX &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; X &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; XSTEP &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; XMIN
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;150         SCALEDY &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Y &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; YSTEP &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; YMIN
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;160         CX &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;170         CY &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;180         ITERATION &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;190         PCOLOR &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;200         REPEAT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;210             &lt;span style=&#34;color:#66d9ef&#34;&gt;IF&lt;/span&gt; ((MX&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;MX) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (MY&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;MY)) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4.0&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;THEN&lt;/span&gt; PCOLOR &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;220             MXTEMP &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MX&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;MX &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; MY&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;MY &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; SCALEDX
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;230             MY &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;MX&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;MY &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; SCALEDY
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;240             MX &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MXTEMP
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;250             ITERATION &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ITERATION &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;260         &lt;span style=&#34;color:#66d9ef&#34;&gt;UNTIL&lt;/span&gt; PCOLOR &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;OR&lt;/span&gt; ITERATION &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;270         &lt;span style=&#34;color:#66d9ef&#34;&gt;IF&lt;/span&gt; PCOLOR &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;THEN&lt;/span&gt; PLOT &lt;span style=&#34;color:#ae81ff&#34;&gt;69&lt;/span&gt;,X,Y &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; PLOT &lt;span style=&#34;color:#ae81ff&#34;&gt;69&lt;/span&gt;,X,&lt;span style=&#34;color:#ae81ff&#34;&gt;1023&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;Y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;280         MY &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MY &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; YSTEP
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;290     &lt;span style=&#34;color:#66d9ef&#34;&gt;NEXT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;300     MX &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MX &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; XSTEP
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;310 &lt;span style=&#34;color:#66d9ef&#34;&gt;NEXT&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which resulted in the following Mandelbrot:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;images/bbcMode2.png&#34; alt=&#34;BBC Mandelbrot&#34;&gt;&lt;/p&gt;
&lt;p&gt;Even though I cheated a little (I knew I wouldn&amp;rsquo;t be zooming and that the Mandelbrot is symmetrical and so I only calculated half the height of the screen and mirrored it) and is running in such a low resolution it took over 1 hour to generate this.&lt;/p&gt;
&lt;p&gt;That said as I mentioned earlier someone has done far far far better than this (12 seconds for a coloured Mandelbrot). Its still remarkable though as a sense of how much faster hardware has got: on my M1 Max MacBook the GPU was rendering this at 60fps+ in basically 4K resolution without skipping a beat.&lt;/p&gt;
&lt;p&gt;Incredible really.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>3D Spectre Maze</title>
      <link>https://www.jamesdrandall.com/shorts/3d_spectre_maze/</link>
      <pubDate>Sun, 30 Apr 2023 20:04:13 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/shorts/3d_spectre_maze/</guid>
      <description>&lt;p&gt;Way back in 1981 a genius named Malcolm Evans managed to coax a 3D game out of a Sinclair ZX81 (an 8-bit micro with a Z80 CPU and 1Kb of RAM). It randomly generated mazes and used low resolution &amp;ldquo;character block&amp;rdquo; graphics to display them in 3D complete with a monster that chased you. The game was called &lt;a href=&#34;https://en.wikipedia.org/wiki/3D_Monster_Maze&#34;&gt;3D Monster Maze&lt;/a&gt; and it really was amazing to see back then and remains an impressive feat of coding.&lt;/p&gt;
&lt;p&gt;Maze generation algorithms are a fun rabbit hole to go down and rendering the view using similar block graphics in a console window is a kinda fun and neat trick and so I figured this might make for a neat little project.&lt;/p&gt;
&lt;p&gt;The source code for this project can be found on &lt;a href=&#34;https://github.com/JamesRandall/ThreeDSpectreMaze&#34;&gt;GitHub&lt;/a&gt; and you can see the video attached to this post.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to take a fairly functional approach to this code. C# isn&amp;rsquo;t perfect as a functional language, unsurprising given its roots, but its passable and I find this kind of project lends itself well to a functional approach. If anything its always interesting to explore an approach that is different (for example  rather than use interfaces, such as IAlgorithm, I&amp;rsquo;ve used function signatures here).&lt;/p&gt;
&lt;p&gt;If you want to spin it up and give it a go then I recommend setting your terminal to 100x48 characters in size as a minimum - this is the size of the canvas we use to render the maze. The keys are:&lt;/p&gt;
&lt;p&gt;Up - move forwards
Down - move backwards
Left - rotate left
Right - rotate right
M - show the overhead map
Escape - quit the game&lt;/p&gt;
&lt;h1 id=&#34;maze-generation&#34;&gt;Maze generation&lt;/h1&gt;
&lt;p&gt;We&amp;rsquo;re going to explore two maze generation algorithms and these can be found in the Algorithms sub-folder of the solution.&lt;/p&gt;
&lt;h2 id=&#34;recursive-backtracking&#34;&gt;Recursive Backtracking&lt;/h2&gt;
&lt;p&gt;Our first algorithm is possibly the simplest maze generation algorithm you can implement. It has the following flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Prefill the maze grid so that its entirely &amp;ldquo;solid&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Choose a random starting point in the grid and mark it as &amp;ldquo;empty&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Pick a random direction to an adjacent cell that hasn&amp;rsquo;t yet been visited and mark this new cell as &amp;ldquo;empty&amp;rdquo;&lt;/li&gt;
&lt;li&gt;If we can&amp;rsquo;t pick a new direction because all the cells have been visited then back up the chain until you reach a cell that has new directions for us to move in.&lt;/li&gt;
&lt;li&gt;Repeat until all cells have been visited - you can recognise this because the back up will go all the way to the starting cell and it will have no new directions to go in.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A worked example is the best way to illustrate this. In the below the blue represents the current path accumulated across the recursion with the light blue being the tip (or current cell) of the path. Dark grey are unvisited cells while white cells are cells that have been visited and completed. At the end of the animation you can observe the path completely unwinding back to the starting point:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;images/smallRecursiveBackTrackingExample.gif&#34; alt=&#34;Recursive back tracking example&#34;&gt;&lt;/p&gt;
&lt;p&gt;As an aside this animation has been created by a tool called MazeGenerationAnimationGenerator (a bit of a mouthful I realised after creating it) that uses the observer approach we&amp;rsquo;ll discuss later to inspect the steps of the maze generation and generate a animated GIF file. The same observer approach is used to display the larger maze shown below. This involves over 8000 frames of animation (I hadn&amp;rsquo;t really built the tool to create animations of this size so it ended up consuming about 18Gb of memory to do this!) and gives a sense of how the number of steps grows significantly as the maze size increases (the 6x6 example shown above is about 130 steps):&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/7dXZAwPPjHA?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;p&gt;The code for the algorithm is really quite simple:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Walk(MapVector position, ImmutableList&amp;lt;MapVector&amp;gt; path)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    path = path.Add(position);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    observer(map, path);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;foreach&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; direction &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; Directions.All.OrderBy(_ =&amp;gt; random.Next()).ToArray())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; next = position + Directions.Vector[direction];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; oppositeDirection = Directions.Opposite[direction];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (next.WithinBounds(width,height) &amp;amp;&amp;amp; map[next.y,next.x] == Direction.None)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            map[position.y,position.x] |= direction;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            map[next.y, next.x] |= oppositeDirection;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Walk(next, path);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (trackPath) observer(map, path);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Walk ( position: &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapVector (x: random. Next(width), :random.Next(height)));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We store our maze as a two-dimensional grid of bitmasked directions, those directions describe which way the player can move within the cell and therefore the inverse of this represents the walls (if a player can&amp;rsquo;t move in a given direction its because their is a wall there).&lt;/p&gt;
&lt;p&gt;Beyond that there&amp;rsquo;s just a couple of things to note in the code:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We use some basic bit twiddling to combine the directions.&lt;/li&gt;
&lt;li&gt;When we move to the next cell we mark the current cell as being able to move in the direction of the next cell, and we mark the next cell as being able to move back to our current cell (the opposite direction).&lt;/li&gt;
&lt;li&gt;We randomise the directions on each walk otherwise we&amp;rsquo;d end up with a maze with a very specific bias:
&lt;img src=&#34;images/screenshot2.webp&#34; alt=&#34;Biased maze&#34;&gt;&lt;/li&gt;
&lt;li&gt;We combine a &amp;ldquo;stay within the bounds of the grid&amp;rdquo; constraint with our untouched check to prevent the maze generator meandering out of the bounds of the array.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;hunt-and-kill&#34;&gt;Hunt and Kill&lt;/h2&gt;
&lt;p&gt;If recursion isn&amp;rsquo;t your thing (it could be problematic if you have large mazes and limited stack space) then an alternative algorithm is the hunt and kill algorithm. It&amp;rsquo;s a little more complicated to both explain and implement but here we go.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Pick a random starting location&lt;/li&gt;
&lt;li&gt;Walk in a random direction to any untouched cell&lt;/li&gt;
&lt;li&gt;Keep walking randomly until there are no untouched cells to move to&lt;/li&gt;
&lt;li&gt;Now &amp;ldquo;hunt&amp;rdquo; - search the maze for an untouched cell that is next to a touched cell then start walking again (step 2)&lt;/li&gt;
&lt;li&gt;Repeat until hunt cannot find a cell, at which time the maze is done&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&amp;rsquo;m never quite sure where the kill comes from in the name, perhaps it was added to make it sound cool. Walk and hunt doesn&amp;rsquo;t have quite the same ring I guess.&lt;/p&gt;
&lt;p&gt;Again a worked example is shown below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;images/smallHuntAndKill.gif&#34; alt=&#34;Hunt and kill example&#34;&gt;&lt;/p&gt;
&lt;p&gt;And again the same algorithm working on a bigger maze - this took far fewer frames, just over 4000, and consequently less memory (partly because there is no backtracking but also because the hunt steps are not illustrated):&lt;/p&gt;


    
    &lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen=&#34;allowfullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/KCx94BKUF7E?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;
      &gt;&lt;/iframe&gt;
    &lt;/div&gt;

&lt;p&gt;The code is a little more involved here, I don&amp;rsquo;t think its particularly complicated but there are a number of moving parts and we introduce some helper functions so we don&amp;rsquo;t repeat ourselves as hunt and walk share a number of common components. Firstly we have our core loop that basically maps onto the steps above:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; position = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapVector(random.Next(width), random. Next(height));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (position.IsValid)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    observer (map, ImmutableList&amp;lt;MapVector&amp;gt;.Empty);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    position = Walk(position);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (Iposition. IsValid) position = Hunt();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Simple enough! Then we have our walk method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MapVector Walk(MapVector position)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; possibleDirections = GetPossibleDirections(position,LookingFor.Untouched);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (possibleDirections.Any())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; direction = possibleDirections.MinBy(_ =&amp;gt; random.Next());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; UpdateMap(position, direction);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; MapVector.Invalid;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This makes use of a simple helper method, GetPossibleDirections, that can return an array of directions we can move in based on whether we are looking to move to an untouched (during walk) or touched (during hunt) cell:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Direction[] GetPossibleDirections(MapVector position, LookingFor lookingFor)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; IsValid(Direction &lt;span style=&#34;color:#66d9ef&#34;&gt;value&lt;/span&gt;) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        lookingFor == LookingFor.Touched ? &lt;span style=&#34;color:#66d9ef&#34;&gt;value&lt;/span&gt; != Direction.None : &lt;span style=&#34;color:#66d9ef&#34;&gt;value&lt;/span&gt; == Direction.None;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; cx = position.x;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; cy = position.y;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; north = cy &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &amp;amp;&amp;amp; IsValid(map[cy - &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, cx]) ? &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;[] {Direction.N} : &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Direction[] { };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; east = cx &amp;lt; (width - &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &amp;amp;&amp;amp; IsValid(map[cy, cx + &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]) ? &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;[] {Direction.E} : &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Direction[] { };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; south = cy &amp;lt; (height - &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &amp;amp;&amp;amp; IsValid(map[cy + &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, cx]) ? &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;[] {Direction.S} : &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Direction[] { };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; west = cx &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &amp;amp;&amp;amp; IsValid(map[cy, cx - &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]) ? &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;[] {Direction.W} : &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Direction[] { };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; possibleDirections = north.Concat(east).Concat(south).Concat(west).ToArray();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; possibleDirections;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally if we manage to walk to an untouched cell then we update the map and return the new position, we do this using another little helper function called UpdateMap:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MapVector UpdateMap(MapVector mapVector, Direction direction)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; nextPosition = mapVector + Directions.Vector[direction];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; oppositeDirection = Directions.Opposite[direction];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    map[mapVector.y, mapVector.x] |= direction;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    map[nextPosition.y, nextPosition.x] |= oppositeDirection;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; nextPosition;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is very similar to what we saw in the recursive backtracker - when we open up a direction we open it up in both directions.&lt;/p&gt;
&lt;p&gt;If walk can&amp;rsquo;t move to an untouched position then we enter our hunt phase. Hunt essentially iterates over our array and looks for a touched cell with an untouched cell next to it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MapVector Hunt()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; mapY = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; mapY &amp;lt; height; mapY++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; mapX = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; mapX &amp;lt; width; mapX++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (map[mapY, mapX] != Direction.None) &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; position = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; MapVector(mapX, mapY);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; possibleDirections = GetPossibleDirections(position, LookingFor.Touched);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (possibleDirections.Any())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; direction = possibleDirections.MinBy(_ =&amp;gt; random.Next());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                UpdateMap(position, direction);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; position;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; MapVector.Invalid;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We&amp;rsquo;re using the same helpers as walk but we reverse the &amp;ldquo;polarity&amp;rdquo;, so to speak, when we look for possible directions.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s it. If hunt fails to find a valid position then we&amp;rsquo;re done and our maze is generated.&lt;/p&gt;
&lt;h1 id=&#34;projecting-the-maze&#34;&gt;Projecting the maze&lt;/h1&gt;
&lt;p&gt;I find it fascinating to watch the two algorithms side by side. The recursive back tracker feels really quite random in the route it takes whereas the hunt and kill algorithm broadly fills the maze in from top to bottom due to how our hunt for loops are indexed.&lt;/p&gt;
&lt;p&gt;In any case at the end of either algorithm we have a two dimensional array that tells us in which direction you can move from any given cell. However the maze in 3D Monster Maze is constructed on a grid from square blocks and so it would be handy for us to project our algorithm output into this format. Despite the code being quite simple strangely I found this the most mind warping part of the project! The code for this projection can be found in the TransformToGrid method in the &lt;a href=&#34;https://github.com/JamesRandall/ThreeDSpectreMaze/blob/6a9445adcbdb7075a94d072ed5d72525840fbd7a/ThreeDSpectreMaze/MapFactory.cs#L22&#34;&gt;MapFactory class&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; ImmutableArray&amp;lt;ImmutableArray&amp;lt;Block&amp;gt;&amp;gt; TransformToGrid(Direction[,] map)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; topRow = Enumerable.Repeat(Block.Solid, Width * &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; + &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;).ToList();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; rows = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;List&amp;lt;Block&amp;gt;&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rows.Add(topRow);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; y = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; y &amp;lt; Height; y++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; row1 = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Block&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; row2 = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; List&amp;lt;Block&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// our rows always start with a wall otherwise the maze would be open&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// because we are checking eastwards we don&amp;#39;t need to terminate with a&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// wall, the maze generation algorithm itself terminates and all last columns&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// will have no eastward openings&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        row1.Add(Block.Solid);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        row2.Add(Block.Solid);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; x = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; x &amp;lt; Width; x++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            row1.Add(Block.Empty);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            row1.Add((map[y, x] &amp;amp; Direction.E) &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; ? Block.Empty : Block.Solid);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            row2.Add((map[y, x] &amp;amp; Direction.S) &amp;gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; ? Block.Empty : Block.Solid);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            row2.Add(Block.Solid);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        rows.Add(row1);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        rows.Add(row2);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; result = rows.Select(row =&amp;gt; row.ToImmutableArray()).ToImmutableArray();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What we&amp;rsquo;re doing in this code is essentially turning the walls into blocks. Put another way we&amp;rsquo;re turning each cell in our source, direction based array, into four cells in our grid based array - two across and two down. We&amp;rsquo;re only really interested in one horizontal direction and one vertical direction from our direction based array and we&amp;rsquo;re looking to see if we can move east or south.&lt;/p&gt;
&lt;p&gt;Every cell in our source directionally based array becomes an empty cell in our target array and that is our top left cell in the new quadrant. If we can travel east then an empty cell is the next cell on the row, if we can&amp;rsquo;t travel right then a solid block is the next cell on the row - the top right cell in our quadrant. We then apply the same logic going sout but add the cells to the next row: if we can travel south then we add an empty cell to the next row, if not then a solid cell. The next cell on the row is always a solid block. Hopefully the example below helps make this a little clearer:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;images/projectionExamples.webp&#34; alt=&#34;Projection example&#34;&gt;&lt;/p&gt;
&lt;h1 id=&#34;rendering&#34;&gt;Rendering&lt;/h1&gt;
&lt;p&gt;To render to the console we&amp;rsquo;re going to make use of the excellent &lt;a href=&#34;https://spectreconsole.net&#34;&gt;Spectre.Console&lt;/a&gt; NuGet package. It contains a class called Canvas that essentially lets us &amp;ldquo;draw&amp;rdquo; pixels and it also contains a neat little live update mechanism so that things can update in place.&lt;/p&gt;
&lt;h2 id=&#34;the-title-screen&#34;&gt;The title screen&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;images/titleScreen.png&#34; alt=&#34;Projection example&#34;&gt;&lt;/p&gt;
&lt;p&gt;The main feature of the title screen is the retro style pixelated font and the code for this can be found in RetroFont.cs. I&amp;rsquo;ll cover this in more detail in the Game Engine Development posts but essentially each character is defined as a set of bits and then to render them we look for a set bit and if found draw a pixel. The letter R is defined like this, for example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;byte&lt;/span&gt;[]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;b01110,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;b10001,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;b10011,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;b10101,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;b11001,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;b10001,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;b01110
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And the key rendering code looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; y = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; y &amp;lt; Layouts[index].Length; y++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; row = Layouts[index][y];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; x = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; x &amp;lt; Width; x++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ((row &amp;amp; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &amp;lt;&amp;lt; x)) != &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            canvas.SetPixel(currentPosition.x + (Width - &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; - x), currentPosition.y + y, color);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&#34;the-overhead-view&#34;&gt;The overhead view&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;images/overhead.png&#34; alt=&#34;Projection example&#34;&gt;&lt;/p&gt;
&lt;p&gt;Rendering the overhead view is as simple as iterating over our maze and drawing a light pixel where we have an empty space and a darker pixel where we have a wall. We add the player in as a red square over the top. This code can be found in Renderer.cs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; RenderOverhead(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Canvas canvas,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    MapVector playerPosition,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ImmutableArray&amp;lt;ImmutableArray&amp;lt;Block&amp;gt;&amp;gt; map)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    DrawRectangle(canvas, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,canvas.Width,canvas.Height,Color.Black);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; y = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; y &amp;lt; map.Length; y++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; row = map[y];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; x = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; x &amp;lt; row.Length; x++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (playerPosition.x == x &amp;amp;&amp;amp; playerPosition.y == y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                canvas.SetPixel(x, y, Color.Red);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                canvas.SetPixel(x, y, row[x] == Block.Solid ? Color.Grey : Color.White);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&#34;the-3d-view&#34;&gt;The 3d view&lt;/h2&gt;
&lt;p&gt;Before we can render the view we need to grab the slice of the map that the player is looking at based on their orientation. The player can see the edge of the walls alongside them and 5 grid squares in front. The nature of our maze, their are only corridors and no room, mean that the player can never see move than one square either side of them and so we form the player view up from a slice of the map that is 6 grid squares long and 3 wide as shown in the example below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;images/playerView.png&#34; alt=&#34;Projection example&#34;&gt;&lt;/p&gt;
&lt;p&gt;The code for creating this array can be found in GetPlayerView.cs in Game.cs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; ImmutableArray&amp;lt;ImmutableArray&amp;lt;Block&amp;gt;&amp;gt; GetPlayerView()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; viewDirection = Directions.Vector[Facing];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// As we build up the view for the player we need to look at the cells to the left and right of them&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// and the vector for this is based on the direction they are facing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; strafeDirection = GetPlayerLeftAndRightOffsets();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; view = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Block[Renderer.DrawDepth][];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; position = PlayerPosition;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; depth = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; depth &amp;lt; Renderer.DrawDepth; depth++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; leftPosition = position + strafeDirection.left;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; rightPosition = position + strafeDirection.right;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        view[depth] = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt;[]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            IsValidPosition(leftPosition) ? _map[leftPosition.y][leftPosition.x] : Block.Solid,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            IsValidPosition(position) ? _map[position.y][position.x] : Block.Solid,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            IsValidPosition(rightPosition) ? _map[rightPosition.y][rightPosition.x] : Block.Solid
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        position += viewDirection;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; view.Select(row =&amp;gt; row.ToImmutableArray()).ToImmutableArray();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With that done we&amp;rsquo;re now able to draw the 3D view. To give a sense of perspective the size at which we draw each wall reduces the further away from us it is and the walls next to the player are somewhat special in that we can only see the edges of them. We also want to shade the walls differently the further away from us that they are. We capture this information in another array (which can be found in Renderer.cs):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;readonly&lt;/span&gt; ImmutableArray&amp;lt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; depth, Color color)&amp;gt; Walls = &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, Color.Black), &lt;span style=&#34;color:#75715e&#34;&gt;// walls alongside the player - they can just see the edge of them&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;, Color.Black),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Color(&lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Color(&lt;span style=&#34;color:#ae81ff&#34;&gt;48&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;48&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;48&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Color(&lt;span style=&#34;color:#ae81ff&#34;&gt;64&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;64&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;64&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Color(&lt;span style=&#34;color:#ae81ff&#34;&gt;80&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;80&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;80&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}.ToImmutableArray();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, to draw our view, all we need do is walk down the two arrays in parallel drawing our side walls and any facing walls and stoppiig if we reach a block directly in front of us:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Render(Canvas canvas, ImmutableArray&amp;lt;ImmutableArray&amp;lt;Block&amp;gt;&amp;gt; playerView)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    DrawRectangle(canvas, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,canvas.Width,canvas.Height,BackgroundColor);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; depth = &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; depth &amp;lt; playerView.Length; depth++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; row = playerView[depth];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; startOffset = Walls.Take(depth).Sum(f =&amp;gt; f.depth);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; endOffset = startOffset + Walls[depth].depth - &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; color = Walls[depth].color;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        DrawSidewalls(canvas, startOffset, endOffset, row, color);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        DrawFacingWalls(canvas, row, startOffset, endOffset);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// Stop drawing if their is a block in front of us&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (row[PlayerMiddle] == Block.Solid) &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The actual rendering using our canvas is fairly simple. For the side walls we simply draw increasingly short vertical lines for each line of the wall:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; DrawSidewalls(Canvas canvas, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; startOffset, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; endOffset, ImmutableArray&amp;lt;Block&amp;gt; row, Color color)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; drawOffset = startOffset; drawOffset &amp;lt;= endOffset; drawOffset++)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (row[PlayerLeft] == Block.Solid) DrawVerticalLine(canvas, drawOffset, color);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (row[PlayerRight] == Block.Solid) DrawVerticalLine(canvas, canvas.Width - drawOffset - &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, color);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And for the facing walls we draw rectangles, they are slightly tricky to position but again fairly simple:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; DrawFacingWalls(Canvas canvas, ImmutableArray&amp;lt;Block&amp;gt; row, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; startOffset, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; endOffset)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (row[PlayerMiddle] == Block.Solid)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// Draw facing wall&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        DrawRectangle(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            canvas,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            startOffset,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            startOffset,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (canvas.Width - startOffset * &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (canvas.Height - startOffset * &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            HorizontalWallColor
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (row[PlayerLeft] == Block.Empty)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// Drawing facing wall of corridor to the left&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        DrawRectangle(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            canvas,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            startOffset,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (endOffset + &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (endOffset - startOffset + &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (canvas.Height - endOffset * &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; - &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            HorizontalWallColor
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (row[PlayerRight] == Block.Empty)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// Draw facing wall of corridor to the right&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; wallWidth = (endOffset - startOffset);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        DrawRectangle(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            canvas,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (canvas.Width - startOffset - wallWidth - &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (endOffset + &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            wallWidth + &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (canvas.Height - endOffset * &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; - &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            HorizontalWallColor
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that&amp;rsquo;s it. That will render our maze in delightyfully chunky three dimensions.&lt;/p&gt;
&lt;h1 id=&#34;bringing-it-all-together---the-game-loop&#34;&gt;Bringing it all together - the game loop&lt;/h1&gt;
&lt;p&gt;We bring all this rendering together in a super simple game loop that also lets the player move around the maze in a class called Game which, go figure, can be found in a file called Game.cs. The key part of the class is the Run method shown below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; Run(Canvas canvas)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; isOverhead = &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    AnsiConsole.Live(canvas).Start(ctx =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; quit = &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (!quit)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (isOverhead)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Renderer.RenderOverhead(canvas, PlayerPosition, _map);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Renderer.Render(canvas, GetPlayerView());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ctx.Refresh();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; key = Console.ReadKey().Key;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; (key)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; ConsoleKey.UpArrow: Move(MovementDirection.Forwards); &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; ConsoleKey.DownArrow: Move(MovementDirection.Backwards); &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; ConsoleKey.LeftArrow: RotateLeft(); &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; ConsoleKey.RightArrow: RotateRight(); &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; ConsoleKey.M: isOverhead = !isOverhead; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; ConsoleKey.Escape: quit = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The most notable part of this is the use of &lt;a href=&#34;http://ansiconsole.live/&#34;&gt;AnsiConsole.Live&lt;/a&gt; which allows us to update our canvas in place. Without this each render of the canvas would cause the console to scroll as normal behaviour is to append console output. Its a really neat feature of Spectre and you can do some super cool stuff with it!&lt;/p&gt;
&lt;p&gt;But otherwise all we are doing here is responding to key presses and updating the display and that&amp;rsquo;s it: a simple maze game. Well. Its not much of a game, all you can do is wonder around. Interactive experience perhaps?&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Simple C&#43;&#43; Voxel Engine</title>
      <link>https://www.jamesdrandall.com/projects/simple_cpp_voxel_engine/</link>
      <pubDate>Thu, 08 Apr 2021 20:04:13 +0100</pubDate>
      
      <guid>https://www.jamesdrandall.com/projects/simple_cpp_voxel_engine/</guid>
      <description>&lt;p&gt;&lt;em&gt;I originally wrote this piece on conclusion of the project in early January 2017. It remains one of my favourite and most fun projects. I went on to do some more demos with it and I&amp;rsquo;ve attached a video from that at the bottom of the post&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Happy New Year everyone - I hope everyone reading this had a great end of year break and has a fantastic 2017.&lt;/p&gt;
&lt;p&gt;I suspect I&amp;rsquo;m not alone in that I like to spend a little time over the Christmas break working (in the fun sense of the word) on something a bit different from the day job. This year I took about 4 days out to work on a really simple voxel engine as both the look and constructible / destructible feel of &lt;a href=&#34;https://en.wikipedia.org/wiki/Voxel&#34;&gt;voxels&lt;/a&gt; have always appealed to me, perhaps as a result of a childhood playing with Lego. Though bizarrely I&amp;rsquo;ve never played Minecraft.&lt;/p&gt;
&lt;p&gt;The code for the engine along with a couple of demonstrations can, as ever, be &lt;a href=&#34;https://github.com/JamesRandall/SimpleVoxelEngine&#34;&gt;found over on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t want to feel like I was writing glue code and so rather than use an existing library I started with a blank C++ file, OpenGL, and an awful lot of ignorance. Now to be fair I do have a reasonable background in C, C++, graphics and systems programming - before this web thing came along I was working with embedded systems and 2d graphics engines using C, C++ and 68000 assembly - but that was back in 1994. I continued as a commercial C++ developer for 4 more years but then found myself working with C# and the first .Net beta via a brief foray with Delphi.&lt;/p&gt;
&lt;p&gt;So I was rusty. Very rusty. However I had enthusiasm on my side and persevered until I had a couple of demo&amp;rsquo;s and some code that I wasn&amp;rsquo;t too embarrassed to share. The video below is me navigating a voxel &amp;ldquo;landscape&amp;rdquo; created using a black and white self portrait as a height map:&lt;/p&gt;

&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://player.vimeo.com/video/537212653&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;vimeo video&#34; webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=aETUvjQp-yc&amp;amp;t=2s&#34;&gt;https://www.youtube.com/watch?v=aETUvjQp-yc&amp;amp;t=2s&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;First step was some early research and I quickly discovered some really useful resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://www.glfw.org/&#34;&gt;GLFW&lt;/a&gt; - a great library for abstracting away the underlying hardware and OS and getting you a window to draw in&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://glad.dav1d.de/&#34;&gt;Glad&lt;/a&gt; - OpenGL is a strange, strange, place and chances are you will need to use a &amp;ldquo;loader&amp;rdquo; to get access to the full API. This web service makes it really easy.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://glm.g-truc.net/0.9.8/index.html&#34;&gt;GLM&lt;/a&gt; - A header only mathematics library based on the OpenGL shader specification&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://learnopengl.com/&#34;&gt;Learn OpenGL&lt;/a&gt; - tutorial series&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.opengl-tutorial.org/&#34;&gt;OpenGL Tutoral&lt;/a&gt; - another OpenGL tutorial series&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Being conscious C++ had changed a fair bit since my youthful glory days I also skim read [Accelerated C++](http://Accelerated C++) and &lt;a href=&#34;https://www.amazon.co.uk/gp/product/1491903996&#34;&gt;Effective Modern C++.&lt;/a&gt; I&amp;rsquo;ll be returning to the latter and reading it in some more depth as I&amp;rsquo;m sure my code is still pretty stinky - but it works!&lt;/p&gt;
&lt;h1 id=&#34;day-1---i-know-nothing&#34;&gt;Day 1 - I know nothing&lt;/h1&gt;
&lt;p&gt;Armed with my new found expertise I set myself a goal of generating and rendering a landscape based on &lt;a href=&#34;https://en.wikipedia.org/wiki/Perlin_noise&#34;&gt;Perlin Noise&lt;/a&gt; but given I couldn&amp;rsquo;t yet draw a cube I thought I&amp;rsquo;d better bootstrap my way up. This first day was the toughest and involved a fair amount of staring at a blank screen, cursing, and feeling extremely thankful for the safety of managed environments. However at the end of the day I managed to get not only a single cube on the screen but a set of voxels arranged into a chunk rendering (a chunk is an organisational concept in a voxel engine - collections of voxels) and you can see that below:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.azurefromthetrenches.com/wp-content/uploads/2017/01/2016_12_29_1.png&#34;&gt;&lt;img src=&#34;images/2016_12_29_1-300x239.png&#34; alt=&#34;2016_12_29_1&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t yet have any lighting so to help me process what I was looking at I did a simple adjustment of the colours around each vertex.&lt;/p&gt;
&lt;p&gt;The next step was to display several chunks together. My first effort at this went&amp;hellip; badly. Only one chunk was ever displayed. After spending an hour looking for obscure OpenGL problems I realised I&amp;rsquo;d done something more basically dumb with C++ (we&amp;rsquo;ve all copied something we thought we were passing by reference right?), pounded my head into the desk a few times, corrected it and hurrah. 4x4 chunks!&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.azurefromthetrenches.com/wp-content/uploads/2017/01/2016_12_29_2.png&#34;&gt;&lt;img src=&#34;images/2016_12_29_2-300x238.png&#34; alt=&#34;2016_12_29_2&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Flush with success wine may have been consumed.&lt;/p&gt;
&lt;h1 id=&#34;day-2---algorithms-and-structures&#34;&gt;Day 2 - Algorithms and structures&lt;/h1&gt;
&lt;p&gt;Sitting at the desk with a slightly groggy head - which the wine had nothing to do with, nothing, not a damn thing - I started to ponder how to arrange and manage chunks.  The memory requirements for voxels can be huge. Imagine that each of your voxels takes 16 bytes to store it and that your landscape is a modest sized 1024 voxels wide, 32 voxels high, and 1024 voxels deep - that&amp;rsquo;s 536,870,912 bytes, better known as 512Mb, of storage needed before you even account for rendering geometry.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s clear you&amp;rsquo;re going to need someway of managing that and for practical usage compressing and/or virtualising it.&lt;/p&gt;
&lt;p&gt;I came up with, and by came up with I mean I&amp;rsquo;d read somewhere and recalled it, the concept of a chunk manager (this came up somewhere in my research), chunk factories (this didn&amp;rsquo;t come up in my research but seems a logical extension) and a chunk optimiser (not yet implemented!).&lt;/p&gt;
&lt;p&gt;The main decision here was how low to go - so to speak. The most optimal route likely involves getting the STL out of the way, pre-allocating memory and organising it in a very specific manner. However that also seemed the route most likely to feel like having your teeth pulled. In the end I decided to go with the path of least resistance and mostly used the std::vector class with pre-allocated sizes where known.&lt;/p&gt;
&lt;p&gt;Nothing to see from my efforts yet except the cube still shows but using the new code. Which while not worth a screenshot was a result!&lt;/p&gt;
&lt;p&gt;With that out the way I needed a perlin noise generator to implement as part of one of my new fangled chunk factories. I&amp;rsquo;ve written one before for a lava type effect at some point back when you had to find the algorithm in a book and figure out how to write it yourself. Having no wish to repeat that painful experience I quickly found a &lt;a href=&#34;http://flafla2.github.io/2014/08/09/perlinnoise.html&#34;&gt;great post and gist&lt;/a&gt; on GitHub written in C# and converted it to C++.&lt;/p&gt;
&lt;p&gt;I hit F5, Visual Studio whirred away for a few seconds, and to my utter amazement my good run of luck continued and I had an ugly but working voxel landscape.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.azurefromthetrenches.com/wp-content/uploads/2017/01/2016_12_30_2.png&#34;&gt;&lt;img src=&#34;images/2016_12_30_2-300x231.png&#34; alt=&#34;2016_12_30_2&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Good times. And good times when you&amp;rsquo;re on your Christmas break deserve wine.&lt;/p&gt;
&lt;h1 id=&#34;day-3---this-is-far-from-light-speed&#34;&gt;Day 3 - This is far from light speed&lt;/h1&gt;
&lt;p&gt;Without any lighting it was hard to really get a sense of the terrain in the landscape that I could see but before I could do something about that I needed to address some performance issues. Rendering was fine - I was cruising at a vertical refresh locked 60 frames per second. When rendering started. Problem was that even on my 4.5GHz 6700K i7 it took about 90 seconds to generate the voxels and the initial geometry.&lt;/p&gt;
&lt;p&gt;Normally I&amp;rsquo;d advocate measuring before going any further but the issue was self evidently (famous last words&amp;hellip;. but not this time) in a fairly narrow section of code so step 1 was a code review which quickly highlighted a moment of stupidity from the previous day: I was calling the perlin noise function 16 times more than I needed to.&lt;/p&gt;
&lt;p&gt;I could see from Visual Studio that memory allocation was also sub-optimal as it was occurring on an &amp;ldquo;as needed&amp;rdquo; basis but as previously noted I&amp;rsquo;m favouring clarity and simplicity over gnarly. However Visual Studio was also highlighting another issue - my CPU was only being used at around 15%. The code is single threaded and I&amp;rsquo;ve got 4 cores capable of supporting 8 threads.&lt;/p&gt;
&lt;p&gt;Haha thinks me with my C# head on. This is eminently parallelisable (the clues being in the names &amp;ldquo;chunk&amp;rdquo; and perlin noise &amp;ldquo;function&amp;rdquo;) and C++ must have a decent task / parallel library by now. Oh yes. Futures. They&amp;rsquo;ll do it.&lt;/p&gt;
&lt;p&gt;Oh boy. Oh boy oh boy. That hurt. Converting the code to use lambda&amp;rsquo;s and futures was straightforward but then the issues began - turns out there is not yet an equivalent for something like Task.WhenAll in the C++ standard library. I got there in the end after a bit of a deep dive into modern C++ threading concepts (I really didn&amp;rsquo;t want to use the OS primitives directly as I want to make this multi platform) and solved this with locks and condition_variables.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve tested it on a variety of systems (with different performance characteristics and cores available) and it hasn&amp;rsquo;t crashed or done anything unpredictable on a variety of different systems but I&amp;rsquo;m sure there is something wrong with this code so if any C++ experts read this I&amp;rsquo;d love some pointers.&lt;/p&gt;
&lt;p&gt;So now its quicker, about 6 or 7 seconds to generate a respectably sized landscape, but it looks the same and most of the day has gone.&lt;/p&gt;
&lt;p&gt;Fortunately adding basic lighting proved much simpler. Simple voxels, cubes, are pretty simple to light as the &lt;a href=&#34;https://en.wikipedia.org/wiki/Normal_(geometry)&#34;&gt;normals&lt;/a&gt; are somewhat obvious and there is plenty of documented examples of using vertex normals inside vertex and fragment shaders to apply the appropriate colouring. A couple of hours later I had ambient, diffuse, and specular lighting up and running and could turn off my dodgy half shading.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.azurefromthetrenches.com/wp-content/uploads/2017/01/2016_12_31_2.png&#34;&gt;&lt;img src=&#34;images/2016_12_31_2-300x225.png&#34; alt=&#34;2016_12_31_2&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A great way to go into New Years Eve and definitely earning of a celebratory brandy. Slightly warmed and in an appropriate glass of course.&lt;/p&gt;
&lt;h1 id=&#34;day-4---this-code-stinks&#34;&gt;Day 4 - This code stinks&lt;/h1&gt;
&lt;p&gt;Old Man Randall (me) fell asleep long before midnight and so I woke up fresh and eager on New Years Day to do some more work. I&amp;rsquo;d had the brainwave a few days ago to use photographs as the source for landscape height maps using the brightness (whiteness in black and white photographs, calculated perceived brightness in colour photographs) of each pixel to build an appropriately high stack of voxels.&lt;/p&gt;
&lt;p&gt;However before I could do anything else the code really needed some work. It had the distinctive whiff of a codebase in which many new things had been tried with little idea of what would work or not. Structure had become scrambled, inconsistencies were everywhere, and commented out attempts at different approaches abound. Not to mention I&amp;rsquo;d built everything into a single executable with no attempt to separate the engine from it&amp;rsquo;s usage.&lt;/p&gt;
&lt;p&gt;A few hours later and although still not perfect I had something that was workable and set about implementing my voxel photographs starting with black and white. Being a supreme narcissist I selected a photograph of myself from my &lt;a href=&#34;http://flickr.com/capnkroaker&#34;&gt;flickr library&lt;/a&gt; and busied myself trying to load it. This proved to be the hardest part - I&amp;rsquo;d forgotten how painful C++ and libraries without package managers can be (is there a package manager for C++?) with chains of dependencies.&lt;/p&gt;
&lt;p&gt;In the end I realised that &lt;a href=&#34;http://cimg.eu/&#34;&gt;cimg&lt;/a&gt; could load bmp files without any dependencies and implemented a new chunk factory that used that to scan the pixels and create voxels based on their whiteness.&lt;/p&gt;
&lt;p&gt;This worked a treat and I captured the video of me moving around the &amp;ldquo;landscape&amp;rdquo; that appears at the top of this post.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.azurefromthetrenches.com/wp-content/uploads/2017/01/2017_01_01_1.png&#34;&gt;&lt;img src=&#34;images/2017_01_01_1-300x225.png&#34; alt=&#34;2017_01_01_1&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A great way to finish off and in a burst of enthusiasm I quickly sent the link to my fans. Or fan. By which I mean my mum. No celebratory brandy sadly as structured cycling training has restarted but happiness abounds.&lt;/p&gt;
&lt;h1 id=&#34;conclusionsand-what-next&#34;&gt;Conclusions and what next?&lt;/h1&gt;
&lt;p&gt;A few things struck me throughout this short project that I think are worth collecting and reflecting on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The modern Internet truly is a wonderful thing. The last time I was coding in C++ (about 1998) it wasn&amp;rsquo;t the Internet of today and things were so much harder. It&amp;rsquo;s an amazing resource.&lt;/li&gt;
&lt;li&gt;The development community is amazing. There are so many people giving their time and expertise away for free in the form of blog posts, articles, and code that unless you&amp;rsquo;re doing something really on the bleeding edge there is someone, somewhere, who can help you.&lt;/li&gt;
&lt;li&gt;Wow C++ has changed - for the better. It&amp;rsquo;s still complicated but there&amp;rsquo;s a lot more available out of the box to help you not make simple mistakes.&lt;/li&gt;
&lt;li&gt;GPUs are incredible number crunching machines. I&amp;rsquo;m fortunate enough to have a pretty top notch GPU in my desktop (a 980Ti) but the sheer number of computations being performed to render, in 60 fps, some of the scenes I threw at this code is astounding. I knew in theory the capabilities of GPUs but didn&amp;rsquo;t &amp;ldquo;feel&amp;rdquo; it - I&amp;rsquo;m now interested where I can apply this power elsewhere.&lt;/li&gt;
&lt;li&gt;Iterative approaches are great for tackling something you don&amp;rsquo;t know much about. I just kept setting myself mini-milestones and evolving the code, not being afraid to do something really badly at first. By setting small milestones you get a series of positive kicks that keeps you motivated through the hard times.&lt;/li&gt;
&lt;li&gt;Coding, particularly when it involves learning, is fun. It really is. I still love it. I&amp;rsquo;ve been coding for around 34 years now, since I was 6 or 7, and it still gives me an enormous thrill. My passion for it at its core is undiminished.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As for this project and what I might do next. I&amp;rsquo;ve got a couple of simple projects in mind I&amp;rsquo;d like to use it with and a small shopping list of things I think will be interesting to implement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mac / *nix support&lt;/li&gt;
&lt;li&gt;General optimisation. I&amp;rsquo;ve only done the really low hanging fruit so far and there is scope for improvement everywhere.&lt;/li&gt;
&lt;li&gt;Voxel sprite support&lt;/li&gt;
&lt;li&gt;Shadows&lt;/li&gt;
&lt;li&gt;Skybox / fog support&lt;/li&gt;
&lt;li&gt;Paging / virtualisation - with this in mind this is one of the main reasons that scenes are constructed using the chunk factory&lt;/li&gt;
&lt;li&gt;Level of detail support&lt;/li&gt;
&lt;li&gt;Simple physics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whether or when I&amp;rsquo;ll get time to do this I&amp;rsquo;d rather not say, I have a big product launch on the horizon and a React book to write, but I&amp;rsquo;ve had so much fun that I&amp;rsquo;m likely to keep tinkering when I can.&lt;/p&gt;
&lt;h1 id=&#34;addendum-2021&#34;&gt;Addendum (2021)&lt;/h1&gt;
&lt;p&gt;Following this post I did some work on collision detection and created a few more demos. I rather liked this city building demo that I recorded at the time.&lt;/p&gt;

&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://player.vimeo.com/video/537212611&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;vimeo video&#34; webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title></title>
      <link>https://www.jamesdrandall.com/8-bit-golf/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/8-bit-golf/</guid>
      <description>&lt;!DOCTYPE html&gt;&lt;html lang=&#34;en&#34; class=&#34;h-full bg-gray-50 antialiased __variable_3b0aa8&#34;&gt;&lt;head&gt;&lt;meta charSet=&#34;utf-8&#34;/&gt;&lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1&#34;/&gt;&lt;link rel=&#34;preload&#34; as=&#34;font&#34; href=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/media/a34f9d1faa5f3315-s.p.woff2&#34; crossorigin=&#34;&#34; type=&#34;font/woff2&#34;/&gt;&lt;link rel=&#34;stylesheet&#34; href=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/css/5de637513889e238.css&#34; data-precedence=&#34;next&#34;/&gt;&lt;link rel=&#34;preload&#34; href=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/chunks/webpack-b14a4650839f6af3.js&#34; as=&#34;script&#34; fetchPriority=&#34;low&#34;/&gt;&lt;script src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/chunks/fd9d1056-c2ad140a17e094a3.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/chunks/596-ab37964487d4ee07.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/chunks/main-app-0ecb18411d336eb6.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;title&gt;VoxEd - Elegant voxel sprite editing&lt;/title&gt;&lt;meta name=&#34;description&#34; content=&#34;Overview of VoxEd features.&#34;/&gt;&lt;link rel=&#34;icon&#34; href=&#34;https://www.jamesdrandall.com/8-bit-golf/favicon.ico&#34; type=&#34;image/x-icon&#34; sizes=&#34;16x16&#34;/&gt;&lt;meta name=&#34;next-size-adjust&#34;/&gt;&lt;script src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js&#34; noModule=&#34;&#34;&gt;&lt;/script&gt;&lt;/head&gt;&lt;body class=&#34;flex h-full flex-col font-pixel&#34;&gt;&lt;div class=&#34;flex min-h-full flex-col&#34;&gt;&lt;header&gt;&lt;nav&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative z-50 flex justify-between py-8&#34;&gt;&lt;div class=&#34;relative z-10 flex items-center gap-16&#34;&gt;&lt;a aria-label=&#34;Home&#34; href=&#34;https://www.jamesdrandall.com/8-bit-golf&#34;&gt;&lt;div class=&#34;flex flex-row items-center&#34;&gt;&lt;img alt=&#34;8-bit Golf&#34; loading=&#34;lazy&#34; width=&#34;80&#34; height=&#34;80&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/media/icon.691872cb.png&#34;/&gt;&lt;span class=&#34;spred-logo-text ml-2&#34;&gt;8-bit Golf&lt;/span&gt;&lt;/div&gt;&lt;/a&gt;&lt;div class=&#34;hidden lg:flex lg:gap-10&#34;&gt;&lt;a href=&#34;#faqs&#34; class=&#34;relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors delay-150 hover:text-gray-900 hover:delay-0&#34;&gt;&lt;span class=&#34;relative z-10&#34;&gt;FAQs&lt;/span&gt;&lt;/a&gt;&lt;a href=&#34;#privacy&#34; class=&#34;relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors delay-150 hover:text-gray-900 hover:delay-0&#34;&gt;&lt;span class=&#34;relative z-10&#34;&gt;Privacy&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;flex items-center gap-6&#34;&gt;&lt;div class=&#34;lg:hidden&#34; data-headlessui-state=&#34;&#34;&gt;&lt;button class=&#34;relative z-10 -m-2 inline-flex items-center rounded-lg stroke-gray-900 p-2 hover:bg-gray-200/50 hover:stroke-gray-600 active:stroke-gray-900 ui-not-focus-visible:outline-none&#34; aria-label=&#34;Toggle site navigation&#34; type=&#34;button&#34; aria-expanded=&#34;false&#34; data-headlessui-state=&#34;&#34;&gt;&lt;svg viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; aria-hidden=&#34;true&#34; class=&#34;h-6 w-6&#34;&gt;&lt;path d=&#34;M5 6h14M5 18h14M5 12h14&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;div style=&#34;position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&#34;flex-auto&#34;&gt;&lt;div class=&#34;overflow-hidden pb-20 sm:pb-32 lg:pb-16 xl:pb-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;lg:grid lg:grid-cols-12&#34;&gt;&lt;div class=&#34;lg:col-span-10 lg:col-start-2 lg:mb-10&#34;&gt;&lt;img alt=&#34;SprEd&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/media/heroImage.dd930745.jpg&#34;/&gt;&lt;/div&gt;&lt;div class=&#34;lg:col-span-10 lg:col-start-2&#34;&gt;&lt;h1 class=&#34;text-center text-2xl font-medium tracking-tight text-gray-900&#34;&gt;A loving homage to the classic golf games of the 1980s with simple gameplay and period visuals&lt;/h1&gt;&lt;/div&gt;&lt;div class=&#34;mt-6 flex flex-wrap justify-center gap-x-6 lg:col-span-12&#34;&gt;&lt;a aria-label=&#34;Download on the App Store&#34; class=&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900&#34; href=&#34;https://apps.apple.com/us/app/8-bit-golf/id6664070211&#34;&gt;&lt;svg viewBox=&#34;0 0 120 40&#34; aria-hidden=&#34;true&#34; class=&#34;h-10&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;section id=&#34;free&#34; aria-label=&#34;Features of the game&#34; class=&#34;bg-white py-16 sm:py-12&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto sm:text-center&#34;&gt;&lt;h2 class=&#34;text-2xl font-medium tracking-tight text-black&#34;&gt;Choose from a variety of rendering styles for maximum nostalgia&lt;/h2&gt;&lt;/div&gt;&lt;div class=&#34;lg:grid lg:grid-cols-12 lg:gap-8&#34;&gt;&lt;div class=&#34;mt-4 lg:col-span-3 lg:mt-16&#34;&gt;&lt;img alt=&#34;PC (EGA)&#34; loading=&#34;lazy&#34; width=&#34;1280&#34; height=&#34;800&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/media/pc.b27c3689.jpg&#34;/&gt;&lt;p class=&#34;mt-2 text-center text-lg text-black&#34;&gt;PC (EGA)&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:col-span-3 lg:mt-16&#34;&gt;&lt;img alt=&#34;BBC Micro&#34; loading=&#34;lazy&#34; width=&#34;1280&#34; height=&#34;800&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/media/bbc.21c36e0e.jpg&#34;/&gt;&lt;p class=&#34;mt-2 text-center text-lg text-black&#34;&gt;BBC Micro&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:col-span-3 lg:mt-16&#34;&gt;&lt;img alt=&#34;Acorn Archimedes&#34; loading=&#34;lazy&#34; width=&#34;1280&#34; height=&#34;800&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/media/archimedes.d7f8b1aa.jpg&#34;/&gt;&lt;p class=&#34;mt-2 text-center text-lg text-black&#34;&gt;Archimedes&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:col-span-3 lg:mt-16&#34;&gt;&lt;img alt=&#34;Commodore 64&#34; loading=&#34;lazy&#34; width=&#34;1280&#34; height=&#34;800&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/media/c64.c067dded.jpg&#34;/&gt;&lt;p class=&#34;mt-2 text-center text-lg text-black&#34;&gt;Commodore 64&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section id=&#34;free&#34; aria-label=&#34;Features of the game&#34; class=&#34;bg-white py-16 sm:py-12&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto sm:text-center&#34;&gt;&lt;h2 class=&#34;text-2xl font-medium tracking-tight text-black&#34;&gt;Simple gameplay with 3 built in courses, or use the editor to recreate your local holes&lt;/h2&gt;&lt;/div&gt;&lt;div class=&#34;lg:grid lg:grid-cols-12 lg:gap-8&#34;&gt;&lt;div class=&#34;mt-4 lg:col-span-3 lg:mt-16&#34;&gt;&lt;img alt=&#34;Archimedes&#34; loading=&#34;lazy&#34; width=&#34;1280&#34; height=&#34;800&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/media/archimedes2.3c3e2946.jpg&#34;/&gt;&lt;p class=&#34;mt-2 text-center text-lg text-black&#34;&gt;Archimedes&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:col-span-3 lg:mt-16&#34;&gt;&lt;img alt=&#34;Map&#34; loading=&#34;lazy&#34; width=&#34;1280&#34; height=&#34;800&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/media/maps.5bd6effd.jpg&#34;/&gt;&lt;p class=&#34;mt-2 text-center text-lg text-black&#34;&gt;Map&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:col-span-3 lg:mt-16&#34;&gt;&lt;img alt=&#34;Clubs&#34; loading=&#34;lazy&#34; width=&#34;1280&#34; height=&#34;800&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/media/clubs.b61d2253.jpg&#34;/&gt;&lt;p class=&#34;mt-2 text-center text-lg text-black&#34;&gt;Clubs&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:col-span-3 lg:mt-16&#34;&gt;&lt;img alt=&#34;Editor&#34; loading=&#34;lazy&#34; width=&#34;1280&#34; height=&#34;800&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/media/editor.e9bd8bbe.jpg&#34;/&gt;&lt;p class=&#34;mt-2 text-center text-lg text-black&#34;&gt;Editor&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/div&gt;&lt;section id=&#34;faqs&#34; aria-labelledby=&#34;faqs-title&#34; class=&#34;border-t border-gray-200 bg-white py-16 sm:py-12&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto&#34;&gt;&lt;h2 id=&#34;faqs-title&#34; class=&#34;text-2xl font-medium tracking-tight text-black&#34;&gt;Frequently asked questions&lt;/h2&gt;&lt;/div&gt;&lt;ul role=&#34;list&#34; class=&#34;mx-auto mt-16 gap-8 sm:mt-20&#34;&gt;&lt;li&gt;&lt;ul role=&#34;list&#34; class=&#34;space-y-10&#34;&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-900&#34;&gt;Will you make the game available on other platforms?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-700&#34;&gt;Maybe. I wrote it for fun but if people enjoy it on iOS and Mac I&amp;#x27;ll certainly consider it.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-900&#34;&gt;I think I&amp;#x27;ve found a bug or have an idea for an improvement, what do I do?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-700&#34;&gt;You can send an email to me at support@accidentalfish.com and I&amp;#x27;ll see if I can help.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-900&#34;&gt;Are you planning on adding GameKit support?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-700&#34;&gt;Yes probably, scoreboards and online multiplayer would be neat.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-900&#34;&gt;How about AI players?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-700&#34;&gt;Not sure - I always played these games multi-player but I&amp;#x27;ll think about it for sure.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/section&gt;&lt;section id=&#34;privacy&#34; aria-label=&#34;Privacy information&#34; class=&#34;bg-gray-900  py-20 sm:py-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto&#34;&gt;&lt;h2 class=&#34;text-2xl font-medium tracking-tight text-gray-200&#34;&gt;What about Privacy?&lt;/h2&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;Our privacy policy is really simple. We don&amp;#x27;t think you should be spied upon and tracked all over the web or on your devices and so we don&amp;#x27;t collect any information about you, or your usage of the app, at all.&lt;/p&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;If you contact us by email then we will never share your email address with a third party or use it for any purpose other than responding to your email.&lt;/p&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;And finally this website contains no tracking cookies or analytics of any kind. We don&amp;#x27;t want to know who you are or what you do on the web. We just want you to enjoy using VoxEd.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/main&gt;&lt;footer class=&#34;border-t border-gray-200&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;flex flex-col items-center border-t border-gray-200 py-12 md:flex-row-reverse md:justify-between&#34;&gt;&lt;p class=&#34;mt-6 text-sm text-gray-500 md:mt-0&#34;&gt;© Copyright &lt;!-- --&gt;2025&lt;!-- --&gt;. All rights reserved.&lt;/p&gt;&lt;a aria-label=&#34;Download on the App Store&#34; class=&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900&#34; href=&#34;https://apps.apple.com/us/app/8-bit-golf/id6664070211&#34;&gt;&lt;svg viewBox=&#34;0 0 120 40&#34; aria-hidden=&#34;true&#34; class=&#34;h-10&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/footer&gt;&lt;/div&gt;&lt;script src=&#34;https://www.jamesdrandall.com/8-bit-golf/_next/static/chunks/webpack-b14a4650839f6af3.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;script&gt;(self.__next_f=self.__next_f||[]).push([0])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;1:HL[\&#34;/8-bit-golf/_next/static/media/a34f9d1faa5f3315-s.p.woff2\&#34;,{\&#34;as\&#34;:\&#34;font\&#34;,\&#34;type\&#34;:\&#34;font/woff2\&#34;}]\n2:HL[\&#34;/8-bit-golf/_next/static/css/5de637513889e238.css\&#34;,{\&#34;as\&#34;:\&#34;style\&#34;}]\n0:\&#34;$L3\&#34;\n&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;4:I{\&#34;id\&#34;:7948,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-b14a4650839f6af3.js\&#34;,\&#34;971:static/chunks/fd9d1056-c2ad140a17e094a3.js\&#34;,\&#34;596:static/chunks/596-ab37964487d4ee07.js\&#34;],\&#34;name\&#34;:\&#34;default\&#34;,\&#34;async\&#34;:false}\n6:I{\&#34;id\&#34;:6628,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-b14a4650839f6af3.js\&#34;,\&#34;971:static/chunks/fd9d1056-c2ad140a17e094a3.js\&#34;,\&#34;596:static/chunks/596-ab37964487d4ee07.js\&#34;],\&#34;name\&#34;:\&#34;\&#34;,\&#34;async\&#34;:false}\n7:I{\&#34;id\&#34;:7767,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-b14a4650839f6af3.js\&#34;,\&#34;971:static/chunks/fd9d1056-c2ad140a17e094a3.js\&#34;,\&#34;5&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;96:static/chunks/596-ab37964487d4ee07.js\&#34;],\&#34;name\&#34;:\&#34;default\&#34;,\&#34;async\&#34;:false}\n8:I{\&#34;id\&#34;:7920,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-b14a4650839f6af3.js\&#34;,\&#34;971:static/chunks/fd9d1056-c2ad140a17e094a3.js\&#34;,\&#34;596:static/chunks/596-ab37964487d4ee07.js\&#34;],\&#34;name\&#34;:\&#34;default\&#34;,\&#34;async\&#34;:false}\n9:I{\&#34;id\&#34;:6111,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-7999d8d8e535fc21.js\&#34;,\&#34;126:static/chunks/126-8b5d834a65f65a9a.js\&#34;,\&#34;964:static/chunks/964-3d5c6dbfe39ac8ea.js\&#34;,\&#34;95:static/chunks/app/(main)/layout-46dde01c9f0d3a3e.js\&#34;],\&#34;name\&#34;:\&#34;Header\&#34;,\&#34;async\&#34;:f&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;alse}\na:I{\&#34;id\&#34;:6685,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-7999d8d8e535fc21.js\&#34;,\&#34;126:static/chunks/126-8b5d834a65f65a9a.js\&#34;,\&#34;964:static/chunks/964-3d5c6dbfe39ac8ea.js\&#34;,\&#34;95:static/chunks/app/(main)/layout-46dde01c9f0d3a3e.js\&#34;],\&#34;name\&#34;:\&#34;\&#34;,\&#34;async\&#34;:false}\nd:I{\&#34;id\&#34;:3222,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-7999d8d8e535fc21.js\&#34;,\&#34;974:static/chunks/app/(main)/page-5b699ba716d17952.js\&#34;],\&#34;name\&#34;:\&#34;Image\&#34;,\&#34;async\&#34;:false}\nf:I{\&#34;id\&#34;:2015,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-7999d8d8e535fc21.js\&#34;,\&#34;974:static/chunks/app/(main)/page-5b699ba71&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;6d17952.js\&#34;],\&#34;name\&#34;:\&#34;PrimaryFeatures\&#34;,\&#34;async\&#34;:false}\n10:I{\&#34;id\&#34;:3852,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-7999d8d8e535fc21.js\&#34;,\&#34;974:static/chunks/app/(main)/page-5b699ba716d17952.js\&#34;],\&#34;name\&#34;:\&#34;Privacy\&#34;,\&#34;async\&#34;:false}\nb:T14cb,&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;e:T14cb,&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;11:T14cb,&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;3:[[[\&#34;$\&#34;,\&#34;link\&#34;,\&#34;0\&#34;,{\&#34;rel\&#34;:\&#34;stylesheet\&#34;,\&#34;href\&#34;:\&#34;/8-bit-golf/_next/static/css/5de637513889e238.css\&#34;,\&#34;precedence\&#34;:\&#34;next\&#34;}]],[\&#34;$\&#34;,\&#34;$L4\&#34;,null,{\&#34;buildId\&#34;:\&#34;rsmwBTzav8wDrgkK-KM7-\&#34;,\&#34;assetPrefix\&#34;:\&#34;/8-bit-golf\&#34;,\&#34;initialCanonicalUrl\&#34;:\&#34;/\&#34;,\&#34;initialTree\&#34;:[\&#34;\&#34;,{\&#34;children\&#34;:[\&#34;(main)\&#34;,{\&#34;children\&#34;:[\&#34;__PAGE__\&#34;,{}]}]},\&#34;$undefined\&#34;,\&#34;$undefined\&#34;,true],\&#34;initialHead\&#34;:\&#34;$L5\&#34;,\&#34;globalErrorComponent\&#34;:\&#34;$6\&#34;,\&#34;children\&#34;:[null,[\&#34;$\&#34;,\&#34;html\&#34;,null,{\&#34;lang\&#34;:\&#34;en\&#34;,\&#34;className\&#34;:\&#34;h-full bg-gray-50 antialiased __variable_3b0aa8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;body\&#34;,null,{\&#34;className\&#34;:\&#34;flex h-full flex-col font-pixel\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;flex min-h-full flex-col\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$L7\&#34;,null,{\&#34;parallelRouterKey\&#34;:\&#34;children\&#34;,\&#34;segmentPath\&#34;:[\&#34;children\&#34;],\&#34;error\&#34;:\&#34;$undefined\&#34;,\&#34;errorStyles\&#34;:\&#34;$undefined\&#34;,\&#34;loading\&#34;:\&#34;$undefined\&#34;,\&#34;loadingStyles\&#34;:\&#34;$undefined\&#34;,\&#34;hasLoading\&#34;:false,\&#34;template\&#34;:[\&#34;$\&#34;,\&#34;$L8\&#34;,null,{}],\&#34;templateStyles\&#34;:\&#34;$undefined\&#34;,\&#34;notFound\&#34;:[[\&#34;$\&#34;,\&#34;$L9\&#34;,null,{}],[\&#34;$\&#34;,\&#34;main\&#34;,null,{\&#34;className\&#34;:\&#34;flex-auto\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative isolate flex h-full flex-col items-center justify-center py-20 text-center sm:py-32\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 1090 1090\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;fill\&#34;:\&#34;none\&#34;,\&#34;preserveAspectRatio\&#34;:\&#34;none\&#34;,\&#34;className\&#34;:\&#34;absolute left-1/2 top-1/2 -z-10 mt-44 w-[68.125rem] -translate-x-1/2 -translate-y-1/2 stroke-gray-300/30 [mask-image:linear-gradient(to_bottom,white_20%,transparent_75%)]\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;544.5\&#34;}],[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;480.5\&#34;}],[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;416.5\&#34;}],[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;352.5\&#34;}]]}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;text-sm font-semibold text-gray-900\&#34;,\&#34;children\&#34;:\&#34;404\&#34;}],[\&#34;$\&#34;,\&#34;h1\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-3xl font-medium tracking-tight text-gray-900\&#34;,\&#34;children\&#34;:\&#34;Page not found\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-lg text-gray-600\&#34;,\&#34;children\&#34;:\&#34;Sorry, we couldn’t find the page you’re looking for.\&#34;}],[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;className\&#34;:\&#34;inline-flex justify-center rounded-lg border py-[calc(theme(spacing.2)-1px)] px-[calc(theme(spacing.3)-1px)] text-sm outline-2 outline-offset-2 transition-colors border-gray-300 text-gray-700 hover:border-gray-400 active:bg-gray-100 active:text-gray-700/80 mt-8\&#34;,\&#34;href\&#34;:\&#34;/\&#34;,\&#34;children\&#34;:\&#34;Go back home\&#34;}]]}]}],[\&#34;$\&#34;,\&#34;footer\&#34;,null,{\&#34;className\&#34;:\&#34;border-t border-gray-200\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;flex flex-col items-center border-t border-gray-200 py-12 md:flex-row-reverse md:justify-between\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-6 text-sm text-gray-500 md:mt-0\&#34;,\&#34;children\&#34;:[\&#34;© Copyright \&#34;,2025,\&#34;. All rights reserved.\&#34;]}],[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;href\&#34;:\&#34;https://apps.apple.com/us/app/8-bit-golf/id6664070211\&#34;,\&#34;aria-label\&#34;:\&#34;Download on the App Store\&#34;,\&#34;className\&#34;:\&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 120 40\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;className\&#34;:\&#34;h-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;path\&#34;,null,{\&#34;fill\&#34;:\&#34;currentColor\&#34;,\&#34;d\&#34;:\&#34;$b\&#34;}]}]}]]}]}]}]],\&#34;notFoundStyles\&#34;:[],\&#34;childProp\&#34;:{\&#34;current\&#34;:[null,[[\&#34;$\&#34;,\&#34;$L9\&#34;,null,{}],[\&#34;$\&#34;,\&#34;main\&#34;,null,{\&#34;className\&#34;:\&#34;flex-auto\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$L7\&#34;,null,{\&#34;parallelRouterKey\&#34;:\&#34;children\&#34;,\&#34;segmentPath\&#34;:[\&#34;children\&#34;,\&#34;(main)\&#34;,\&#34;children\&#34;],\&#34;error\&#34;:\&#34;$undefined\&#34;,\&#34;errorStyles\&#34;:\&#34;$undefined\&#34;,\&#34;loading\&#34;:\&#34;$undefined\&#34;,\&#34;loadingStyles\&#34;:\&#34;$undefined\&#34;,\&#34;hasLoading\&#34;:false,\&#34;template\&#34;:[\&#34;$\&#34;,\&#34;$L8\&#34;,null,{}],\&#34;templateStyles\&#34;:\&#34;$undefined\&#34;,\&#34;notFound\&#34;:[[\&#34;$\&#34;,\&#34;title\&#34;,null,{\&#34;children\&#34;:\&#34;404: This page could not be found.\&#34;}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;style\&#34;:{\&#34;fontFamily\&#34;:\&#34;system-ui,\\\&#34;Segoe UI\\\&#34;,Roboto,Helvetica,Arial,sans-serif,\\\&#34;Apple Color Emoji\\\&#34;,\\\&#34;Segoe UI Emoji\\\&#34;\&#34;,\&#34;height\&#34;:\&#34;100vh\&#34;,\&#34;textAlign\&#34;:\&#34;center\&#34;,\&#34;display\&#34;:\&#34;flex\&#34;,\&#34;flexDirection\&#34;:\&#34;column\&#34;,\&#34;alignItems\&#34;:\&#34;center\&#34;,\&#34;justifyContent\&#34;:\&#34;center\&#34;},\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;style\&#34;,null,{\&#34;dangerouslySetInnerHTML\&#34;:{\&#34;__html\&#34;:\&#34;body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\&#34;}}],[\&#34;$\&#34;,\&#34;h1\&#34;,null,{\&#34;className\&#34;:\&#34;next-error-h1\&#34;,\&#34;style\&#34;:{\&#34;display\&#34;:\&#34;inline-block\&#34;,\&#34;margin\&#34;:\&#34;0 20px 0 0\&#34;,\&#34;padding\&#34;:\&#34;0 23px 0 0\&#34;,\&#34;fontSize\&#34;:24,\&#34;fontWeight\&#34;:500,\&#34;verticalAlign\&#34;:\&#34;top\&#34;,\&#34;lineHeight\&#34;:\&#34;49px\&#34;},\&#34;children\&#34;:\&#34;404\&#34;}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;style\&#34;:{\&#34;display\&#34;:\&#34;inline-block\&#34;},\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;h2\&#34;,null,{\&#34;style\&#34;:{\&#34;fontSize\&#34;:14,\&#34;fontWeight\&#34;:400,\&#34;lineHeight\&#34;:\&#34;49px\&#34;,\&#34;margin\&#34;:0},\&#34;children\&#34;:\&#34;This page could not be found.\&#34;}]}]]}]}]],\&#34;notFoundStyles\&#34;:[],\&#34;childProp\&#34;:{\&#34;current\&#34;:[\&#34;$Lc\&#34;,[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;overflow-hidden pb-20 sm:pb-32 lg:pb-16 xl:pb-16\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:grid lg:grid-cols-12\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:col-span-10 lg:col-start-2 lg:mb-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$Ld\&#34;,null,{\&#34;src\&#34;:{\&#34;src\&#34;:\&#34;/8-bit-golf/_next/static/media/heroImage.dd930745.jpg\&#34;,\&#34;height\&#34;:2290,\&#34;width\&#34;:2960,\&#34;blurDataURL\&#34;:\&#34;data:image/jpeg;base64,/9j/2wBDAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9Pjv/2wBDAQoLCw4NDhwQEBw7KCIoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozv/wAARCAAGAAgDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAX/xAAdEAABBAIDAAAAAAAAAAAAAAABAAIDEQQhFDFR/8QAFQEBAQAAAAAAAAAAAAAAAAAAAwT/xAAbEQACAwADAAAAAAAAAAAAAAABAgARIQNBUf/aAAwDAQACEQMRAD8AgyZknEcTHG2qFNHexu/URFOrMSbJNekweN2UsFzesn//2Q==\&#34;,\&#34;blurWidth\&#34;:8,\&#34;blurHeight\&#34;:6},\&#34;alt\&#34;:\&#34;SprEd\&#34;}]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:col-span-10 lg:col-start-2\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;h1\&#34;,null,{\&#34;className\&#34;:\&#34;text-center text-2xl font-medium tracking-tight text-gray-900\&#34;,\&#34;children\&#34;:\&#34;A loving homage to the classic golf games of the 1980s with simple gameplay and period visuals\&#34;}]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mt-6 flex flex-wrap justify-center gap-x-6 lg:col-span-12\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;href\&#34;:\&#34;https://apps.apple.com/us/app/8-bit-golf/id6664070211\&#34;,\&#34;aria-label\&#34;:\&#34;Download on the App Store\&#34;,\&#34;className\&#34;:\&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 120 40\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;className\&#34;:\&#34;h-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;path\&#34;,null,{\&#34;fill\&#34;:\&#34;currentColor\&#34;,\&#34;d\&#34;:\&#34;$e\&#34;}]}]}]}]]}]}]}],[\&#34;$\&#34;,\&#34;$Lf\&#34;,null,{}],[\&#34;$\&#34;,\&#34;section\&#34;,null,{\&#34;id\&#34;:\&#34;faqs\&#34;,\&#34;aria-labelledby\&#34;:\&#34;faqs-title\&#34;,\&#34;className\&#34;:\&#34;border-t border-gray-200 bg-white py-16 sm:py-12\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;h2\&#34;,null,{\&#34;id\&#34;:\&#34;faqs-title\&#34;,\&#34;className\&#34;:\&#34;text-2xl font-medium tracking-tight text-black\&#34;,\&#34;children\&#34;:\&#34;Frequently asked questions\&#34;}]}],[\&#34;$\&#34;,\&#34;ul\&#34;,null,{\&#34;role\&#34;:\&#34;list\&#34;,\&#34;className\&#34;:\&#34;mx-auto mt-16 gap-8 sm:mt-20\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;0\&#34;,{\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;ul\&#34;,null,{\&#34;role\&#34;:\&#34;list\&#34;,\&#34;className\&#34;:\&#34;space-y-10\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;0\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-900\&#34;,\&#34;children\&#34;:\&#34;Will you make the game available on other platforms?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-700\&#34;,\&#34;children\&#34;:\&#34;Maybe. I wrote it for fun but if people enjoy it on iOS and Mac I&#39;ll certainly consider it.\&#34;}]]}],[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;1\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-900\&#34;,\&#34;children\&#34;:\&#34;I think I&#39;ve found a bug or have an idea for an improvement, what do I do?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-700\&#34;,\&#34;children\&#34;:\&#34;You can send an email to me at support@accidentalfish.com and I&#39;ll see if I can help.\&#34;}]]}],[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;2\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-900\&#34;,\&#34;children\&#34;:\&#34;Are you planning on adding GameKit support?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-700\&#34;,\&#34;children\&#34;:\&#34;Yes probably, scoreboards and online multiplayer would be neat.\&#34;}]]}],[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;3\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-900\&#34;,\&#34;children\&#34;:\&#34;How about AI players?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-700\&#34;,\&#34;children\&#34;:\&#34;Not sure - I always played these games multi-player but I&#39;ll think about it for sure.\&#34;}]]}]]}]}]]}]]}]}],[\&#34;$\&#34;,\&#34;$L10\&#34;,null,{}]],null],\&#34;segment\&#34;:\&#34;__PAGE__\&#34;},\&#34;styles\&#34;:[]}]}],[\&#34;$\&#34;,\&#34;footer\&#34;,null,{\&#34;className\&#34;:\&#34;border-t border-gray-200\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;flex flex-col items-center border-t border-gray-200 py-12 md:flex-row-reverse md:justify-between\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-6 text-sm text-gray-500 md:mt-0\&#34;,\&#34;children\&#34;:[\&#34;© Copyright \&#34;,2025,\&#34;. All rights reserved.\&#34;]}],[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;href\&#34;:\&#34;https://apps.apple.com/us/app/8-bit-golf/id6664070211\&#34;,\&#34;aria-label\&#34;:\&#34;Download on the App Store\&#34;,\&#34;className\&#34;:\&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 120 40\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;className\&#34;:\&#34;h-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;path\&#34;,null,{\&#34;fill\&#34;:\&#34;currentColor\&#34;,\&#34;d\&#34;:\&#34;$11\&#34;}]}]}]]}]}]}]],null],\&#34;segment\&#34;:\&#34;(main)\&#34;},\&#34;styles\&#34;:[]}]}]}]}],null]}]]\n&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;5:[[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;0\&#34;,{\&#34;charSet\&#34;:\&#34;utf-8\&#34;}],[\&#34;$\&#34;,\&#34;title\&#34;,\&#34;1\&#34;,{\&#34;children\&#34;:\&#34;VoxEd - Elegant voxel sprite editing\&#34;}],[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;2\&#34;,{\&#34;name\&#34;:\&#34;description\&#34;,\&#34;content\&#34;:\&#34;Overview of VoxEd features.\&#34;}],[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;3\&#34;,{\&#34;name\&#34;:\&#34;viewport\&#34;,\&#34;content\&#34;:\&#34;width=device-width, initial-scale=1\&#34;}],[\&#34;$\&#34;,\&#34;link\&#34;,\&#34;4\&#34;,{\&#34;rel\&#34;:\&#34;icon\&#34;,\&#34;href\&#34;:\&#34;/8-bit-golf/favicon.ico\&#34;,\&#34;type\&#34;:\&#34;image/x-icon\&#34;,\&#34;sizes\&#34;:\&#34;16x16\&#34;}],[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;5\&#34;,{\&#34;name\&#34;:\&#34;next-size-adjust\&#34;}]]\nc:null\n&#34;])&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://www.jamesdrandall.com/csharpwolf3d/part6/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/csharpwolf3d/part6/</guid>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;

&lt;head&gt;
    &lt;meta charset=&#34;utf-8&#34; /&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no&#34; /&gt;
    &lt;title&gt;CSharpWolfenstein&lt;/title&gt;
    &lt;base href=&#34;https://www.jamesdrandall.com/csharpwolf3d/part6/&#34; /&gt;
    &lt;link href=&#34;css/bootstrap/bootstrap.min.css&#34; rel=&#34;stylesheet&#34; /&gt;
    &lt;link href=&#34;css/app.css&#34; rel=&#34;stylesheet&#34; /&gt;
    &lt;link href=&#34;CSharpWolfenstein.styles.css&#34; rel=&#34;stylesheet&#34; /&gt;
    &lt;script&gt;
        window.configureGameEvents = (dotNetHelper,_) =&gt; {
            document.addEventListener(&#34;keydown&#34;, event =&gt; {
                if (!event.repeat)  dotNetHelper.invokeMethodAsync(&#39;OnKeyDown&#39;, event.key);
            });
            document.addEventListener(&#34;keyup&#34;, event =&gt; {
                if (!event.repeat) dotNetHelper.invokeMethodAsync(&#39;OnKeyUp&#39;, event.key);
            });
        };
    &lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;div id=&#34;app&#34;&gt;Loading...&lt;/div&gt;

    &lt;div id=&#34;blazor-error-ui&#34;&gt;
        An unhandled error has occurred.
        &lt;a href=&#34;&#34; class=&#34;reload&#34;&gt;Reload&lt;/a&gt;
        &lt;a class=&#34;dismiss&#34;&gt;🗙&lt;/a&gt;
    &lt;/div&gt;
    &lt;script src=&#34;_framework/blazor.webassembly.js&#34;&gt;&lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://www.jamesdrandall.com/csharpwolf3d/part6b/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/csharpwolf3d/part6b/</guid>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;

&lt;head&gt;
    &lt;meta charset=&#34;utf-8&#34; /&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no&#34; /&gt;
    &lt;title&gt;CSharpWolfenstein&lt;/title&gt;
    &lt;base href=&#34;https://www.jamesdrandall.com/csharpwolf3d/part6b/&#34; /&gt;
    &lt;link href=&#34;css/bootstrap/bootstrap.min.css&#34; rel=&#34;stylesheet&#34; /&gt;
    &lt;link href=&#34;css/app.css&#34; rel=&#34;stylesheet&#34; /&gt;
    &lt;script&gt;
        window.configureGameEvents = (dotNetHelper,_) =&gt; {
            document.addEventListener(&#34;keydown&#34;, event =&gt; {
                if (!event.repeat)  dotNetHelper.invokeMethodAsync(&#39;OnKeyDown&#39;, event.key);
                event.preventDefault();
            });
            document.addEventListener(&#34;keyup&#34;, event =&gt; {
                if (!event.repeat) dotNetHelper.invokeMethodAsync(&#39;OnKeyUp&#39;, event.key);
                event.preventDefault();
            });
        };
    &lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;div id=&#34;app&#34;&gt;Loading...&lt;/div&gt;

    &lt;div id=&#34;blazor-error-ui&#34;&gt;
        An unhandled error has occurred.
        &lt;a href=&#34;&#34; class=&#34;reload&#34;&gt;Reload&lt;/a&gt;
        &lt;a class=&#34;dismiss&#34;&gt;🗙&lt;/a&gt;
    &lt;/div&gt;
    &lt;script src=&#34;_framework/blazor.webassembly.js&#34;&gt;&lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://www.jamesdrandall.com/csharpwolf3d/part7/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/csharpwolf3d/part7/</guid>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;

&lt;head&gt;
    &lt;meta charset=&#34;utf-8&#34; /&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no&#34; /&gt;
    &lt;title&gt;CSharpWolfenstein&lt;/title&gt;
    &lt;base href=&#34;https://www.jamesdrandall.com/csharpwolf3d/part7/&#34; /&gt;
    &lt;link href=&#34;css/bootstrap/bootstrap.min.css&#34; rel=&#34;stylesheet&#34; /&gt;
    &lt;link href=&#34;css/app.css&#34; rel=&#34;stylesheet&#34; /&gt;
    &lt;link href=&#34;CSharpWolfenstein.styles.css&#34; rel=&#34;stylesheet&#34; /&gt;
    &lt;script&gt;
        window.configureGameEvents = (dotNetHelper,_) =&gt; {
            document.addEventListener(&#34;keydown&#34;, event =&gt; {
                if (!event.repeat)  dotNetHelper.invokeMethodAsync(&#39;OnKeyDown&#39;, event.key);
                event.preventDefault();
            });
            document.addEventListener(&#34;keyup&#34;, event =&gt; {
                if (!event.repeat) dotNetHelper.invokeMethodAsync(&#39;OnKeyUp&#39;, event.key);
                event.preventDefault();
            });
        };
    &lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;div id=&#34;app&#34;&gt;Loading...&lt;/div&gt;

    &lt;div id=&#34;blazor-error-ui&#34;&gt;
        An unhandled error has occurred.
        &lt;a href=&#34;&#34; class=&#34;reload&#34;&gt;Reload&lt;/a&gt;
        &lt;a class=&#34;dismiss&#34;&gt;🗙&lt;/a&gt;
    &lt;/div&gt;
    &lt;script src=&#34;_framework/blazor.webassembly.js&#34;&gt;&lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://www.jamesdrandall.com/csharpwolf3d/part8/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/csharpwolf3d/part8/</guid>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;

&lt;head&gt;
    &lt;meta charset=&#34;utf-8&#34; /&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no&#34; /&gt;
    &lt;title&gt;CSharpWolfenstein&lt;/title&gt;
    &lt;base href=&#34;https://www.jamesdrandall.com/csharpwolf3d/part8/&#34; /&gt;
    &lt;link href=&#34;css/bootstrap/bootstrap.min.css&#34; rel=&#34;stylesheet&#34; /&gt;
    &lt;link href=&#34;css/app.css&#34; rel=&#34;stylesheet&#34; /&gt;
    &lt;link href=&#34;CSharpWolfenstein.styles.css&#34; rel=&#34;stylesheet&#34; /&gt;
    &lt;script&gt;
        window.configureGameEvents = (dotNetHelper,_) =&gt; {
            document.addEventListener(&#34;keydown&#34;, event =&gt; {
                if (!event.repeat)  dotNetHelper.invokeMethodAsync(&#39;OnKeyDown&#39;, event.key);
                event.preventDefault();
            });
            document.addEventListener(&#34;keyup&#34;, event =&gt; {
                if (!event.repeat) dotNetHelper.invokeMethodAsync(&#39;OnKeyUp&#39;, event.key);
                event.preventDefault();
            });
        };
    &lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;div id=&#34;app&#34;&gt;Loading...&lt;/div&gt;

    &lt;div id=&#34;blazor-error-ui&#34;&gt;
        An unhandled error has occurred.
        &lt;a href=&#34;&#34; class=&#34;reload&#34;&gt;Reload&lt;/a&gt;
        &lt;a class=&#34;dismiss&#34;&gt;🗙&lt;/a&gt;
    &lt;/div&gt;
    &lt;script src=&#34;_framework/blazor.webassembly.js&#34;&gt;&lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://www.jamesdrandall.com/gpumandelbrot/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/gpumandelbrot/</guid>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
  &lt;head&gt;
    &lt;meta charset=&#34;utf-8&#34; /&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no&#34; /&gt;
    &lt;title&gt;GPU Mandelbrot&lt;/title&gt;
    &lt;script src=&#39;./zingtouch.min.js&#39;&gt;&lt;/script&gt;
    &lt;script
      src=&#34;gl-matrix-min-2.8.1.js&#34;
      integrity=&#34;sha512-zhHQR0/H5SEBL3Wn6yYSaTTZej12z0hVZKOv3TwCUXT1z5qeqGcXJLLrbERYRScEDDpYIJhPC1fk31gqR783iQ==&#34;
      crossorigin=&#34;anonymous&#34;
      defer
    &gt;&lt;/script&gt;
    &lt;script src=&#34;mandelbrot.js&#34; type=&#34;module&#34;&gt;&lt;/script&gt;
  &lt;/head&gt;

  &lt;body style=&#34;margin: 0; padding: 0; overflow: hidden;&#34;&gt;
    &lt;canvas id=&#34;glcanvas&#34; style=&#34;width:100vw; height: 100vh;&#34; width=&#34;960&#34; height=&#34;600&#34;&gt;&lt;/canvas&gt;
  &lt;/body&gt;
&lt;/html&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://www.jamesdrandall.com/graphpaper/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/graphpaper/</guid>
      <description>&lt;!DOCTYPE html&gt;&lt;html lang=&#34;en&#34; class=&#34;h-full bg-gray-50 antialiased __variable_311810&#34;&gt;&lt;head&gt;&lt;meta charSet=&#34;utf-8&#34;/&gt;&lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1&#34;/&gt;&lt;link rel=&#34;preload&#34; as=&#34;font&#34; href=&#34;https://www.jamesdrandall.com/graphpaper/_next/static/media/a34f9d1faa5f3315-s.p.woff2&#34; crossorigin=&#34;&#34; type=&#34;font/woff2&#34;/&gt;&lt;link rel=&#34;stylesheet&#34; href=&#34;https://www.jamesdrandall.com/graphpaper/_next/static/css/ffa6bef7c8a3671a.css&#34; data-precedence=&#34;next&#34;/&gt;&lt;link rel=&#34;preload&#34; href=&#34;https://www.jamesdrandall.com/graphpaper/_next/static/chunks/webpack-5011315d884df383.js&#34; as=&#34;script&#34; fetchPriority=&#34;low&#34;/&gt;&lt;script src=&#34;https://www.jamesdrandall.com/graphpaper/_next/static/chunks/fd9d1056-c2ad140a17e094a3.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://www.jamesdrandall.com/graphpaper/_next/static/chunks/596-828b5a8a8ce15974.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://www.jamesdrandall.com/graphpaper/_next/static/chunks/main-app-57819d3e196c7313.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;title&gt;VoxEd - Elegant voxel sprite editing&lt;/title&gt;&lt;meta name=&#34;description&#34; content=&#34;Overview of VoxEd features.&#34;/&gt;&lt;link rel=&#34;icon&#34; href=&#34;https://www.jamesdrandall.com/graphpaper/favicon.ico&#34; type=&#34;image/x-icon&#34; sizes=&#34;16x16&#34;/&gt;&lt;meta name=&#34;next-size-adjust&#34;/&gt;&lt;script src=&#34;https://www.jamesdrandall.com/graphpaper/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js&#34; noModule=&#34;&#34;&gt;&lt;/script&gt;&lt;/head&gt;&lt;body class=&#34;flex h-full flex-col font-pixel&#34;&gt;&lt;div class=&#34;flex min-h-full flex-col&#34;&gt;&lt;header&gt;&lt;nav&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative z-50 flex justify-between py-8&#34;&gt;&lt;div class=&#34;relative z-10 flex items-center gap-16&#34;&gt;&lt;a aria-label=&#34;Home&#34; href=&#34;https://www.jamesdrandall.com/graphpaper&#34;&gt;&lt;div class=&#34;flex flex-row items-center&#34;&gt;&lt;img alt=&#34;Graph Paper&#34; loading=&#34;lazy&#34; width=&#34;80&#34; height=&#34;80&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/graphpaper/_next/static/media/graphpaper.0bb4d6dd.png&#34;/&gt;&lt;span class=&#34;ml-2 spred-logo-text&#34;&gt;Graph Paper&lt;/span&gt;&lt;/div&gt;&lt;/a&gt;&lt;div class=&#34;hidden lg:flex lg:gap-10&#34;&gt;&lt;a href=&#34;#faqs&#34; class=&#34;relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors delay-150 hover:text-gray-900 hover:delay-0&#34;&gt;&lt;span class=&#34;relative z-10&#34;&gt;FAQs&lt;/span&gt;&lt;/a&gt;&lt;a href=&#34;#privacy&#34; class=&#34;relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors delay-150 hover:text-gray-900 hover:delay-0&#34;&gt;&lt;span class=&#34;relative z-10&#34;&gt;Privacy&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;flex items-center gap-6&#34;&gt;&lt;div class=&#34;lg:hidden&#34; data-headlessui-state=&#34;&#34;&gt;&lt;button class=&#34;relative z-10 -m-2 inline-flex items-center rounded-lg stroke-gray-900 p-2 hover:bg-gray-200/50 hover:stroke-gray-600 active:stroke-gray-900 ui-not-focus-visible:outline-none&#34; aria-label=&#34;Toggle site navigation&#34; type=&#34;button&#34; aria-expanded=&#34;false&#34; data-headlessui-state=&#34;&#34;&gt;&lt;svg viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; aria-hidden=&#34;true&#34; class=&#34;h-6 w-6&#34;&gt;&lt;path d=&#34;M5 6h14M5 18h14M5 12h14&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;div style=&#34;position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&#34;flex-auto&#34;&gt;&lt;div class=&#34;overflow-hidden pb-20 sm:pb-32 lg:pb-16 xl:pb-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;lg:grid lg:grid-cols-12&#34;&gt;&lt;div class=&#34;lg:col-start-2 lg:col-span-10 lg:mb-10&#34;&gt;&lt;img alt=&#34;SprEd&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/graphpaper/_next/static/media/heroImage.913f6ec8.png&#34;/&gt;&lt;/div&gt;&lt;div class=&#34;lg:col-start-2 lg:col-span-10&#34;&gt;&lt;h1 class=&#34;text-center text-4xl font-medium tracking-tight text-gray-900&#34;&gt;Satisfying sketching on graph paper for your iPad, iPhone and Mac&lt;/h1&gt;&lt;/div&gt;&lt;div class=&#34;lg:col-span-12 flex justify-center flex-wrap gap-x-6 mt-6&#34;&gt;&lt;a aria-label=&#34;Download on the App Store&#34; class=&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900&#34; href=&#34;https://apps.apple.com/us/app/spred/id6463485447&#34;&gt;&lt;svg viewBox=&#34;0 0 120 40&#34; aria-hidden=&#34;true&#34; class=&#34;h-10&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;section id=&#34;free&#34; aria-label=&#34;Features available in the basic version&#34; class=&#34;bg-gray-900 py-20 sm:py-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto sm:text-center&#34;&gt;&lt;h2 class=&#34;text-3xl font-medium tracking-tight text-white&#34;&gt;Download the app and start creating sketches&lt;/h2&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;Graph Paper is completely free and has a simple design for creating sketches.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section id=&#34;faqs&#34; aria-labelledby=&#34;faqs-title&#34; class=&#34;bg-white border-t border-gray-200 py-16 sm:py-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto max-w-2xl lg:mx-0&#34;&gt;&lt;h2 id=&#34;faqs-title&#34; class=&#34;text-3xl font-medium tracking-tight text-black&#34;&gt;Frequently asked questions&lt;/h2&gt;&lt;/div&gt;&lt;ul role=&#34;list&#34; class=&#34;mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-8 sm:mt-20 lg:max-w-none lg:grid-cols-3&#34;&gt;&lt;li&gt;&lt;ul role=&#34;list&#34; class=&#34;space-y-10&#34;&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-900&#34;&gt;Why isn&amp;#x27;t there an eraser?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-700&#34;&gt;Deliberate design choice - you can&amp;#x27;t erase ink! I&amp;#x27;m still on the fence on this one, I&amp;#x27;ll probably put one in&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-900&#34;&gt;I think I&amp;#x27;ve found a bug or have an idea for an improvement, what do I do?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-700&#34;&gt;You can send an email to me at support@accidentalfish.com and I&amp;#x27;ll see if I can help.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-900&#34;&gt;Are you planning on adding more features?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-700&#34;&gt;Yeah probably, a few more simple ones but I don&amp;#x27;t want this to become a complex app.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/section&gt;&lt;section id=&#34;privacy&#34; aria-label=&#34;Privacy information&#34; class=&#34;bg-gray-900  py-20 sm:py-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto&#34;&gt;&lt;h2 class=&#34;text-3xl font-medium tracking-tight text-gray-200&#34;&gt;What about Privacy?&lt;/h2&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;Our privacy policy is really simple. We don&amp;#x27;t think you should be spied upon and tracked all over the web or on your devices and so we don&amp;#x27;t collect any information about you, or your usage of the app, at all.&lt;/p&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;If you contact us by email then we will never share your email address with a third party or use it for any purpose other than responding to your email.&lt;/p&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;And finally this website contains no tracking cookies or analytics of any kind. We don&amp;#x27;t want to know who you are or what you do on the web. We just want you to enjoy using VoxEd.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/main&gt;&lt;footer class=&#34;border-t border-gray-200&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;flex flex-col items-center border-t border-gray-200 py-12 md:flex-row-reverse md:justify-between&#34;&gt;&lt;p class=&#34;mt-6 text-sm text-gray-500 md:mt-0&#34;&gt;© Copyright &lt;!-- --&gt;2024&lt;!-- --&gt;. All rights reserved.&lt;/p&gt;&lt;a aria-label=&#34;Download on the App Store&#34; class=&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900&#34; href=&#34;https://apps.apple.com/us/app/spred/id6463485447&#34;&gt;&lt;svg viewBox=&#34;0 0 120 40&#34; aria-hidden=&#34;true&#34; class=&#34;h-10&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/footer&gt;&lt;/div&gt;&lt;script src=&#34;https://www.jamesdrandall.com/graphpaper/_next/static/chunks/webpack-5011315d884df383.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;script&gt;(self.__next_f=self.__next_f||[]).push([0])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;1:HL[\&#34;/graphpaper/_next/static/media/a34f9d1faa5f3315-s.p.woff2\&#34;,{\&#34;as\&#34;:\&#34;font\&#34;,\&#34;type\&#34;:\&#34;font/woff2\&#34;}]\n2:HL[\&#34;/graphpaper/_next/static/css/ffa6bef7c8a3671a.css\&#34;,{\&#34;as\&#34;:\&#34;style\&#34;}]\n0:\&#34;$L3\&#34;\n&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;4:I{\&#34;id\&#34;:7948,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-5011315d884df383.js\&#34;,\&#34;971:static/chunks/fd9d1056-c2ad140a17e094a3.js\&#34;,\&#34;596:static/chunks/596-828b5a8a8ce15974.js\&#34;],\&#34;name\&#34;:\&#34;default\&#34;,\&#34;async\&#34;:false}\n6:I{\&#34;id\&#34;:6628,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-5011315d884df383.js\&#34;,\&#34;971:static/chunks/fd9d1056-c2ad140a17e094a3.js\&#34;,\&#34;596:static/chunks/596-828b5a8a8ce15974.js\&#34;],\&#34;name\&#34;:\&#34;\&#34;,\&#34;async\&#34;:false}\n7:I{\&#34;id\&#34;:7767,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-5011315d884df383.js\&#34;,\&#34;971:static/chunks/fd9d1056-c2ad140a17e094a3.js\&#34;,\&#34;5&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;96:static/chunks/596-828b5a8a8ce15974.js\&#34;],\&#34;name\&#34;:\&#34;default\&#34;,\&#34;async\&#34;:false}\n8:I{\&#34;id\&#34;:7920,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-5011315d884df383.js\&#34;,\&#34;971:static/chunks/fd9d1056-c2ad140a17e094a3.js\&#34;,\&#34;596:static/chunks/596-828b5a8a8ce15974.js\&#34;],\&#34;name\&#34;:\&#34;default\&#34;,\&#34;async\&#34;:false}\n9:I{\&#34;id\&#34;:6111,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-5f7d3d54369b9d01.js\&#34;,\&#34;126:static/chunks/126-e953ce4a6f123ff8.js\&#34;,\&#34;95:static/chunks/app/(main)/layout-fd6f56012aa720fa.js\&#34;],\&#34;name\&#34;:\&#34;Header\&#34;,\&#34;async\&#34;:false}\na:I{\&#34;id\&#34;:6685,\&#34;chunks\&#34;:[\&#34;451:static/ch&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;unks/451-5f7d3d54369b9d01.js\&#34;,\&#34;126:static/chunks/126-e953ce4a6f123ff8.js\&#34;,\&#34;95:static/chunks/app/(main)/layout-fd6f56012aa720fa.js\&#34;],\&#34;name\&#34;:\&#34;\&#34;,\&#34;async\&#34;:false}\nd:I{\&#34;id\&#34;:3222,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-5f7d3d54369b9d01.js\&#34;,\&#34;974:static/chunks/app/(main)/page-dfc9bb63aa28f2cf.js\&#34;],\&#34;name\&#34;:\&#34;Image\&#34;,\&#34;async\&#34;:false}\nf:I{\&#34;id\&#34;:3558,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-5f7d3d54369b9d01.js\&#34;,\&#34;974:static/chunks/app/(main)/page-dfc9bb63aa28f2cf.js\&#34;],\&#34;name\&#34;:\&#34;PrimaryFeatures\&#34;,\&#34;async\&#34;:false}\n10:I{\&#34;id\&#34;:3852,\&#34;chunks\&#34;:[\&#34;451:stati&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;c/chunks/451-5f7d3d54369b9d01.js\&#34;,\&#34;974:static/chunks/app/(main)/page-dfc9bb63aa28f2cf.js\&#34;],\&#34;name\&#34;:\&#34;Privacy\&#34;,\&#34;async\&#34;:false}\nb:T14cb,&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;e:T14cb,&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;11:T14cb,&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;3:[[[\&#34;$\&#34;,\&#34;link\&#34;,\&#34;0\&#34;,{\&#34;rel\&#34;:\&#34;stylesheet\&#34;,\&#34;href\&#34;:\&#34;/graphpaper/_next/static/css/ffa6bef7c8a3671a.css\&#34;,\&#34;precedence\&#34;:\&#34;next\&#34;}]],[\&#34;$\&#34;,\&#34;$L4\&#34;,null,{\&#34;buildId\&#34;:\&#34;wHtTrOFrNZ0wfqocefdAy\&#34;,\&#34;assetPrefix\&#34;:\&#34;/graphpaper\&#34;,\&#34;initialCanonicalUrl\&#34;:\&#34;/\&#34;,\&#34;initialTree\&#34;:[\&#34;\&#34;,{\&#34;children\&#34;:[\&#34;(main)\&#34;,{\&#34;children\&#34;:[\&#34;__PAGE__\&#34;,{}]}]},\&#34;$undefined\&#34;,\&#34;$undefined\&#34;,true],\&#34;initialHead\&#34;:\&#34;$L5\&#34;,\&#34;globalErrorComponent\&#34;:\&#34;$6\&#34;,\&#34;children\&#34;:[null,[\&#34;$\&#34;,\&#34;html\&#34;,null,{\&#34;lang\&#34;:\&#34;en\&#34;,\&#34;className\&#34;:\&#34;h-full bg-gray-50 antialiased __variable_311810\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;body\&#34;,null,{\&#34;className\&#34;:\&#34;flex h-full flex-col font-pixel\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;flex min-h-full flex-col\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$L7\&#34;,null,{\&#34;parallelRouterKey\&#34;:\&#34;children\&#34;,\&#34;segmentPath\&#34;:[\&#34;children\&#34;],\&#34;error\&#34;:\&#34;$undefined\&#34;,\&#34;errorStyles\&#34;:\&#34;$undefined\&#34;,\&#34;loading\&#34;:\&#34;$undefined\&#34;,\&#34;loadingStyles\&#34;:\&#34;$undefined\&#34;,\&#34;hasLoading\&#34;:false,\&#34;template\&#34;:[\&#34;$\&#34;,\&#34;$L8\&#34;,null,{}],\&#34;templateStyles\&#34;:\&#34;$undefined\&#34;,\&#34;notFound\&#34;:[[\&#34;$\&#34;,\&#34;$L9\&#34;,null,{}],[\&#34;$\&#34;,\&#34;main\&#34;,null,{\&#34;className\&#34;:\&#34;flex-auto\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative isolate flex h-full flex-col items-center justify-center py-20 text-center sm:py-32\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 1090 1090\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;fill\&#34;:\&#34;none\&#34;,\&#34;preserveAspectRatio\&#34;:\&#34;none\&#34;,\&#34;className\&#34;:\&#34;absolute left-1/2 top-1/2 -z-10 mt-44 w-[68.125rem] -translate-x-1/2 -translate-y-1/2 stroke-gray-300/30 [mask-image:linear-gradient(to_bottom,white_20%,transparent_75%)]\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;544.5\&#34;}],[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;480.5\&#34;}],[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;416.5\&#34;}],[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;352.5\&#34;}]]}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;text-sm font-semibold text-gray-900\&#34;,\&#34;children\&#34;:\&#34;404\&#34;}],[\&#34;$\&#34;,\&#34;h1\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-3xl font-medium tracking-tight text-gray-900\&#34;,\&#34;children\&#34;:\&#34;Page not found\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-lg text-gray-600\&#34;,\&#34;children\&#34;:\&#34;Sorry, we couldn’t find the page you’re looking for.\&#34;}],[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;className\&#34;:\&#34;inline-flex justify-center rounded-lg border py-[calc(theme(spacing.2)-1px)] px-[calc(theme(spacing.3)-1px)] text-sm outline-2 outline-offset-2 transition-colors border-gray-300 text-gray-700 hover:border-gray-400 active:bg-gray-100 active:text-gray-700/80 mt-8\&#34;,\&#34;href\&#34;:\&#34;/\&#34;,\&#34;children\&#34;:\&#34;Go back home\&#34;}]]}]}],[\&#34;$\&#34;,\&#34;footer\&#34;,null,{\&#34;className\&#34;:\&#34;border-t border-gray-200\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;flex flex-col items-center border-t border-gray-200 py-12 md:flex-row-reverse md:justify-between\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-6 text-sm text-gray-500 md:mt-0\&#34;,\&#34;children\&#34;:[\&#34;© Copyright \&#34;,2024,\&#34;. All rights reserved.\&#34;]}],[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;href\&#34;:\&#34;https://apps.apple.com/us/app/spred/id6463485447\&#34;,\&#34;aria-label\&#34;:\&#34;Download on the App Store\&#34;,\&#34;className\&#34;:\&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 120 40\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;className\&#34;:\&#34;h-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;path\&#34;,null,{\&#34;fill\&#34;:\&#34;currentColor\&#34;,\&#34;d\&#34;:\&#34;$b\&#34;}]}]}]]}]}]}]],\&#34;notFoundStyles\&#34;:[],\&#34;childProp\&#34;:{\&#34;current\&#34;:[null,[[\&#34;$\&#34;,\&#34;$L9\&#34;,null,{}],[\&#34;$\&#34;,\&#34;main\&#34;,null,{\&#34;className\&#34;:\&#34;flex-auto\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$L7\&#34;,null,{\&#34;parallelRouterKey\&#34;:\&#34;children\&#34;,\&#34;segmentPath\&#34;:[\&#34;children\&#34;,\&#34;(main)\&#34;,\&#34;children\&#34;],\&#34;error\&#34;:\&#34;$undefined\&#34;,\&#34;errorStyles\&#34;:\&#34;$undefined\&#34;,\&#34;loading\&#34;:\&#34;$undefined\&#34;,\&#34;loadingStyles\&#34;:\&#34;$undefined\&#34;,\&#34;hasLoading\&#34;:false,\&#34;template\&#34;:[\&#34;$\&#34;,\&#34;$L8\&#34;,null,{}],\&#34;templateStyles\&#34;:\&#34;$undefined\&#34;,\&#34;notFound\&#34;:[[\&#34;$\&#34;,\&#34;title\&#34;,null,{\&#34;children\&#34;:\&#34;404: This page could not be found.\&#34;}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;style\&#34;:{\&#34;fontFamily\&#34;:\&#34;system-ui,\\\&#34;Segoe UI\\\&#34;,Roboto,Helvetica,Arial,sans-serif,\\\&#34;Apple Color Emoji\\\&#34;,\\\&#34;Segoe UI Emoji\\\&#34;\&#34;,\&#34;height\&#34;:\&#34;100vh\&#34;,\&#34;textAlign\&#34;:\&#34;center\&#34;,\&#34;display\&#34;:\&#34;flex\&#34;,\&#34;flexDirection\&#34;:\&#34;column\&#34;,\&#34;alignItems\&#34;:\&#34;center\&#34;,\&#34;justifyContent\&#34;:\&#34;center\&#34;},\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;style\&#34;,null,{\&#34;dangerouslySetInnerHTML\&#34;:{\&#34;__html\&#34;:\&#34;body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\&#34;}}],[\&#34;$\&#34;,\&#34;h1\&#34;,null,{\&#34;className\&#34;:\&#34;next-error-h1\&#34;,\&#34;style\&#34;:{\&#34;display\&#34;:\&#34;inline-block\&#34;,\&#34;margin\&#34;:\&#34;0 20px 0 0\&#34;,\&#34;padding\&#34;:\&#34;0 23px 0 0\&#34;,\&#34;fontSize\&#34;:24,\&#34;fontWeight\&#34;:500,\&#34;verticalAlign\&#34;:\&#34;top\&#34;,\&#34;lineHeight\&#34;:\&#34;49px\&#34;},\&#34;children\&#34;:\&#34;404\&#34;}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;style\&#34;:{\&#34;display\&#34;:\&#34;inline-block\&#34;},\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;h2\&#34;,null,{\&#34;style\&#34;:{\&#34;fontSize\&#34;:14,\&#34;fontWeight\&#34;:400,\&#34;lineHeight\&#34;:\&#34;49px\&#34;,\&#34;margin\&#34;:0},\&#34;children\&#34;:\&#34;This page could not be found.\&#34;}]}]]}]}]],\&#34;notFoundStyles\&#34;:[],\&#34;childProp\&#34;:{\&#34;current\&#34;:[\&#34;$Lc\&#34;,[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;overflow-hidden pb-20 sm:pb-32 lg:pb-16 xl:pb-16\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:grid lg:grid-cols-12\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:col-start-2 lg:col-span-10 lg:mb-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$Ld\&#34;,null,{\&#34;src\&#34;:{\&#34;src\&#34;:\&#34;/graphpaper/_next/static/media/heroImage.913f6ec8.png\&#34;,\&#34;height\&#34;:2290,\&#34;width\&#34;:2960,\&#34;blurDataURL\&#34;:\&#34;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAAJ1BMVEXLz9KlqKrW3N7P1Napra7a4OJ7fX3V2tySlZadoqPf5eeusrSxtLYfy/rIAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAMUlEQVR4nBXIxxHAMAwEsWU8UlL/9XqMJ+jufXcaqwiplqwA3P4BtOSRu44xL83yzgcZ6wDtSneHkwAAAABJRU5ErkJggg==\&#34;,\&#34;blurWidth\&#34;:8,\&#34;blurHeight\&#34;:6},\&#34;alt\&#34;:\&#34;SprEd\&#34;}]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:col-start-2 lg:col-span-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;h1\&#34;,null,{\&#34;className\&#34;:\&#34;text-center text-4xl font-medium tracking-tight text-gray-900\&#34;,\&#34;children\&#34;:\&#34;Satisfying sketching on graph paper for your iPad, iPhone and Mac\&#34;}]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:col-span-12 flex justify-center flex-wrap gap-x-6 mt-6\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;href\&#34;:\&#34;https://apps.apple.com/us/app/spred/id6463485447\&#34;,\&#34;aria-label\&#34;:\&#34;Download on the App Store\&#34;,\&#34;className\&#34;:\&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 120 40\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;className\&#34;:\&#34;h-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;path\&#34;,null,{\&#34;fill\&#34;:\&#34;currentColor\&#34;,\&#34;d\&#34;:\&#34;$e\&#34;}]}]}]}]]}]}]}],[\&#34;$\&#34;,\&#34;$Lf\&#34;,null,{}],[\&#34;$\&#34;,\&#34;section\&#34;,null,{\&#34;id\&#34;:\&#34;faqs\&#34;,\&#34;aria-labelledby\&#34;:\&#34;faqs-title\&#34;,\&#34;className\&#34;:\&#34;bg-white border-t border-gray-200 py-16 sm:py-16\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-2xl lg:mx-0\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;h2\&#34;,null,{\&#34;id\&#34;:\&#34;faqs-title\&#34;,\&#34;className\&#34;:\&#34;text-3xl font-medium tracking-tight text-black\&#34;,\&#34;children\&#34;:\&#34;Frequently asked questions\&#34;}]}],[\&#34;$\&#34;,\&#34;ul\&#34;,null,{\&#34;role\&#34;:\&#34;list\&#34;,\&#34;className\&#34;:\&#34;mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-8 sm:mt-20 lg:max-w-none lg:grid-cols-3\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;0\&#34;,{\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;ul\&#34;,null,{\&#34;role\&#34;:\&#34;list\&#34;,\&#34;className\&#34;:\&#34;space-y-10\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;0\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-900\&#34;,\&#34;children\&#34;:\&#34;Why isn&#39;t there an eraser?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-700\&#34;,\&#34;children\&#34;:\&#34;Deliberate design choice - you can&#39;t erase ink! I&#39;m still on the fence on this one, I&#39;ll probably put one in\&#34;}]]}],[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;1\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-900\&#34;,\&#34;children\&#34;:\&#34;I think I&#39;ve found a bug or have an idea for an improvement, what do I do?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-700\&#34;,\&#34;children\&#34;:\&#34;You can send an email to me at support@accidentalfish.com and I&#39;ll see if I can help.\&#34;}]]}],[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;2\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-900\&#34;,\&#34;children\&#34;:\&#34;Are you planning on adding more features?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-700\&#34;,\&#34;children\&#34;:\&#34;Yeah probably, a few more simple ones but I don&#39;t want this to become a complex app.\&#34;}]]}]]}]}]]}]]}]}],[\&#34;$\&#34;,\&#34;$L10\&#34;,null,{}]],null],\&#34;segment\&#34;:\&#34;__PAGE__\&#34;},\&#34;styles\&#34;:[]}]}],[\&#34;$\&#34;,\&#34;footer\&#34;,null,{\&#34;className\&#34;:\&#34;border-t border-gray-200\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;flex flex-col items-center border-t border-gray-200 py-12 md:flex-row-reverse md:justify-between\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-6 text-sm text-gray-500 md:mt-0\&#34;,\&#34;children\&#34;:[\&#34;© Copyright \&#34;,2024,\&#34;. All rights reserved.\&#34;]}],[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;href\&#34;:\&#34;https://apps.apple.com/us/app/spred/id6463485447\&#34;,\&#34;aria-label\&#34;:\&#34;Download on the App Store\&#34;,\&#34;className\&#34;:\&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 120 40\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;className\&#34;:\&#34;h-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;path\&#34;,null,{\&#34;fill\&#34;:\&#34;currentColor\&#34;,\&#34;d\&#34;:\&#34;$11\&#34;}]}]}]]}]}]}]],null],\&#34;segment\&#34;:\&#34;(main)\&#34;},\&#34;styles\&#34;:[]}]}]}]}],null]}]]\n&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;5:[[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;0\&#34;,{\&#34;charSet\&#34;:\&#34;utf-8\&#34;}],[\&#34;$\&#34;,\&#34;title\&#34;,\&#34;1\&#34;,{\&#34;children\&#34;:\&#34;VoxEd - Elegant voxel sprite editing\&#34;}],[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;2\&#34;,{\&#34;name\&#34;:\&#34;description\&#34;,\&#34;content\&#34;:\&#34;Overview of VoxEd features.\&#34;}],[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;3\&#34;,{\&#34;name\&#34;:\&#34;viewport\&#34;,\&#34;content\&#34;:\&#34;width=device-width, initial-scale=1\&#34;}],[\&#34;$\&#34;,\&#34;link\&#34;,\&#34;4\&#34;,{\&#34;rel\&#34;:\&#34;icon\&#34;,\&#34;href\&#34;:\&#34;/graphpaper/favicon.ico\&#34;,\&#34;type\&#34;:\&#34;image/x-icon\&#34;,\&#34;sizes\&#34;:\&#34;16x16\&#34;}],[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;5\&#34;,{\&#34;name\&#34;:\&#34;next-size-adjust\&#34;}]]\nc:null\n&#34;])&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://www.jamesdrandall.com/spred/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/spred/</guid>
      <description>&lt;!DOCTYPE html&gt;&lt;html lang=&#34;en&#34; class=&#34;h-full bg-gray-50 antialiased __variable_2b8e48&#34;&gt;&lt;head&gt;&lt;meta charSet=&#34;utf-8&#34;/&gt;&lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1&#34;/&gt;&lt;link rel=&#34;preload&#34; as=&#34;font&#34; href=&#34;https://www.jamesdrandall.com/spred/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2&#34; crossorigin=&#34;&#34; type=&#34;font/woff2&#34;/&gt;&lt;link rel=&#34;stylesheet&#34; href=&#34;https://www.jamesdrandall.com/spred/_next/static/css/81f197f724b148b6.css&#34; data-precedence=&#34;next&#34;/&gt;&lt;link rel=&#34;preload&#34; href=&#34;https://www.jamesdrandall.com/spred/_next/static/chunks/webpack-60382efa82877252.js&#34; as=&#34;script&#34; fetchPriority=&#34;low&#34;/&gt;&lt;script src=&#34;https://www.jamesdrandall.com/spred/_next/static/chunks/fd9d1056-828e77a11ad50db3.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://www.jamesdrandall.com/spred/_next/static/chunks/596-84ff07fcffc95942.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://cdn.usefathom.com/script.js&#34; data-site=&#34;QJBJTFCI&#34; defer&gt;&lt;/script&gt;&lt;script src=&#34;https://www.jamesdrandall.com/spred/_next/static/chunks/main-app-652fd898328bf51f.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;title&gt;SprEd - Retro sprite editing brought up to date.&lt;/title&gt;&lt;meta name=&#34;description&#34; content=&#34;By leveraging insights from our network of industry insiders, you’ll know exactly when to buy to maximize profit, and exactly when to sell to avoid painful losses.&#34;/&gt;&lt;link rel=&#34;icon&#34; href=&#34;https://www.jamesdrandall.com/spred/favicon.ico&#34; type=&#34;image/x-icon&#34; sizes=&#34;16x16&#34;/&gt;&lt;meta name=&#34;next-size-adjust&#34;/&gt;&lt;script src=&#34;https://www.jamesdrandall.com/spred/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js&#34; noModule=&#34;&#34;&gt;&lt;/script&gt;&lt;/head&gt;&lt;body class=&#34;flex h-full flex-col font-pixel&#34;&gt;&lt;div class=&#34;flex min-h-full flex-col&#34;&gt;&lt;header&gt;&lt;nav&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative z-50 flex justify-between py-8&#34;&gt;&lt;div class=&#34;relative z-10 flex items-center gap-16&#34;&gt;&lt;a aria-label=&#34;Home&#34; href=&#34;https://www.jamesdrandall.com/spred&#34;&gt;&lt;div class=&#34;flex flex-row items-center&#34;&gt;&lt;img alt=&#34;SprEd&#34; loading=&#34;lazy&#34; width=&#34;40&#34; height=&#34;40&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/spred/_next/static/media/spred.cc4da57b.png&#34;/&gt;&lt;span class=&#34;ml-2 spred-logo-text&#34;&gt;SprEd&lt;/span&gt;&lt;/div&gt;&lt;/a&gt;&lt;div class=&#34;hidden lg:flex lg:gap-10&#34;&gt;&lt;a href=&#34;#free&#34; class=&#34;relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors delay-150 hover:text-gray-900 hover:delay-0&#34;&gt;&lt;span class=&#34;relative z-10&#34;&gt;Free&lt;/span&gt;&lt;/a&gt;&lt;a href=&#34;#premium&#34; class=&#34;relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors delay-150 hover:text-gray-900 hover:delay-0&#34;&gt;&lt;span class=&#34;relative z-10&#34;&gt;Premium&lt;/span&gt;&lt;/a&gt;&lt;a href=&#34;#faqs&#34; class=&#34;relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors delay-150 hover:text-gray-900 hover:delay-0&#34;&gt;&lt;span class=&#34;relative z-10&#34;&gt;FAQs&lt;/span&gt;&lt;/a&gt;&lt;a href=&#34;#privacy&#34; class=&#34;relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors delay-150 hover:text-gray-900 hover:delay-0&#34;&gt;&lt;span class=&#34;relative z-10&#34;&gt;Privacy&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;flex items-center gap-6&#34;&gt;&lt;div class=&#34;lg:hidden&#34; data-headlessui-state=&#34;&#34;&gt;&lt;button class=&#34;relative z-10 -m-2 inline-flex items-center rounded-lg stroke-gray-900 p-2 hover:bg-gray-200/50 hover:stroke-gray-600 active:stroke-gray-900 ui-not-focus-visible:outline-none&#34; aria-label=&#34;Toggle site navigation&#34; type=&#34;button&#34; aria-expanded=&#34;false&#34; data-headlessui-state=&#34;&#34;&gt;&lt;svg viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; aria-hidden=&#34;true&#34; class=&#34;h-6 w-6&#34;&gt;&lt;path d=&#34;M5 6h14M5 18h14M5 12h14&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;div style=&#34;position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&#34;flex-auto&#34;&gt;&lt;div class=&#34;overflow-hidden pb-20 sm:pb-32 lg:pb-16 xl:pb-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;lg:grid lg:grid-cols-12&#34;&gt;&lt;div class=&#34;lg:col-start-2 lg:col-span-10 lg:mb-10&#34;&gt;&lt;img alt=&#34;SprEd&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/spred/_next/static/media/heroImage.b6e1ed2b.png&#34;/&gt;&lt;/div&gt;&lt;div class=&#34;lg:col-start-2 lg:col-span-10&#34;&gt;&lt;h1 class=&#34;text-center text-4xl font-medium tracking-tight text-gray-900&#34;&gt;Retro sprite editing for your iPad.&lt;/h1&gt;&lt;/div&gt;&lt;div class=&#34;lg:col-span-12 flex justify-center flex-wrap gap-x-6 mt-6&#34;&gt;&lt;a aria-label=&#34;Download on the App Store&#34; class=&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900&#34; href=&#34;https://apps.apple.com/us/app/spred/id6463485447&#34;&gt;&lt;svg viewBox=&#34;0 0 120 40&#34; aria-hidden=&#34;true&#34; class=&#34;h-10&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;section id=&#34;free&#34; aria-label=&#34;Features available in the basic version&#34; class=&#34;bg-gray-900 py-20 sm:py-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto sm:text-center&#34;&gt;&lt;h2 class=&#34;text-3xl font-medium tracking-tight text-white&#34;&gt;Download the app and start creating pixel art&lt;/h2&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;The standard version of SprEd is free and comes packed with features for editing single sprites.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;lg:grid lg:grid-cols-12 lg:gap-8&#34;&gt;&lt;div class=&#34;mt-4 lg:mt-16 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Editor features&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/spred/_next/static/media/standardEditor.21e94931.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;Use a wide variety of drawing tools and configure the editor in a way that works for you with control over the grid, tool placement and the background.&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:mt-16 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Palettes&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/spred/_next/static/media/standardPalettes.b2a34ef7.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;Use custom palettes or choose from a selection of pre-made classic palettes to get started.&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:mt-16 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Drawing tools&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/spred/_next/static/media/standardTools.8b529d6e.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;Editing tools include a pencil, spray can, eraser, flood fill, a variety of shapes and cut, copy and paste.&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;hidden lg:block lg:col-span-4&#34;&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:mt-6 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Creating new images&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/spred/_next/static/media/standardCreate.9b559963.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;Either create an image with a blank canvas or import an image from Photos, Files or the clipboard.&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;hidden lg:block lg:col-span-4&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section id=&#34;premium&#34; aria-label=&#34;Premium features&#34; class=&#34;py-20 sm:py-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto sm:text-center&#34;&gt;&lt;h2 class=&#34;text-3xl font-medium tracking-tight text-gray-900&#34;&gt;Upgrade to the Premium version for more advanced features&lt;/h2&gt;&lt;/div&gt;&lt;div class=&#34;mx-auto max-w-2xl sm:text-center&#34;&gt;&lt;p class=&#34;mt-2 text-lg text-gray-600&#34;&gt;Unlock a set of advanced features by upgrading to the Premium version with a one-time In-App Purchase.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;lg:grid lg:grid-cols-12 lg:gap-8&#34;&gt;&lt;div class=&#34;mt-4 lg:mt-16 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Animation previews&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/spred/_next/static/media/premiumAnimation.f3e94bac.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-600&#34;&gt;Preview animations that update in real-time as you edit the frames and control the playback speed and animation source in your spritesheet. You can export your animations as video to share with others.&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:mt-16 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Repeating tile preview&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/spred/_next/static/media/premiumRepeating.41f9ab02.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-600&#34;&gt;Use the repeating sprite / texture preview tool to ensure that your repeating sprites will look great when tiled.&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:mt-16 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Spritesheets&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/spred/_next/static/media/premiumTilesheet.896176cd.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-600&#34;&gt;Work on sprite sheets that contain multiple related sprites and tiles - great for use in level editors and tile based games.&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;hidden lg:block lg:col-span-2&#34;&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:mt-6 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Import spritesheets&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/spred/_next/static/media/premiumImport.4e6ea450.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-600&#34;&gt;Import and compose spritesheets from multiple source images or slice from an existing single image&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:mt-6 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Using layers&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/spred/_next/static/media/premiumLayers.26e3d38a.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-600&#34;&gt;Use layers to structure your images and apply lighting and color effects.&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;hidden lg:block lg:col-span-2&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section id=&#34;faqs&#34; aria-labelledby=&#34;faqs-title&#34; class=&#34;bg-gray-900 border-t border-gray-200 py-16 sm:py-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto max-w-2xl lg:mx-0&#34;&gt;&lt;h2 id=&#34;faqs-title&#34; class=&#34;text-3xl font-medium tracking-tight text-white&#34;&gt;Frequently asked questions&lt;/h2&gt;&lt;/div&gt;&lt;ul role=&#34;list&#34; class=&#34;mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-8 sm:mt-20 lg:max-w-none lg:grid-cols-3&#34;&gt;&lt;li&gt;&lt;ul role=&#34;list&#34; class=&#34;space-y-10&#34;&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-200&#34;&gt;How do I scroll without drawing?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-400&#34;&gt;You can use one finger to draw and two fingers to scroll. If you&amp;#x27;re using an Apple Pencil then you can enable Pencil Only Mode and use a single finger to scroll.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-200&#34;&gt;How do I change the pencil width?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-400&#34;&gt;Hole your finger (or the pencil) on the width indicator and move it left and right.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-200&#34;&gt;How do I access the RGB / HSL color picker?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-400&#34;&gt;Tap and hold the color indicator.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;ul role=&#34;list&#34; class=&#34;space-y-10&#34;&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-200&#34;&gt;Why do my pictures appear blurry in the Photos app?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-400&#34;&gt;Images are scaled for viewing in the Photos app and this scaling results in the blurry appearance. Exporting them from Photos should result in a clear image, alternatively (and this is how I work) I recommend exporting directly to Files.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-200&#34;&gt;How do I select and use a colour in my image?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-400&#34;&gt;You can long press at any time in the editor and it will pick up the colour under your finger or pencil and set it as the current colour. You can also use the palette options to pick up all the colours in the image and place them in the palette.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/section&gt;&lt;section id=&#34;privacy&#34; aria-label=&#34;Privacy information&#34; class=&#34;bg-white py-20 sm:py-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto&#34;&gt;&lt;h2 class=&#34;text-3xl font-medium tracking-tight text-black&#34;&gt;What about Privacy?&lt;/h2&gt;&lt;p class=&#34;mt-2 text-lg text-gray-900&#34;&gt;Our privacy policy is really simple. We don&amp;#x27;t think you should be spied upon and tracked all over the web or on your devices and so we don&amp;#x27;t collect any information about you, or your usage of the app, at all.&lt;/p&gt;&lt;p class=&#34;mt-2 text-lg text-gray-900&#34;&gt;If you contact us by email then we will never share your email address with a third party or use it for any purpose other than responding to your email.&lt;/p&gt;&lt;p class=&#34;mt-2 text-lg text-gray-900&#34;&gt;And finally this website contains no tracking cookies or analytics of any kind. We don&amp;#x27;t want to know who you are or what you do on the web. We just want you to enjoy using SprEd.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section id=&#34;privacy&#34; aria-label=&#34;Featured images&#34; class=&#34;bg-black py-20 sm:py-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto&#34;&gt;&lt;h2 class=&#34;text-3xl font-medium tracking-tight text-white&#34;&gt;Where do the cool sprites on this website come from?&lt;/h2&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;Its a combination of things drawn by the author and things either purchased or available for free. I can generally tweak artwork to suit my purposes or work from a reference but I&amp;#x27;m definitely more a developer than an artist so I tend to look for great affordable artwork that I can use directly or as a base. There are some links below to some of the resources I have used:&lt;/p&gt;&lt;ul class=&#34;list-inside list-disc mt-6 mb-6 text-lg text-gray-200&#34;&gt;&lt;li&gt;&lt;a href=&#34;https://www.gameart2d.com&#34; class=&#34;hover:text-red-500&#34;&gt;Game Art 2D - some great quality artwork, with a super clear license, and very affordable&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p class=&#34;default-font mt-2 text-gray-200 italic&#34;&gt;Where the artwork indicates it is commercial an appropriate license has been purchased.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/main&gt;&lt;footer class=&#34;border-t border-gray-200&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;flex flex-col items-center border-t border-gray-200 py-12 md:flex-row-reverse md:justify-between&#34;&gt;&lt;p class=&#34;mt-6 text-sm text-gray-500 md:mt-0&#34;&gt;© Copyright &lt;!-- --&gt;2023&lt;!-- --&gt;. All rights reserved.&lt;/p&gt;&lt;a aria-label=&#34;Download on the App Store&#34; class=&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900&#34; href=&#34;https://apps.apple.com/us/app/spred/id6463485447&#34;&gt;&lt;svg viewBox=&#34;0 0 120 40&#34; aria-hidden=&#34;true&#34; class=&#34;h-10&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/footer&gt;&lt;/div&gt;&lt;script src=&#34;https://www.jamesdrandall.com/spred/_next/static/chunks/webpack-60382efa82877252.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;script&gt;(self.__next_f=self.__next_f||[]).push([0])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;1:HL[\&#34;/spred/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2\&#34;,{\&#34;as\&#34;:\&#34;font\&#34;,\&#34;type\&#34;:\&#34;font/woff2\&#34;}]\n2:HL[\&#34;/spred/_next/static/css/81f197f724b148b6.css\&#34;,{\&#34;as\&#34;:\&#34;style\&#34;}]\n0:\&#34;$L3\&#34;\n&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;4:I{\&#34;id\&#34;:7948,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-60382efa82877252.js\&#34;,\&#34;971:static/chunks/fd9d1056-828e77a11ad50db3.js\&#34;,\&#34;596:static/chunks/596-84ff07fcffc95942.js\&#34;],\&#34;name\&#34;:\&#34;default\&#34;,\&#34;async\&#34;:false}\n6:I{\&#34;id\&#34;:6628,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-60382efa82877252.js\&#34;,\&#34;971:static/chunks/fd9d1056-828e77a11ad50db3.js\&#34;,\&#34;596:static/chunks/596-84ff07fcffc95942.js\&#34;],\&#34;name\&#34;:\&#34;\&#34;,\&#34;async\&#34;:false}\n7:I{\&#34;id\&#34;:7767,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-60382efa82877252.js\&#34;,\&#34;971:static/chunks/fd9d1056-828e77a11ad50db3.js\&#34;,\&#34;5&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;96:static/chunks/596-84ff07fcffc95942.js\&#34;],\&#34;name\&#34;:\&#34;default\&#34;,\&#34;async\&#34;:false}\n8:I{\&#34;id\&#34;:7920,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-60382efa82877252.js\&#34;,\&#34;971:static/chunks/fd9d1056-828e77a11ad50db3.js\&#34;,\&#34;596:static/chunks/596-84ff07fcffc95942.js\&#34;],\&#34;name\&#34;:\&#34;default\&#34;,\&#34;async\&#34;:false}\n9:I{\&#34;id\&#34;:6111,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-2c8818795983fe52.js\&#34;,\&#34;122:static/chunks/122-07209bec8c1f93e7.js\&#34;,\&#34;216:static/chunks/216-49fb20a8f90680ac.js\&#34;,\&#34;81:static/chunks/81-bf1fd96854c4857a.js\&#34;,\&#34;95:static/chunks/app/(main)/layout-8be3f&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;f5b05dd65fc.js\&#34;],\&#34;name\&#34;:\&#34;Header\&#34;,\&#34;async\&#34;:false}\na:I{\&#34;id\&#34;:6685,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-2c8818795983fe52.js\&#34;,\&#34;122:static/chunks/122-07209bec8c1f93e7.js\&#34;,\&#34;216:static/chunks/216-49fb20a8f90680ac.js\&#34;,\&#34;81:static/chunks/81-bf1fd96854c4857a.js\&#34;,\&#34;95:static/chunks/app/(main)/layout-8be3ff5b05dd65fc.js\&#34;],\&#34;name\&#34;:\&#34;\&#34;,\&#34;async\&#34;:false}\nd:I{\&#34;id\&#34;:3222,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-2c8818795983fe52.js\&#34;,\&#34;122:static/chunks/122-07209bec8c1f93e7.js\&#34;,\&#34;974:static/chunks/app/(main)/page-183a52930a3954f5.js\&#34;],\&#34;name\&#34;:\&#34;Image\&#34;&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;,\&#34;async\&#34;:false}\nf:I{\&#34;id\&#34;:1730,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-2c8818795983fe52.js\&#34;,\&#34;122:static/chunks/122-07209bec8c1f93e7.js\&#34;,\&#34;974:static/chunks/app/(main)/page-183a52930a3954f5.js\&#34;],\&#34;name\&#34;:\&#34;PrimaryFeatures\&#34;,\&#34;async\&#34;:false}\n10:I{\&#34;id\&#34;:6163,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-2c8818795983fe52.js\&#34;,\&#34;122:static/chunks/122-07209bec8c1f93e7.js\&#34;,\&#34;974:static/chunks/app/(main)/page-183a52930a3954f5.js\&#34;],\&#34;name\&#34;:\&#34;Privacy\&#34;,\&#34;async\&#34;:false}\n11:I{\&#34;id\&#34;:2705,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-2c8818795983fe52.js\&#34;,\&#34;122:static/chun&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;ks/122-07209bec8c1f93e7.js\&#34;,\&#34;974:static/chunks/app/(main)/page-183a52930a3954f5.js\&#34;],\&#34;name\&#34;:\&#34;FeaturedImages\&#34;,\&#34;async\&#34;:false}\nb:T14cb,&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;e:T14cb,&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;12:T14cb,&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;3:[[[\&#34;$\&#34;,\&#34;link\&#34;,\&#34;0\&#34;,{\&#34;rel\&#34;:\&#34;stylesheet\&#34;,\&#34;href\&#34;:\&#34;/spred/_next/static/css/81f197f724b148b6.css\&#34;,\&#34;precedence\&#34;:\&#34;next\&#34;}]],[\&#34;$\&#34;,\&#34;$L4\&#34;,null,{\&#34;buildId\&#34;:\&#34;so4O5mHk_LVi5OwYwhwIY\&#34;,\&#34;assetPrefix\&#34;:\&#34;/spred\&#34;,\&#34;initialCanonicalUrl\&#34;:\&#34;/\&#34;,\&#34;initialTree\&#34;:[\&#34;\&#34;,{\&#34;children\&#34;:[\&#34;(main)\&#34;,{\&#34;children\&#34;:[\&#34;__PAGE__\&#34;,{}]}]},\&#34;$undefined\&#34;,\&#34;$undefined\&#34;,true],\&#34;initialHead\&#34;:\&#34;$L5\&#34;,\&#34;globalErrorComponent\&#34;:\&#34;$6\&#34;,\&#34;children\&#34;:[null,[\&#34;$\&#34;,\&#34;html\&#34;,null,{\&#34;lang\&#34;:\&#34;en\&#34;,\&#34;className\&#34;:\&#34;h-full bg-gray-50 antialiased __variable_2b8e48\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;body\&#34;,null,{\&#34;className\&#34;:\&#34;flex h-full flex-col font-pixel\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;flex min-h-full flex-col\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$L7\&#34;,null,{\&#34;parallelRouterKey\&#34;:\&#34;children\&#34;,\&#34;segmentPath\&#34;:[\&#34;children\&#34;],\&#34;error\&#34;:\&#34;$undefined\&#34;,\&#34;errorStyles\&#34;:\&#34;$undefined\&#34;,\&#34;loading\&#34;:\&#34;$undefined\&#34;,\&#34;loadingStyles\&#34;:\&#34;$undefined\&#34;,\&#34;hasLoading\&#34;:false,\&#34;template\&#34;:[\&#34;$\&#34;,\&#34;$L8\&#34;,null,{}],\&#34;templateStyles\&#34;:\&#34;$undefined\&#34;,\&#34;notFound\&#34;:[[\&#34;$\&#34;,\&#34;$L9\&#34;,null,{}],[\&#34;$\&#34;,\&#34;main\&#34;,null,{\&#34;className\&#34;:\&#34;flex-auto\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative isolate flex h-full flex-col items-center justify-center py-20 text-center sm:py-32\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 1090 1090\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;fill\&#34;:\&#34;none\&#34;,\&#34;preserveAspectRatio\&#34;:\&#34;none\&#34;,\&#34;className\&#34;:\&#34;absolute left-1/2 top-1/2 -z-10 mt-44 w-[68.125rem] -translate-x-1/2 -translate-y-1/2 stroke-gray-300/30 [mask-image:linear-gradient(to_bottom,white_20%,transparent_75%)]\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;544.5\&#34;}],[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;480.5\&#34;}],[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;416.5\&#34;}],[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;352.5\&#34;}]]}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;text-sm font-semibold text-gray-900\&#34;,\&#34;children\&#34;:\&#34;404\&#34;}],[\&#34;$\&#34;,\&#34;h1\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-3xl font-medium tracking-tight text-gray-900\&#34;,\&#34;children\&#34;:\&#34;Page not found\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-lg text-gray-600\&#34;,\&#34;children\&#34;:\&#34;Sorry, we couldn’t find the page you’re looking for.\&#34;}],[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;className\&#34;:\&#34;inline-flex justify-center rounded-lg border py-[calc(theme(spacing.2)-1px)] px-[calc(theme(spacing.3)-1px)] text-sm outline-2 outline-offset-2 transition-colors border-gray-300 text-gray-700 hover:border-gray-400 active:bg-gray-100 active:text-gray-700/80 mt-8\&#34;,\&#34;href\&#34;:\&#34;/\&#34;,\&#34;children\&#34;:\&#34;Go back home\&#34;}]]}]}],[\&#34;$\&#34;,\&#34;footer\&#34;,null,{\&#34;className\&#34;:\&#34;border-t border-gray-200\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;flex flex-col items-center border-t border-gray-200 py-12 md:flex-row-reverse md:justify-between\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-6 text-sm text-gray-500 md:mt-0\&#34;,\&#34;children\&#34;:[\&#34;© Copyright \&#34;,2023,\&#34;. All rights reserved.\&#34;]}],[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;href\&#34;:\&#34;https://apps.apple.com/us/app/spred/id6463485447\&#34;,\&#34;aria-label\&#34;:\&#34;Download on the App Store\&#34;,\&#34;className\&#34;:\&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 120 40\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;className\&#34;:\&#34;h-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;path\&#34;,null,{\&#34;fill\&#34;:\&#34;currentColor\&#34;,\&#34;d\&#34;:\&#34;$b\&#34;}]}]}]]}]}]}]],\&#34;notFoundStyles\&#34;:[],\&#34;childProp\&#34;:{\&#34;current\&#34;:[null,[[\&#34;$\&#34;,\&#34;$L9\&#34;,null,{}],[\&#34;$\&#34;,\&#34;main\&#34;,null,{\&#34;className\&#34;:\&#34;flex-auto\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$L7\&#34;,null,{\&#34;parallelRouterKey\&#34;:\&#34;children\&#34;,\&#34;segmentPath\&#34;:[\&#34;children\&#34;,\&#34;(main)\&#34;,\&#34;children\&#34;],\&#34;error\&#34;:\&#34;$undefined\&#34;,\&#34;errorStyles\&#34;:\&#34;$undefined\&#34;,\&#34;loading\&#34;:\&#34;$undefined\&#34;,\&#34;loadingStyles\&#34;:\&#34;$undefined\&#34;,\&#34;hasLoading\&#34;:false,\&#34;template\&#34;:[\&#34;$\&#34;,\&#34;$L8\&#34;,null,{}],\&#34;templateStyles\&#34;:\&#34;$undefined\&#34;,\&#34;notFound\&#34;:[[\&#34;$\&#34;,\&#34;title\&#34;,null,{\&#34;children\&#34;:\&#34;404: This page could not be found.\&#34;}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;style\&#34;:{\&#34;fontFamily\&#34;:\&#34;system-ui,\\\&#34;Segoe UI\\\&#34;,Roboto,Helvetica,Arial,sans-serif,\\\&#34;Apple Color Emoji\\\&#34;,\\\&#34;Segoe UI Emoji\\\&#34;\&#34;,\&#34;height\&#34;:\&#34;100vh\&#34;,\&#34;textAlign\&#34;:\&#34;center\&#34;,\&#34;display\&#34;:\&#34;flex\&#34;,\&#34;flexDirection\&#34;:\&#34;column\&#34;,\&#34;alignItems\&#34;:\&#34;center\&#34;,\&#34;justifyContent\&#34;:\&#34;center\&#34;},\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;style\&#34;,null,{\&#34;dangerouslySetInnerHTML\&#34;:{\&#34;__html\&#34;:\&#34;body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\&#34;}}],[\&#34;$\&#34;,\&#34;h1\&#34;,null,{\&#34;className\&#34;:\&#34;next-error-h1\&#34;,\&#34;style\&#34;:{\&#34;display\&#34;:\&#34;inline-block\&#34;,\&#34;margin\&#34;:\&#34;0 20px 0 0\&#34;,\&#34;padding\&#34;:\&#34;0 23px 0 0\&#34;,\&#34;fontSize\&#34;:24,\&#34;fontWeight\&#34;:500,\&#34;verticalAlign\&#34;:\&#34;top\&#34;,\&#34;lineHeight\&#34;:\&#34;49px\&#34;},\&#34;children\&#34;:\&#34;404\&#34;}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;style\&#34;:{\&#34;display\&#34;:\&#34;inline-block\&#34;},\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;h2\&#34;,null,{\&#34;style\&#34;:{\&#34;fontSize\&#34;:14,\&#34;fontWeight\&#34;:400,\&#34;lineHeight\&#34;:\&#34;49px\&#34;,\&#34;margin\&#34;:0},\&#34;children\&#34;:\&#34;This page could not be found.\&#34;}]}]]}]}]],\&#34;notFoundStyles\&#34;:[],\&#34;childProp\&#34;:{\&#34;current\&#34;:[\&#34;$Lc\&#34;,[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;overflow-hidden pb-20 sm:pb-32 lg:pb-16 xl:pb-16\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:grid lg:grid-cols-12\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:col-start-2 lg:col-span-10 lg:mb-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$Ld\&#34;,null,{\&#34;src\&#34;:{\&#34;src\&#34;:\&#34;/spred/_next/static/media/heroImage.b6e1ed2b.png\&#34;,\&#34;height\&#34;:2290,\&#34;width\&#34;:2960,\&#34;blurDataURL\&#34;:\&#34;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAAS1BMVEVDQ0QlIyFZW15UkBUpKioWFRYcIR1RVFQAAAAhISEGBweBc2dOQyOTloNQRR+aqo9KSkxMSk2cj5K4v7StlZ7Ryc2We4KInmqOnmOlukpgAAAAEXRSTlP9+/L+9PTz8vLh4f7+/v7++omwKlsAAAAJcEhZcwAALiMAAC4jAXilP3YAAAA4SURBVHicBcGJEYAwCATAMwEO/DLkUfuv1F2EkIUScMynLygcx31eKfActbY02Nbfb6QiDCzU/QcwlAHWv2f6UwAAAABJRU5ErkJggg==\&#34;,\&#34;blurWidth\&#34;:8,\&#34;blurHeight\&#34;:6},\&#34;alt\&#34;:\&#34;SprEd\&#34;}]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:col-start-2 lg:col-span-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;h1\&#34;,null,{\&#34;className\&#34;:\&#34;text-center text-4xl font-medium tracking-tight text-gray-900\&#34;,\&#34;children\&#34;:\&#34;Retro sprite editing for your iPad.\&#34;}]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:col-span-12 flex justify-center flex-wrap gap-x-6 mt-6\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;href\&#34;:\&#34;https://apps.apple.com/us/app/spred/id6463485447\&#34;,\&#34;aria-label\&#34;:\&#34;Download on the App Store\&#34;,\&#34;className\&#34;:\&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 120 40\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;className\&#34;:\&#34;h-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;path\&#34;,null,{\&#34;fill\&#34;:\&#34;currentColor\&#34;,\&#34;d\&#34;:\&#34;$e\&#34;}]}]}]}]]}]}]}],[\&#34;$\&#34;,\&#34;$Lf\&#34;,null,{}],[\&#34;$\&#34;,\&#34;section\&#34;,null,{\&#34;id\&#34;:\&#34;premium\&#34;,\&#34;aria-label\&#34;:\&#34;Premium features\&#34;,\&#34;className\&#34;:\&#34;py-20 sm:py-16\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto sm:text-center\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;h2\&#34;,null,{\&#34;className\&#34;:\&#34;text-3xl font-medium tracking-tight text-gray-900\&#34;,\&#34;children\&#34;:\&#34;Upgrade to the Premium version for more advanced features\&#34;}]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-2xl sm:text-center\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-lg text-gray-600\&#34;,\&#34;children\&#34;:\&#34;Unlock a set of advanced features by upgrading to the Premium version with a one-time In-App Purchase.\&#34;}]}]]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:grid lg:grid-cols-12 lg:gap-8\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 lg:mt-16 lg:col-span-4\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;$Ld\&#34;,null,{\&#34;src\&#34;:{\&#34;src\&#34;:\&#34;/spred/_next/static/media/premiumAnimation.f3e94bac.png\&#34;,\&#34;height\&#34;:2290,\&#34;width\&#34;:2960,\&#34;blurDataURL\&#34;:\&#34;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAAPFBMVEUJCQlKSEmDbmcqMTIrKys5OjpNTkocHBwCAgOCnFUiIiILCwsnJiZ5eHxTU1MAAAAvMDESEhJhZ1uSkXpRJQsrAAAADnRSTlPx/v7y4fL19PT+3+Hz/k+OJ7sAAAAJcEhZcwAALiMAAC4jAXilP3YAAAA0SURBVHicBcGBAYAgDASxU4EWwX6B/Xc1oRiAvaCVuWSg6z6hSo2v99BDO7F3alKaubuPHyyfAc+sXjBRAAAAAElFTkSuQmCC\&#34;,\&#34;blurWidth\&#34;:8,\&#34;blurHeight\&#34;:6},\&#34;alt\&#34;:\&#34;Animation previews\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-lg text-gray-600\&#34;,\&#34;children\&#34;:\&#34;Preview animations that update in real-time as you edit the frames and control the playback speed and animation source in your spritesheet. You can export your animations as video to share with others.\&#34;}]]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 lg:mt-16 lg:col-span-4\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;$Ld\&#34;,null,{\&#34;src\&#34;:{\&#34;src\&#34;:\&#34;/spred/_next/static/media/premiumRepeating.41f9ab02.png\&#34;,\&#34;height\&#34;:2290,\&#34;width\&#34;:2960,\&#34;blurDataURL\&#34;:\&#34;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAATlBMVEUgIR4TEhgKCwsPCwgkJCREMBseHiACAwYqJBIyMTIdHh4+QDdqiSR6kCeFry2XzDZfYiE6LhhUORsWDAUvMBQ2RRkeIQes11iw4VNsQSATEsgyAAAAEnRSTlPy8+H24PP18vvz4fL+/v7+/vpT7J9GAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAOElEQVR4nBXGURLAEAwFwEeRoMWkCPe/qLFfCxdwOdBYqmsY0PxqeWcE/dKEe77Zwj3CPuy9CekAOU0CKkAnEeYAAAAASUVORK5CYII=\&#34;,\&#34;blurWidth\&#34;:8,\&#34;blurHeight\&#34;:6},\&#34;alt\&#34;:\&#34;Repeating tile preview\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-lg text-gray-600\&#34;,\&#34;children\&#34;:\&#34;Use the repeating sprite / texture preview tool to ensure that your repeating sprites will look great when tiled.\&#34;}]]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 lg:mt-16 lg:col-span-4\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;$Ld\&#34;,null,{\&#34;src\&#34;:{\&#34;src\&#34;:\&#34;/spred/_next/static/media/premiumTilesheet.896176cd.png\&#34;,\&#34;height\&#34;:2290,\&#34;width\&#34;:2960,\&#34;blurDataURL\&#34;:\&#34;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAATlBMVEUAAAEvMSWyxI8LCwtgYlqeqIA8OzUGBgcYGReWpnCdsXIhISEkIyMrKSsKCgqtsZ4mKhofJBVGRzszMjSCkFyaoIRQVz4WGA+NmWhbYFA0KVIoAAAAEXRSTlP08f7h8v758fT+/t/z4eH++d9St9kAAAAJcEhZcwAALiMAAC4jAXilP3YAAAA5SURBVHicHcEBEoAgCATAI1FQKwfU0v9/tJl2camIiFaovda7naAx81EGg1ZsMU+Guqf9eMKNX/gAOyQB/gKXmxYAAAAASUVORK5CYII=\&#34;,\&#34;blurWidth\&#34;:8,\&#34;blurHeight\&#34;:6},\&#34;alt\&#34;:\&#34;Spritesheets\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-lg text-gray-600\&#34;,\&#34;children\&#34;:\&#34;Work on sprite sheets that contain multiple related sprites and tiles - great for use in level editors and tile based games.\&#34;}]]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;hidden lg:block lg:col-span-2\&#34;}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 lg:mt-6 lg:col-span-4\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;$Ld\&#34;,null,{\&#34;src\&#34;:{\&#34;src\&#34;:\&#34;/spred/_next/static/media/premiumImport.4e6ea450.png\&#34;,\&#34;height\&#34;:2290,\&#34;width\&#34;:2960,\&#34;blurDataURL\&#34;:\&#34;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAAMFBMVEUGBgYtKildXVsLCwtFRENKSEgdHh04OThVTksODw8CAgILCAY0Jx81HxVsb21/fntKPYHbAAAACnRSTlP08vPh4Pny8/PxO5IrYgAAAAlwSFlzAAAuIwAALiMBeKU/dgAAADBJREFUeJwtyMkNACAMBLHZcCcc/XeLhPDTZJMky/Qz15q7YCXCw2H4A/ifhJoq6QIjIwFC7jns/QAAAABJRU5ErkJggg==\&#34;,\&#34;blurWidth\&#34;:8,\&#34;blurHeight\&#34;:6},\&#34;alt\&#34;:\&#34;Import spritesheets\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-lg text-gray-600\&#34;,\&#34;children\&#34;:\&#34;Import and compose spritesheets from multiple source images or slice from an existing single image\&#34;}]]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 lg:mt-6 lg:col-span-4\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;$Ld\&#34;,null,{\&#34;src\&#34;:{\&#34;src\&#34;:\&#34;/spred/_next/static/media/premiumLayers.26e3d38a.png\&#34;,\&#34;height\&#34;:2290,\&#34;width\&#34;:2960,\&#34;blurDataURL\&#34;:\&#34;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAARVBMVEUpKypbPTIhISEoKCg4OzcZGRpLfxYAAAAgISF5enoFBQWDameEhYIAAQGstaBLSkw/QT+xpKXT0tKil5WEiYCKoFJ1lEl0EoKQAAAAD3RSTlP0/t/h+fn+8vLz4f7y4f5fQI0CAAAACXBIWXMAAC4jAAAuIwF4pT92AAAANklEQVR4nAXBCQKAIAgAwTUPwA5Qy/8/tRmyKqgeSBlrzKhInCktB/GntagF8bm/16FjZhf3Dy4jAdC/YsAlAAAAAElFTkSuQmCC\&#34;,\&#34;blurWidth\&#34;:8,\&#34;blurHeight\&#34;:6},\&#34;alt\&#34;:\&#34;Using layers\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-lg text-gray-600\&#34;,\&#34;children\&#34;:\&#34;Use layers to structure your images and apply lighting and color effects.\&#34;}]]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;hidden lg:block lg:col-span-2\&#34;}]]}]}]]}],[\&#34;$\&#34;,\&#34;section\&#34;,null,{\&#34;id\&#34;:\&#34;faqs\&#34;,\&#34;aria-labelledby\&#34;:\&#34;faqs-title\&#34;,\&#34;className\&#34;:\&#34;bg-gray-900 border-t border-gray-200 py-16 sm:py-16\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-2xl lg:mx-0\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;h2\&#34;,null,{\&#34;id\&#34;:\&#34;faqs-title\&#34;,\&#34;className\&#34;:\&#34;text-3xl font-medium tracking-tight text-white\&#34;,\&#34;children\&#34;:\&#34;Frequently asked questions\&#34;}]}],[\&#34;$\&#34;,\&#34;ul\&#34;,null,{\&#34;role\&#34;:\&#34;list\&#34;,\&#34;className\&#34;:\&#34;mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-8 sm:mt-20 lg:max-w-none lg:grid-cols-3\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;0\&#34;,{\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;ul\&#34;,null,{\&#34;role\&#34;:\&#34;list\&#34;,\&#34;className\&#34;:\&#34;space-y-10\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;0\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-200\&#34;,\&#34;children\&#34;:\&#34;How do I scroll without drawing?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-400\&#34;,\&#34;children\&#34;:\&#34;You can use one finger to draw and two fingers to scroll. If you&#39;re using an Apple Pencil then you can enable Pencil Only Mode and use a single finger to scroll.\&#34;}]]}],[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;1\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-200\&#34;,\&#34;children\&#34;:\&#34;How do I change the pencil width?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-400\&#34;,\&#34;children\&#34;:\&#34;Hole your finger (or the pencil) on the width indicator and move it left and right.\&#34;}]]}],[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;2\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-200\&#34;,\&#34;children\&#34;:\&#34;How do I access the RGB / HSL color picker?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-400\&#34;,\&#34;children\&#34;:\&#34;Tap and hold the color indicator.\&#34;}]]}]]}]}],[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;1\&#34;,{\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;ul\&#34;,null,{\&#34;role\&#34;:\&#34;list\&#34;,\&#34;className\&#34;:\&#34;space-y-10\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;0\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-200\&#34;,\&#34;children\&#34;:\&#34;Why do my pictures appear blurry in the Photos app?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-400\&#34;,\&#34;children\&#34;:\&#34;Images are scaled for viewing in the Photos app and this scaling results in the blurry appearance. Exporting them from Photos should result in a clear image, alternatively (and this is how I work) I recommend exporting directly to Files.\&#34;}]]}],[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;1\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-200\&#34;,\&#34;children\&#34;:\&#34;How do I select and use a colour in my image?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-400\&#34;,\&#34;children\&#34;:\&#34;You can long press at any time in the editor and it will pick up the colour under your finger or pencil and set it as the current colour. You can also use the palette options to pick up all the colours in the image and place them in the palette.\&#34;}]]}]]}]}]]}]]}]}],[\&#34;$\&#34;,\&#34;$L10\&#34;,null,{}],[\&#34;$\&#34;,\&#34;$L11\&#34;,null,{}]],null],\&#34;segment\&#34;:\&#34;__PAGE__\&#34;},\&#34;styles\&#34;:[]}]}],[\&#34;$\&#34;,\&#34;footer\&#34;,null,{\&#34;className\&#34;:\&#34;border-t border-gray-200\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;flex flex-col items-center border-t border-gray-200 py-12 md:flex-row-reverse md:justify-between\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-6 text-sm text-gray-500 md:mt-0\&#34;,\&#34;children\&#34;:[\&#34;© Copyright \&#34;,2023,\&#34;. All rights reserved.\&#34;]}],[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;href\&#34;:\&#34;https://apps.apple.com/us/app/spred/id6463485447\&#34;,\&#34;aria-label\&#34;:\&#34;Download on the App Store\&#34;,\&#34;className\&#34;:\&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 120 40\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;className\&#34;:\&#34;h-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;path\&#34;,null,{\&#34;fill\&#34;:\&#34;currentColor\&#34;,\&#34;d\&#34;:\&#34;$12\&#34;}]}]}]]}]}]}]],null],\&#34;segment\&#34;:\&#34;(main)\&#34;},\&#34;styles\&#34;:[]}]}]}]}],null]}]]\n&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;5:[[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;0\&#34;,{\&#34;charSet\&#34;:\&#34;utf-8\&#34;}],[\&#34;$\&#34;,\&#34;title\&#34;,\&#34;1\&#34;,{\&#34;children\&#34;:\&#34;SprEd - Retro sprite editing brought up to date.\&#34;}],[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;2\&#34;,{\&#34;name\&#34;:\&#34;description\&#34;,\&#34;content\&#34;:\&#34;By leveraging insights from our network of industry insiders, you’ll know exactly when to buy to maximize profit, and exactly when to sell to avoid painful losses.\&#34;}],[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;3\&#34;,{\&#34;name\&#34;:\&#34;viewport\&#34;,\&#34;content\&#34;:\&#34;width=device-width, initial-scale=1\&#34;}],[\&#34;$\&#34;,\&#34;link\&#34;,\&#34;4\&#34;,{\&#34;rel\&#34;:\&#34;icon\&#34;,\&#34;href\&#34;:\&#34;/spred/favicon.ico\&#34;,\&#34;type\&#34;:\&#34;image/x-icon\&#34;,\&#34;sizes\&#34;:\&#34;16x16\&#34;}],[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;5\&#34;,{\&#34;name\&#34;:\&#34;next-size-adjust\&#34;}]]\n&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;c:null\n&#34;])&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://www.jamesdrandall.com/tetris/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/tetris/</guid>
      <description>&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;Fable&lt;/title&gt;
  &lt;meta http-equiv=&#39;Content-Type&#39; content=&#39;text/html; charset=utf-8&#39;&gt;
  &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1&#34;&gt;
  &lt;link rel=&#34;shortcut icon&#34; href=&#34;fable.ico&#34; /&gt;
&lt;/head&gt;
&lt;body style=&#34;margin:0;padding:0;height:100vh;overflow: hidden;background-color:black;&#34;&gt;
    &lt;canvas style=&#34;width: 100vw; height: 100vh;&#34; class=&#34;game-canvas&#34;&gt;&lt;/canvas&gt;
    &lt;script src=&#34;bundle.js&#34;&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://www.jamesdrandall.com/voxed/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/voxed/</guid>
      <description>&lt;!DOCTYPE html&gt;&lt;html lang=&#34;en&#34; class=&#34;h-full bg-gray-50 antialiased __variable_e165c7&#34;&gt;&lt;head&gt;&lt;meta charSet=&#34;utf-8&#34;/&gt;&lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1&#34;/&gt;&lt;link rel=&#34;preload&#34; as=&#34;font&#34; href=&#34;https://www.jamesdrandall.com/voxed/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2&#34; crossorigin=&#34;&#34; type=&#34;font/woff2&#34;/&gt;&lt;link rel=&#34;stylesheet&#34; href=&#34;https://www.jamesdrandall.com/voxed/_next/static/css/91e7e34991d0b065.css&#34; data-precedence=&#34;next&#34;/&gt;&lt;link rel=&#34;preload&#34; href=&#34;https://www.jamesdrandall.com/voxed/_next/static/chunks/webpack-a6a234cfe0c26d4c.js&#34; as=&#34;script&#34; fetchPriority=&#34;low&#34;/&gt;&lt;script src=&#34;https://www.jamesdrandall.com/voxed/_next/static/chunks/fd9d1056-c2ad140a17e094a3.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://www.jamesdrandall.com/voxed/_next/static/chunks/596-25963c8087224989.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;script src=&#34;https://www.jamesdrandall.com/voxed/_next/static/chunks/main-app-c586e4e197cbd7b3.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;title&gt;VoxEd - Elegant voxel sprite editing&lt;/title&gt;&lt;meta name=&#34;description&#34; content=&#34;Overview of VoxEd features.&#34;/&gt;&lt;link rel=&#34;icon&#34; href=&#34;https://www.jamesdrandall.com/voxed/favicon.ico&#34; type=&#34;image/x-icon&#34; sizes=&#34;16x16&#34;/&gt;&lt;meta name=&#34;next-size-adjust&#34;/&gt;&lt;script src=&#34;https://www.jamesdrandall.com/voxed/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js&#34; noModule=&#34;&#34;&gt;&lt;/script&gt;&lt;/head&gt;&lt;body class=&#34;flex h-full flex-col font-pixel&#34;&gt;&lt;div class=&#34;flex min-h-full flex-col&#34;&gt;&lt;header&gt;&lt;nav&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative z-50 flex justify-between py-8&#34;&gt;&lt;div class=&#34;relative z-10 flex items-center gap-16&#34;&gt;&lt;a aria-label=&#34;Home&#34; href=&#34;https://www.jamesdrandall.com/voxed&#34;&gt;&lt;div class=&#34;flex flex-row items-center&#34;&gt;&lt;img alt=&#34;SprEd&#34; loading=&#34;lazy&#34; width=&#34;40&#34; height=&#34;40&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/voxed/_next/static/media/spred.cc4da57b.png&#34;/&gt;&lt;span class=&#34;ml-2 spred-logo-text&#34;&gt;VoxEd&lt;/span&gt;&lt;/div&gt;&lt;/a&gt;&lt;div class=&#34;hidden lg:flex lg:gap-10&#34;&gt;&lt;a href=&#34;#faqs&#34; class=&#34;relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors delay-150 hover:text-gray-900 hover:delay-0&#34;&gt;&lt;span class=&#34;relative z-10&#34;&gt;FAQs&lt;/span&gt;&lt;/a&gt;&lt;a href=&#34;#privacy&#34; class=&#34;relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors delay-150 hover:text-gray-900 hover:delay-0&#34;&gt;&lt;span class=&#34;relative z-10&#34;&gt;Privacy&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;flex items-center gap-6&#34;&gt;&lt;div class=&#34;lg:hidden&#34; data-headlessui-state=&#34;&#34;&gt;&lt;button class=&#34;relative z-10 -m-2 inline-flex items-center rounded-lg stroke-gray-900 p-2 hover:bg-gray-200/50 hover:stroke-gray-600 active:stroke-gray-900 ui-not-focus-visible:outline-none&#34; aria-label=&#34;Toggle site navigation&#34; type=&#34;button&#34; aria-expanded=&#34;false&#34; data-headlessui-state=&#34;&#34;&gt;&lt;svg viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; aria-hidden=&#34;true&#34; class=&#34;h-6 w-6&#34;&gt;&lt;path d=&#34;M5 6h14M5 18h14M5 12h14&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;div style=&#34;position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none&#34;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&#34;flex-auto&#34;&gt;&lt;div class=&#34;overflow-hidden pb-20 sm:pb-32 lg:pb-16 xl:pb-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;lg:grid lg:grid-cols-12&#34;&gt;&lt;div class=&#34;lg:col-start-2 lg:col-span-10 lg:mb-10&#34;&gt;&lt;img alt=&#34;VoxEd&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/voxed/_next/static/media/heroImage.f76365b4.jpg&#34;/&gt;&lt;/div&gt;&lt;div class=&#34;lg:col-start-2 lg:col-span-10&#34;&gt;&lt;h1 class=&#34;text-center text-4xl font-medium tracking-tight text-gray-900&#34;&gt;Voxel sprite editing for your Mac and iPad&lt;/h1&gt;&lt;/div&gt;&lt;div class=&#34;lg:col-span-12 flex justify-center flex-wrap gap-x-6 mt-6&#34;&gt;&lt;a aria-label=&#34;Download on the App Store&#34; class=&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900&#34; href=&#34;https://apps.apple.com/us/app/spred/id6463485447&#34;&gt;&lt;svg viewBox=&#34;0 0 120 40&#34; aria-hidden=&#34;true&#34; class=&#34;h-10&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;section id=&#34;free&#34; aria-label=&#34;Features available in the basic version&#34; class=&#34;bg-gray-900 py-20 sm:py-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto sm:text-center&#34;&gt;&lt;h2 class=&#34;text-3xl font-medium tracking-tight text-white&#34;&gt;Download the app and start creating pixel art&lt;/h2&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;VoxEd is completely free and comes packed with features for creating and editing voxel based artwork.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;lg:grid lg:grid-cols-12 lg:gap-8&#34;&gt;&lt;div class=&#34;mt-4 lg:mt-16 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Editor features&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/voxed/_next/static/media/standardEditor.878bdad5.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;Use a wide variety of drawing tools to create your models.&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:mt-16 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Palettes&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/voxed/_next/static/media/standardPalettes.f8ea57be.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;Use custom palettes or choose from a selection of pre-made classic palettes to get started.&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:mt-16 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Creating new images&#34; loading=&#34;lazy&#34; width=&#34;2960&#34; height=&#34;2290&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/voxed/_next/static/media/standardCreate.2606bf78.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-200&#34;&gt;Either create an model with a black canvas or create from an image to generate including using the image as a height map.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section id=&#34;premium&#34; aria-label=&#34;Premium features&#34; class=&#34;py-20 sm:py-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto sm:text-center&#34;&gt;&lt;h2 class=&#34;text-3xl font-medium tracking-tight text-gray-900&#34;&gt;Work seamlessly across your iPad and Mac with iCloud&lt;/h2&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;lg:grid lg:grid-cols-12 lg:gap-8&#34;&gt;&lt;div class=&#34;mt-4 lg:mt-16 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Work on the Mac&#34; loading=&#34;lazy&#34; width=&#34;2560&#34; height=&#34;1600&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/voxed/_next/static/media/macEditor.f61e5d2b.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-600&#34;&gt;Work seamlessly across your iPad and Mac with iCloud.&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:mt-16 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Export as JSON&#34; loading=&#34;lazy&#34; width=&#34;2560&#34; height=&#34;1600&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/voxed/_next/static/media/export.dd9fee05.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-600&#34;&gt;Export your models as JSON for use in your own projects or as an image.&lt;/p&gt;&lt;/div&gt;&lt;div class=&#34;mt-4 lg:mt-16 lg:col-span-4&#34;&gt;&lt;img alt=&#34;Settings&#34; loading=&#34;lazy&#34; width=&#34;2560&#34; height=&#34;1600&#34; decoding=&#34;async&#34; data-nimg=&#34;1&#34; style=&#34;color:transparent&#34; src=&#34;https://www.jamesdrandall.com/voxed/_next/static/media/settings.045d184b.png&#34;/&gt;&lt;p class=&#34;mt-2 text-lg text-gray-600&#34;&gt;Configure the Editor to work the way you want with a range of settings.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section id=&#34;faqs&#34; aria-labelledby=&#34;faqs-title&#34; class=&#34;bg-gray-900 border-t border-gray-200 py-16 sm:py-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto max-w-2xl lg:mx-0&#34;&gt;&lt;h2 id=&#34;faqs-title&#34; class=&#34;text-3xl font-medium tracking-tight text-white&#34;&gt;Frequently asked questions&lt;/h2&gt;&lt;/div&gt;&lt;ul role=&#34;list&#34; class=&#34;mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-8 sm:mt-20 lg:max-w-none lg:grid-cols-3&#34;&gt;&lt;li&gt;&lt;ul role=&#34;list&#34; class=&#34;space-y-10&#34;&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-200&#34;&gt;How do I navigate around my model?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-400&#34;&gt;You can use the dedicated cemera controls (the eye icon) but also a wide range of gestures are supported. A single finger drag in empty space will rotate or move the camera (depending on your last choice in the camera tools), two finger dragging will pan, while pinching will move in and out.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-200&#34;&gt;How big can my models be?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-400&#34;&gt;VoxEd is really designed for working on models up to around 128x128x128 voxels. It can handle larger models but performance may be impacted.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-200&#34;&gt;How do I add a colour to the palette?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-400&#34;&gt;Tap and hold the palette button&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;ul role=&#34;list&#34; class=&#34;space-y-10&#34;&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-200&#34;&gt;I think I&amp;#x27;ve found a bug or have an idea for an improvement, what do I do?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-400&#34;&gt;You can send an email to me at support@accidentalfish.com and I&amp;#x27;ll see if I can help.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;h3 class=&#34;text-lg font-semibold leading-6 text-gray-200&#34;&gt;Are you planning on adding more features?&lt;/h3&gt;&lt;p class=&#34;mt-4 text-sm text-gray-400&#34;&gt;For sure, I&amp;#x27;m using VoxEd myself to design models for a city builder game and have a roadmap of items. Check my blog for details.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/section&gt;&lt;section id=&#34;privacy&#34; aria-label=&#34;Privacy information&#34; class=&#34;bg-white py-20 sm:py-16&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;mx-auto&#34;&gt;&lt;h2 class=&#34;text-3xl font-medium tracking-tight text-black&#34;&gt;What about Privacy?&lt;/h2&gt;&lt;p class=&#34;mt-2 text-lg text-gray-900&#34;&gt;Our privacy policy is really simple. We don&amp;#x27;t think you should be spied upon and tracked all over the web or on your devices and so we don&amp;#x27;t collect any information about you, or your usage of the app, at all.&lt;/p&gt;&lt;p class=&#34;mt-2 text-lg text-gray-900&#34;&gt;If you contact us by email then we will never share your email address with a third party or use it for any purpose other than responding to your email.&lt;/p&gt;&lt;p class=&#34;mt-2 text-lg text-gray-900&#34;&gt;And finally this website contains no tracking cookies or analytics of any kind. We don&amp;#x27;t want to know who you are or what you do on the web. We just want you to enjoy using VoxEd.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/main&gt;&lt;footer class=&#34;border-t border-gray-200&#34;&gt;&lt;div class=&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8&#34;&gt;&lt;div class=&#34;flex flex-col items-center border-t border-gray-200 py-12 md:flex-row-reverse md:justify-between&#34;&gt;&lt;p class=&#34;mt-6 text-sm text-gray-500 md:mt-0&#34;&gt;© Copyright &lt;!-- --&gt;2024&lt;!-- --&gt;. All rights reserved.&lt;/p&gt;&lt;a aria-label=&#34;Download on the App Store&#34; class=&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900&#34; href=&#34;https://apps.apple.com/us/app/spred/id6463485447&#34;&gt;&lt;svg viewBox=&#34;0 0 120 40&#34; aria-hidden=&#34;true&#34; class=&#34;h-10&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/footer&gt;&lt;/div&gt;&lt;script src=&#34;https://www.jamesdrandall.com/voxed/_next/static/chunks/webpack-a6a234cfe0c26d4c.js&#34; async=&#34;&#34;&gt;&lt;/script&gt;&lt;script&gt;(self.__next_f=self.__next_f||[]).push([0])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;1:HL[\&#34;/voxed/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2\&#34;,{\&#34;as\&#34;:\&#34;font\&#34;,\&#34;type\&#34;:\&#34;font/woff2\&#34;}]\n2:HL[\&#34;/voxed/_next/static/css/91e7e34991d0b065.css\&#34;,{\&#34;as\&#34;:\&#34;style\&#34;}]\n0:\&#34;$L3\&#34;\n&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;4:I{\&#34;id\&#34;:7948,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-a6a234cfe0c26d4c.js\&#34;,\&#34;971:static/chunks/fd9d1056-c2ad140a17e094a3.js\&#34;,\&#34;596:static/chunks/596-25963c8087224989.js\&#34;],\&#34;name\&#34;:\&#34;default\&#34;,\&#34;async\&#34;:false}\n6:I{\&#34;id\&#34;:6628,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-a6a234cfe0c26d4c.js\&#34;,\&#34;971:static/chunks/fd9d1056-c2ad140a17e094a3.js\&#34;,\&#34;596:static/chunks/596-25963c8087224989.js\&#34;],\&#34;name\&#34;:\&#34;\&#34;,\&#34;async\&#34;:false}\n7:I{\&#34;id\&#34;:7767,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-a6a234cfe0c26d4c.js\&#34;,\&#34;971:static/chunks/fd9d1056-c2ad140a17e094a3.js\&#34;,\&#34;5&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;96:static/chunks/596-25963c8087224989.js\&#34;],\&#34;name\&#34;:\&#34;default\&#34;,\&#34;async\&#34;:false}\n8:I{\&#34;id\&#34;:7920,\&#34;chunks\&#34;:[\&#34;272:static/chunks/webpack-a6a234cfe0c26d4c.js\&#34;,\&#34;971:static/chunks/fd9d1056-c2ad140a17e094a3.js\&#34;,\&#34;596:static/chunks/596-25963c8087224989.js\&#34;],\&#34;name\&#34;:\&#34;default\&#34;,\&#34;async\&#34;:false}\n9:I{\&#34;id\&#34;:6111,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-de26bb9f04f0be31.js\&#34;,\&#34;126:static/chunks/126-17f71cd0d417be97.js\&#34;,\&#34;95:static/chunks/app/(main)/layout-0fdf7459ab88b26b.js\&#34;],\&#34;name\&#34;:\&#34;Header\&#34;,\&#34;async\&#34;:false}\na:I{\&#34;id\&#34;:6685,\&#34;chunks\&#34;:[\&#34;451:static/ch&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;unks/451-de26bb9f04f0be31.js\&#34;,\&#34;126:static/chunks/126-17f71cd0d417be97.js\&#34;,\&#34;95:static/chunks/app/(main)/layout-0fdf7459ab88b26b.js\&#34;],\&#34;name\&#34;:\&#34;\&#34;,\&#34;async\&#34;:false}\nd:I{\&#34;id\&#34;:3222,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-de26bb9f04f0be31.js\&#34;,\&#34;974:static/chunks/app/(main)/page-13e4363ca26477a2.js\&#34;],\&#34;name\&#34;:\&#34;Image\&#34;,\&#34;async\&#34;:false}\nf:I{\&#34;id\&#34;:876,\&#34;chunks\&#34;:[\&#34;451:static/chunks/451-de26bb9f04f0be31.js\&#34;,\&#34;974:static/chunks/app/(main)/page-13e4363ca26477a2.js\&#34;],\&#34;name\&#34;:\&#34;PrimaryFeatures\&#34;,\&#34;async\&#34;:false}\n10:I{\&#34;id\&#34;:3852,\&#34;chunks\&#34;:[\&#34;451:static&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;/chunks/451-de26bb9f04f0be31.js\&#34;,\&#34;974:static/chunks/app/(main)/page-13e4363ca26477a2.js\&#34;],\&#34;name\&#34;:\&#34;Privacy\&#34;,\&#34;async\&#34;:false}\nb:T14cb,&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;e:T14cb,&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;11:T14cb,&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;M24.769 20.301a4.947 4.947 0 0 1 2.357-4.152 5.066 5.066 0 0 0-3.992-2.157c-1.679-.177-3.307 1.004-4.163 1.004-.872 0-2.19-.987-3.608-.958a5.315 5.315 0 0 0-4.473 2.728c-1.934 3.349-.491 8.27 1.361 10.976.927 1.326 2.01 2.806 3.428 2.753 1.387-.057 1.905-.884 3.58-.884 1.658 0 2.144.884 3.59.851 1.489-.024 2.426-1.331 3.32-2.669a10.96 10.96 0 0 0 1.52-3.092 4.782 4.782 0 0 1-2.92-4.4ZM22.037 12.211a4.872 4.872 0 0 0 1.115-3.49 4.957 4.957 0 0 0-3.208 1.66 4.635 4.635 0 0 0-1.143 3.36 4.099 4.099 0 0 0 3.236-1.53ZM42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039l-1.136-3.356Zm-4.243-1.55h3.752l-1.85-5.446h-.051l-1.85 5.447ZM55.16 25.97c0 2.813-1.506 4.62-3.779 4.62a3.068 3.068 0 0 1-2.848-1.584h-.043v4.485H46.63V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622Zm-1.91 0c0-1.833-.948-3.039-2.393-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM65.124 25.97c0 2.813-1.505 4.62-3.778 4.62a3.07 3.07 0 0 1-2.848-1.584h-.043v4.485h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.621Zm-1.91 0c0-1.834-.947-3.039-2.392-3.039-1.42 0-2.375 1.23-2.375 3.038 0 1.825.955 3.046 2.375 3.046 1.445 0 2.392-1.196 2.392-3.046ZM71.71 27.036c.138 1.232 1.335 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987-1.497 0-2.521.757-2.521 1.858 0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667h1.902ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137.204-.004.408-.018.611-.043v1.463c-.34.064-.686.092-1.032.086-1.833 0-2.548-.689-2.548-2.444v-5.19h-1.316v-1.472h1.316V19.3h1.867ZM86.065 25.97c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638-2.633 0-4.294-1.782-4.294-4.638Zm6.695 0c0-1.954-.895-3.108-2.401-3.108-1.506 0-2.4 1.162-2.4 3.108 0 1.962.894 3.106 2.4 3.106 1.506 0 2.401-1.144 2.401-3.106ZM96.186 21.442h1.772v1.541h.043a2.16 2.16 0 0 1 2.178-1.636c.214 0 .428.023.637.07v1.738a2.594 2.594 0 0 0-.835-.112 1.872 1.872 0 0 0-1.937 2.083v5.37h-1.858v-9.054ZM109.384 27.837c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.191-4.682 2.505 0 4.08 1.72 4.08 4.466v.637h-6.395v.112a2.353 2.353 0 0 0 .639 1.832 2.364 2.364 0 0 0 1.797.732 2.045 2.045 0 0 0 2.091-1.273h1.764Zm-6.282-2.702h4.526a2.167 2.167 0 0 0-.608-1.634 2.168 2.168 0 0 0-1.612-.664 2.293 2.293 0 0 0-2.306 2.298ZM37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.731h2.155Zm-1.228 5.123h1.125a1.877 1.877 0 0 0 1.967-2.146 1.881 1.881 0 0 0-1.967-2.133h-1.125v4.28ZM41.68 12.445a2.133 2.133 0 1 1 4.248 0 2.132 2.132 0 1 1-4.247 0Zm3.334 0c0-.976-.439-1.547-1.209-1.547-.772 0-1.206.57-1.206 1.547 0 .984.434 1.55 1.207 1.55.769 0 1.208-.57 1.208-1.55ZM51.573 14.697h-.922l-.93-3.316h-.07l-.927 3.316h-.913l-1.242-4.503h.902l.806 3.436h.067l.925-3.436h.853l.926 3.436h.07l.803-3.436h.889l-1.237 4.503ZM53.853 10.195h.856v.715h.066a1.348 1.348 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM59.094 8.437h.888v6.26h-.888v-6.26ZM61.218 12.444a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.248 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.772 0-1.207.57-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM66.4 13.425c0-.81.604-1.278 1.676-1.344l1.22-.07v-.39c0-.475-.315-.744-.922-.744-.497 0-.84.183-.939.5h-.86c.09-.773.818-1.269 1.84-1.269 1.128 0 1.765.562 1.765 1.514v3.076h-.855v-.633h-.07a1.515 1.515 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.347Zm2.895-.385v-.376l-1.1.07c-.62.041-.9.252-.9.65 0 .405.351.64.834.64a1.062 1.062 0 0 0 1.166-.984ZM71.348 12.444c0-1.423.732-2.324 1.87-2.324a1.484 1.484 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.711h-.07a1.563 1.563 0 0 1-1.415.785c-1.145 0-1.869-.9-1.869-2.327Zm.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.939-.468-1.53-1.212-1.53-.748 0-1.203.579-1.203 1.526ZM79.23 12.445a2.133 2.133 0 1 1 4.247 0 2.132 2.132 0 1 1-4.247 0Zm3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.57-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55ZM84.67 10.195h.855v.715h.066a1.349 1.349 0 0 1 1.344-.802 1.466 1.466 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.315-1.084-.972-1.084a1.034 1.034 0 0 0-1.075 1.141v2.635h-.889v-4.503ZM93.515 9.074v1.142h.976v.748h-.976v2.316c0 .472.195.678.637.678.113 0 .226-.007.339-.02v.74c-.16.028-.322.043-.484.045-.988 0-1.382-.348-1.382-1.216v-2.543h-.714v-.748h.715V9.074h.89ZM95.705 8.437h.88v2.481h.07a1.386 1.386 0 0 1 1.374-.807 1.485 1.485 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.719-.335-1.083-.963-1.083a1.05 1.05 0 0 0-1.134 1.141v2.63h-.888v-6.26ZM104.761 13.482a1.823 1.823 0 0 1-1.951 1.302 2.047 2.047 0 0 1-2.08-2.324 2.093 2.093 0 0 1 .071-.88 2.08 2.08 0 0 1 2.005-1.473c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.077 1.077 0 0 0 1.071-.545h.855Zm-3.126-1.452h2.275a1.094 1.094 0 0 0-.667-1.084 1.086 1.086 0 0 0-.442-.082 1.151 1.151 0 0 0-1.166 1.166Z&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;3:[[[\&#34;$\&#34;,\&#34;link\&#34;,\&#34;0\&#34;,{\&#34;rel\&#34;:\&#34;stylesheet\&#34;,\&#34;href\&#34;:\&#34;/voxed/_next/static/css/91e7e34991d0b065.css\&#34;,\&#34;precedence\&#34;:\&#34;next\&#34;}]],[\&#34;$\&#34;,\&#34;$L4\&#34;,null,{\&#34;buildId\&#34;:\&#34;MNkrljBNizATObQMfA-sH\&#34;,\&#34;assetPrefix\&#34;:\&#34;/voxed\&#34;,\&#34;initialCanonicalUrl\&#34;:\&#34;/\&#34;,\&#34;initialTree\&#34;:[\&#34;\&#34;,{\&#34;children\&#34;:[\&#34;(main)\&#34;,{\&#34;children\&#34;:[\&#34;__PAGE__\&#34;,{}]}]},\&#34;$undefined\&#34;,\&#34;$undefined\&#34;,true],\&#34;initialHead\&#34;:\&#34;$L5\&#34;,\&#34;globalErrorComponent\&#34;:\&#34;$6\&#34;,\&#34;children\&#34;:[null,[\&#34;$\&#34;,\&#34;html\&#34;,null,{\&#34;lang\&#34;:\&#34;en\&#34;,\&#34;className\&#34;:\&#34;h-full bg-gray-50 antialiased __variable_e165c7\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;body\&#34;,null,{\&#34;className\&#34;:\&#34;flex h-full flex-col font-pixel\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;flex min-h-full flex-col\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$L7\&#34;,null,{\&#34;parallelRouterKey\&#34;:\&#34;children\&#34;,\&#34;segmentPath\&#34;:[\&#34;children\&#34;],\&#34;error\&#34;:\&#34;$undefined\&#34;,\&#34;errorStyles\&#34;:\&#34;$undefined\&#34;,\&#34;loading\&#34;:\&#34;$undefined\&#34;,\&#34;loadingStyles\&#34;:\&#34;$undefined\&#34;,\&#34;hasLoading\&#34;:false,\&#34;template\&#34;:[\&#34;$\&#34;,\&#34;$L8\&#34;,null,{}],\&#34;templateStyles\&#34;:\&#34;$undefined\&#34;,\&#34;notFound\&#34;:[[\&#34;$\&#34;,\&#34;$L9\&#34;,null,{}],[\&#34;$\&#34;,\&#34;main\&#34;,null,{\&#34;className\&#34;:\&#34;flex-auto\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative isolate flex h-full flex-col items-center justify-center py-20 text-center sm:py-32\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 1090 1090\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;fill\&#34;:\&#34;none\&#34;,\&#34;preserveAspectRatio\&#34;:\&#34;none\&#34;,\&#34;className\&#34;:\&#34;absolute left-1/2 top-1/2 -z-10 mt-44 w-[68.125rem] -translate-x-1/2 -translate-y-1/2 stroke-gray-300/30 [mask-image:linear-gradient(to_bottom,white_20%,transparent_75%)]\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;544.5\&#34;}],[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;480.5\&#34;}],[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;416.5\&#34;}],[\&#34;$\&#34;,\&#34;circle\&#34;,null,{\&#34;cx\&#34;:545,\&#34;cy\&#34;:545,\&#34;r\&#34;:\&#34;352.5\&#34;}]]}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;text-sm font-semibold text-gray-900\&#34;,\&#34;children\&#34;:\&#34;404\&#34;}],[\&#34;$\&#34;,\&#34;h1\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-3xl font-medium tracking-tight text-gray-900\&#34;,\&#34;children\&#34;:\&#34;Page not found\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-lg text-gray-600\&#34;,\&#34;children\&#34;:\&#34;Sorry, we couldn’t find the page you’re looking for.\&#34;}],[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;className\&#34;:\&#34;inline-flex justify-center rounded-lg border py-[calc(theme(spacing.2)-1px)] px-[calc(theme(spacing.3)-1px)] text-sm outline-2 outline-offset-2 transition-colors border-gray-300 text-gray-700 hover:border-gray-400 active:bg-gray-100 active:text-gray-700/80 mt-8\&#34;,\&#34;href\&#34;:\&#34;/\&#34;,\&#34;children\&#34;:\&#34;Go back home\&#34;}]]}]}],[\&#34;$\&#34;,\&#34;footer\&#34;,null,{\&#34;className\&#34;:\&#34;border-t border-gray-200\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;flex flex-col items-center border-t border-gray-200 py-12 md:flex-row-reverse md:justify-between\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-6 text-sm text-gray-500 md:mt-0\&#34;,\&#34;children\&#34;:[\&#34;© Copyright \&#34;,2024,\&#34;. All rights reserved.\&#34;]}],[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;href\&#34;:\&#34;https://apps.apple.com/us/app/spred/id6463485447\&#34;,\&#34;aria-label\&#34;:\&#34;Download on the App Store\&#34;,\&#34;className\&#34;:\&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 120 40\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;className\&#34;:\&#34;h-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;path\&#34;,null,{\&#34;fill\&#34;:\&#34;currentColor\&#34;,\&#34;d\&#34;:\&#34;$b\&#34;}]}]}]]}]}]}]],\&#34;notFoundStyles\&#34;:[],\&#34;childProp\&#34;:{\&#34;current\&#34;:[null,[[\&#34;$\&#34;,\&#34;$L9\&#34;,null,{}],[\&#34;$\&#34;,\&#34;main\&#34;,null,{\&#34;className\&#34;:\&#34;flex-auto\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$L7\&#34;,null,{\&#34;parallelRouterKey\&#34;:\&#34;children\&#34;,\&#34;segmentPath\&#34;:[\&#34;children\&#34;,\&#34;(main)\&#34;,\&#34;children\&#34;],\&#34;error\&#34;:\&#34;$undefined\&#34;,\&#34;errorStyles\&#34;:\&#34;$undefined\&#34;,\&#34;loading\&#34;:\&#34;$undefined\&#34;,\&#34;loadingStyles\&#34;:\&#34;$undefined\&#34;,\&#34;hasLoading\&#34;:false,\&#34;template\&#34;:[\&#34;$\&#34;,\&#34;$L8\&#34;,null,{}],\&#34;templateStyles\&#34;:\&#34;$undefined\&#34;,\&#34;notFound\&#34;:[[\&#34;$\&#34;,\&#34;title\&#34;,null,{\&#34;children\&#34;:\&#34;404: This page could not be found.\&#34;}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;style\&#34;:{\&#34;fontFamily\&#34;:\&#34;system-ui,\\\&#34;Segoe UI\\\&#34;,Roboto,Helvetica,Arial,sans-serif,\\\&#34;Apple Color Emoji\\\&#34;,\\\&#34;Segoe UI Emoji\\\&#34;\&#34;,\&#34;height\&#34;:\&#34;100vh\&#34;,\&#34;textAlign\&#34;:\&#34;center\&#34;,\&#34;display\&#34;:\&#34;flex\&#34;,\&#34;flexDirection\&#34;:\&#34;column\&#34;,\&#34;alignItems\&#34;:\&#34;center\&#34;,\&#34;justifyContent\&#34;:\&#34;center\&#34;},\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;style\&#34;,null,{\&#34;dangerouslySetInnerHTML\&#34;:{\&#34;__html\&#34;:\&#34;body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\&#34;}}],[\&#34;$\&#34;,\&#34;h1\&#34;,null,{\&#34;className\&#34;:\&#34;next-error-h1\&#34;,\&#34;style\&#34;:{\&#34;display\&#34;:\&#34;inline-block\&#34;,\&#34;margin\&#34;:\&#34;0 20px 0 0\&#34;,\&#34;padding\&#34;:\&#34;0 23px 0 0\&#34;,\&#34;fontSize\&#34;:24,\&#34;fontWeight\&#34;:500,\&#34;verticalAlign\&#34;:\&#34;top\&#34;,\&#34;lineHeight\&#34;:\&#34;49px\&#34;},\&#34;children\&#34;:\&#34;404\&#34;}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;style\&#34;:{\&#34;display\&#34;:\&#34;inline-block\&#34;},\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;h2\&#34;,null,{\&#34;style\&#34;:{\&#34;fontSize\&#34;:14,\&#34;fontWeight\&#34;:400,\&#34;lineHeight\&#34;:\&#34;49px\&#34;,\&#34;margin\&#34;:0},\&#34;children\&#34;:\&#34;This page could not be found.\&#34;}]}]]}]}]],\&#34;notFoundStyles\&#34;:[],\&#34;childProp\&#34;:{\&#34;current\&#34;:[\&#34;$Lc\&#34;,[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;overflow-hidden pb-20 sm:pb-32 lg:pb-16 xl:pb-16\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:grid lg:grid-cols-12\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:col-start-2 lg:col-span-10 lg:mb-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$Ld\&#34;,null,{\&#34;src\&#34;:{\&#34;src\&#34;:\&#34;/voxed/_next/static/media/heroImage.f76365b4.jpg\&#34;,\&#34;height\&#34;:2290,\&#34;width\&#34;:2960,\&#34;blurDataURL\&#34;:\&#34;data:image/jpeg;base64,/9j/2wBDAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9Pjv/2wBDAQoLCw4NDhwQEBw7KCIoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozv/wAARCAAGAAgDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAeEAABBAEFAAAAAAAAAAAAAAACAAEDBSEEERITcf/EABUBAQEAAAAAAAAAAAAAAAAAAAAD/8QAGBEAAwEBAAAAAAAAAAAAAAAAAAECITH/2gAMAwEAAhEDEQA/AIaxPUtbTNFKQRdg8RE3bbDY8RETmFJ2U2f/2Q==\&#34;,\&#34;blurWidth\&#34;:8,\&#34;blurHeight\&#34;:6},\&#34;alt\&#34;:\&#34;VoxEd\&#34;}]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:col-start-2 lg:col-span-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;h1\&#34;,null,{\&#34;className\&#34;:\&#34;text-center text-4xl font-medium tracking-tight text-gray-900\&#34;,\&#34;children\&#34;:\&#34;Voxel sprite editing for your Mac and iPad\&#34;}]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:col-span-12 flex justify-center flex-wrap gap-x-6 mt-6\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;href\&#34;:\&#34;https://apps.apple.com/us/app/spred/id6463485447\&#34;,\&#34;aria-label\&#34;:\&#34;Download on the App Store\&#34;,\&#34;className\&#34;:\&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 120 40\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;className\&#34;:\&#34;h-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;path\&#34;,null,{\&#34;fill\&#34;:\&#34;currentColor\&#34;,\&#34;d\&#34;:\&#34;$e\&#34;}]}]}]}]]}]}]}],[\&#34;$\&#34;,\&#34;$Lf\&#34;,null,{}],[\&#34;$\&#34;,\&#34;section\&#34;,null,{\&#34;id\&#34;:\&#34;premium\&#34;,\&#34;aria-label\&#34;:\&#34;Premium features\&#34;,\&#34;className\&#34;:\&#34;py-20 sm:py-16\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto sm:text-center\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;h2\&#34;,null,{\&#34;className\&#34;:\&#34;text-3xl font-medium tracking-tight text-gray-900\&#34;,\&#34;children\&#34;:\&#34;Work seamlessly across your iPad and Mac with iCloud\&#34;}]}]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;lg:grid lg:grid-cols-12 lg:gap-8\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 lg:mt-16 lg:col-span-4\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;$Ld\&#34;,null,{\&#34;src\&#34;:{\&#34;src\&#34;:\&#34;/voxed/_next/static/media/macEditor.f61e5d2b.png\&#34;,\&#34;height\&#34;:1600,\&#34;width\&#34;:2560,\&#34;blurDataURL\&#34;:\&#34;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAMAAABPT11nAAAAMFBMVEVlb4RiaHtcY3JQXnEQERFoZ15ed39ha4ipIAd7f2iwcAeDcE9naUtSWVFuZmklJyp2RROqAAAACXBIWXMAABYlAAAWJQFJUiTwAAAALUlEQVR4nBXJyQ0AIAwEsdmEQLj77xbxs2S2kEAUrxEVo/jK0dvHyfnrGmrAAw61AKoJZW3sAAAAAElFTkSuQmCC\&#34;,\&#34;blurWidth\&#34;:8,\&#34;blurHeight\&#34;:5},\&#34;alt\&#34;:\&#34;Work on the Mac\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-lg text-gray-600\&#34;,\&#34;children\&#34;:\&#34;Work seamlessly across your iPad and Mac with iCloud.\&#34;}]]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 lg:mt-16 lg:col-span-4\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;$Ld\&#34;,null,{\&#34;src\&#34;:{\&#34;src\&#34;:\&#34;/voxed/_next/static/media/export.dd9fee05.png\&#34;,\&#34;height\&#34;:1600,\&#34;width\&#34;:2560,\&#34;blurDataURL\&#34;:\&#34;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAMAAABPT11nAAAASFBMVEVcaXk7V3xZXU9zfYXcDARdXm6FWGdiboUQERNUXmtwaXNyYHIlJyqETw4UW2cbTIC0GxNBJ0qRLhcrh0wGLW6WOzmWESNIg3EPPz8FAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAMUlEQVR4nBXGSRIAEAwEwEFI7Dv//6nSp4YG6x2MhdibqUVA/BxE7qesfhyQPKuqmB8ecAFizOKYMAAAAABJRU5ErkJggg==\&#34;,\&#34;blurWidth\&#34;:8,\&#34;blurHeight\&#34;:5},\&#34;alt\&#34;:\&#34;Export as JSON\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-lg text-gray-600\&#34;,\&#34;children\&#34;:\&#34;Export your models as JSON for use in your own projects or as an image.\&#34;}]]}],[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 lg:mt-16 lg:col-span-4\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;$Ld\&#34;,null,{\&#34;src\&#34;:{\&#34;src\&#34;:\&#34;/voxed/_next/static/media/settings.045d184b.png\&#34;,\&#34;height\&#34;:1600,\&#34;width\&#34;:2560,\&#34;blurDataURL\&#34;:\&#34;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAMAAABPT11nAAAAMFBMVEVlb4RkZmVdZ3VrdYoPEBJxdHdrb3FgaXpjaXOHiohUXGxXX259fXhNTFImKCpgWlE/zeg9AAAACXBIWXMAABYlAAAWJQFJUiTwAAAALklEQVR4nB3GSQ4AIAgDwJZFFFD//1sT5zTYgghAYKmKn5o96ANWfcnlOKmL5HwRBwDUwNmf2QAAAABJRU5ErkJggg==\&#34;,\&#34;blurWidth\&#34;:8,\&#34;blurHeight\&#34;:5},\&#34;alt\&#34;:\&#34;Settings\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-2 text-lg text-gray-600\&#34;,\&#34;children\&#34;:\&#34;Configure the Editor to work the way you want with a range of settings.\&#34;}]]}]]}]}]]}],[\&#34;$\&#34;,\&#34;section\&#34;,null,{\&#34;id\&#34;:\&#34;faqs\&#34;,\&#34;aria-labelledby\&#34;:\&#34;faqs-title\&#34;,\&#34;className\&#34;:\&#34;bg-gray-900 border-t border-gray-200 py-16 sm:py-16\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-2xl lg:mx-0\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;h2\&#34;,null,{\&#34;id\&#34;:\&#34;faqs-title\&#34;,\&#34;className\&#34;:\&#34;text-3xl font-medium tracking-tight text-white\&#34;,\&#34;children\&#34;:\&#34;Frequently asked questions\&#34;}]}],[\&#34;$\&#34;,\&#34;ul\&#34;,null,{\&#34;role\&#34;:\&#34;list\&#34;,\&#34;className\&#34;:\&#34;mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-8 sm:mt-20 lg:max-w-none lg:grid-cols-3\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;0\&#34;,{\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;ul\&#34;,null,{\&#34;role\&#34;:\&#34;list\&#34;,\&#34;className\&#34;:\&#34;space-y-10\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;0\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-200\&#34;,\&#34;children\&#34;:\&#34;How do I navigate around my model?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-400\&#34;,\&#34;children\&#34;:\&#34;You can use the dedicated cemera controls (the eye icon) but also a wide range of gestures are supported. A single finger drag in empty space will rotate or move the camera (depending on your last choice in the camera tools), two finger dragging will pan, while pinching will move in and out.\&#34;}]]}],[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;1\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-200\&#34;,\&#34;children\&#34;:\&#34;How big can my models be?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-400\&#34;,\&#34;children\&#34;:\&#34;VoxEd is really designed for working on models up to around 128x128x128 voxels. It can handle larger models but performance may be impacted.\&#34;}]]}],[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;2\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-200\&#34;,\&#34;children\&#34;:\&#34;How do I add a colour to the palette?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-400\&#34;,\&#34;children\&#34;:\&#34;Tap and hold the palette button\&#34;}]]}]]}]}],[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;1\&#34;,{\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;ul\&#34;,null,{\&#34;role\&#34;:\&#34;list\&#34;,\&#34;className\&#34;:\&#34;space-y-10\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;0\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-200\&#34;,\&#34;children\&#34;:\&#34;I think I&#39;ve found a bug or have an idea for an improvement, what do I do?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-400\&#34;,\&#34;children\&#34;:\&#34;You can send an email to me at support@accidentalfish.com and I&#39;ll see if I can help.\&#34;}]]}],[\&#34;$\&#34;,\&#34;li\&#34;,\&#34;1\&#34;,{\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;h3\&#34;,null,{\&#34;className\&#34;:\&#34;text-lg font-semibold leading-6 text-gray-200\&#34;,\&#34;children\&#34;:\&#34;Are you planning on adding more features?\&#34;}],[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-4 text-sm text-gray-400\&#34;,\&#34;children\&#34;:\&#34;For sure, I&#39;m using VoxEd myself to design models for a city builder game and have a roadmap of items. Check my blog for details.\&#34;}]]}]]}]}]]}]]}]}],[\&#34;$\&#34;,\&#34;$L10\&#34;,null,{}]],null],\&#34;segment\&#34;:\&#34;__PAGE__\&#34;},\&#34;styles\&#34;:[]}]}],[\&#34;$\&#34;,\&#34;footer\&#34;,null,{\&#34;className\&#34;:\&#34;border-t border-gray-200\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;div\&#34;,null,{\&#34;className\&#34;:\&#34;flex flex-col items-center border-t border-gray-200 py-12 md:flex-row-reverse md:justify-between\&#34;,\&#34;children\&#34;:[[\&#34;$\&#34;,\&#34;p\&#34;,null,{\&#34;className\&#34;:\&#34;mt-6 text-sm text-gray-500 md:mt-0\&#34;,\&#34;children\&#34;:[\&#34;© Copyright \&#34;,2024,\&#34;. All rights reserved.\&#34;]}],[\&#34;$\&#34;,\&#34;$La\&#34;,null,{\&#34;href\&#34;:\&#34;https://apps.apple.com/us/app/spred/id6463485447\&#34;,\&#34;aria-label\&#34;:\&#34;Download on the App Store\&#34;,\&#34;className\&#34;:\&#34;rounded-lg transition-colors bg-gray-800 text-white hover:bg-gray-900\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;svg\&#34;,null,{\&#34;viewBox\&#34;:\&#34;0 0 120 40\&#34;,\&#34;aria-hidden\&#34;:\&#34;true\&#34;,\&#34;className\&#34;:\&#34;h-10\&#34;,\&#34;children\&#34;:[\&#34;$\&#34;,\&#34;path\&#34;,null,{\&#34;fill\&#34;:\&#34;currentColor\&#34;,\&#34;d\&#34;:\&#34;$11\&#34;}]}]}]]}]}]}]],null],\&#34;segment\&#34;:\&#34;(main)\&#34;},\&#34;styles\&#34;:[]}]}]}]}],null]}]]\n&#34;])&lt;/script&gt;&lt;script&gt;self.__next_f.push([1,&#34;5:[[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;0\&#34;,{\&#34;charSet\&#34;:\&#34;utf-8\&#34;}],[\&#34;$\&#34;,\&#34;title\&#34;,\&#34;1\&#34;,{\&#34;children\&#34;:\&#34;VoxEd - Elegant voxel sprite editing\&#34;}],[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;2\&#34;,{\&#34;name\&#34;:\&#34;description\&#34;,\&#34;content\&#34;:\&#34;Overview of VoxEd features.\&#34;}],[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;3\&#34;,{\&#34;name\&#34;:\&#34;viewport\&#34;,\&#34;content\&#34;:\&#34;width=device-width, initial-scale=1\&#34;}],[\&#34;$\&#34;,\&#34;link\&#34;,\&#34;4\&#34;,{\&#34;rel\&#34;:\&#34;icon\&#34;,\&#34;href\&#34;:\&#34;/voxed/favicon.ico\&#34;,\&#34;type\&#34;:\&#34;image/x-icon\&#34;,\&#34;sizes\&#34;:\&#34;16x16\&#34;}],[\&#34;$\&#34;,\&#34;meta\&#34;,\&#34;5\&#34;,{\&#34;name\&#34;:\&#34;next-size-adjust\&#34;}]]\nc:null\n&#34;])&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://www.jamesdrandall.com/webglite/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/webglite/</guid>
      <description>&lt;!DOCTYPE html&gt;&lt;html lang=&#34;en&#34;&gt;&lt;head&gt;&lt;meta charset=&#34;utf-8&#34;&gt;&lt;title&gt;webGLite&lt;/title&gt;&lt;/head&gt;&lt;body style=&#34;background-color:#000;overflow:hidden&#34;&gt;  &lt;div style=&#34;grid-template-columns:800px 400px;display:grid&#34;&gt; &lt;canvas width=&#34;800&#34; height=&#34;760&#34; id=&#34;viewcanvas&#34; style=&#34;width:800px;height:760px;display:block&#34;&gt;&lt;/canvas&gt; &lt;canvas width=&#34;480&#34; height=&#34;760&#34; id=&#34;doccanvas&#34; style=&#34;width:480px;height:760px;display:block&#34;&gt;&lt;/canvas&gt; &lt;div id=&#34;game-console&#34; style=&#34;background-color:#000;border:1px solid green;grid-column:1/-1;height:200px;max-height:200px;margin-top:1em;display:none;overflow-y:auto&#34;&gt; &lt;div style=&#34;color:#0f0;padding:3px&#34;&gt;&amp;gt; Game started&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;script&gt;&lt;/script&gt; &lt;script type=&#34;module&#34; src=&#34;index.be1a6030.js&#34;&gt;&lt;/script&gt; &lt;/body&gt;&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://www.jamesdrandall.com/wolf3d/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://www.jamesdrandall.com/wolf3d/</guid>
      <description>&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;F# Wolfenstein 3D&lt;/title&gt;
  &lt;meta http-equiv=&#39;Content-Type&#39; content=&#39;text/html; charset=utf-8&#39;&gt;
  &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1&#34;&gt;
  &lt;link rel=&#34;shortcut icon&#34; href=&#34;fable.ico&#34; /&gt;
&lt;/head&gt;
&lt;body style=&#34;margin:0;padding:0;height:100vh;overflow: hidden;background-color:black;&#34;&gt;
    &lt;!--&lt;div class=&#34;loading-screen&#34; style=&#34;position: absolute; width: 100vw; height: 100vh; background-position: center; background-image: url(&#39;/assets/loadingScreen.png&#39;); background-size: contain; background-repeat: no-repeat;&#34;&gt;&lt;/div&gt;--&gt;
    &lt;canvas class=&#34;game-canvas&#34; style=&#34;width: 100vw; height: 100vh;&#34;&gt;&lt;/canvas&gt;
    &lt;script src=&#34;bundle.js&#34;&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</description>
    </item>
    
  </channel>
</rss>
