<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>The OmaCRT log</title>
    <link>https://omacrt.com/log/</link>
    <description>What changed, what was measured, and what turned out to be wrong: the working notes behind OmaCRT.</description>
    <language>en</language>
    <lastBuildDate>Sat, 03 Oct 2026 17:32:20 +0000</lastBuildDate>
    <atom:link href="https://omacrt.com/log/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Sega Model 3 on a real CRT television</title>
      <link>https://omacrt.com/log/model-3-at-the-tubes-own-line-count/</link>
      <guid isPermaLink="true">https://omacrt.com/log/model-3-at-the-tubes-own-line-count/</guid>
      <pubDate>Fri, 02 Oct 2026 00:00:00 +0000</pubDate>
      <category>explainer</category>
      <description>Sega&#x27;s Model 3 drew its races at 384 lines for a 24 kHz arcade monitor. OmaCRT 0.14.0 plays them on a 15 kHz television through Supermodel, which draws the scene at the tube&#x27;s 240 lines rather than shrinking it to them. Getting there took one relaxed check in an OpenGL loader, two words of an EEPROM, and a flyer in place of a box.</description>
      <content:encoded><![CDATA[<figure class="manual-figure fig-wide"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/srally2-race.webp"><img src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/srally2-race.webp" alt="Sega Rally 2, fifteen seconds into the desert stage: the Escort at 158 km/h down the first straight, the time, the position and the rev counter around it. Supermodel drew it at 3520 by 240, the mode the television is set to, and it is shown here at the 4:3 the set shows it at." width="1280" height="960" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Emulator output</span>Sega Rally 2, fifteen seconds into the desert stage: the Escort at 158 km/h down the first straight, the time, the position and the rev counter around it. Supermodel drew it at 3520 by 240, the mode the television is set to, and it is shown here at the 4:3 the set shows it at.</figcaption></figure>
<p>I wanted to play Sega Rally on the television in the living room, the arcade version. The first Sega Rally runs on Sega's Model 2 board, and that came first: MAME emulates it, RetroArch carries MAME, and OmaCRT already drives RetroArch. Its sequel, Sega Rally 2, runs on the Model 3 that followed in 1996, with a graphics board Sega built with Lockheed Martin's Real3D. MAME lists its Model 3 driver as preliminary, which in MAME's words means not playable. Supermodel is playable, and has been for years, but it is a program of its own, and nothing in OmaCRT had ever started one that was not RetroArch or mpv.</p>
<p>It does now, and the Model 3 games look sharper on the glass than the Model 2 games beside them.</p>
<h2 id="a-board-drawn-for-another-monitor" class="log-sec log-sec-1"><a class="log-anchor" href="#a-board-drawn-for-another-monitor" aria-label="Link to this section"></a><span class="log-sec-n">01</span>A board drawn for another monitor</h2>
<p>Model 3 games draw 496 by 384 at 57.524 frames a second, for a medium resolution arcade monitor whose lines run at 24 kHz. A 15 kHz television draws 240 lines in a frame and cannot be talked into more without interlace, which the stock kernel cannot scan out. So 384 lines have to become 240 somewhere.</p>
<p>On Model 2 that happens after the drawing. MAME renders the board's 384 lines, and RetroArch fits them into the tube's 240. Fitting with nearest neighbour keeps five lines in every eight and throws the other three away, unevenly, and the rows of a timer's digits come and go. The launcher has filtered that reduction since 0.11.2, which brings the rows back as a softer picture.</p>
<p>Supermodel works the other way round: its renderer draws the 3D scene at whatever size it is asked for. The launcher hands it the whole frame the television is set to, 3520 by 240, and the polygons are drawn at 240 lines from the start: every line the set draws is a line the renderer drew, with nothing thrown away and nothing averaged. Only the 2D layers, the text and the gauges, are still scaled, with Supermodel's own filter. The tube runs at the board's own 57.524 Hz rather than 60.</p>
<figure class="log-diagram m3 m3-lines" data-m3="lines"><div class="dg"><h3 class="dg-headline">Drawn at 240 lines, not reduced to them.</h3><p class="dg-deck">One frame of Sega Rally 2, three ways. The board draws 384 lines and the tube has 240: either the 384 are reduced, as Model 2 games are, or the renderer is asked for 240 in the first place.</p><div class="dg-run dg-steps m3-ways" role="group" aria-label="How the frame reaches 240 lines"><button type="button" class="dg-button" data-way="nearest" data-say="Drawn at 384 lines, then five of every eight kept and three thrown away. A row that falls between two of the tube&#x27;s lines is simply lost, and thin edges come and go along their length." aria-pressed="false">Reduced, nearest</button><button type="button" class="dg-button" data-way="filtered" data-say="Drawn at 384 lines, then each of the tube&#x27;s lines blended from the two nearest. Nothing is lost, and everything is a little soft. This is what the launcher does for Model 2." aria-pressed="false">Reduced, filtered</button><button type="button" class="dg-button" data-way="native" data-say="Supermodel asked for 240 lines and drew 240 lines: every line on the glass is a line the renderer drew. Only the text and gauges are still scaled. This is Model 3 on the tube." aria-pressed="true">Drawn at 240</button></div><p class="m3-say" data-out="way" aria-live="polite">Supermodel asked for 240 lines and drew 240 lines: every line on the glass is a line the renderer drew. Only the text and gauges are still scaled. This is Model 3 on the tube.</p><div class="m3-lines-body"><div><div class="m3-frame"><img data-way-pic="nearest" src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/frame-nearest.webp" width="1280" height="960" alt="One frame of Sega Rally 2, reduced, nearest" loading="lazy" hidden /><img data-way-pic="filtered" src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/frame-filtered.webp" width="1280" height="960" alt="One frame of Sega Rally 2, reduced, filtered" loading="lazy" hidden /><img data-way-pic="native" src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/frame-native.webp" width="1280" height="960" alt="One frame of Sega Rally 2, drawn at 240" loading="lazy" /><i class="m3-look" style="left:35.9%;top:50.0%;width:25.8%;height:22.9%"></i></div><p class="m3-label">The frame, at the 4:3 the set shows it at</p></div><div class="m3-side"><div><div class="m3-zoom"><img data-way-zoom="nearest" src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/zoom-nearest.png" alt="The back of the car, enlarged, reduced, nearest" loading="lazy" hidden /><img data-way-zoom="filtered" src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/zoom-filtered.png" alt="The back of the car, enlarged, reduced, filtered" loading="lazy" hidden /><img data-way-zoom="native" src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/zoom-native.png" alt="The back of the car, enlarged, drawn at 240" loading="lazy" /></div><p class="m3-label">The marked part, enlarged</p></div><div class="m3-strip-box"><div class="m3-strip" data-strip><svg viewBox="0 0 180 214" role="img" aria-label="Ten of the tube's lines and the board lines each one is made of"><text x="4" y="10">board, 384</text><text x="118" y="10">tube, 240</text><g data-for="nearest"><rect class="src" data-src="0" x="4" y="18" width="58" height="9"/><rect class="src" data-src="1" x="4" y="30" width="58" height="9"/><rect class="src" data-src="2" x="4" y="42" width="58" height="9"/><rect class="src" data-src="3" x="4" y="54" width="58" height="9"/><rect class="src" data-src="4" x="4" y="66" width="58" height="9"/><rect class="src" data-src="5" x="4" y="78" width="58" height="9"/><rect class="src" data-src="6" x="4" y="90" width="58" height="9"/><rect class="src" data-src="7" x="4" y="102" width="58" height="9"/><rect class="src" data-src="8" x="4" y="114" width="58" height="9"/><rect class="src" data-src="9" x="4" y="126" width="58" height="9"/><rect class="src" data-src="10" x="4" y="138" width="58" height="9"/><rect class="src" data-src="11" x="4" y="150" width="58" height="9"/><rect class="src" data-src="12" x="4" y="162" width="58" height="9"/><rect class="src" data-src="13" x="4" y="174" width="58" height="9"/><rect class="src" data-src="14" x="4" y="186" width="58" height="9"/><rect class="src" data-src="15" x="4" y="198" width="58" height="9"/><rect class="src kept" x="4" y="18" width="58" height="9"/><rect class="src kept" x="4" y="42" width="58" height="9"/><rect class="src kept" x="4" y="66" width="58" height="9"/><rect class="src kept" x="4" y="78" width="58" height="9"/><rect class="src kept" x="4" y="102" width="58" height="9"/><rect class="src kept" x="4" y="114" width="58" height="9"/><rect class="src kept" x="4" y="138" width="58" height="9"/><rect class="src kept" x="4" y="162" width="58" height="9"/><rect class="src kept" x="4" y="174" width="58" height="9"/><rect class="src kept" x="4" y="198" width="58" height="9"/><path class="wire" d="M62 22.5 L118 26.5"/><path class="wire" d="M62 46.5 L118 45.7"/><path class="wire" d="M62 70.5 L118 64.9"/><path class="wire" d="M62 82.5 L118 84.1"/><path class="wire" d="M62 106.5 L118 103.3"/><path class="wire" d="M62 118.5 L118 122.5"/><path class="wire" d="M62 142.5 L118 141.7"/><path class="wire" d="M62 166.5 L118 160.9"/><path class="wire" d="M62 178.5 L118 180.1"/><path class="wire" d="M62 202.5 L118 199.3"/></g><g data-for="filtered"><rect class="src kept" data-src="0" x="4" y="18" width="58" height="9"/><rect class="src kept" data-src="1" x="4" y="30" width="58" height="9"/><rect class="src kept" data-src="2" x="4" y="42" width="58" height="9"/><rect class="src kept" data-src="3" x="4" y="54" width="58" height="9"/><rect class="src kept" data-src="4" x="4" y="66" width="58" height="9"/><rect class="src kept" data-src="5" x="4" y="78" width="58" height="9"/><rect class="src kept" data-src="6" x="4" y="90" width="58" height="9"/><rect class="src kept" data-src="7" x="4" y="102" width="58" height="9"/><rect class="src kept" data-src="8" x="4" y="114" width="58" height="9"/><rect class="src kept" data-src="9" x="4" y="126" width="58" height="9"/><rect class="src kept" data-src="10" x="4" y="138" width="58" height="9"/><rect class="src kept" data-src="11" x="4" y="150" width="58" height="9"/><rect class="src kept" data-src="12" x="4" y="162" width="58" height="9"/><rect class="src kept" data-src="13" x="4" y="174" width="58" height="9"/><rect class="src kept" data-src="14" x="4" y="186" width="58" height="9"/><rect class="src kept" data-src="15" x="4" y="198" width="58" height="9"/><path class="wire" d="M62 22.5 L118 26.5"/><path class="wire half" d="M62 34.5 L118 26.5"/><path class="wire half" d="M62 34.5 L118 45.7"/><path class="wire" d="M62 46.5 L118 45.7"/><path class="wire half" d="M62 58.5 L118 64.9"/><path class="wire" d="M62 70.5 L118 64.9"/><path class="wire" d="M62 82.5 L118 84.1"/><path class="wire half" d="M62 94.5 L118 84.1"/><path class="wire half" d="M62 94.5 L118 103.3"/><path class="wire" d="M62 106.5 L118 103.3"/><path class="wire" d="M62 118.5 L118 122.5"/><path class="wire half" d="M62 130.5 L118 122.5"/><path class="wire half" d="M62 130.5 L118 141.7"/><path class="wire" d="M62 142.5 L118 141.7"/><path class="wire half" d="M62 154.5 L118 160.9"/><path class="wire" d="M62 166.5 L118 160.9"/><path class="wire" d="M62 178.5 L118 180.1"/><path class="wire half" d="M62 190.5 L118 180.1"/><path class="wire half" d="M62 190.5 L118 199.3"/><path class="wire" d="M62 202.5 L118 199.3"/></g><g data-for="native"><text x="4" y="102">not drawn</text><text x="4" y="114">at 384 at all</text></g><rect class="out" x="118" y="20.0" width="58" height="13"/><rect class="out" x="118" y="39.2" width="58" height="13"/><rect class="out" x="118" y="58.4" width="58" height="13"/><rect class="out" x="118" y="77.6" width="58" height="13"/><rect class="out" x="118" y="96.8" width="58" height="13"/><rect class="out" x="118" y="116.0" width="58" height="13"/><rect class="out" x="118" y="135.2" width="58" height="13"/><rect class="out" x="118" y="154.4" width="58" height="13"/><rect class="out" x="118" y="173.6" width="58" height="13"/><rect class="out" x="118" y="192.8" width="58" height="13"/></svg></div><p class="m3-label">The board lines each tube line is made of</p></div></div></div></div><figcaption>All three from one moment of the race, saved in Supermodel and loaded again at 496 by 384 and at 3520 by 240. The two reductions are made from the 384 line frame the way RetroArch makes them, nearest neighbour and bilinear. Each of the tube's lines is shown four pixels tall, so a line on the page is a line on the glass.</figcaption></figure>
<p>The 3520 counts how finely the television's mode slices each line in time. It is the arcade world's "super resolution", width to spare, so that a console's 320 pixels land on exactly eleven of it each. The set shows it as 4:3, the way the cabinet's monitor did.</p>
<h2 id="one-check-between-supermodel-and-the-tube" class="log-sec log-sec-2"><a class="log-anchor" href="#one-check-between-supermodel-and-the-tube" aria-label="Link to this section"></a><span class="log-sec-n">02</span>One check between Supermodel and the tube</h2>
<p>OmaCRT does not give the television to the desktop. It takes the connector for itself, and the programs on the tube are clients of <code>flyback</code>, a compositor for that one output that speaks Wayland and nothing else. There is no X server on the television.</p>
<p>The Supermodel package in the Arch User Repository starts and stops with "OpenGL initialization failed". Supermodel carries its own copy of GLEW, the library that looks up OpenGL's functions, and <code>glewInit()</code> does two things: it loads the functions, then it asks the X server for its GLX extensions. Under Wayland there is no X server to ask, so the call returns <code>GLEW_ERROR_NO_GLX_DISPLAY</code> with every function Supermodel uses already loaded, and Supermodel treats any answer but success as fatal.</p>
<p>The change lets that one answer through:</p>
<pre data-lang="cpp"><code>if (GLEW_OK != err &amp;&amp; GLEW_ERROR_NO_GLX_DISPLAY != err)</code></pre>
<p>It lives in <a href="https://github.com/stefanomainardi/omacrt/tree/main/packaging/supermodel-omacrt"><code>packaging/supermodel-omacrt</code></a>, which builds the same upstream release as the AUR package with that line changed and replaces it. The launcher then starts the binary directly, tells it where its list of games is, and gives it the television's frame, its rate and a name the compositor can raise it by.</p>
<h2 id="a-cabinet-waiting-for-its-twin" class="log-sec log-sec-3"><a class="log-anchor" href="#a-cabinet-waiting-for-its-twin" aria-label="Link to this section"></a><span class="log-sec-n">03</span>A cabinet waiting for its twin</h2>
<p>Sega Rally 2 started. Scud Race did not.</p>
<div class="figure-row fig-grid fig-grid-2 fig-wide"><figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/network.png"><img src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/network.png" alt="Scud Race with no network board: the link is cancelled before the attract mode starts." width="496" height="384" data-px="496x384" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Emulator output</span>Scud Race with no network board: the link is cancelled before the attract mode starts.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/checking.png"><img src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/checking.png" alt="With Supermodel&#x27;s simulated board, the same game waits for the other cabinet, for ever. Both at the board&#x27;s own 496 by 384." width="496" height="384" data-px="496x384" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Emulator output</span>With Supermodel's simulated board, the same game waits for the other cabinet, for ever. Both at the board's own 496 by 384.</figcaption></figure></div>
<p>Scud Race and Daytona USA 2 were built to run linked: two cabinets side by side, joined by a network board so two people could race each other. Out of the box each board is the master of a pair. With no network board it gives up at once. With Supermodel's simulated one it gets further and waits at "network checking" for a second cabinet that never answers.</p>
<p>An operator who installed one of them alone fixed this in the game's test menu, under game assignments, by setting LINK ID to single. The agent did the same: it ran the game in a virtual X display, built a thirty line program that presses keys through XTest, and stepped through the menus one key a second, with a screenshot after each press. Faster than that, presses were lost or doubled and the cursor landed in the wrong test.</p>
<figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/assignments.png"><img src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/assignments.png" alt="The game assignments page of Daytona USA 2&#x27;s test menu, with LINK ID set to single. Supermodel&#x27;s output, at 496 by 384." width="496" height="384" data-px="496x384" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Emulator output</span>The game assignments page of Daytona USA 2's test menu, with LINK ID set to single. Supermodel's output, at 496 by 384.</figcaption></figure>
<p>That fixed one machine. To fix every machine, the agent compared what the test menu writes. Supermodel saves the board's EEPROM to a file beside the game when it exits, and a factory file set against one saved after the change differed in five words: the link and a flag beside it, each in two copies of the settings, and a checksum. The checksum was the part that had to be right, since a game that finds its settings corrupt goes back to the factory ones. The agent wrote a short C program that tried every 16 bit CRC polynomial, with every common starting value, bit order and byte order, over every range of words the sum could cover, and checked each against both files. In forty seconds there was one answer: CRC-16/XMODEM over the 29 words of the first copy, high byte first. Daytona USA 2 keeps the same layout one word further along and agreed with it.</p>
<figure class="log-diagram m3 m3-eeprom" data-m3="eeprom"><div class="dg"><h3 class="dg-headline">Two words decide how many cabinets there are.</h3><p class="dg-deck">Scud Race keeps its operator settings in a 128 byte EEPROM, 64 words of 16 bits. Supermodel saves it beside the game. Set LINK ID the way the test menu does and watch which words change, and what the checksum becomes.</p><div class="dg-run dg-steps m3-links" role="group" aria-label="LINK ID"><button type="button" class="dg-button" data-link="1" aria-pressed="true">Master, as shipped</button><button type="button" class="dg-button" data-link="2" aria-pressed="false">Single</button></div><div class="m3-eeprom-body"><div class="m3-words" role="img" aria-label="The 64 words of the EEPROM in hexadecimal"><span class="m3-w m3-w-head" data-i="0" title="Word 0: Header, M3SEGA">4D33</span><span class="m3-w m3-w-head" data-i="1" title="Word 1: Header, M3SEGA">5345</span><span class="m3-w m3-w-head" data-i="2" title="Word 2: Header, M3SEGA">4741</span><span class="m3-w m3-w-sum" data-i="3" title="Word 3: Checksum">593B</span><span class="m3-w m3-w-id" data-i="4" title="Word 4: The game's identity">A218</span><span class="m3-w m3-w-id" data-i="5" title="Word 5: The game's identity">0104</span><span class="m3-w m3-w-set m3-w-link" data-i="6" title="Word 6: Settings">0001</span><span class="m3-w m3-w-set" data-i="7" title="Word 7: Settings">0001</span><span class="m3-w m3-w-set" data-i="8" title="Word 8: Settings">FFFF</span><span class="m3-w m3-w-set" data-i="9" title="Word 9: Settings">FF01</span><span class="m3-w m3-w-set" data-i="10" title="Word 10: Settings">0101</span><span class="m3-w m3-w-set" data-i="11" title="Word 11: Settings">0001</span><span class="m3-w m3-w-set" data-i="12" title="Word 12: Settings">0200</span><span class="m3-w m3-w-set" data-i="13" title="Word 13: Settings">0000</span><span class="m3-w m3-w-set" data-i="14" title="Word 14: Settings">0000</span><span class="m3-w m3-w-set m3-w-flag" data-i="15" title="Word 15: Settings">0100</span><span class="m3-w m3-w-set" data-i="16" title="Word 16: Settings">0101</span><span class="m3-w m3-w-set" data-i="17" title="Word 17: Settings">0100</span><span class="m3-w m3-w-set" data-i="18" title="Word 18: Settings">8080</span><span class="m3-w m3-w-set" data-i="19" title="Word 19: Settings">0300</span><span class="m3-w m3-w-set" data-i="20" title="Word 20: Settings">0000</span><span class="m3-w m3-w-set" data-i="21" title="Word 21: Settings">0000</span><span class="m3-w m3-w-set" data-i="22" title="Word 22: Settings">0000</span><span class="m3-w m3-w-set" data-i="23" title="Word 23: Settings">0000</span><span class="m3-w m3-w-set" data-i="24" title="Word 24: Settings">0000</span><span class="m3-w m3-w-set" data-i="25" title="Word 25: Settings">0000</span><span class="m3-w m3-w-set" data-i="26" title="Word 26: Settings">0000</span><span class="m3-w m3-w-set" data-i="27" title="Word 27: Settings">0000</span><span class="m3-w m3-w-set" data-i="28" title="Word 28: Settings">0000</span><span class="m3-w m3-w-set" data-i="29" title="Word 29: Settings">0000</span><span class="m3-w m3-w-set" data-i="30" title="Word 30: Settings">0000</span><span class="m3-w m3-w-set" data-i="31" title="Word 31: Settings">0000</span><span class="m3-w m3-w-set" data-i="32" title="Word 32: Settings">0000</span><span class="m3-w m3-w-set" data-i="33" title="Word 33: Settings">0000</span><span class="m3-w m3-w-set" data-i="34" title="Word 34: Settings">0000</span><span class="m3-w m3-w-copy m3-w-link" data-i="35" title="Word 35: Settings, kept a second time">0001</span><span class="m3-w m3-w-copy" data-i="36" title="Word 36: Settings, kept a second time">0001</span><span class="m3-w m3-w-copy" data-i="37" title="Word 37: Settings, kept a second time">FFFF</span><span class="m3-w m3-w-copy" data-i="38" title="Word 38: Settings, kept a second time">FF01</span><span class="m3-w m3-w-copy" data-i="39" title="Word 39: Settings, kept a second time">0101</span><span class="m3-w m3-w-copy" data-i="40" title="Word 40: Settings, kept a second time">0001</span><span class="m3-w m3-w-copy" data-i="41" title="Word 41: Settings, kept a second time">0200</span><span class="m3-w m3-w-copy" data-i="42" title="Word 42: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="43" title="Word 43: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy m3-w-flag" data-i="44" title="Word 44: Settings, kept a second time">0100</span><span class="m3-w m3-w-copy" data-i="45" title="Word 45: Settings, kept a second time">0101</span><span class="m3-w m3-w-copy" data-i="46" title="Word 46: Settings, kept a second time">0100</span><span class="m3-w m3-w-copy" data-i="47" title="Word 47: Settings, kept a second time">8080</span><span class="m3-w m3-w-copy" data-i="48" title="Word 48: Settings, kept a second time">0300</span><span class="m3-w m3-w-copy" data-i="49" title="Word 49: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="50" title="Word 50: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="51" title="Word 51: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="52" title="Word 52: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="53" title="Word 53: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="54" title="Word 54: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="55" title="Word 55: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="56" title="Word 56: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="57" title="Word 57: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="58" title="Word 58: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="59" title="Word 59: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="60" title="Word 60: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="61" title="Word 61: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="62" title="Word 62: Settings, kept a second time">0000</span><span class="m3-w m3-w-copy" data-i="63" title="Word 63: Settings, kept a second time">0000</span></div><div class="m3-read"><dl class="m3-stats" aria-live="polite" aria-atomic="true"><div><dt>LINK ID, words 6 and 35</dt><dd data-out="link">0001 · master</dd></div><div><dt>Flag, words 15 and 44</dt><dd data-out="flag">0100</dd></div><div><dt>CRC-16 of words 6 to 34</dt><dd data-out="sum">593B</dd></div><div><dt>What the game does</dt><dd data-out="says">Waits for a second cabinet: “network board not present”.</dd></div></dl><ul class="m3-legend"><li><i class="m3-key m3-w-head"></i>Header</li><li><i class="m3-key m3-w-sum"></i>Checksum</li><li><i class="m3-key m3-w-id"></i>Identity</li><li><i class="m3-key m3-w-set"></i>Settings</li><li><i class="m3-key m3-w-copy"></i>Second copy</li></ul></div></div></div><figcaption>Read off Supermodel's NVRAM file for Scud Race. The test menu changes the link and a flag beside it in both copies of the settings, and writes a new checksum: a CRC-16/XMODEM over the first copy, the high byte of each word first. The launcher does exactly this before every start, and only to a file laid out this way whose checksum already adds up.</figcaption></figure>
<p>The launcher now does this before every start of a twin set, and only to a file laid out exactly this way whose checksum already adds up. The very first time one of these games runs there is no file yet, so it shows its network error once. The file it saves on the way out is set right as it closes, the launcher says to start it again, and from then on it plays.</p>
<h2 id="the-rest-of-the-cabinet" class="log-sec log-sec-4"><a class="log-anchor" href="#the-rest-of-the-cabinet" aria-label="Link to this section"></a><span class="log-sec-n">04</span>The rest of the cabinet</h2>
<p>A few smaller things make Supermodel behave like everything else on the tube.</p>
<p>Supermodel counts a pad's buttons from one, and the controls file it writes for itself on a first run puts Start and the coin on buttons nine and ten, which a pad does not have. The launcher writes one for a pad instead. Select is the coin, the left stick and the d-pad steer, the triggers are the pedals and the shoulders change gear. A trigger rests at the bottom of its axis rather than in the middle, so its rest point is moved there, or the first half of the pedal would do nothing. Select and Start together leave the game.</p>
<p>SDL opens the desktop's sound device by name whatever it is told, so the launcher moves Supermodel's stream once it opens: through the leveller that brings every game to one loudness, and on to the television.</p>
<p>libretro's thumbnail repository has no Model 3 games at all. An arcade game it does not know now takes its original flyer from the Arcade Database, the set's own or else its parent's, and its title screen as a last resort. A flyer is what an arcade game had in place of a box.</p>
<figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/list.png"><img src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/list.png" alt="The Model 3 list in the launcher, with Daytona USA 2&#x27;s flyer on the right." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>The Model 3 list in the launcher, with Daytona USA 2's flyer on the right.</figcaption></figure>
<h2 id="twelve-games" class="log-sec log-sec-1"><a class="log-anchor" href="#twelve-games" aria-label="Link to this section"></a><span class="log-sec-n">05</span>Twelve games</h2>
<p>There are twelve Model 3 games on my disk, one of each: where I had two versions of a game I kept the one that runs on its own and in English. Eleven of them play, all at the tube's line count; the twelfth is missing a file, as the last section explains. Here are eight more, as Supermodel drew them at the television's mode during each game's attract sequence.</p>
<div class="figure-row fig-grid fig-grid-2 fig-wide"><figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/daytona2.webp"><img src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/daytona2.webp" alt="Daytona USA 2: Battle on the Edge." width="1280" height="960" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Emulator output</span>Daytona USA 2: Battle on the Edge.</figcaption></figure> <figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/scud.webp"><img src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/scud.webp" alt="Scud Race." width="1280" height="960" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Emulator output</span>Scud Race.</figcaption></figure></div>
<div class="figure-row fig-grid fig-grid-2 fig-wide"><figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/vf3.webp"><img src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/vf3.webp" alt="Virtua Fighter 3." width="1280" height="960" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Emulator output</span>Virtua Fighter 3.</figcaption></figure> <figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/spikeofe.webp"><img src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/spikeofe.webp" alt="Spikeout: Final Edition." width="1280" height="960" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Emulator output</span>Spikeout: Final Edition.</figcaption></figure></div>
<div class="figure-row fig-grid fig-grid-2 fig-wide"><figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/lemans24.webp"><img src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/lemans24.webp" alt="Le Mans 24." width="1280" height="960" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Emulator output</span>Le Mans 24.</figcaption></figure> <figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/fvipers2.webp"><img src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/fvipers2.webp" alt="Fighting Vipers 2." width="1280" height="960" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Emulator output</span>Fighting Vipers 2.</figcaption></figure></div>
<div class="figure-row fig-grid fig-grid-2 fig-wide"><figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/harley.webp"><img src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/harley.webp" alt="Harley-Davidson and L.A. Riders." width="1280" height="960" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Emulator output</span>Harley-Davidson and L.A. Riders.</figcaption></figure> <figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/dirtdvls.webp"><img src="https://omacrt.com/assets/media/log/model-3-at-the-tubes-own-line-count/dirtdvls.webp" alt="Dirt Devils." width="1280" height="960" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Emulator output</span>Dirt Devils.</figcaption></figure></div>
<h2 id="trying-it" class="log-sec log-sec-2"><a class="log-anchor" href="#trying-it" aria-label="Link to this section"></a><span class="log-sec-n">06</span>Trying it</h2>
<p>Model 3 is in <a href="https://github.com/stefanomainardi/omacrt/releases/tag/v0.14.0">OmaCRT 0.14.0</a>. Build and install the Supermodel package first:</p>
<pre><code>cd packaging/supermodel-omacrt
makepkg -si</code></pre>
<p>Then put your own sets in a folder called <code>model3</code> or <code>arcade_model3</code> under one of the library's roots, all in that one folder: a revision such as <code>dayto2pe</code> loads the files it shares with <code>daytona2</code> from beside it. They appear as Arcade (Sega Model 3) after the next scan. The details are in <a href="https://github.com/stefanomainardi/omacrt/blob/main/docs/systems.md"><code>docs/systems.md</code></a>.</p>
<p>Star Wars Trilogy Arcade needs its set complete, including <code>epr-21119.ic8</code>, the program of the board behind the flight stick. Without it the game stops at boot because its stick has no force feedback. The light gun games, The Lost World and The Ocean Hunter, are listed but untested: OmaCRT has no gun yet.<span class="log-end" aria-hidden="true"></span></p>]]></content:encoded>
    </item>
    <item>
      <title>How people walk in the hall</title>
      <link>https://omacrt.com/log/how-people-walk-in-the-hall/</link>
      <guid isPermaLink="true">https://omacrt.com/log/how-people-walk-in-the-hall/</guid>
      <pubDate>Wed, 30 Sep 2026 00:00:00 +0000</pubDate>
      <category>design</category>
      <description>The people in OmaCRT&#x27;s arcade hall took sixteen rounds to walk properly. The first ones copied old adventure games; the ones that worked came from a gait laboratory&#x27;s tables. This entry follows the rounds in five working figures, and the sprites, the joint table and the study plates are free to reuse.</description>
      <content:encoded><![CDATA[<p>The 0.11.0 hall had people in it: a man walking in, players at the cabinets, a janitor with a mop. They were built from capsules and moved like them. The knees barely bent, the body floated at one height and the arms hung. I told the agent the movement did not convince me, and that started two days of rounds.</p>
<p>This is where they ended. Press play, drag along the curves, or step one pose at a time; every figure in this entry runs on the launcher's own sprites and joint table.</p>
<figure class="walk walk-lab" data-walk="lab"><div class="walk-stage walk-stage-lab"><canvas class="walk-canvas" width="156" height="104" role="img" aria-label="The launcher&#x27;s walker in profile, walking one stride after another across a floor."></canvas><img class="walk-still" src="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/keys.png" alt="The launcher&#x27;s walker in profile, walking one stride after another across a floor." loading="lazy" /></div><div class="walk-grid"><div class="walk-left"><div class="walk-readout" aria-live="polite"><p class="walk-now"><output class="walk-pct" data-out="pct">0%</output><span class="walk-now-names"><span data-out="key">Contact</span><span data-out="phase">Initial contact</span></span></p><p class="walk-say" data-out="say">The heel strikes with the toe up and the knee all but straight.</p></div><div class="walk-row"><button type="button" class="dg-button" data-play aria-pressed="false" disabled>Play</button><button type="button" class="dg-button" data-step="-1" disabled aria-label="One pose back">◀</button><button type="button" class="dg-button" data-step="1" disabled aria-label="One pose on">▶</button><button type="button" class="dg-button" data-slow aria-pressed="false" disabled>Quarter speed</button></div><div class="walk-row walk-layers" role="group" aria-label="What to draw"><button type="button" class="dg-button" data-layer="sprite" aria-pressed="true" disabled>Sprite</button><button type="button" class="dg-button" data-layer="bones" aria-pressed="false" disabled>Skeleton</button><button type="button" class="dg-button" data-layer="ghost" aria-pressed="false" disabled>Last pose</button></div><div class="walk-row walk-ground" role="group" aria-label="How fast the ground goes by"><button type="button" class="dg-button" data-ground="1" aria-pressed="true" disabled>Ground matched</button><button type="button" class="dg-button" data-ground="0.6" aria-pressed="false" disabled>Ground too slow</button></div><p class="walk-note walk-live-only" data-out="slide"></p></div><div class="walk-right"><div class="walk-track"><ol class="walk-keys"><li><button type="button" class="walk-key" data-key="0" disabled><b>1</b> Contact</button></li><li><button type="button" class="walk-key" data-key="1" disabled><b>2</b> Down</button></li><li><button type="button" class="walk-key" data-key="2" disabled><b>3</b> Passing</button></li><li><button type="button" class="walk-key" data-key="3" disabled><b>4</b> Up</button></li><li><button type="button" class="walk-key" data-key="4" disabled><b>5</b> Contact</button></li><li><button type="button" class="walk-key" data-key="5" disabled><b>6</b> Down</button></li><li><button type="button" class="walk-key" data-key="6" disabled><b>7</b> Passing</button></li><li><button type="button" class="walk-key" data-key="7" disabled><b>8</b> Up</button></li></ol><div class="walk-feet" aria-hidden="true"><p class="walk-foot"><span>Near foot</span><svg viewBox="0 0 1000 12" preserveAspectRatio="none"><rect class="walk-down" x="0" y="0" width="600" height="12"/><rect class="walk-both" x="0" y="0" width="100" height="12"/><rect class="walk-both" x="500" y="0" width="100" height="12"/></svg></p><p class="walk-foot"><span>Far foot</span><svg viewBox="0 0 1000 12" preserveAspectRatio="none"><rect class="walk-down" x="0" y="0" width="100" height="12"/><rect class="walk-down" x="500" y="0" width="500" height="12"/><rect class="walk-both" x="0" y="0" width="100" height="12"/><rect class="walk-both" x="500" y="0" width="100" height="12"/></svg></p></div><div class="walk-charts"><div class="walk-chart" data-chart="hip"><p class="walk-chart-label"><span>Hip</span><output class="walk-value" data-out="hip">30°</output></p><svg class="walk-curve" viewBox="0 0 1000 100" preserveAspectRatio="none" role="img" aria-label="Hip through one stride: flexed 30 degrees as the heel strikes, extended 12 as the toe leaves"><rect class="walk-stance" x="0" y="0" width="600" height="100"/><line class="walk-rule" x1="0" x2="1000" y1="100.00" y2="100.00"/><line class="walk-rule" x1="0" x2="1000" y1="83.33" y2="83.33"/><line class="walk-zero" x1="0" x2="1000" y1="66.67" y2="66.67"/><line class="walk-rule" x1="0" x2="1000" y1="50.00" y2="50.00"/><line class="walk-rule" x1="0" x2="1000" y1="33.33" y2="33.33"/><line class="walk-rule" x1="0" x2="1000" y1="16.67" y2="16.67"/><line class="walk-rule" x1="0" x2="1000" y1="0.00" y2="0.00"/><line class="walk-rule" x1="125" x2="125" y1="0" y2="100"/><line class="walk-rule" x1="250" x2="250" y1="0" y2="100"/><line class="walk-rule" x1="375" x2="375" y1="0" y2="100"/><line class="walk-rule" x1="500" x2="500" y1="0" y2="100"/><line class="walk-rule" x1="625" x2="625" y1="0" y2="100"/><line class="walk-rule" x1="750" x2="750" y1="0" y2="100"/><line class="walk-rule" x1="875" x2="875" y1="0" y2="100"/><path class="walk-line" d="M0.0,16.67 L5.0,17.03 L10.0,17.45 L15.0,17.92 L20.0,18.43 L25.0,18.96 L30.0,19.51 L35.0,20.06 L40.0,20.61 L45.0,21.15 L50.0,21.67 L55.0,22.15 L60.0,22.61 L65.0,23.06 L70.0,23.51 L75.0,23.96 L80.0,24.43 L85.0,24.92 L90.0,25.45 L95.0,26.03 L100.0,26.67 L105.0,27.36 L110.0,28.12 L115.0,28.92 L120.0,29.76 L125.0,30.62 L130.0,31.51 L135.0,32.39 L140.0,33.28 L145.0,34.15 L150.0,35.00 L155.0,35.83 L160.0,36.64 L165.0,37.45 L170.0,38.25 L175.0,39.06 L180.0,39.88 L185.0,40.71 L190.0,41.56 L195.0,42.43 L200.0,43.33 L205.0,44.27 L210.0,45.25 L215.0,46.26 L220.0,47.29 L225.0,48.33 L230.0,49.37 L235.0,50.40 L240.0,51.41 L245.0,52.39 L250.0,53.33 L255.0,54.23 L260.0,55.11 L265.0,55.96 L270.0,56.79 L275.0,57.60 L280.0,58.41 L285.0,59.22 L290.0,60.03 L295.0,60.84 L300.0,61.67 L305.0,62.50 L310.0,63.33 L315.0,64.17 L320.0,65.00 L325.0,65.83 L330.0,66.67 L335.0,67.50 L340.0,68.33 L345.0,69.17 L350.0,70.00 L355.0,70.84 L360.0,71.69 L365.0,72.55 L370.0,73.41 L375.0,74.27 L380.0,75.12 L385.0,75.96 L390.0,76.77 L395.0,77.57 L400.0,78.33 L405.0,79.09 L410.0,79.85 L415.0,80.61 L420.0,81.36 L425.0,82.08 L430.0,82.77 L435.0,83.42 L440.0,84.01 L445.0,84.54 L450.0,85.00 L455.0,85.40 L460.0,85.76 L465.0,86.08 L470.0,86.35 L475.0,86.56 L480.0,86.72 L485.0,86.81 L490.0,86.84 L495.0,86.79 L500.0,86.67 L505.0,86.47 L510.0,86.20 L515.0,85.87 L520.0,85.47 L525.0,85.00 L530.0,84.47 L535.0,83.87 L540.0,83.20 L545.0,82.47 L550.0,81.67 L555.0,80.78 L560.0,79.81 L565.0,78.76 L570.0,77.64 L575.0,76.46 L580.0,75.23 L585.0,73.96 L590.0,72.65 L595.0,71.33 L600.0,70.00 L605.0,68.63 L610.0,67.19 L615.0,65.69 L620.0,64.16 L625.0,62.60 L630.0,61.04 L635.0,59.48 L640.0,57.95 L645.0,56.45 L650.0,55.00 L655.0,53.59 L660.0,52.20 L665.0,50.82 L670.0,49.47 L675.0,48.12 L680.0,46.80 L685.0,45.49 L690.0,44.20 L695.0,42.93 L700.0,41.67 L705.0,40.42 L710.0,39.17 L715.0,37.94 L720.0,36.72 L725.0,35.52 L730.0,34.35 L735.0,33.20 L740.0,32.09 L745.0,31.02 L750.0,30.00 L755.0,29.02 L760.0,28.07 L765.0,27.15 L770.0,26.27 L775.0,25.42 L780.0,24.60 L785.0,23.82 L790.0,23.07 L795.0,22.35 L800.0,21.67 L805.0,21.02 L810.0,20.40 L815.0,19.82 L820.0,19.27 L825.0,18.75 L830.0,18.27 L835.0,17.82 L840.0,17.40 L845.0,17.02 L850.0,16.67 L855.0,16.36 L860.0,16.09 L865.0,15.87 L870.0,15.68 L875.0,15.52 L880.0,15.39 L885.0,15.27 L890.0,15.17 L895.0,15.08 L900.0,15.00 L905.0,14.92 L910.0,14.87 L915.0,14.83 L920.0,14.80 L925.0,14.79 L930.0,14.80 L935.0,14.82 L940.0,14.87 L945.0,14.92 L950.0,15.00 L955.0,15.08 L960.0,15.17 L965.0,15.27 L970.0,15.39 L975.0,15.52 L980.0,15.68 L985.0,15.87 L990.0,16.09 L995.0,16.36 L1000.0,16.67"/></svg><p class="walk-chart-range">-20° to 40°</p></div><div class="walk-chart" data-chart="knee"><p class="walk-chart-label"><span>Knee</span><output class="walk-value" data-out="knee">5°</output></p><svg class="walk-curve" viewBox="0 0 1000 100" preserveAspectRatio="none" role="img" aria-label="Knee through one stride: 18 degrees under the weight, 63 in swing"><rect class="walk-stance" x="0" y="0" width="600" height="100"/><line class="walk-zero" x1="0" x2="1000" y1="100.00" y2="100.00"/><line class="walk-rule" x1="0" x2="1000" y1="75.00" y2="75.00"/><line class="walk-rule" x1="0" x2="1000" y1="50.00" y2="50.00"/><line class="walk-rule" x1="0" x2="1000" y1="25.00" y2="25.00"/><line class="walk-rule" x1="0" x2="1000" y1="0.00" y2="0.00"/><line class="walk-rule" x1="125" x2="125" y1="0" y2="100"/><line class="walk-rule" x1="250" x2="250" y1="0" y2="100"/><line class="walk-rule" x1="375" x2="375" y1="0" y2="100"/><line class="walk-rule" x1="500" x2="500" y1="0" y2="100"/><line class="walk-rule" x1="625" x2="625" y1="0" y2="100"/><line class="walk-rule" x1="750" x2="750" y1="0" y2="100"/><line class="walk-rule" x1="875" x2="875" y1="0" y2="100"/><path class="walk-line" d="M0.0,93.75 L5.0,93.33 L10.0,92.70 L15.0,91.91 L20.0,91.00 L25.0,90.00 L30.0,88.95 L35.0,87.89 L40.0,86.85 L45.0,85.88 L50.0,85.00 L55.0,84.15 L60.0,83.26 L65.0,82.34 L70.0,81.43 L75.0,80.55 L80.0,79.72 L85.0,78.98 L90.0,78.34 L95.0,77.84 L100.0,77.50 L105.0,77.33 L110.0,77.30 L115.0,77.40 L120.0,77.60 L125.0,77.89 L130.0,78.25 L135.0,78.66 L140.0,79.10 L145.0,79.55 L150.0,80.00 L155.0,80.48 L160.0,81.03 L165.0,81.64 L170.0,82.29 L175.0,82.97 L180.0,83.66 L185.0,84.35 L190.0,85.02 L195.0,85.66 L200.0,86.25 L205.0,86.81 L210.0,87.37 L215.0,87.92 L220.0,88.46 L225.0,88.98 L230.0,89.49 L235.0,89.97 L240.0,90.43 L245.0,90.86 L250.0,91.25 L255.0,91.61 L260.0,91.93 L265.0,92.22 L270.0,92.49 L275.0,92.73 L280.0,92.96 L285.0,93.17 L290.0,93.37 L295.0,93.56 L300.0,93.75 L305.0,93.94 L310.0,94.14 L315.0,94.33 L320.0,94.52 L325.0,94.69 L330.0,94.83 L335.0,94.94 L340.0,95.01 L345.0,95.03 L350.0,95.00 L355.0,94.91 L360.0,94.76 L365.0,94.56 L370.0,94.33 L375.0,94.06 L380.0,93.77 L385.0,93.46 L390.0,93.14 L395.0,92.82 L400.0,92.50 L405.0,92.20 L410.0,91.93 L415.0,91.66 L420.0,91.39 L425.0,91.09 L430.0,90.76 L435.0,90.37 L440.0,89.92 L445.0,89.38 L450.0,88.75 L455.0,88.04 L460.0,87.29 L465.0,86.48 L470.0,85.62 L475.0,84.69 L480.0,83.68 L485.0,82.59 L490.0,81.41 L495.0,80.13 L500.0,78.75 L505.0,77.23 L510.0,75.58 L515.0,73.81 L520.0,71.94 L525.0,70.00 L530.0,68.01 L535.0,65.99 L540.0,63.97 L545.0,61.96 L550.0,60.00 L555.0,58.03 L560.0,56.00 L565.0,53.93 L570.0,51.85 L575.0,49.77 L580.0,47.70 L585.0,45.67 L590.0,43.70 L595.0,41.80 L600.0,40.00 L605.0,38.25 L610.0,36.50 L615.0,34.78 L620.0,33.10 L625.0,31.48 L630.0,29.95 L635.0,28.52 L640.0,27.20 L645.0,26.02 L650.0,25.00 L655.0,24.12 L660.0,23.37 L665.0,22.73 L670.0,22.21 L675.0,21.80 L680.0,21.49 L685.0,21.29 L690.0,21.18 L695.0,21.17 L700.0,21.25 L705.0,21.43 L710.0,21.72 L715.0,22.11 L720.0,22.61 L725.0,23.20 L730.0,23.89 L735.0,24.67 L740.0,25.53 L745.0,26.48 L750.0,27.50 L755.0,28.62 L760.0,29.86 L765.0,31.20 L770.0,32.63 L775.0,34.14 L780.0,35.72 L785.0,37.36 L790.0,39.04 L795.0,40.76 L800.0,42.50 L805.0,44.30 L810.0,46.20 L815.0,48.17 L820.0,50.20 L825.0,52.27 L830.0,54.35 L835.0,56.43 L840.0,58.50 L845.0,60.53 L850.0,62.50 L855.0,64.47 L860.0,66.47 L865.0,68.49 L870.0,70.51 L875.0,72.50 L880.0,74.44 L885.0,76.31 L890.0,78.08 L895.0,79.73 L900.0,81.25 L905.0,82.64 L910.0,83.93 L915.0,85.13 L920.0,86.24 L925.0,87.27 L930.0,88.21 L935.0,89.08 L940.0,89.87 L945.0,90.59 L950.0,91.25 L955.0,91.85 L960.0,92.41 L965.0,92.91 L970.0,93.33 L975.0,93.67 L980.0,93.92 L985.0,94.06 L990.0,94.09 L995.0,93.99 L1000.0,93.75"/></svg><p class="walk-chart-range">0° to 80°</p></div><div class="walk-chart" data-chart="ankle"><p class="walk-chart-label"><span>Ankle</span><output class="walk-value" data-out="ankle">0°</output></p><svg class="walk-curve" viewBox="0 0 1000 100" preserveAspectRatio="none" role="img" aria-label="Ankle through one stride: pushing off by 14 degrees before the foot leaves"><rect class="walk-stance" x="0" y="0" width="600" height="100"/><line class="walk-rule" x1="0" x2="1000" y1="100.00" y2="100.00"/><line class="walk-rule" x1="0" x2="1000" y1="75.00" y2="75.00"/><line class="walk-zero" x1="0" x2="1000" y1="50.00" y2="50.00"/><line class="walk-rule" x1="0" x2="1000" y1="25.00" y2="25.00"/><line class="walk-rule" x1="0" x2="1000" y1="0.00" y2="0.00"/><line class="walk-rule" x1="125" x2="125" y1="0" y2="100"/><line class="walk-rule" x1="250" x2="250" y1="0" y2="100"/><line class="walk-rule" x1="375" x2="375" y1="0" y2="100"/><line class="walk-rule" x1="500" x2="500" y1="0" y2="100"/><line class="walk-rule" x1="625" x2="625" y1="0" y2="100"/><line class="walk-rule" x1="750" x2="750" y1="0" y2="100"/><line class="walk-rule" x1="875" x2="875" y1="0" y2="100"/><path class="walk-line" d="M0.0,50.00 L5.0,50.65 L10.0,51.56 L15.0,52.66 L20.0,53.88 L25.0,55.16 L30.0,56.42 L35.0,57.60 L40.0,58.64 L45.0,59.46 L50.0,60.00 L55.0,60.30 L60.0,60.46 L65.0,60.48 L70.0,60.38 L75.0,60.16 L80.0,59.82 L85.0,59.38 L90.0,58.84 L95.0,58.21 L100.0,57.50 L105.0,56.63 L110.0,55.56 L115.0,54.33 L120.0,52.98 L125.0,51.56 L130.0,50.12 L135.0,48.70 L140.0,47.34 L145.0,46.09 L150.0,45.00 L155.0,44.04 L160.0,43.14 L165.0,42.30 L170.0,41.52 L175.0,40.78 L180.0,40.08 L185.0,39.41 L190.0,38.76 L195.0,38.13 L200.0,37.50 L205.0,36.90 L210.0,36.34 L215.0,35.82 L220.0,35.32 L225.0,34.84 L230.0,34.38 L235.0,33.92 L240.0,33.46 L245.0,32.99 L250.0,32.50 L255.0,31.99 L260.0,31.46 L265.0,30.92 L270.0,30.38 L275.0,29.84 L280.0,29.32 L285.0,28.82 L290.0,28.34 L295.0,27.90 L300.0,27.50 L305.0,27.13 L310.0,26.76 L315.0,26.41 L320.0,26.08 L325.0,25.78 L330.0,25.52 L335.0,25.30 L340.0,25.14 L345.0,25.04 L350.0,25.00 L355.0,25.02 L360.0,25.10 L365.0,25.22 L370.0,25.40 L375.0,25.62 L380.0,25.90 L385.0,26.23 L390.0,26.60 L395.0,27.03 L400.0,27.50 L405.0,28.01 L410.0,28.56 L415.0,29.15 L420.0,29.78 L425.0,30.47 L430.0,31.22 L435.0,32.04 L440.0,32.94 L445.0,33.92 L450.0,35.00 L455.0,36.13 L460.0,37.28 L465.0,38.48 L470.0,39.74 L475.0,41.09 L480.0,42.56 L485.0,44.16 L490.0,45.92 L495.0,47.86 L500.0,50.00 L505.0,52.50 L510.0,55.44 L515.0,58.69 L520.0,62.12 L525.0,65.62 L530.0,69.08 L535.0,72.37 L540.0,75.36 L545.0,77.95 L550.0,80.00 L555.0,81.60 L560.0,82.92 L565.0,83.97 L570.0,84.76 L575.0,85.31 L580.0,85.64 L585.0,85.76 L590.0,85.68 L595.0,85.42 L600.0,85.00 L605.0,84.30 L610.0,83.24 L615.0,81.89 L620.0,80.32 L625.0,78.59 L630.0,76.78 L635.0,74.95 L640.0,73.16 L645.0,71.49 L650.0,70.00 L655.0,68.63 L660.0,67.26 L665.0,65.91 L670.0,64.58 L675.0,63.28 L680.0,62.02 L685.0,60.80 L690.0,59.64 L695.0,58.54 L700.0,57.50 L705.0,56.52 L710.0,55.60 L715.0,54.73 L720.0,53.90 L725.0,53.12 L730.0,52.40 L735.0,51.73 L740.0,51.10 L745.0,50.53 L750.0,50.00 L755.0,49.54 L760.0,49.14 L765.0,48.80 L770.0,48.52 L775.0,48.28 L780.0,48.08 L785.0,47.91 L790.0,47.76 L795.0,47.63 L800.0,47.50 L805.0,47.39 L810.0,47.30 L815.0,47.24 L820.0,47.20 L825.0,47.19 L830.0,47.20 L835.0,47.24 L840.0,47.30 L845.0,47.39 L850.0,47.50 L855.0,47.66 L860.0,47.88 L865.0,48.15 L870.0,48.44 L875.0,48.75 L880.0,49.06 L885.0,49.36 L890.0,49.62 L895.0,49.84 L900.0,50.00 L905.0,50.10 L910.0,50.16 L915.0,50.18 L920.0,50.18 L925.0,50.16 L930.0,50.12 L935.0,50.08 L940.0,50.04 L945.0,50.01 L950.0,50.00 L955.0,49.95 L960.0,49.84 L965.0,49.69 L970.0,49.52 L975.0,49.38 L980.0,49.28 L985.0,49.27 L990.0,49.36 L995.0,49.59 L1000.0,50.00"/></svg><p class="walk-chart-range">-20° to 20°</p></div><i class="walk-cursor" aria-hidden="true"></i></div><input class="walk-scrub" type="range" min="0" max="999" value="0" step="1" aria-label="Where in the stride" disabled /></div></div></div><figcaption>One stride of the hall's walker, from the near heel striking to the same heel striking again. The sprite is the launcher's own, drawn at sixteen poses a stride, and the skeleton is posed from the same joint table. The shaded part of each curve is the near foot on the floor. Ground too slow moves the body at 60% of the pace the legs are walking, which is what a sprite does when its speed is set apart from its stride. Drag the walker, or the curves, to walk him by hand.</figcaption></figure>
<h2 id="copying-the-classics" class="log-sec log-sec-1"><a class="log-anchor" href="#copying-the-classics" aria-label="Link to this section"></a><span class="log-sec-n">01</span>Copying the classics</h2>
<p>The first fix was the animator's one: eight key poses, the knee bent, the pelvis bobbing, the arms in counterphase, each pose held for four frames the way a sprite of the period would be.</p>
<figure class="manual-figure manual-figure-video fig-wide"><video controls playsinline loop preload="none" poster="/assets/media/log/how-people-walk-in-the-hall/before-after.webp" width="960" height="436" aria-label="Left, the capsules of 0.11.0; right, eight key poses held at 15 a second."><source src="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/before-after.webm" type="video/webm" /><source src="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/before-after.mp4" type="video/mp4" /></video><figcaption><span class="fig-kind">Rendered from the launcher</span>Left, the capsules of 0.11.0; right, eight key poses held at 15 a second.</figcaption></figure>
<p>I still did not like it, and asked for the LucasArts games as the model. The agent read the SCUMM engine's rules (the ground moves with the pose, the head is large, a walker stops in profile before turning) and then measured Guybrush from The Secret of Monkey Island's sprite sheet: six poses, knees never straight, the head a quarter of the height.</p>
<figure class="manual-figure manual-figure-video fig-wide"><video controls playsinline loop preload="none" poster="/assets/media/log/how-people-walk-in-the-hall/guybrush.webp" width="960" height="406" aria-label="Our walker as he was, left, and with Guybrush&#x27;s proportions and timing, right."><source src="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/guybrush.webm" type="video/webm" /><source src="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/guybrush.mp4" type="video/mp4" /></video><figcaption><span class="fig-kind">Rendered from the launcher</span>Our walker as he was, left, and with Guybrush's proportions and timing, right.</figcaption></figure>
<p>I called it clumsy and slow. It was slow by measurement too: 0.42 heights a second, about half an ordinary walk, which suits a cartoon pirate in a small room. A man crossing a hall walks twice as fast. Indy in Fate of Atlantis was closer, with realistic proportions, a long stride and an upright torso. Copied onto our man, it still did not work, and I said the character itself probably needed redrawing.</p>
<h2 id="a-figure-drawn-again" class="log-sec log-sec-2"><a class="log-anchor" href="#a-figure-drawn-again" aria-label="Link to this section"></a><span class="log-sec-n">02</span>A figure drawn again</h2>
<p>The agent stopped posing capsules and drew the man as shapes four times larger than the sprite, lit from the upper left, then reduced each 4x4 block to its commonest colour. Studying Indy at eight times his size gave the rest. He has no black outline: the dark of each material sits on its shadow side only. Every material has four tones, and the shadows drift cool. The cloth breaks into small clusters of two tones, and the whole sprite uses a few dozen colours.</p>
<p>Faces did not survive the reduction. At nine pixels across a reduced face is noise, so the heads are drawn by hand, pixel by pixel, in grids of 12 by 13. The launcher still makes every sprite this way when it starts, and the figure below stops it at each step.</p>
<figure class="walk walk-make" data-walk="make" data-texts="[&quot;Polygons, ellipses and tapered limbs, lit from the upper left, four tones to each material. Nothing here is a pixel of the sprite yet.&quot;, &quot;Each block of four by four becomes one pixel, in its commonest colour, where more than half of it is covered. Edges come out stepped the way a hand would step them.&quot;, &quot;A pixel whose four neighbours all share another colour is noise at this size, and takes theirs.&quot;, &quot;Pairs of the shade and single lights break up the flat cloth, from a fixed seed, so the jacket reads as fabric and is the same every time.&quot;, &quot;Twelve by thirteen pixels drawn one at a time. A reduced face at this size is noise, so the heads are written out as grids of pixels instead of drawn from shapes.&quot;, &quot;An empty pixel right of or below the figure takes a dark of what it borders. The lit side stays open, and there is no black outline.&quot;]"><div class="walk-make-grid"><div class="walk-stage walk-stage-make"><canvas class="walk-canvas" width="248" height="304" role="img" aria-label="One sprite at a step of its making, enlarged four times."></canvas></div><div class="walk-make-side"><ol class="walk-steps"><li><button type="button" class="walk-step" data-stage="0" aria-pressed="false" disabled><b>1</b> Shapes at four times the size</button></li><li><button type="button" class="walk-step" data-stage="1" aria-pressed="false" disabled><b>2</b> Reduced</button></li><li><button type="button" class="walk-step" data-stage="2" aria-pressed="false" disabled><b>3</b> Cleaned</button></li><li><button type="button" class="walk-step" data-stage="3" aria-pressed="false" disabled><b>4</b> Grain</button></li><li><button type="button" class="walk-step" data-stage="4" aria-pressed="false" disabled><b>5</b> Head by hand</button></li><li><button type="button" class="walk-step" data-stage="5" aria-pressed="true" disabled><b>6</b> Edge on the shadow side</button></li></ol><div class="walk-readout" aria-live="polite"><p class="walk-say" data-out="stage">An empty pixel right of or below the figure takes a dark of what it borders. The lit side stays open, and there is no black outline.</p><p class="walk-say walk-live-only" data-out="colours"></p></div><div class="walk-row"><button type="button" class="dg-button" data-diff aria-pressed="false" disabled>Mark what changed</button></div><div class="walk-row" role="group" aria-label="Whose sprite"><button type="button" class="dg-button" data-look="denim" aria-pressed="true" disabled>Denim</button><button type="button" class="dg-button" data-look="mullet" aria-pressed="false" disabled>Mullet</button><button type="button" class="dg-button" data-look="janitor" aria-pressed="false" disabled>Janitor</button></div><div class="walk-stage walk-stage-size"><canvas class="walk-canvas" width="200" height="160" role="img" aria-label="The same sprite at its real size and at twice it."></canvas></div></div></div><figcaption>How the launcher makes a sprite when it starts, step by step, for one pose. The large view is enlarged four times; beside it is the sprite at the size the hall draws it, and at twice that.</figcaption></figure>
<p>Then I looked at the legs and said they walked like two logs.</p>
<h2 id="the-laboratorys-numbers" class="log-sec log-sec-3"><a class="log-anchor" href="#the-laboratorys-numbers" aria-label="Link to this section"></a><span class="log-sec-n">03</span>The laboratory's numbers</h2>
<p>Animation books teach a walk as a handful of poses. A gait laboratory records the angle of each joint through the stride, and David Winter published the normal curves for hip, knee and ankle. The agent sampled them every 5% and posed the legs from them with a spline.</p>
<figure class="walk walk-knee" data-walk="knee"><div class="walk-stage walk-stage-knee"><canvas class="walk-canvas" width="288" height="96" role="img" aria-label="Four skeletons walking on the spot, one per knee rule: straight, kicking, crouching, and Winter&#x27;s."></canvas><img class="walk-still" src="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/plate-knee.png" alt="Four skeletons walking on the spot, one per knee rule: straight, kicking, crouching, and Winter&#x27;s." loading="lazy" /></div><div class="walk-row walk-rules" role="group" aria-label="Which knee to follow"><button type="button" class="dg-button" data-rule="logs" aria-pressed="false" disabled>Logs</button><button type="button" class="dg-button" data-rule="kick" aria-pressed="false" disabled>Kick</button><button type="button" class="dg-button" data-rule="crouch" aria-pressed="false" disabled>Crouch</button><button type="button" class="dg-button" data-rule="winter" aria-pressed="true" disabled>Winter</button></div><div class="walk-knee-body"><div class="walk-readout" aria-live="polite"><p class="walk-say" data-out="why">The laboratory&#x27;s curve: 18 degrees as the heel takes the weight, nearly straight at mid stance, 63 in swing.</p><p class="walk-say walk-live-only" data-out="angles"></p></div><div class="walk-track"><div class="walk-charts"><div class="walk-chart" data-chart="knee"><p class="walk-chart-label"><span>Knee bend</span></p><svg class="walk-curve" viewBox="0 0 1000 100" preserveAspectRatio="none" role="img" aria-label="The knee&#x27;s bend through one stride under four rules"><rect class="walk-stance" x="0" y="0" width="600" height="100"/><line class="walk-zero" x1="0" x2="1000" y1="100.00" y2="100.00"/><line class="walk-rule" x1="0" x2="1000" y1="75.00" y2="75.00"/><line class="walk-rule" x1="0" x2="1000" y1="50.00" y2="50.00"/><line class="walk-rule" x1="0" x2="1000" y1="25.00" y2="25.00"/><line class="walk-rule" x1="0" x2="1000" y1="0.00" y2="0.00"/><line class="walk-rule" x1="125" x2="125" y1="0" y2="100"/><line class="walk-rule" x1="250" x2="250" y1="0" y2="100"/><line class="walk-rule" x1="375" x2="375" y1="0" y2="100"/><line class="walk-rule" x1="500" x2="500" y1="0" y2="100"/><line class="walk-rule" x1="625" x2="625" y1="0" y2="100"/><line class="walk-rule" x1="750" x2="750" y1="0" y2="100"/><line class="walk-rule" x1="875" x2="875" y1="0" y2="100"/><path class="walk-line walk-knee-logs" data-rule="logs" d="M0.0,96.25 L5.0,96.25 L10.0,96.25 L15.0,96.25 L20.0,96.25 L25.0,96.25 L30.0,96.25 L35.0,96.25 L40.0,96.25 L45.0,96.25 L50.0,96.25 L55.0,96.25 L60.0,96.25 L65.0,96.25 L70.0,96.25 L75.0,96.25 L80.0,96.25 L85.0,96.25 L90.0,96.25 L95.0,96.25 L100.0,96.25 L105.0,96.25 L110.0,96.25 L115.0,96.25 L120.0,96.25 L125.0,96.25 L130.0,96.25 L135.0,96.25 L140.0,96.25 L145.0,96.25 L150.0,96.25 L155.0,96.25 L160.0,96.25 L165.0,96.25 L170.0,96.25 L175.0,96.25 L180.0,96.25 L185.0,96.25 L190.0,96.25 L195.0,96.25 L200.0,96.25 L205.0,96.25 L210.0,96.25 L215.0,96.25 L220.0,96.25 L225.0,96.25 L230.0,96.25 L235.0,96.25 L240.0,96.25 L245.0,96.25 L250.0,96.25 L255.0,96.25 L260.0,96.25 L265.0,96.25 L270.0,96.25 L275.0,96.25 L280.0,96.25 L285.0,96.25 L290.0,96.25 L295.0,96.25 L300.0,96.25 L305.0,96.25 L310.0,96.25 L315.0,96.25 L320.0,96.25 L325.0,96.25 L330.0,96.25 L335.0,96.25 L340.0,96.25 L345.0,96.25 L350.0,96.25 L355.0,96.25 L360.0,96.25 L365.0,96.25 L370.0,96.25 L375.0,96.25 L380.0,96.25 L385.0,96.25 L390.0,96.25 L395.0,96.25 L400.0,96.25 L405.0,96.25 L410.0,96.25 L415.0,96.25 L420.0,96.25 L425.0,96.25 L430.0,96.25 L435.0,96.25 L440.0,96.25 L445.0,96.25 L450.0,96.25 L455.0,96.25 L460.0,96.25 L465.0,96.25 L470.0,96.25 L475.0,96.25 L480.0,96.25 L485.0,96.25 L490.0,96.25 L495.0,96.25 L500.0,96.25 L505.0,96.25 L510.0,96.25 L515.0,96.25 L520.0,96.25 L525.0,96.25 L530.0,96.25 L535.0,96.25 L540.0,96.25 L545.0,96.25 L550.0,96.25 L555.0,96.25 L560.0,96.25 L565.0,96.25 L570.0,96.25 L575.0,96.25 L580.0,96.25 L585.0,96.25 L590.0,96.25 L595.0,96.25 L600.0,96.25 L605.0,96.25 L610.0,96.25 L615.0,96.25 L620.0,96.25 L625.0,96.25 L630.0,96.25 L635.0,96.25 L640.0,96.25 L645.0,96.25 L650.0,96.25 L655.0,96.25 L660.0,96.25 L665.0,96.25 L670.0,96.25 L675.0,96.25 L680.0,96.25 L685.0,96.25 L690.0,96.25 L695.0,96.25 L700.0,96.25 L705.0,96.25 L710.0,96.25 L715.0,96.25 L720.0,96.25 L725.0,96.25 L730.0,96.25 L735.0,96.25 L740.0,96.25 L745.0,96.25 L750.0,96.25 L755.0,96.25 L760.0,96.25 L765.0,96.25 L770.0,96.25 L775.0,96.25 L780.0,96.25 L785.0,96.25 L790.0,96.25 L795.0,96.25 L800.0,96.25 L805.0,96.25 L810.0,96.25 L815.0,96.25 L820.0,96.25 L825.0,96.25 L830.0,96.25 L835.0,96.25 L840.0,96.25 L845.0,96.25 L850.0,96.25 L855.0,96.25 L860.0,96.25 L865.0,96.25 L870.0,96.25 L875.0,96.25 L880.0,96.25 L885.0,96.25 L890.0,96.25 L895.0,96.25 L900.0,96.25 L905.0,96.25 L910.0,96.25 L915.0,96.25 L920.0,96.25 L925.0,96.25 L930.0,96.25 L935.0,96.25 L940.0,96.25 L945.0,96.25 L950.0,96.25 L955.0,96.25 L960.0,96.25 L965.0,96.25 L970.0,96.25 L975.0,96.25 L980.0,96.25 L985.0,96.25 L990.0,96.25 L995.0,96.25 L1000.0,96.25"/><path class="walk-line walk-knee-kick" data-rule="kick" d="M0.0,71.88 L5.0,71.66 L10.0,71.62 L15.0,71.75 L20.0,72.00 L25.0,72.36 L30.0,72.81 L35.0,73.32 L40.0,73.88 L45.0,74.44 L50.0,75.00 L55.0,75.60 L60.0,76.29 L65.0,77.05 L70.0,77.86 L75.0,78.71 L80.0,79.57 L85.0,80.44 L90.0,81.28 L95.0,82.07 L100.0,82.81 L105.0,83.51 L110.0,84.21 L115.0,84.90 L120.0,85.58 L125.0,86.23 L130.0,86.86 L135.0,87.47 L140.0,88.04 L145.0,88.57 L150.0,89.06 L155.0,89.51 L160.0,89.91 L165.0,90.28 L170.0,90.61 L175.0,90.92 L180.0,91.20 L185.0,91.46 L190.0,91.71 L195.0,91.95 L200.0,92.19 L205.0,92.43 L210.0,92.67 L215.0,92.92 L220.0,93.15 L225.0,93.36 L230.0,93.54 L235.0,93.67 L240.0,93.76 L245.0,93.79 L250.0,93.75 L255.0,93.63 L260.0,93.45 L265.0,93.21 L270.0,92.91 L275.0,92.58 L280.0,92.21 L285.0,91.83 L290.0,91.42 L295.0,91.02 L300.0,90.62 L305.0,90.25 L310.0,89.91 L315.0,89.58 L320.0,89.24 L325.0,88.87 L330.0,88.45 L335.0,87.97 L340.0,87.40 L345.0,86.73 L350.0,85.94 L355.0,85.05 L360.0,84.11 L365.0,83.11 L370.0,82.03 L375.0,80.86 L380.0,79.60 L385.0,78.24 L390.0,76.76 L395.0,75.17 L400.0,73.44 L405.0,71.54 L410.0,69.48 L415.0,67.26 L420.0,64.92 L425.0,62.50 L430.0,60.01 L435.0,57.49 L440.0,54.96 L445.0,52.46 L450.0,50.00 L455.0,47.54 L460.0,45.00 L465.0,42.42 L470.0,39.81 L475.0,37.21 L480.0,34.62 L485.0,32.09 L490.0,29.63 L495.0,27.25 L500.0,25.00 L505.0,22.81 L510.0,20.63 L515.0,18.47 L520.0,16.38 L525.0,14.36 L530.0,12.44 L535.0,10.64 L540.0,9.00 L545.0,7.53 L550.0,6.25 L555.0,5.16 L560.0,4.21 L565.0,3.42 L570.0,2.76 L575.0,2.25 L580.0,1.86 L585.0,1.61 L590.0,1.48 L595.0,1.46 L600.0,1.56 L605.0,1.79 L610.0,2.15 L615.0,2.64 L620.0,3.26 L625.0,4.00 L630.0,4.86 L635.0,5.83 L640.0,6.91 L645.0,8.09 L650.0,9.38 L655.0,10.78 L660.0,12.32 L665.0,14.00 L670.0,15.79 L675.0,17.68 L680.0,19.65 L685.0,21.70 L690.0,23.80 L695.0,25.95 L700.0,28.12 L705.0,30.38 L710.0,32.75 L715.0,35.21 L720.0,37.75 L725.0,40.33 L730.0,42.94 L735.0,45.54 L740.0,48.13 L745.0,50.66 L750.0,53.12 L755.0,55.58 L760.0,58.09 L765.0,60.62 L770.0,63.14 L775.0,65.62 L780.0,68.05 L785.0,70.38 L790.0,72.60 L795.0,74.67 L800.0,76.56 L805.0,78.30 L810.0,79.91 L815.0,81.41 L820.0,82.80 L825.0,84.08 L830.0,85.26 L835.0,86.35 L840.0,87.34 L845.0,88.24 L850.0,89.06 L855.0,89.82 L860.0,90.51 L865.0,91.13 L870.0,91.66 L875.0,92.09 L880.0,92.40 L885.0,92.58 L890.0,92.61 L895.0,92.49 L900.0,92.19 L905.0,91.66 L910.0,90.88 L915.0,89.89 L920.0,88.75 L925.0,87.50 L930.0,86.19 L935.0,84.86 L940.0,83.56 L945.0,82.34 L950.0,81.25 L955.0,80.19 L960.0,79.07 L965.0,77.93 L970.0,76.79 L975.0,75.68 L980.0,74.65 L985.0,73.72 L990.0,72.92 L995.0,72.30 L1000.0,71.88"/><path class="walk-line walk-knee-crouch" data-rule="crouch" d="M0.0,68.75 L5.0,68.50 L10.0,68.12 L15.0,67.65 L20.0,67.10 L25.0,66.50 L30.0,65.87 L35.0,65.23 L40.0,64.61 L45.0,64.03 L50.0,63.50 L55.0,62.99 L60.0,62.46 L65.0,61.91 L70.0,61.36 L75.0,60.83 L80.0,60.33 L85.0,59.89 L90.0,59.50 L95.0,59.20 L100.0,59.00 L105.0,58.90 L110.0,58.88 L115.0,58.94 L120.0,59.06 L125.0,59.23 L130.0,59.45 L135.0,59.70 L140.0,59.96 L145.0,60.23 L150.0,60.50 L155.0,60.79 L160.0,61.12 L165.0,61.48 L170.0,61.87 L175.0,62.28 L180.0,62.70 L185.0,63.11 L190.0,63.51 L195.0,63.90 L200.0,64.25 L205.0,64.59 L210.0,64.92 L215.0,65.25 L220.0,65.58 L225.0,65.89 L230.0,66.19 L235.0,66.48 L240.0,66.76 L245.0,67.01 L250.0,67.25 L255.0,67.46 L260.0,67.66 L265.0,67.83 L270.0,67.99 L275.0,68.14 L280.0,68.28 L285.0,68.40 L290.0,68.52 L295.0,68.64 L300.0,68.75 L305.0,68.87 L310.0,68.98 L315.0,69.10 L320.0,69.21 L325.0,69.31 L330.0,69.40 L335.0,69.46 L340.0,69.51 L345.0,69.52 L350.0,69.50 L355.0,69.44 L360.0,69.36 L365.0,69.24 L370.0,69.10 L375.0,68.94 L380.0,68.76 L385.0,68.58 L390.0,68.38 L395.0,68.19 L400.0,68.00 L405.0,67.82 L410.0,67.66 L415.0,67.50 L420.0,67.33 L425.0,67.16 L430.0,66.96 L435.0,66.72 L440.0,66.45 L445.0,66.13 L450.0,65.75 L455.0,65.33 L460.0,64.87 L465.0,64.39 L470.0,63.87 L475.0,63.31 L480.0,62.71 L485.0,62.05 L490.0,61.35 L495.0,60.58 L500.0,59.75 L505.0,58.84 L510.0,57.85 L515.0,56.78 L520.0,55.66 L525.0,54.50 L530.0,53.31 L535.0,52.10 L540.0,50.88 L545.0,49.68 L550.0,48.50 L555.0,47.32 L560.0,46.10 L565.0,44.86 L570.0,43.61 L575.0,42.36 L580.0,41.12 L585.0,39.90 L590.0,38.72 L595.0,37.58 L600.0,36.50 L605.0,35.45 L610.0,34.40 L615.0,33.37 L620.0,32.36 L625.0,31.39 L630.0,30.47 L635.0,29.61 L640.0,28.82 L645.0,28.11 L650.0,27.50 L655.0,26.97 L660.0,26.52 L665.0,26.14 L670.0,25.83 L675.0,25.58 L680.0,25.39 L685.0,25.27 L690.0,25.21 L695.0,25.20 L700.0,25.25 L705.0,25.36 L710.0,25.53 L715.0,25.77 L720.0,26.07 L725.0,26.42 L730.0,26.83 L735.0,27.30 L740.0,27.82 L745.0,28.39 L750.0,29.00 L755.0,29.67 L760.0,30.42 L765.0,31.22 L770.0,32.08 L775.0,32.98 L780.0,33.93 L785.0,34.91 L790.0,35.92 L795.0,36.95 L800.0,38.00 L805.0,39.08 L810.0,40.22 L815.0,41.40 L820.0,42.62 L825.0,43.86 L830.0,45.11 L835.0,46.36 L840.0,47.60 L845.0,48.82 L850.0,50.00 L855.0,51.18 L860.0,52.38 L865.0,53.60 L870.0,54.81 L875.0,56.00 L880.0,57.16 L885.0,58.28 L890.0,59.35 L895.0,60.34 L900.0,61.25 L905.0,62.08 L910.0,62.86 L915.0,63.58 L920.0,64.24 L925.0,64.86 L930.0,65.43 L935.0,65.95 L940.0,66.42 L945.0,66.86 L950.0,67.25 L955.0,67.61 L960.0,67.95 L965.0,68.24 L970.0,68.50 L975.0,68.70 L980.0,68.85 L985.0,68.94 L990.0,68.95 L995.0,68.89 L1000.0,68.75"/><path class="walk-line walk-knee-winter" data-rule="winter" d="M0.0,93.75 L5.0,93.33 L10.0,92.70 L15.0,91.91 L20.0,91.00 L25.0,90.00 L30.0,88.95 L35.0,87.89 L40.0,86.85 L45.0,85.88 L50.0,85.00 L55.0,84.15 L60.0,83.26 L65.0,82.34 L70.0,81.43 L75.0,80.55 L80.0,79.72 L85.0,78.98 L90.0,78.34 L95.0,77.84 L100.0,77.50 L105.0,77.33 L110.0,77.30 L115.0,77.40 L120.0,77.60 L125.0,77.89 L130.0,78.25 L135.0,78.66 L140.0,79.10 L145.0,79.55 L150.0,80.00 L155.0,80.48 L160.0,81.03 L165.0,81.64 L170.0,82.29 L175.0,82.97 L180.0,83.66 L185.0,84.35 L190.0,85.02 L195.0,85.66 L200.0,86.25 L205.0,86.81 L210.0,87.37 L215.0,87.92 L220.0,88.46 L225.0,88.98 L230.0,89.49 L235.0,89.97 L240.0,90.43 L245.0,90.86 L250.0,91.25 L255.0,91.61 L260.0,91.93 L265.0,92.22 L270.0,92.49 L275.0,92.73 L280.0,92.96 L285.0,93.17 L290.0,93.37 L295.0,93.56 L300.0,93.75 L305.0,93.94 L310.0,94.14 L315.0,94.33 L320.0,94.52 L325.0,94.69 L330.0,94.83 L335.0,94.94 L340.0,95.01 L345.0,95.03 L350.0,95.00 L355.0,94.91 L360.0,94.76 L365.0,94.56 L370.0,94.33 L375.0,94.06 L380.0,93.77 L385.0,93.46 L390.0,93.14 L395.0,92.82 L400.0,92.50 L405.0,92.20 L410.0,91.93 L415.0,91.66 L420.0,91.39 L425.0,91.09 L430.0,90.76 L435.0,90.37 L440.0,89.92 L445.0,89.38 L450.0,88.75 L455.0,88.04 L460.0,87.29 L465.0,86.48 L470.0,85.62 L475.0,84.69 L480.0,83.68 L485.0,82.59 L490.0,81.41 L495.0,80.13 L500.0,78.75 L505.0,77.23 L510.0,75.58 L515.0,73.81 L520.0,71.94 L525.0,70.00 L530.0,68.01 L535.0,65.99 L540.0,63.97 L545.0,61.96 L550.0,60.00 L555.0,58.03 L560.0,56.00 L565.0,53.93 L570.0,51.85 L575.0,49.77 L580.0,47.70 L585.0,45.67 L590.0,43.70 L595.0,41.80 L600.0,40.00 L605.0,38.25 L610.0,36.50 L615.0,34.78 L620.0,33.10 L625.0,31.48 L630.0,29.95 L635.0,28.52 L640.0,27.20 L645.0,26.02 L650.0,25.00 L655.0,24.12 L660.0,23.37 L665.0,22.73 L670.0,22.21 L675.0,21.80 L680.0,21.49 L685.0,21.29 L690.0,21.18 L695.0,21.17 L700.0,21.25 L705.0,21.43 L710.0,21.72 L715.0,22.11 L720.0,22.61 L725.0,23.20 L730.0,23.89 L735.0,24.67 L740.0,25.53 L745.0,26.48 L750.0,27.50 L755.0,28.62 L760.0,29.86 L765.0,31.20 L770.0,32.63 L775.0,34.14 L780.0,35.72 L785.0,37.36 L790.0,39.04 L795.0,40.76 L800.0,42.50 L805.0,44.30 L810.0,46.20 L815.0,48.17 L820.0,50.20 L825.0,52.27 L830.0,54.35 L835.0,56.43 L840.0,58.50 L845.0,60.53 L850.0,62.50 L855.0,64.47 L860.0,66.47 L865.0,68.49 L870.0,70.51 L875.0,72.50 L880.0,74.44 L885.0,76.31 L890.0,78.08 L895.0,79.73 L900.0,81.25 L905.0,82.64 L910.0,83.93 L915.0,85.13 L920.0,86.24 L925.0,87.27 L930.0,88.21 L935.0,89.08 L940.0,89.87 L945.0,90.59 L950.0,91.25 L955.0,91.85 L960.0,92.41 L965.0,92.91 L970.0,93.33 L975.0,93.67 L980.0,93.92 L985.0,94.06 L990.0,94.09 L995.0,93.99 L1000.0,93.75"/></svg><p class="walk-chart-range">0° to 80°</p></div><i class="walk-cursor" aria-hidden="true"></i></div><input class="walk-scrub" type="range" min="0" max="999" value="0" step="1" aria-label="Where in the stride" disabled /><div class="walk-row"><button type="button" class="dg-button" data-play aria-pressed="false" disabled>Play</button><button type="button" class="dg-button" data-step="-1" disabled aria-label="One pose back">◀</button><button type="button" class="dg-button" data-step="1" disabled aria-label="One pose on">▶</button><button type="button" class="dg-button" data-slow aria-pressed="false" disabled>Quarter speed</button></div></div></div><figcaption>The same stride under four knee rules, walked on the spot so they can be compared pose for pose. The hip follows Winter in all four; only the knee changes. The first three are the mistakes of the rounds before the laboratory's numbers came in.</figcaption></figure>
<p>Winter's curves show what our straight legs lacked. The knee gives 15 to 20 degrees as the heel lands and takes the weight. It bends to about 63 in swing while the hip carries the thigh forward, which the crouching version got wrong by bending it everywhere and the kicking version by bending it too early. The foot stays on the ground for 60% of the stride, and the ankle pushes off before it leaves.</p>
<p>The arms came from published studies of arm swing. An arm is furthest back as the heel on its own side strikes, swings 18 degrees each way and runs 5% of the stride behind the leg, while the elbow opens and closes between 14 and 40 degrees.</p>
<p>With the curves in, the legs alone lift and lower the hip by 2.3% of the height, inside the 1.5 to 2.7% measured in people. The bounce added in the first round doubled that, so we took it out.</p>
<figure class="walk walk-rise" data-walk="rise"><div class="walk-stage walk-stage-rise"><canvas class="walk-canvas" width="288" height="104" role="img" aria-label="A skeleton walking on the spot beside a trace of its hip&#x27;s height over the last strides."></canvas></div><div class="walk-row"><button type="button" class="dg-button" data-play aria-pressed="false" disabled>Play</button><button type="button" class="dg-button" data-bob aria-pressed="false" disabled>Add the bounce of round two</button></div><div class="walk-readout" aria-live="polite"><p class="walk-now"><output class="walk-pct" data-out="rise">2.3%</output><span class="walk-now-names"><span>of the height, low to high</span><span data-out="verdict">inside the 1.5 to 2.7% measured in walkers</span></span></p></div><figcaption>The hip's height, traced as the legs alone set it. The band is the range measured in people walking at an ordinary pace. The bounce added in round two, before the legs bent properly, doubled the rise and fall and took the walk out of the band.</figcaption></figure>
<h2 id="into-the-launcher" class="log-sec log-sec-4"><a class="log-anchor" href="#into-the-launcher" aria-label="Link to this section"></a><span class="log-sec-n">04</span>Into the launcher</h2>
<p><code>people.rs</code> does in Rust what the prototype did in Python, and draws each person and pose once and keeps it. The pelvis turns a little with the swinging leg and the chest against it, and the front and back views use the same curves end on, with a pixel of sway over the loaded leg. The new people shipped in <a href="https://github.com/stefanomainardi/omacrt/releases/tag/v0.11.1">OmaCRT 0.11.1</a>.</p>
<figure class="manual-figure manual-figure-video fig-wide"><video controls playsinline loop preload="none" poster="/assets/media/log/how-people-walk-in-the-hall/hall.webp" width="960" height="720" aria-label="The hall at 60 frames a second, people walking in and out of the aisle."><source src="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/hall.webm" type="video/webm" /><source src="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/hall.mp4" type="video/mp4" /></video><figcaption><span class="fig-kind">Launcher framebuffer</span>The hall at 60 frames a second, people walking in and out of the aisle.</figcaption></figure>
<p>The sprites were first drawn at 50 pixels and enlarged by a third, so one pixel in three came out doubled. Drawn at 68, the height the hall stands them, they scale by less than a tenth.</p>
<p>The janitor was the last one. He first appeared with the mop already in his hands, turned to face the camera, and mopped in slow motion. Now he carries the mop in, rests it, and mops standing, with a half step every second and a half and a sweep that moves his shoulders.</p>
<figure class="manual-figure manual-figure-video"><video controls playsinline loop preload="none" poster="/assets/media/log/how-people-walk-in-the-hall/janitor.webp" width="640" height="480" aria-label="The janitor carries the mop in, mops and takes it away."><source src="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/janitor.webm" type="video/webm" /><source src="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/janitor.mp4" type="video/mp4" /></video><figcaption><span class="fig-kind">Rendered from the launcher</span>The janitor carries the mop in, mops and takes it away.</figcaption></figure>
<h2 id="the-cast-and-the-plates" class="log-sec log-sec-1"><a class="log-anchor" href="#the-cast-and-the-plates" aria-label="Link to this section"></a><span class="log-sec-n">05</span>The cast and the plates</h2>
<p>Everybody in the hall is on one sheet, and so is the joint table. Both are yours to use, as are the two plates below them, drawn from the same sprites and numbers in the launcher's 8x8 font. A link back is welcome.</p>
<figure class="walk walk-cast" data-walk="cast"><div class="walk-cast-grid"><div class="walk-stage walk-stage-cast"><canvas class="walk-canvas" width="96" height="84" role="img" aria-label="A sprite from the hall, playing through its frames."></canvas></div><div class="walk-cast-side"><div class="walk-row" role="group" aria-label="Whose sprite"><button type="button" class="dg-button" data-look="denim" aria-pressed="true" disabled>Denim</button><button type="button" class="dg-button" data-look="mullet" aria-pressed="false" disabled>Mullet</button><button type="button" class="dg-button" data-look="janitor" aria-pressed="false" disabled>Janitor</button></div><div class="walk-row walk-acts" role="group" aria-label="What they are doing"><button type="button" class="dg-button" data-act="side-walk" aria-pressed="true" disabled>Walk</button><button type="button" class="dg-button" data-act="front-walk" aria-pressed="false" disabled>Toward</button><button type="button" class="dg-button" data-act="back-walk" aria-pressed="false" disabled>Away</button><button type="button" class="dg-button" data-act="playside" aria-pressed="false" disabled>Play</button><button type="button" class="dg-button" data-act="play" aria-pressed="false" disabled>Play, from behind</button><button type="button" class="dg-button" data-act="cheer" aria-pressed="false" disabled>Win</button><button type="button" class="dg-button" data-act="over" aria-pressed="false" disabled>Lose</button><button type="button" class="dg-button" data-act="carry" aria-pressed="false" disabled>Carry</button><button type="button" class="dg-button" data-act="mop" aria-pressed="false" disabled>Mop</button></div><div class="walk-strip"><canvas class="walk-canvas walk-strip-canvas" width="992" height="76" role="img" aria-label="Every frame of the chosen sprite, in order."></canvas></div><p class="walk-note">The sheet: <a href="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/people.png" download>people.png</a>, every sprite at 62 by 76 with the feet at 31, 72. The joint table: <a href="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/gait.csv" download>gait.csv</a>, hip, knee and ankle every 5% of the stride.</p></div></div><figcaption>Everybody in the hall, as the launcher draws them. Choose a person and what they are doing; the strip underneath is every frame, and a press on one holds it.</figcaption></figure>
<figure class="manual-figure fig-wide fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/plate-walk.png"><img src="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/plate-walk.png" alt="Plate I: one stride in eight keys, with the stance and swing bar and the joint curves." width="864" height="1106" data-px="432x553" loading="eager" decoding="async" /></a><figcaption>Plate I: one stride in eight keys, with the stance and swing bar and the joint curves.</figcaption></figure>
<figure class="manual-figure fig-wide fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/plate-knee.png"><img src="https://omacrt.com/assets/media/log/how-people-walk-in-the-hall/plate-knee.png" alt="Plate II: three wrong knees and the right one, at four moments of the stride." width="864" height="1164" data-px="432x582" loading="eager" decoding="async" /></a><figcaption>Plate II: three wrong knees and the right one, at four moments of the stride.</figcaption></figure>
<p>Sources: David A. Winter, <em>The Biomechanics and Motor Control of Human Gait</em> (1991); Richard Williams, <em>The Animator's Survival Kit</em>; <a href="https://www.slynyrd.com/blog/2024/5/24/pixelblog-50-human-walk-cycle">Pixelblog 50, human walk cycle</a> by SLYNYRD; the sprite sheets of <a href="https://www.spriters-resource.com/ms_dos/secretofmonkeyisland/asset/26971/">The Secret of Monkey Island</a> and <a href="https://www.spriters-resource.com/ms_dos/indy4/asset/24189/">Fate of Atlantis</a> on The Spriters Resource, which the agent measured; no frame of them appears here.<span class="log-end" aria-hidden="true"></span></p>]]></content:encoded>
    </item>
    <item>
      <title>An arcade hall, drawn with an agent</title>
      <link>https://omacrt.com/log/an-arcade-hall-drawn-with-an-agent/</link>
      <guid isPermaLink="true">https://omacrt.com/log/an-arcade-hall-drawn-with-an-agent/</guid>
      <pubDate>Tue, 29 Sep 2026 00:00:00 +0000</pubDate>
      <category>design</category>
      <description>Between a Monday evening and Tuesday afternoon, Claude Opus 5.5 redrew OmaCRT&#x27;s pixel art from prompts a line or two long. This entry shows the design canvases it proposed, the attempts I turned down, and the Rust that stayed.</description>
      <content:encoded><![CDATA[<figure class="manual-figure fig-wide fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/hall.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/hall.png" alt="The home screen today: an aisle between two rows of lit arcade cabinets, the OmaCRT sign in neon on the far wall, a man walking in, and the menu in a window of dark glass." width="960" height="720" data-px="320x240" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>The home screen today: an aisle between two rows of lit arcade cabinets, the OmaCRT sign in neon on the far wall, a man walking in, and the menu in a window of dark glass.</figcaption></figure>
<p>On Monday evening I typed this into Claude Code:</p>
<blockquote>
<p>OK, now I want us to bring out Opus 5.5's artistic talents and improve OmaCRT's pixel art overall. I want it to be fantastic.</p>
</blockquote>
<p>I wrote every prompt in Italian, and they are translated here. By Tuesday afternoon the launcher had an arcade hall for a home screen, twelve consoles built as 3D models, a new sky over Brussels and a small gallery of René Magritte's paintings that move.</p>
<h2 id="how-a-language-model-draws" class="log-sec log-sec-1"><a class="log-anchor" href="#how-a-language-model-draws" aria-label="Link to this section"></a><span class="log-sec-n">01</span>How a language model draws</h2>
<p>OmaCRT's launcher draws every frame itself, into a buffer of 320 by 240 pixels that goes to the television as it is. There are no image files for the interface. A cabinet, a cloud or a console is a Rust function that decides the colour of each pixel, sixty times a second.</p>
<p>Opus 5.5 draws by writing code, and it can read an image back. Articles written since its release on 22 September describe the same method, <a href="https://ciyo.ai/blog/can-claude-opus-5-5-generate-images">code that places every pixel</a>. In this project every proposal went through four steps:</p>
<ol class="prose-list">
<li>A mockup in Python on a design canvas, drawn with the launcher's own font, icons and colour themes, which the agent exported from the code before it started.</li>
<li>My answer on the canvas, usually one line.</li>
<li>The same idea in Rust, inside the launcher.</li>
<li>A headless render of the launcher, frame by frame, which the agent read as an image and criticised before I saw it.</li>
</ol>
<p>The agent cannot see my television, and the preview window on my desktop does not run at full speed. So it rendered the screen it was working on at the second it cared about, looked at the frame and fixed what it saw. Most of its corrections were made that way, before I had seen anything.</p>
<h2 id="three-directions-and-an-honest-answer" class="log-sec log-sec-2"><a class="log-anchor" href="#three-directions-and-an-honest-answer" aria-label="Link to this section"></a><span class="log-sec-n">02</span>Three directions and an honest answer</h2>
<p>The first canvas put the same four screens side by side at real size: the launcher as it was, and three directions.</p>
<div class="figure-row fig-grid fig-grid-4 fig-wide"><figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-today.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-today.png" alt="Today: the home screen as the launcher drew it on Monday." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>Today: the home screen as the launcher drew it on Monday.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-a.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-a.png" alt="A: every screen in full windows, after the role playing games on the Super Famicom." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>A: every screen in full windows, after the role playing games on the Super Famicom.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-b.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-b.png" alt="B: the same layout with a raised selection and a light that sweeps across it." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>B: the same layout with a raised selection and a light that sweeps across it.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-c.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-c.png" alt="C: a heading bar and a denser list." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>C: a heading bar and a denser list.</figcaption></figure></div>
<p>I liked B and asked whether we would really gain anything compared with the current version. The agent answered:</p>
<blockquote>
<p>Honestly, not much. B is today's layout, refined. Someone on the sofa sees the same launcher, a bit more polished. They don't see "fantastic".</p>
</blockquote>
<p>It argued that almost every pixel on those screens is text and icons, so a new frame around them changes little. The next board redrew the font glyph by glyph, in a thin cut with a drop shadow and a heavy one. It also redrew the icons so that each has one meaning: a floppy disk for saving, an hourglass for slow motion, a house for home.</p>
<div class="figure-row fig-grid fig-grid-2 fig-wide"><figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-font.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-font.png" alt="The font as it was, a thin cut with a shadow and a heavy cut, set on the same sample lines." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>The font as it was, a thin cut with a shadow and a heavy cut, set on the same sample lines.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-icons.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-icons.png" alt="The icons as they were, and the new set." width="640" height="240" data-px="320x120" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>The icons as they were, and the new set.</figcaption></figure></div>
<h2 id="still-too-timid" class="log-sec log-sec-3"><a class="log-anchor" href="#still-too-timid" aria-label="Link to this section"></a><span class="log-sec-n">03</span>Still too timid</h2>
<p>I thought we were still being timid, and I sent the agent <a href="https://github.com/Odiriuss/PixelArtGameOpus/blob/main/prompts/ravenshore_garden_prompt.txt">a prompt somebody had published</a> for a pixel art game made with the same model. The agent applied that prompt's rules: colour ramps whose shadows drift toward purple and whose highlights warm up, a rim of light on every edge, grain in the darks, and animation at ten frames a second.</p>
<p>The next board was a sunset over a bay, with a sun sliced into scanlines and mountains with a rim of light. I asked where the bay had come from.</p>
<blockquote>
<p>Honestly, nothing of yours. It works as a demonstration of technique, but it's an arbitrary landscape. It says nothing about OmaCRT or about you.</p>
</blockquote>
<p>Staying with retro games, it offered a Mode 7 flight after F-Zero and Pilotwings, or an Amiga demo with copper bars. I picked the flight. The curving track made me seasick, so it straightened the road and slowed it down. Then it looked like a car game, and the agent took the road away and kept only the floor. After that it wrote:</p>
<blockquote>
<p>When an idea hasn't convinced you after three attempts, it's usually the wrong idea.</p>
</blockquote>
<div class="figure-row fig-grid fig-grid-3 fig-wide"><figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-bay.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-bay.png" alt="The bay at sunset." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>The bay at sunset.</figcaption></figure> <figure class="manual-figure manual-figure-video"><video controls playsinline loop preload="none" poster="/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-road.webp" width="640" height="480" aria-label="The Mode 7 track, turning."><source src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-road.webm" type="video/webm" /><source src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-road.mp4" type="video/mp4" /></video><figcaption><span class="fig-kind">Design canvas</span>The Mode 7 track, turning.</figcaption></figure> <figure class="manual-figure manual-figure-video"><video controls playsinline loop preload="none" poster="/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-straight.webp" width="640" height="480" aria-label="The road straightened and slowed down, with a blade of light every two seconds."><source src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-straight.webm" type="video/webm" /><source src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c1-straight.mp4" type="video/mp4" /></video><figcaption><span class="fig-kind">Design canvas</span>The road straightened and slowed down, with a blade of light every two seconds.</figcaption></figure></div>
<p>We went ahead with B. Before a line of it went into the launcher, I asked for a way back. The agent made a branch, a tag called <code>pre-pixel-art</code> and a copy of the installed binaries.</p>
<h2 id="the-consoles-four-times" class="log-sec log-sec-4"><a class="log-anchor" href="#the-consoles-four-times" aria-label="Link to this section"></a><span class="log-sec-n">04</span>The consoles, four times</h2>
<p>The systems list shows the selected console on a lit stage. The first round was drawn from memory, and its Nintendo 64 looked like nothing Nintendo ever made. The second was traced from <a href="https://commons.wikimedia.org/wiki/User:Evan-Amos">Evan Amos's photographs</a> at three quarters, and at a hundred pixels wide a traced photograph comes out dirty and crooked. The third stood the consoles up straight over the reference photographs, and the Saturn and the Super Nintendo still did not look like themselves. Then I wrote:</p>
<blockquote>
<p>The Saturn doesn't look faithful, nor the SNES. I liked the isometric attempt. Think for a moment about how to do them really well, maybe animate them a little when they change. Let's do something properly that nobody has done.</p>
</blockquote>
<div class="figure-row fig-grid fig-grid-4 fig-full fig-grid-small"><figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/n64-sp.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/n64-sp.png" alt="Round one, drawn from memory." width="328" height="196" data-px="164x98" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>Round one, drawn from memory.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/n64-sp2.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/n64-sp2.png" alt="Round two, traced from a photograph." width="328" height="196" data-px="164x98" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>Round two, traced from a photograph.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/n64-sp3.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/n64-sp3.png" alt="Round three, drawn straight over the reference." width="328" height="196" data-px="164x98" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>Round three, drawn straight over the reference.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/n64-vx2.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/n64-vx2.png" alt="Round four, a voxel model built from the real dimensions." width="328" height="196" data-px="164x98" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>Round four, a voxel model built from the real dimensions.</figcaption></figure></div>
<p>Twelve minutes later a Super Nintendo and a Saturn were on the canvas and on the television as voxel models. Instead of drawing the consoles, the agent had built them and written a renderer for them.</p>
<p>Each model is a grid of cells, one for every 3.5 millimetres of the real machine. The Super Nintendo is 200 by 242 by 72 millimetres. Boxes, rounded boxes, cylinders and slots are added to the grid or cut out of it. The renderer sends one ray per pixel through the grid and stops at the first full cell, which gives hard faces and the right occlusion at any angle. Each face takes a tone from its material's ramp according to the stage's spotlight, with a 4x4 ordered dither between the tones, a dark outline and a highlight along the top edges.</p>
<p>The landing lasts 0.7 seconds. The console drops onto the stage, bounces once and turns a full circle. The turn still to go shrinks with the cube of the time left, so the spin slows into the three-quarter view.</p>
<p>A model costs under a millisecond a frame while it moves. At rest the picture is kept and costs nothing. By Tuesday morning there were twelve machines, from the NES to a GameCube and a beige PC for ScummVM.</p>
<figure class="manual-figure manual-figure-video fig-wide"><video controls playsinline loop preload="none" poster="/assets/media/log/an-arcade-hall-drawn-with-an-agent/consoles.webp" width="960" height="720" aria-label="Eight consoles landing one after another as the cursor walks down the systems list, at 60 frames a second with the launcher&#x27;s sounds."><source src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/consoles.webm" type="video/webm" /><source src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/consoles.mp4" type="video/mp4" /></video><figcaption><span class="fig-kind">Rendered from the launcher</span>Eight consoles landing one after another as the cursor walks down the systems list, at 60 frames a second with the launcher's sounds.</figcaption></figure>
<p>Starting a game plays the matching gesture. A cartridge goes into its slot, a coin drops into the arcade cabinet, and the disc goes into the PlayStation. When I said its lid did not open, the next build hinged the lid at the back, printed the game's cover on the disc and lit the power light as it locked in.</p>
<h2 id="an-arcade-hall" class="log-sec log-sec-1"><a class="log-anchor" href="#an-arcade-hall" aria-label="Link to this section"></a><span class="log-sec-n">05</span>An arcade hall</h2>
<p>For the home screen I asked for an eighties arcade hall, sketched on the canvas first. The agent drew two ideas: the menu on the screen of one OmaCRT cabinet, and a corridor of cabinets in perspective with a neon sign at the end. I chose the corridor, because it reads as a place, and asked for it to be drawn better and joined to the power-on animation.</p>
<div class="figure-row fig-grid fig-grid-3 fig-wide"><figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-cabinet.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-cabinet.png" alt="A: the menu on the screen of a single cabinet." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>A: the menu on the screen of a single cabinet.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-corridor.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-corridor.png" alt="B: the corridor, first round." width="640" height="480" data-px="320x240" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>B: the corridor, first round.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-corridor-2.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-corridor-2.png" alt="B: the corridor, second round." width="640" height="480" data-px="320x240" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>B: the corridor, second round.</figcaption></figure></div>
<p>The next board replaced my boot sequence with a new one that ended in the hall. I wrote back that I had not wanted to lose the opening animation, with its floor, its laser and its logo.</p>
<div class="figure-row fig-grid fig-grid-3 fig-wide"><figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-poweron-oma.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-poweron-oma.png" alt="The proposed new boot, with the word writing itself in the dark." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>The proposed new boot, with the word writing itself in the dark.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-poweron-lampo.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-poweron-lampo.png" alt="The tubes flash." width="640" height="480" data-px="320x240" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>The tubes flash.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-poweron-cabinati.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-poweron-cabinati.png" alt="The cabinets light." width="640" height="480" data-px="320x240" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>The cabinets light.</figcaption></figure></div>
<blockquote>
<p>You're right, I misunderstood: it shouldn't replace today's animation, it should connect to the hall.</p>
</blockquote>
<p>The next board kept the boot whole and added a bridge after it. The laser that ends the boot climbs the screen and the hall stands up behind it. The wordmark flies to the far wall and becomes the sign, and the cabinets come on in pairs.</p>
<div class="figure-row fig-grid fig-grid-3 fig-wide"><figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-bridge-laser.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-bridge-laser.png" alt="The boot&#x27;s laser climbing." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>The boot's laser climbing.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-bridge-volo.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-bridge-volo.png" alt="The wordmark flying to the far wall." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>The wordmark flying to the far wall.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-bridge-insegna.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-bridge-insegna.png" alt="The sign striking." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>The sign striking.</figcaption></figure></div>
<p>In the launcher the hall is a small 3D scene. Every wall, cabinet side, screen and sign is a flat patch in space, projected through a fixed camera. A depth buffer keeps the nearest patch for each pixel. The static layer is drawn once for each state of the lights and kept, and only the cabinets' attract modes and the marquees are drawn every frame. The whole bridge costs 3.8 milliseconds a frame, only while the hall is lighting up.</p>
<figure class="manual-figure manual-figure-video fig-wide"><video controls playsinline loop preload="none" poster="/assets/media/log/an-arcade-hall-drawn-with-an-agent/boot-hall.webp" width="960" height="720" aria-label="The boot&#x27;s laser climbing into the arcade hall in the launcher, the sign striking and the cabinets lighting in pairs."><source src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/boot-hall.webm" type="video/webm" /><source src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/boot-hall.mp4" type="video/mp4" /></video><figcaption><span class="fig-kind">Rendered from the launcher</span>The boot's laser climbing into the arcade hall in the launcher, the sign striking and the cabinets lighting in pairs.</figcaption></figure>
<h2 id="people-who-come-in-to-play" class="log-sec log-sec-2"><a class="log-anchor" href="#people-who-come-in-to-play" aria-label="Link to this section"></a><span class="log-sec-n">06</span>People who come in to play</h2>
<p>I asked for somebody to walk in after a while and start playing, and maybe a jukebox. The next board had three characters and a Wurlitzer by the wall. I said it was too much, and the jukebox went.</p>
<figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-jukebox.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-jukebox.png" alt="The living hall on the canvas, with the jukebox that was dropped." width="640" height="480" data-px="320x240" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>The living hall on the canvas, with the jukebox that was dropped.</figcaption></figure>
<p>The people were first drawn as sprites that slid. The agent rebuilt them as skeletons of capsules with hips and knees, and drove the walk cycle by the distance covered, so a foot on the carpet stays where it was put. There are four scenes now, one at a time: somebody loses to a GAME OVER, two people play side by side, somebody makes a record and jumps, and a janitor mops the aisle and leaves a wet trail behind him. A day later they were redrawn as sprites and walked from a gait laboratory's numbers, which <a href="https://omacrt.com/log/how-people-walk-in-the-hall/">How people walk in the hall</a> tells.</p>
<div class="figure-row fig-grid fig-grid-3 fig-full fig-grid-small"><figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-gameover.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-gameover.png" alt="GAME OVER." width="480" height="330" data-px="160x110" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>GAME OVER.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-record.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-record.png" alt="A new record." width="480" height="330" data-px="160x110" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>A new record.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-straccio.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c2-straccio.png" alt="The janitor." width="480" height="330" data-px="160x110" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>The janitor.</figcaption></figure></div>
<p>I told the agent that the people passed in front of and behind things in ways that made no sense. It found three causes. They appeared inside cabinets, they cut corners diagonally, and they walked between a player and his screen. The agent rebuilt the hall around them, with two doors at the far end and a lit corridor behind. The rows of cabinets moved forward to leave a gap, and everybody walks on paths laid over the open floor. Each figure is tested against the hall's depth, so a cabinet in front of somebody hides him.</p>
<h2 id="small-works-of-art" class="log-sec log-sec-3"><a class="log-anchor" href="#small-works-of-art" aria-label="Link to this section"></a><span class="log-sec-n">07</span>Small works of art</h2>
<p>The clock and weather page already drew the real sky over a small town. I asked for it redrawn "as little works of art in pixels", now that the agent knew how.</p>
<div class="figure-row fig-grid fig-grid-2 fig-wide"><figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/weather-before.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/weather-before.png" alt="The page at half past eleven at night before the redraw." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>The page at half past eleven at night before the redraw.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/weather-after.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/weather-after.png" alt="The same hour and weather after it." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>The same hour and weather after it.</figcaption></figure></div>
<p>Each hour of the day has its own palette of sky, sun and shadow, and the weather greys it. A cloud is a cluster of balls, and every pixel of a ball is shaded by that ball's own normal in five dithered tones, which gives it volume. A far city stands in the haze behind the near one. The digits of the clock have a bevel, lit on the top and left edges of each stroke.</p>
<div class="figure-row fig-grid fig-grid-4 fig-wide"><figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/sky-dawn.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/sky-dawn.png" alt="Dawn, partly cloudy." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>Dawn, partly cloudy.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/sky-day.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/sky-day.png" alt="An afternoon with some cloud." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>An afternoon with some cloud.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/sky-rain.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/sky-rain.png" alt="Light rain at twenty to five." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>Light rain at twenty to five.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/sky-storm.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/sky-storm.png" alt="A thunderstorm at three in the morning." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>A thunderstorm at three in the morning.</figcaption></figure></div>
<p>The skyline has one local detail, because I live in Brussels: the Atomium stands on the horizon when the weather is for Brussels. I did not like the first one. My whole prompt was "Draw the Atomium properly, I don't like how you're doing it." The agent went back to the building. The Atomium is a cube standing on one corner, with spheres at the corners and the centre joined by tubes. So the agent rotated a real cube onto its corner, projected it, and tried four turns about the vertical before choosing the silhouette on the postcards. By day the spheres are chrome and reflect the sky.</p>
<figure class="manual-figure fig-wide"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/atomium-angles.webp"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/atomium-angles.webp" alt="The Atomium projected at four turns, from 0 to 0.79 radians. The launcher uses 0.26." width="1320" height="315" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>The Atomium projected at four turns, from 0 to 0.79 radians. The launcher uses 0.26.</figcaption></figure>
<h2 id="three-paintings-that-move" class="log-sec log-sec-4"><a class="log-anchor" href="#three-paintings-that-move" aria-label="Link to this section"></a><span class="log-sec-n">08</span>Three paintings that move</h2>
<p>René Magritte lived and painted in Brussels, and I asked for a few moments after his work on the same page. There are ten. Each one comes up rarely, for twenty five seconds, and only when the weather suits it. It fades in and out through the same ordered dither, so the page never blends two pictures.</p>
<div class="figure-row fig-grid fig-grid-4 fig-wide"><figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/moment-golconda.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/moment-golconda.png" alt="Rain brings the men of <em>Golconde</em> down over the town." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>Rain brings the men of <em>Golconde</em> down over the town.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/moment-empire.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/moment-empire.png" alt="A clear evening can bring <em>L&#x27;empire des lumières</em>." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>A clear evening can bring <em>L'empire des lumières</em>.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/moment-rock.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/moment-rock.png" alt="A clear day can bring the castle on its rock." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>A clear day can bring the castle on its rock.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/moment-dove.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/moment-dove.png" alt="A storm can bring the dove cut out of the daylight sky." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>A storm can bring the dove cut out of the daylight sky.</figcaption></figure></div>
<p>Then I asked for three of his paintings redrawn whole, as homages, and hung in a gallery. On the canvas <em>Le fils de l'homme</em> kept its tall format. I asked for 4:3, with artistic liberties, and for all three to move a little while staying in their mood.</p>
<div class="figure-row fig-grid fig-grid-2 fig-wide"><figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c3-son-portrait.webp"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/c3-son-portrait.webp" alt="The first gallery board, with the painting in its own tall format." width="643" height="482" loading="lazy" decoding="async" /></a><figcaption><span class="fig-kind">Design canvas</span>The first gallery board, with the painting in its own tall format.</figcaption></figure> <figure class="manual-figure fig-pixel"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/gallery-0.png"><img src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/gallery-0.png" alt="The painting in 4:3, as the launcher hangs it." width="640" height="480" data-px="320x240" loading="eager" decoding="async" /></a><figcaption><span class="fig-kind">Launcher framebuffer</span>The painting in 4:3, as the launcher hangs it.</figcaption></figure></div>
<p>The apple in front of the face slides aside to show another apple, and the eye behind it blinks. A bird crosses the daylight sky over the lamplit street, and one window goes dark. In <em>Golconde</em> the men bob in the air, and every so often one of them raises his hat.</p>
<figure class="manual-figure manual-figure-video fig-wide"><video controls playsinline loop preload="none" poster="/assets/media/log/an-arcade-hall-drawn-with-an-agent/gallery.webp" width="960" height="720" aria-label="The gallery: <em>Le fils de l&#x27;homme</em>, <em>L&#x27;empire des lumières</em> and <em>Golconde</em>, nine seconds each."><source src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/gallery.webm" type="video/webm" /><source src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/gallery.mp4" type="video/mp4" /></video><figcaption><span class="fig-kind">Rendered from the launcher</span>The gallery: <em>Le fils de l'homme</em>, <em>L'empire des lumières</em> and <em>Golconde</em>, nine seconds each.</figcaption></figure>
<h2 id="what-the-model-did" class="log-sec log-sec-1"><a class="log-anchor" href="#what-the-model-did" aria-label="Link to this section"></a><span class="log-sec-n">09</span>What the model did</h2>
<p>Anthropic's page for Opus 5.5 quotes a tester who had several models build a game from one prompt, and <a href="https://www.anthropic.com/claude-opus-5-5">Opus 5.5 "scored higher than any other model on the strength of its graphics and polish"</a>. Other people have had it write <a href="https://www.spritefusion.com/blog/opus-5-5-for-pixel-art-scenes">animated pixel art scenes in a single HTML file</a>. Here the job was longer. It had to keep one look across an existing program for a day, on a screen of 76,800 pixels.</p>
<p>It worked in the vocabulary of the period, Super Famicom windows, Mode 7 floors, copper bars, colour ramps and ordered dither, and it could say why one suited a screen and another did not. It found its own references: the consoles' measurements, Evan Amos's photographs, the Atomium's geometry and the paintings. When drawing the consoles failed three times, it changed method and wrote a renderer. Its animation has easing, a bounce, feet that stay planted and a hat raised at the right moment. It checked its frames before I saw them, and it told me when one of its own ideas was weak.</p>
<p>It also got things wrong. It drew consoles from memory, reverted work I had wanted improved, replaced a boot sequence I wanted to keep and put more in the hall than it needed. Each time, one line from me put it right.</p>
<p>Some of the ideas were silly, like a Magritte gallery in a launcher for retro games, or a janitor mopping an empty aisle. I enjoyed asking for those most, and each of them was on the television the same day.</p>
<p>The work took two canvases and fourteen boards, 39 commits after the <code>pre-pixel-art</code> tag and about 9,800 lines of Rust. Nothing in this entry was made with an image generator. Each picture is either a mockup the agent drew with code on a canvas, or the launcher's own framebuffer, rendered offline and enlarged by whole pixels.<span class="log-end" aria-hidden="true"></span></p>
<figure class="manual-figure manual-figure-video fig-wide"><video controls playsinline loop preload="none" poster="/assets/media/log/an-arcade-hall-drawn-with-an-agent/tour.webp" width="960" height="720" aria-label="The whole tour: the boot, the hall and a man walking in, eight consoles, the weather and the three paintings, at 60 frames a second with sound."><source src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/tour.webm" type="video/webm" /><source src="https://omacrt.com/assets/media/log/an-arcade-hall-drawn-with-an-agent/tour.mp4" type="video/mp4" /></video><figcaption><span class="fig-kind">Rendered from the launcher</span>The whole tour: the boot, the hall and a man walking in, eight consoles, the weather and the three paintings, at 60 frames a second with sound.</figcaption></figure>]]></content:encoded>
    </item>
    <item>
      <title>Getting controllers to behave in OmaCRT</title>
      <link>https://omacrt.com/log/getting-controllers-to-behave/</link>
      <guid isPermaLink="true">https://omacrt.com/log/getting-controllers-to-behave/</guid>
      <pubDate>Fri, 18 Sep 2026 00:00:00 +0000</pubDate>
      <category>note</category>
      <description>Bluetooth could report success after a failed pairing step. A controller could confirm on A in a game and B in the launcher. Fixing those problems led to a new pads screen on the television and controller controls in Quickshell.</description>
      <content:encoded><![CDATA[<p>A controller could work in OmaCRT's menus and behave differently once a game opened. On a Sega-style pad, the button marked B confirmed in the launcher while A went back. Bluetooth had a separate problem: the launcher could announce a connection after an earlier pairing step had failed.</p>
<p>In 0.9.0 and 0.10.0, we changed how the launcher connects a pad, remembers it and hands it to RetroArch. The new pads screen brings those changes together on the television.</p>
<figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/getting-controllers-to-behave/ports.png"><img src="https://omacrt.com/assets/media/log/getting-controllers-to-behave/ports.png" alt="An export of the pads interface for the television. Two controllers are connected, a third is remembered but absent, and the fourth port is empty." width="1920" height="1440" loading="lazy" decoding="async" /></a><figcaption>An export of the pads interface for the television. Two controllers are connected, a third is remembered but absent, and the fourth port is empty.</figcaption></figure>
<h2 id="checking-what-bluetooth-actually-did" class="log-sec log-sec-1"><a class="log-anchor" href="#checking-what-bluetooth-actually-did" aria-label="Link to this section"></a><span class="log-sec-n">01</span>Checking what Bluetooth actually did</h2>
<p>Connecting a Bluetooth controller involves three commands: pair, trust and connect. The old code discarded the results of the first two and reported the result of the third. If pairing failed, the screen could give a misleading account of what had happened.</p>
<p>Each step now has its own deadline and its result is checked before the next step starts. A failure names the step and shows the reason Bluetooth returned. The player can retry or cancel from the controller already in hand.</p>
<figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/getting-controllers-to-behave/connecting.png"><img src="https://omacrt.com/assets/media/log/getting-controllers-to-behave/connecting.png" alt="Pairing the M30. The interface shows the trust step in progress and keeps the cancel action available." width="1920" height="1440" loading="lazy" decoding="async" /></a><figcaption>Pairing the M30. The interface shows the trust step in progress and keeps the cancel action available.</figcaption></figure>
<p>The search also needed to keep running when the player left its screen. Previously, progress depended on that screen being drawn. Moving the work out of the drawing path lets the interface stay responsive while discovery or pairing continues.</p>
<p>A failed connection now leaves a specific step and an error to investigate.</p>
<h2 id="a-model-name-does-not-identify-an-individual-pad" class="log-sec log-sec-2"><a class="log-anchor" href="#a-model-name-does-not-identify-an-individual-pad" aria-label="Link to this section"></a><span class="log-sec-n">02</span>A model name does not identify an individual pad</h2>
<p>The names devices report are awkward on a small screen. “8BitDo Ultimate 2C Wireless Controller” does not fit in a twelve-character label. Cutting it after twelve characters leaves “8BitDo Ultim”, which loses the part that identifies the model.</p>
<p>The short name now drops generic words first, then the maker where necessary. The television can show “Ultimate 2C” or “M30”, alongside the connection type. Battery charge appears only when the kernel supplies it.</p>
<p>Remembering the device requires more than a readable name. SDL gives us an identifier shared by controllers of the same model. To distinguish individual units, OmaCRT also reads a Bluetooth address or USB serial from sysfs, the kernel's device information.</p>
<p>Some controllers provide neither. Two identical pads without an individual identifier remain ambiguous, and the interface says so. Assigning them reassuring labels would not give us a reliable way to recognise them next time.</p>
<h2 id="the-letter-on-the-button" class="log-sec log-sec-3"><a class="log-anchor" href="#the-letter-on-the-button" aria-label="Link to this section"></a><span class="log-sec-n">03</span>The letter on the button</h2>
<p>The launcher reads controllers through SDL. RetroArch has its own controller profiles. Their names for buttons do not always match, so a mapping that works in the launcher needs translation before a game can use it.</p>
<p>The Sega-style layout exposed another difference. SDL names face buttons by position. On a Mega Drive or Saturn-style controller, the button in the southern position can be marked B. Treating SDL's A as the printed A made the launcher confirm on B, while RetroArch used the expected letter inside the game.</p>
<p>OmaCRT now reads the button labels from the RetroArch profile for that controller. It swaps the pair when that profile establishes the mismatch. Without a matching profile, it keeps SDL's mapping instead of guessing from the device name.</p>
<p>The existing mapping wizard still handles unknown controllers. That path needed fixes too: a second unknown pad arriving at startup could cancel the first pad's mapping request, and unplugging a controller during mapping could leave the wizard waiting for input from a device that had gone.</p>
<h2 id="giving-the-controllers-somewhere-to-go" class="log-sec log-sec-4"><a class="log-anchor" href="#giving-the-controllers-somewhere-to-go" aria-label="Link to this section"></a><span class="log-sec-n">04</span>Giving the controllers somewhere to go</h2>
<p>The television screen now has four visible sockets. Each shows the controller assigned to it, with different states for a connected pad, a remembered pad that is switched off, and an empty socket.</p>
<p>Switching a pad off should not erase the player's arrangement. A newly discovered controller goes at the end of the saved list. The shoulders move the selected controller through that order, and Identify makes it vibrate, where the controller supports rumble.</p>
<figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/getting-controllers-to-behave/move.png"><img src="https://omacrt.com/assets/media/log/getting-controllers-to-behave/move.png" alt="Moving the M30 between ports with the shoulder buttons. The controller moves across the screen so its destination can be followed." width="1920" height="1440" loading="lazy" decoding="async" /></a><figcaption>Moving the M30 between ports with the shoulder buttons. The controller moves across the screen so its destination can be followed.</figcaption></figure>
<p>The order lives in <code>pads.toml</code>. When a game starts, the connected controllers receive player ports in that order; disconnected controllers are skipped. If the first controller is off and the second is on, the second becomes player one for that launch. The saved order remains intact.</p>
<p>RetroArch fixes those assignments when it starts, so rearranging the list affects the next launch. OmaCRT writes the requested player indices, then reads RetroArch's device enumeration from its log to check the result. If they disagree, the launcher reports the mismatch.</p>
<p>Testing the screen uncovered a more basic inconsistency: its header could count two connected controllers while the sockets showed one. Two copies of the list were overwriting each other. Fixing the display meant fixing how that state was held.</p>
<p>There were smaller input bugs along the way. Unplugging a controller with its stick held could leave the menu scrolling. Controllers present before startup followed a different discovery path and were not being saved. Discovery at startup now records controllers in the saved list too.</p>
<h2 id="managing-them-with-the-television-off" class="log-sec log-sec-1"><a class="log-anchor" href="#managing-them-with-the-television-off" aria-label="Link to this section"></a><span class="log-sec-n">05</span>Managing them with the television off</h2>
<p>Once the television could show and rearrange its controllers, the desktop panel needed access to the same information. The Quickshell panel now lists the pads, their connection state and controls for moving or forgetting them. Its Pads button opens the four-port overlay.</p>
<figure class="manual-figure manual-figure-portrait"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/getting-controllers-to-behave/desktop-panel.jpg"><img src="https://omacrt.com/assets/media/log/getting-controllers-to-behave/desktop-panel.jpg" alt="The Quickshell panel with the television in standby. The pads list remains available, including the remembered M30 that is switched off." width="500" height="969" loading="lazy" decoding="async" /></a><figcaption>The Quickshell panel with the television in standby. The pads list remains available, including the remembered M30 that is switched off.</figcaption></figure>
<p>The overlay has room for details that would crowd the television: the device identifier, RetroArch's index and information about the face-button mapping. It also provides Identify and the ordering controls.</p>
<figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/shots/pads-overlay.webp"><img src="https://omacrt.com/assets/media/shots/pads-overlay.webp" alt="The desktop pads overlay. One controller is connected by cable and another is remembered but switched off." width="1004" height="468" loading="lazy" decoding="async" /></a><figcaption>The desktop pads overlay. One controller is connected by cable and another is remembered but switched off.</figcaption></figure>
<p>The two interfaces must not overwrite each other's changes. While the launcher is running, desktop actions ask it to update the order and save the file. When it is stopped, the command-line tool edits the file directly. This keeps a single writer responsible for the saved order.</p>
<p>There was a deployment problem here too. Updating the plugin files did not replace the QML already loaded in the desktop shell. The installer now restarts the shell to load those changes, or reports that a restart is needed when the session is locked.</p>
<h2 id="what-still-needs-testing" class="log-sec log-sec-2"><a class="log-anchor" href="#what-still-needs-testing" aria-label="Link to this section"></a><span class="log-sec-n">06</span>What still needs testing</h2>
<p>This work has been tested on one machine with its connected controllers. Other models may expose different mappings or omit the identifiers needed to tell identical units apart. Battery reporting and rumble also depend on what the device provides.</p>
<p>The <a href="https://omacrt.com/manual/pads-and-input/">pads chapter in the manual</a> covers the controls and commands. The implementation changes are in <a href="https://github.com/stefanomainardi/omacrt/releases/tag/v0.9.0">0.9.0</a> and <a href="https://github.com/stefanomainardi/omacrt/releases/tag/v0.10.0">0.10.0</a>. A useful next test is another person's controller, particularly one that behaves differently between the launcher and a game.<span class="log-end" aria-hidden="true"></span></p>]]></content:encoded>
    </item>
    <item>
      <title>Flyback: a new Wayland compositor, written to drive a television</title>
      <link>https://omacrt.com/log/flyback/</link>
      <guid isPermaLink="true">https://omacrt.com/log/flyback/</guid>
      <pubDate>Sat, 12 Sep 2026 00:00:00 +0000</pubDate>
      <category>study</category>
      <description>Flyback leases a display connector from Hyprland and drives a television at fifteen kilohertz. This study covers its architecture, frame scheduling and latency measurements, including the limits of variable refresh and four corrections to earlier results.</description>
      <content:encoded><![CDATA[<figure class="log-diagram" id="fig-architecture"><div class="dg signal-architecture" data-diagram><h3 class="dg-headline">From the Linux desktop to the CRT.</h3><p class="dg-deck">Hyprland lends one display output to Flyback. Flyback controls that connector and its CRTC; the desktop keeps the other outputs.</p><div class="signal-lease"><strong>The display lease</strong><span>Hyprland → Flyback</span><code>wp_drm_lease_v1</code></div><ol class="signal-path"><li class="signal-stage signal-desktop"><h4>Desktop</h4><div class="signal-link" aria-hidden="true"><span></span></div><p>Hyprland manages the other outputs.</p></li><li class="signal-stage signal-flyback"><h4>Flyback</h4><div class="signal-link" aria-hidden="true"><span></span></div><p>One process, one thread, one calloop event loop.</p></li><li class="signal-stage signal-driver"><h4>amdgpu</h4><div class="signal-link" aria-hidden="true"><span></span></div><p>Atomic commit, page flip and vblank.</p></li><li class="signal-stage signal-converter"><h4>RGB-Pi 2</h4><div class="signal-link" aria-hidden="true"><span></span></div><p>HDMI input → RGB SCART output.</p></li><li class="signal-stage signal-tube"><h4>CRT</h4><svg class="signal-tube-art" viewBox="595 35 390 250" preserveAspectRatio="xMinYMid meet" role="img" aria-label="CRT cross-section: the electron gun emits a beam, the deflection yoke steers it, and the phosphor screen lights up."><clipPath id="dg-glass-clip"><path d="M600 145 H742 L944 54 Q980 160 944 266 L742 175 H600 Z"/></clipPath><path class="dg-envelope" d="M600 145 H742 L944 54 Q980 160 944 266 L742 175 H600 Z"/><path class="dg-phosphor" d="M944 54 Q974 160 944 266 L941 266 L941 54 Z"/><rect class="dg-part" x="612" y="149" width="13" height="22"/><path class="dg-grid" d="M640 146 V155 M640 165 V174"/><path class="dg-grid" d="M653 146 V155 M653 165 V174"/><rect class="dg-yoke" x="700" y="123" width="46" height="22" rx="5"/><rect class="dg-yoke" x="700" y="175" width="46" height="22" rx="5"/><path class="dg-envelope-beam" d="M656 160 H724" fill="none"/><path class="dg-envelope-beam" d="M724 160 L950 64" fill="none"/><path class="dg-envelope-beam" d="M724 160 L950 256" fill="none"/><g class="dg-raster" clip-path="url(#dg-glass-clip)"><path class="dg-line" data-line="65.0" d="M929 65 H948"/><path class="dg-line" data-line="71.7" d="M933 72 H952"/><path class="dg-line" data-line="78.4" d="M937 78 H956"/><path class="dg-line" data-line="85.1" d="M940 85 H959"/><path class="dg-line" data-line="91.8" d="M943 92 H962"/><path class="dg-line" data-line="98.5" d="M946 98 H965"/><path class="dg-line" data-line="105.2" d="M948 105 H967"/><path class="dg-line" data-line="111.9" d="M951 112 H970"/><path class="dg-line" data-line="118.6" d="M953 119 H972"/><path class="dg-line" data-line="125.3" d="M954 125 H973"/><path class="dg-line" data-line="132.0" d="M955 132 H974"/><path class="dg-line is-dark" data-line="138.7" d="M957 139 H976"/><path class="dg-line is-dark" data-line="145.4" d="M957 145 H976"/><path class="dg-line is-dark" data-line="152.1" d="M958 152 H977"/><path class="dg-line is-dark" data-line="158.8" d="M958 159 H977"/><path class="dg-line is-dark" data-line="165.5" d="M958 166 H977"/><path class="dg-line is-dark" data-line="172.2" d="M958 172 H977"/><path class="dg-line is-dark" data-line="178.9" d="M957 179 H976"/><path class="dg-line is-dark" data-line="185.6" d="M956 186 H975"/><path class="dg-line is-dark" data-line="192.3" d="M955 192 H974"/><path class="dg-line is-dark" data-line="199.0" d="M953 199 H972"/><path class="dg-line is-dark" data-line="205.7" d="M951 206 H970"/><path class="dg-line is-dark" data-line="212.4" d="M949 212 H968"/><path class="dg-line is-dark" data-line="219.1" d="M947 219 H966"/><path class="dg-line is-dark" data-line="225.8" d="M944 226 H963"/><path class="dg-line is-dark" data-line="232.5" d="M941 232 H960"/><path class="dg-line is-dark" data-line="239.2" d="M938 239 H957"/><path class="dg-line is-dark" data-line="245.9" d="M934 246 H953"/><path class="dg-line is-dark" data-line="252.6" d="M931 253 H950"/><path class="dg-line is-dark" data-line="259.3" d="M926 259 H945"/></g><g class="dg-beam" clip-path="url(#dg-glass-clip)" data-beam data-bend="724,160" data-face="944,36,54,266" data-rows="65.0,259.3"><path class="dg-beam-path" d="M656 160 H724" fill="none"/><path class="dg-beam-path" data-beam-bent d="M724 160 L978 134" fill="none"/><circle class="dg-beam-bend" cx="724" cy="160" r="3.4"/><circle class="dg-beam-land" data-beam-land cx="974" cy="134" r="4"/><path class="dg-beam-line" data-beam-lit d="M948 134 H974"/></g></svg><p class="signal-tube-parts">Electron gun → deflection yoke → phosphor</p><p class="signal-specs">240 lines · 60.04 Hz · 15.731 kHz</p></li></ol><div class="signal-results"><section class="signal-measurement"><h4>Client commit → scanout start</h4><p>Same client and machine, before and after this work.</p><dl class="signal-times"><div><dt>Before</dt><dd>33.36 <span>ms</span></dd></div><div><dt>Flyback</dt><dd>3.79 <span>ms</span></dd></div></dl></section><section class="signal-output"><h4>How the television draws it</h4><p>The RGB signal controls the beam as it scans the phosphor screen, one line at a time. The CRT displays the incoming signal without a frame buffer or scaler.</p></section></div></div><p class="dg-run"><button type="button" class="dg-button" data-diagram-run hidden>Draw a frame</button></p><figcaption>Measured on a BeoCenter 1 through an RGB-Pi 2, 300 samples per figure, both ends of every measurement on CLOCK_MONOTONIC.</figcaption></figure>
<p>A CRT television’s horizontal deflection uses a tuned circuit, with a flyback transformer and an output transistor designed for a particular line frequency. On this set, the beam returns to the start of a line fifteen thousand seven hundred and thirty times a second. That return motion gave Flyback its name.</p>
<p>I built it to drive the television from my desktop PC. The measurements below cover the software path up to the start of scanout, not the full delay from a physical button press to light on the screen.</p>
<h2 id="what-it-is" class="log-sec log-sec-1"><a class="log-anchor" href="#what-it-is" aria-label="Link to this section"></a><span class="log-sec-n">01</span>What it is</h2>
<p><strong>Flyback</strong> is a Wayland compositor. One process, one thread, one event loop, 3616 lines across four files, built on smithay 0.7.<sup class="log-fn"><a id="fn-1" href="#ref-1">1</a></sup> It is part of OmaCRT, a launcher for a television, the piece of that project that touches hardware.</p>
<p class="log-code-ref"><a href="https://github.com/stefanomainardi/omacrt/tree/main/shell/src/bin/flyback"><code>shell/src/bin/flyback/</code></a> &middot; the four files this study is about</p>
<p>A desktop compositor manages the system’s outputs and arranges windows on them. A kiosk compositor typically puts one window on an output. Flyback instead requests a connector that the desktop has been told to leave alone.</p>
<p>It receives a file descriptor granting control of that connector, programs the television’s timing and manages scanout. Hyprland continues to run the other screens.</p>
<p>Four clients use it, all local and all started by the same project: the launcher, RetroArch, mpv, and a measuring instrument that exists only to take the numbers in this study. They reach it through <code>WAYLAND_DISPLAY=wayland-crt</code>.</p>
<p><strong>This is experimental.</strong> Before sending a timing to the kernel, Flyback checks its line rate against the band in <code>crt.toml</code>, 15 to 16.5 kHz as shipped. It also checks that the field rate is between 40 and 90 Hz and that the timing values are in order. The project had no such guard for its first several months.</p>
<p class="log-code-ref"><a href="https://github.com/stefanomainardi/omacrt/blob/main/shell/src/crt/output.rs"><code>shell/src/crt/output.rs</code></a> &middot; the guard, and what it refuses</p>
<figure class="manual-figure manual-figure-portrait"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/flyback/the-television.webp"><img src="https://omacrt.com/assets/media/log/flyback/the-television.webp" alt="The BeoCenter 1 in the room, running the launcher: the television every number in this study was measured on" width="1200" height="2133" loading="lazy" decoding="async" /></a><figcaption>The BeoCenter 1 in the room, running the launcher: the television every number in this study was measured on</figcaption></figure>
<p>The set is a BeoCenter 1 from 1998, running the launcher over a leased connector, and it is the hardware every number below was taken on.</p>
<h2 id="why-a-desktop-will-not-do-it" class="log-sec log-sec-2"><a class="log-anchor" href="#why-a-desktop-will-not-do-it" aria-label="Link to this section"></a><span class="log-sec-n">02</span>Why a desktop will not do it</h2>
<p>A television at 15 kHz wants a modeline whose line frequency is about a quarter of the slowest thing any monitor has asked for since 1987. Three parts of desktop output management get in the way; only the first concerns the mode itself.</p>
<p><strong>The compositor will not set the timing.</strong> No desktop compositor exposes an arbitrary modeline, and the ones that expose custom modes validate them against what a monitor plausibly wants.</p>
<p><strong>The compositor will not stop managing the output.</strong> Even where a mode can be forced, the desktop keeps ownership: it will re-probe, re-arrange, apply its own scaling, and put its own idea of a cursor on the screen.</p>
<p><strong>The compositor will not give away the scanout.</strong> Frame scheduling is the compositor's own business, and <a href="#what-the-protocol-gives-a-compositor-to-work-with">what the protocol gives it to work with</a> is most of this study. A client cannot decide when its buffer latches.</p>
<p>DRM leasing provides a way to hand that control to another process.</p>
<h2 id="taking-the-connector-and-what-a-lease-actually-grants" class="log-sec log-sec-3"><a class="log-anchor" href="#taking-the-connector-and-what-a-lease-actually-grants" aria-label="Link to this section"></a><span class="log-sec-n">03</span>Taking the connector, and what a lease actually grants</h2>
<p><code>wp_drm_lease_v1</code> exists because virtual reality headsets are displays that no desktop should ever try to use as a desktop. A connector marked non-desktop is offered for leasing instead, and a client that takes the lease gets a DRM file descriptor that is master for that connector and its CRTC.<sup class="log-fn"><a id="fn-2" href="#ref-2">2</a></sup> The protocol's own wording is general (a compositor "will not use this output at all and will make it available for leasing"), and its established use is with headsets.<sup class="log-fn"><a id="fn-3" href="#ref-3">3</a></sup></p>
<p>The same mechanism can mark the television’s connector non-desktop. The flag comes from an EDID vendor block that Microsoft specified for head-mounted and specialised displays. The parser sets the flag for versions 1 and 2 unconditionally, and for version 3 when the block does not claim desktop usage.<sup class="log-fn"><a id="fn-4" href="#ref-4">4</a></sup> This project writes that block into a fabricated EDID and loads it as a connector override, so Hyprland stops configuring the connector and starts offering it.</p>
<p>A lease is a <code>drm_master</code> in its own right: a lessee holding only the objects named in the lease.<sup class="log-fn"><a id="fn-5" href="#ref-5">5</a></sup> Flyback can set a mode on that connector and flip its planes. It cannot touch the desktop's outputs, and the desktop cannot touch its. When the lease is revoked the connector goes back.</p>
<p>I found no prior report of a television being leased this way. That search does not establish that this is the first.</p>
<p class="log-code-ref"><a href="https://github.com/stefanomainardi/omacrt/blob/main/shell/src/bin/flyback/lease.rs"><code>shell/src/bin/flyback/lease.rs</code></a> &middot; asking for the connector, and holding it</p>
<h2 id="the-edid-we-write-ourselves" class="log-sec log-sec-4"><a class="log-anchor" href="#the-edid-we-write-ourselves" aria-label="Link to this section"></a><span class="log-sec-n">04</span>The EDID we write ourselves</h2>
<p>A connector’s classification comes from the display’s EDID. The identification supplied by this converter does not mark it non-desktop, so the project generates an override.</p>
<p>An EDID has a 128-byte base block, optionally followed by extension blocks. The driver reads it over the display data channel when probing the connector. The one that matters here is the CTA-861 extension, which carries a collection of data blocks and then a list of detailed timings. Each data block has a tag and a length in its first byte, and tag 3 is a vendor block: three bytes of registration identifier followed by whatever that vendor wants.</p>
<p>The first vendor block is Microsoft’s. The kernel parser uses its version byte to decide whether to mark the connector non-desktop. Versions 1 and 2 mark the connector non-desktop unconditionally; version 3 does unless the block explicitly claims desktop usage.<sup class="log-fn"><a id="fn-5b" href="#ref-5b">5b</a></sup></p>
<p>So the block this project appends is tag 3, length 21, the Microsoft identifier <code>5C 12 CA</code>, version <code>02</code>, a flags byte, and a sixteen byte container identifier. That identifier is derived from a name, so that two televisions in one house cannot claim to be the same device.</p>
<p>The block changes how the kernel classifies a connector, and nothing about what is sent down it. Every other decision in this project (the modeline, the line rate, the guard that refuses a line rate outside the band) is unaffected by it.</p>
<p>The second vendor block is AMD's, and what makes <a href="#a-variable-refresh-rate-on-a-television-from-1998">a variable refresh rate</a> possible at all. Nine bytes: <code>68 1a 00 00 01 01 &lt;min&gt; &lt;max&gt; 00</code>. A tag and a length, the AMD registration identifier, a version, the two refresh rates in hertz, and a flags byte.</p>
<p>Those bytes were worked out by trying them, because no code in the kernel parses them. The CEA extension block is handed to the display microcontroller and the firmware hands back a version and two rates.<sup class="log-fn"><a id="fn-6" href="#ref-6">6</a></sup> There was nothing in the tree to read, and <code>edid-decode</code> reading the result back as "Vendor-Specific Data Block (AMD), Version 1.1" was the only confirmation available.</p>
<p>The flags byte must remain zero on this chain. A monitor that sets it is asking for a conversation over the monitor control command set. When a block declares such a control code, the driver goes on to require that the display really support FreeSync over MCCS, and revokes the capability when it does not.<sup class="log-fn"><a id="fn-7" href="#ref-7">7</a></sup> A television does not answer. The initial tests had settled on zero before the driver check was identified.</p>
<p>The declared refresh range must pass two checks. To be considered capable at all, the driver wants the declared range to be <strong>strictly greater than ten hertz</strong>.<sup class="log-fn"><a id="fn-8" href="#ref-8">8</a></sup> To reach the active variable state, the FreeSync module first caps the declared maximum at the mode's own nominal rate and then wants what is left to be <strong>ten or more</strong>.<sup class="log-fn"><a id="fn-9" href="#ref-9">9</a></sup></p>
<p>So a range of 55 to 66 declared against a 60.041 Hz mode becomes 55 to 60.041, a range of five, and is refused in silence.</p>
<p>The range this project ships is 48 to 62 for exactly that reason, and the generator refuses to emit anything narrower than eleven.</p>
<p>Then the whole block is rebuilt and the checksum recomputed, because an EDID whose bytes do not sum to zero is discarded, and the new one is handed to the kernel through <code>edid_override</code> in debugfs. That alone is not enough: the kernel read the real EDID when the connector was probed and is not going to read it again because a file changed.</p>
<p>So the script simulates an unplug and a plug through <code>trigger_hotplug</code>, and the connector comes back carrying the identity we wrote for it. Undoing it is the same two steps with <code>reset</code> written instead.</p>
<p class="log-code-ref"><a href="https://github.com/stefanomainardi/omacrt/blob/main/shell/src/crt/output.rs"><code>shell/src/crt/output.rs</code></a> &middot; the connector, the EDID and the modelines</p>
<p>The override takes 87 lines of Python and 104 of shell. The same approach may be useful for other displays that need a non-desktop connector classification.</p>
<h2 id="what-the-protocol-gives-a-compositor-to-work-with" class="log-sec log-sec-1"><a class="log-anchor" href="#what-the-protocol-gives-a-compositor-to-work-with" aria-label="Link to this section"></a><span class="log-sec-n">05</span>What the protocol gives a compositor to work with</h2>
<p>The scheduler uses the following Wayland mechanisms.</p>
<p>A surface is double-buffered. A client accumulates pending state (a buffer, a damage region, a scale, a callback request), and <code>wl_surface.commit</code> applies all of it at once. So a commit is an event with a timestamp, and it is the only moment at which a compositor learns that a client has finished drawing. Every scheduling decision in Flyback starts in the commit handler.</p>
<p>A role decides what a surface is. Flyback implements <code>xdg_shell</code> and configures every toplevel fullscreen at the output's size, so a client is told 3520x240 and draws into it. It also implements <code>wp_viewporter</code>. That is how the launcher draws 320x240 and has it scaled to the output without knowing the output's real width. <code>xdg_popup</code> is configured so that a client making one is not broken, but popups are not mapped or drawn.</p>
<p><strong>Frame callbacks pace the clients.</strong> A client requests a callback for each frame it intends to draw. The compositor answers <code>done</code> with a timestamp meaning _now_. This callback cannot specify "draw a frame intended for time T", and it gives the client no way to request 59.92 Hz. Flyback controls the pacing by choosing when to send <code>done</code>.</p>
<p>Flyback therefore accepts refresh-rate requests on a private control pipe. Newer protocols provide more timing control, but Flyback does not implement them yet. <code>wp_commit_timing_v1</code> lets a client put a time constraint on a content update (present this as close as possible to, but not before, time T), and <code>wp_fifo_v1</code> gives first-in-first-out semantics without the client having to guess.</p>
<p>Both landed in wayland-protocols 1.38, both are implemented by Mutter and KWin, and both are already in smithay: adding them here is a delegate and a handler.<sup class="log-fn"><a id="fn-15" href="#ref-15">15</a></sup> They are candidates for a future version, but none of the clients used on this television speaks them yet.</p>
<p><code>wp_presentation</code> is the answer in the other direction. Without it a client cannot know when its frame reached the screen and has to guess. Flyback implements it on <code>CLOCK_MONOTONIC</code>, fed by the timestamp the kernel puts on the page flip event, and claims <code>HwClock | HwCompletion</code> in the feedback flags only when the driver's stamp really is on the monotonic clock.</p>
<p>mpv has used this for years; RetroArch gained it in July 2026.<sup class="log-fn"><a id="fn-16" href="#ref-16">16</a></sup> Getting it working was also the first piece of this whole study, because without it there is nothing to measure with.</p>
<p>Buffers reach the plane without a copy. <code>zwp_linux_dmabuf_v1</code> with default feedback lets a client hand over a GPU buffer directly; a buffer that cannot be imported is refused through the protocol's own notifier instead of taken and dropped.</p>
<p class="log-code-ref"><a href="https://github.com/stefanomainardi/omacrt/blob/main/shell/src/bin/flyback/host.rs"><code>shell/src/bin/flyback/host.rs</code></a> &middot; the socket, the seat and the protocols</p>
<p>Input support is deliberately limited. There is no pointer and no touch on the seat (it has a keyboard and nothing else), and Flyback opens no input devices at all. The pad is read by the programs themselves through evdev. This arrangement avoids routing gamepad input through the compositor, but would not suit a general desktop.</p>
<h2 id="the-scheduler-and-why-a-variable-rate-has-no-deadline" class="log-sec log-sec-2"><a class="log-anchor" href="#the-scheduler-and-why-a-variable-rate-has-no-deadline" aria-label="Link to this section"></a><span class="log-sec-n">06</span>The scheduler, and why a variable rate has no deadline</h2>
<p>Each frame, the compositor decides when to wake its clients, when to render and when to queue the result for scanout. The usual answer to all three is "at the vblank". It is safe and it costs most of a frame.</p>
<p>At a fixed refresh there is a deadline. A flip has to be queued before the vblank or the picture waits a whole frame.</p>
<p>So Flyback draws at the last safe moment: one frame, less a margin taken from what recent frames actually cost. And it tells the clients early enough that their commit arrives before that, which it works out as twice their own measured drawing time plus a slack.</p>
<p>Telling a client at the vblank instead, the way this compositor used to work, throws away almost a whole frame: the flip goes out before the client has committed, and the client's picture then waits for the frame after. This is the same idea GroovyMAME calls frame delay, done for every client at once instead of inside one emulator.</p>
<p>At a variable refresh there is no deadline at all. A flip that arrives after the frame's minimum length simply makes that frame longer, and every frame is kept. So the compositor draws the moment a client commits, and tells the clients as late as it dares.</p>
<p>The one thing it must never do under a variable rate is draw at the vblank. Anything committed while the last flip was in flight is about to be superseded by the frame the client is drawing now, and flipping it puts a stale picture in the air that the fresh one then waits behind. That single mistake cost a frame and a half.</p>
<figure class="log-diagram" id="fig-sweep"><div class="dg sweep-comparison"><h3 class="dg-headline">From frame submission to scanout.</h3><p class="dg-deck">Both intervals start when the client submits a frame. Compare when the display begins scanning that frame.</p><div class="sweep-prelude"><span>Before submission</span><p><span>Frame callback</span> <b aria-hidden="true">→</b> <span>Client renders</span> <b aria-hidden="true">→</b> <span>Client submits frame</span></p></div><div class="sweep-axis" aria-hidden="true"><span>Client submits frame · 0 ms</span><span>8 ms</span></div><section class="sweep-row sweep-variable" aria-label="Variable refresh"><div class="sweep-summary"><h4>Variable refresh</h4><p class="sweep-value">2.50<span> ms</span></p><p class="sweep-value-label">until scanout starts</p></div><div class="sweep-detail"><div class="sweep-track" aria-hidden="true"><div class="sweep-interval"><span class="sweep-start"></span><span class="sweep-end"></span></div></div><p class="sweep-description">The compositor renders and queues the frame as soon as the client submits it.</p><p class="sweep-sequence-label">Scheduling steps · schematic</p><ol class="sweep-sequence"><li class="sweep-stage sweep-stage-work"><span class="sweep-stage-number">01</span>Render</li><li class="sweep-stage sweep-stage-work"><span class="sweep-stage-number">02</span>Queue flip</li><li class="sweep-stage sweep-stage-scan"><span class="sweep-stage-number">03</span>Scanout</li></ol></div></section><section class="sweep-row sweep-fixed" aria-label="Fixed refresh"><div class="sweep-summary"><h4>Fixed refresh</h4><p class="sweep-value">7.16<span> ms</span></p><p class="sweep-value-label">until scanout starts</p></div><div class="sweep-detail"><div class="sweep-track" aria-hidden="true"><div class="sweep-interval"><span class="sweep-start"></span><span class="sweep-end"></span></div></div><p class="sweep-description">The compositor waits for its scheduled render time before queuing the frame.</p><p class="sweep-sequence-label">Scheduling steps · schematic</p><ol class="sweep-sequence"><li class="sweep-stage sweep-stage-wait"><span class="sweep-stage-number">01</span>Wait for deadline</li><li class="sweep-stage sweep-stage-work"><span class="sweep-stage-number">02</span>Render</li><li class="sweep-stage sweep-stage-work"><span class="sweep-stage-number">03</span>Queue flip</li><li class="sweep-stage sweep-stage-scan"><span class="sweep-stage-number">04</span>Scanout</li></ol></div></section><p class="sweep-key"><span class="sweep-key-marker" aria-hidden="true"></span>The end marker shows where scanout starts. Both bars use the same time scale.</p><details class="sweep-notes"><summary>How to read this example</summary><p>The 2.50 ms and 7.16 ms intervals are retained from the original schematic. They are illustrative totals, not the measured percentiles in the table below.</p><p>The original frame reference is 16.655 ms. This view starts at the client’s submission, after the frame callback and client rendering. It does not assign durations to individual scheduling stages or show the full scanout.</p></details></div><figcaption>Illustrated timing comparison. Measured scheduling results and their percentiles follow in the table below.</figcaption></figure>
<p>Here is the difference, measured on the compositor's own trace rather than drawn. The same client in both, drawing one millisecond, 200 frames each, microseconds as 5th / 50th / 95th percentile:</p>
<div class="table-scroll"><table>
<thead><tr><th></th><th class="num">variable rate</th><th class="num">fixed rate</th></tr></thead>
<tbody><tr><td class="rowhead">commit to flip queued</td><td class="num">55 / <strong>75</strong> / 244</td><td class="num">4069 / <strong>4144</strong> / 13905</td></tr><tr><td class="rowhead">flip queued to vblank</td><td class="num">2238 / <strong>2423</strong> / 2487</td><td class="num">1531 / <strong>1643</strong> / 1894</td></tr><tr><td class="rowhead">vblank interval</td><td class="num">16641 / 16655 / 16668</td><td class="num">16637 / 16655 / 16672</td></tr></tbody>
</table></div>
<p>Under a variable rate the commit is drawn and queued in 75 microseconds, because there is nothing to wait for. Under a fixed one it sits for four milliseconds waiting for the deadline, and the tail says that on some frames it sat for nearly the whole frame.</p>
<p class="log-code-ref"><a href="https://github.com/stefanomainardi/omacrt/blob/main/shell/src/bin/flyback/comp.rs"><code>shell/src/bin/flyback/comp.rs</code></a> &middot; the event loop, the frame clock and the queue</p>
<p>The scheduler maintains three estimates to account for client wake-up, rendering, queuing and the hardware latch. <code>render_cost</code> is what this compositor takes to draw and queue. <code>client_cost</code> is what a client takes between being told and committing. When a rate has been asked for by name, <code>rate_trim</code> is the measured error, a quarter of it corrected each frame.</p>
<h3 id="what-was-wrong-with-that-and-how-it-was-found">What was wrong with that, and how it was found</h3>
<p><code>client_cost</code> was one number for the whole compositor. Frame callbacks all go out together, so a single estimate is really the slowest client on the tube, and with the launcher mapped underneath a game, that is the launcher, and it is not the one being watched.</p>
<p>Re-running the instrument with and without the launcher exposed the problem. A client that draws in a millisecond measured <strong>18.70 ms</strong> from commit to scanout with the launcher mapped, and <strong>3.56 ms</strong> with the launcher stopped. The only change was the hidden launcher window.</p>
<p>The estimate is per window now, kept in the window's own user data, and the callbacks are paced by the window on top, the one being looked at. A window underneath answering late costs nothing; the window on top answering late costs a frame.</p>
<p>Two more changes followed from chasing the tail. A commit from a window that is not on top marks the frame dirty but does not decide when the flip goes out: flipping for a window nobody can see takes the slot the top window's next commit needs. And a window's first measurement replaces the whole-frame assumption outright instead of losing to it for the fifty frames a decaying maximum takes to come down. Fifty frames is the half second anybody actually watches.</p>
<p>With the launcher mapped underneath, the fast client now measures <strong>3.79 ms</strong>, against 3.56 with the tube to itself. The cost of a hidden window is 0.23 ms.</p>
<h2 id="a-variable-refresh-rate-on-a-television-from-1998" class="log-sec log-sec-3"><a class="log-anchor" href="#a-variable-refresh-rate-on-a-television-from-1998" aria-label="Link to this section"></a><span class="log-sec-n">07</span>A variable refresh rate, on a television from 1998</h2>
<p>A set's horizontal rate must not move: the flyback transformer and the deflection circuit are tuned for one. The vertical rate is another matter, because the vertical oscillator re-triggers on sync. So every refresh an emulation asks for (59.92 for a Mega Drive, 60.0988 for a NES, 57.5 for one arcade board or another) is reachable by changing the vertical total alone and leaving the line rate at 15730.8 Hz.</p>
<p>That is exactly what adaptive sync does in hardware: it stretches the vertical blanking and touches nothing else. I tested whether this consumer television from 1998 would follow those changes. The one prior report of adaptive sync on a CRT is on multisync PC monitors rather than on televisions.<sup class="log-fn"><a id="fn-10" href="#ref-10">10</a></sup></p>
<p>The driver requires two settings. An earlier version of this project’s documentation incorrectly listed a third. To reach <code>VRR_STATE_ACTIVE_VARIABLE</code> the driver wants the connector to be <code>freesync_capable</code> with the mode's refresh inside the declared range, and the CRTC's <code>VRR_ENABLED</code> property set.<sup class="log-fn"><a id="fn-11" href="#ref-11">11</a></sup> That is all.</p>
<p>This project published, for a week, that <code>amdgpu.freesync_video=1</code> was also required. It is not. That parameter gates a different mechanism: a smooth change of the front porch that skips the modeset and produces <code>VRR_STATE_ACTIVE_FIXED</code>.<sup class="log-fn"><a id="fn-12" href="#ref-12">12</a></sup></p>
<p>It was tested directly on this chain, with the parameter set and a timing shaped exactly as the driver's own condition requires: same clock, same htotal, vertical total moved, <code>vsync_start</code> moved with it, sync width unchanged.<sup class="log-fn"><a id="fn-13" href="#ref-13">13</a></sup> The change still cost 222.8 and 229.1 ms. That path does not trigger here, and the parameter has not been shown to do anything.</p>
<p>With the range declared, the timing generator is programmed with room. <code>amdgpu_dm_dtn_log</code> reported <code>vmin 261 vmax 327</code> for the tube's timing generator. Those registers hold the total minus one,<sup class="log-fn"><a id="fn-14" href="#ref-14">14</a></sup> so that is a vertical total of 262 to 328 lines: 60.041 Hz down to 47.96 Hz, at a line rate that never moves.</p>
<p>And the scanout follows. Asked for a rate by name and measured as the median of the last 300 vblank intervals, with fifteen seconds of settling: 60.041 asked gives 60.04, 59.92 gives 60.02, 57.5 gives 57.59, 55 gives 55.01. Ask for 50 and it is held at 55, and says so, which brings us to the part that is a property of the television, not of any software.</p>
<figure class="log-diagram bl" id="fig-blanking" data-blanking><div class="dg blanking-comparison"><h3 class="dg-headline">Change the refresh rate, keep the line rate.</h3><p class="dg-deck">The line rate stays at 15,730.8 Hz. Adding vertical blanking lengthens the frame. Choose a refresh rate to compare the measured picture height on this television.</p><div class="dg-run dg-steps blanking-controls" role="group" aria-label="Refresh rate"><button type="button" class="dg-button" data-blank-step="0" data-vtotal="262" data-hz="60.04" data-ms="16.66" data-height="0.0" data-follows="true" aria-pressed="true">60.04 Hz</button><button type="button" class="dg-button" data-blank-step="1" data-vtotal="274" data-hz="57.41" data-ms="17.42" data-height="-0.4" data-follows="true" aria-pressed="false">57.41 Hz</button><button type="button" class="dg-button" data-blank-step="2" data-vtotal="286" data-hz="55.0" data-ms="18.18" data-height="-0.6" data-follows="true" aria-pressed="false">55.00 Hz</button><button type="button" class="dg-button" data-blank-step="3" data-vtotal="315" data-hz="49.94" data-ms="20.02" data-height="-11.5" data-follows="false" aria-pressed="false">49.94 Hz</button></div><div class="blanking-layout"><div class="blanking-preview"><div class="blanking-screen" aria-hidden="true"><div class="blanking-picture" data-blank-picture></div></div><p>Schematic of the measured picture height</p></div><div class="blanking-data"><dl class="blanking-stats" aria-live="polite" aria-atomic="true"><div><dt>Refresh rate</dt><dd class="dg-cond-rate">60.04 Hz</dd></div><div><dt>Frame duration</dt><dd class="dg-cond-frame">16.66 ms</dd></div><div><dt>Total lines · vtotal</dt><dd class="dg-cond-vtotal">262</dd></div><div><dt>Picture height change</dt><dd class="dg-cond-height">0% · reference</dd></div></dl><div class="blanking-timing"><h4>Active picture and vertical blanking</h4><div class="blanking-track" aria-hidden="true"><div class="blanking-frame" data-blank-frame></div><div class="blanking-active"></div></div><div class="blanking-legend"><span>240 active lines</span><span data-blank-lines>22 blanking lines</span></div><p>The blue segment stays the same length. The amber segment grows as blanking lines are added. All four settings use the same scale.</p></div></div></div><p class="blanking-warning" data-blank-warn hidden></p></div><figcaption>Based on footage of the tested television, eight seconds per setting. Picture height was measured relative to picture width.</figcaption></figure>
<h3 id="where-the-set-stops-following">Where the set stops following</h3>
<p>A television's vertical deflection follows a longer frame only so far, and past that the picture loses height and keeps it for as long as the rate does. Filmed on the BeoCenter 1, each step held for eight seconds, the height measured against the picture's own width so that the camera's drift cancels, which works because the width cannot change while the line rate does not:</p>
<figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/flyback/height-55-vs-50.webp"><img src="https://omacrt.com/assets/media/log/flyback/height-55-vs-50.webp" alt="The same picture on the television at 55 Hz and at 50, side by side, with the lines the height was measured between drawn on: 1237 pixels tall at 55 and 1069 at 50" width="1400" height="591" loading="lazy" decoding="async" /></a><figcaption>The same picture on the television at 55 Hz and at 50, side by side, with the lines the height was measured between drawn on: 1237 pixels tall at 55 and 1069 at 50</figcaption></figure>
<p>The image compares two frames from the same film, at 55 Hz and 50 Hz, with the height measurements marked: 1237 pixels against 1069. This measures the visible picture, which the software timestamps cannot capture.</p>
<div class="table-scroll"><table>
<thead><tr><th>refresh</th><th class="num">frame longer by</th><th>height</th><th class="num">brightness</th></tr></thead>
<tbody><tr><td class="rowhead">59.92 Hz</td><td class="num">+0.2%</td><td>reference</td><td class="num">170.4 ±1.7</td></tr><tr><td class="rowhead">57.5 Hz</td><td class="num">+4.4%</td><td>-0.4%</td><td class="num">169.4 ±1.4</td></tr><tr><td class="rowhead">55 Hz</td><td class="num">+9.2%</td><td>-0.6%</td><td class="num">169.6 ±3.2</td></tr><tr><td class="rowhead">50 Hz</td><td class="num">+20.1%</td><td><strong>-11.5%</strong></td><td class="num">162.8 ±4.3</td></tr><tr><td class="rowhead">back to 60.04 Hz</td><td class="num">+0.0%</td><td>-0.1%</td><td class="num">170.7 ±0.8</td></tr></tbody>
</table></div>
<p>The last row is the control: it repeats the first, and its coming back to -0.1% is what says the -11.5% was the television, not the camera. So the useful range on this set is 60.04 Hz down to about 55, and the whole NTSC family sits four times inside it. PAL at 49.70 is outside it and does not need to be inside: a PAL frame is 288 active lines and wants its own modeline anyway.</p>
<p>Brightness was measured in the same frames. A phosphor's brightness depends on how long it is left between refreshes, so a television whose frame length keeps changing will pulse. Steady to within two parts in a hundred at every step is what that looks like when it is not happening.</p>
<p>Where a set gives up is a calibration, like the picture shift, and it lives in the configuration as <code>output.vrr_min_hz</code>. Nothing asks the tube for a slower rate than that, however slowly a program runs.</p>
<h3 id="the-rate-has-to-be-asked-for-not-discovered">The rate has to be asked for, not discovered</h3>
<p>Put an emulator on the tube whose content rate is not the compositor's cadence and the two beat. Half the frames end at the hardware's minimum vertical total and half run to its maximum, four milliseconds apart, and the picture flickers visibly. Pacing the frame callbacks to the client's own commit interval instead of to the mode's period removes it: on a Mega Drive core the step between one frame and the next fell from 4283 microseconds to 24.</p>
<p>What that does not do is let a program choose a rate. A client paced by frame callbacks runs at the cadence it is given, and the cadence is taken from the cadence it runs at, so wherever it settles is where it stays.</p>
<p>Taking the brake off lets the core set the rate, and on an NTSC title it does exactly that. On a PAL one RetroArch then had nothing holding it at all and ran at 80 Hz, faster than the hardware's shortest frame, and the pacing collapsed again.</p>
<p>So the rate is asked for: <code>rate 59.92</code> on the control pipe. The launcher knows which system is running and what that system's refresh is, and telling the compositor is one line on a pipe it already has.</p>
<h3 id="what-the-variable-rate-costs-found-two-days-late">What the variable rate costs, found two days late</h3>
<p>For two days I noticed an occasional shimmer, at one point describing it as a format change. Switching variable refresh off stopped it; switching it back on brought it back. I repeated this twice each way, judging the picture by eye.</p>
<p>The instrument could not see it, for a reason that took two days to find. The compositor timed the gap between two vblanks with its own clock, at the moment its handler ran, which is when the process woke up rather than when the hardware flipped. The driver's own timestamp for that vblank was in hand and was being handed to clients, and was not used here.</p>
<p>The two quantities are the same size, so a frame that was genuinely longer and a wakeup that was merely late read identically, and both states reported the same spread. Using that existing hardware timestamp made it possible to distinguish the two.</p>
<p>And the threshold is not a percentage. A television's vertical countdown, once it has locked, works inside a narrow window. A Philips jungle datasheet gives 261 to 264 lines a field at 60 Hz, and says that a sync pulse arriving outside that window starts the retrace at the end of the window instead of on the sync. This mode is 262 lines nominal, so there are two lines of room. <strong>127 microseconds.</strong> Counted against that, on the driver's clock, three alternating windows of twelve hundred frames each:</p>
<div class="table-scroll"><table>
<thead><tr><th></th><th class="num">fields</th><th>outside 264 lines</th><th class="num">longest field</th></tr></thead>
<tbody><tr><td class="rowhead">rate fixed</td><td class="num">3601</td><td>0</td><td class="num">262.1 lines</td></tr><tr><td class="rowhead">rate variable</td><td class="num">3600</td><td>3, one in twelve hundred</td><td class="num"><strong>291.0 lines</strong></td></tr></tbody>
</table></div>
<p>With the rate fixed the set never loses its window. With it variable it loses it about once every twenty seconds. That interval was consistent with what I had noticed.</p>
<p><strong>And 291 lines is the far side of this set's cliff.</strong> The rate walk filmed the day before, for an unrelated reason, puts that cliff between 286 lines and 291: at 286 the picture is 0.2% short, inside the noise of a hand-held film, and at 291 it is 15% short. The cliff is a range. The worst field this compositor produces lands on the far side of it, within five lines of where it begins, and that was measured a day earlier for another purpose.</p>
<p>There are two limits to this explanation. The datasheet is for a family of Philips jungle circuits, and the identification of this set's is an inference from its service menu. So the window is a documented design for sets of this kind, and not a measurement of this one. The link between stretched fields and the visible shimmer also remains an inference: the tube has not been filmed during one of those events.</p>
<p>Turning variable refresh off removes the shimmer but increases latency. The same client, commit to the start of scanout, three windows each way: <strong>2.02 ms with the variable rate, 5.16 ms without it.</strong> Both effects come from the way variable refresh ends a frame. The frame ends when the flip lands, and that one fact lets a client draw at the last moment and stretches the field when the flip is late.</p>
<p>Every stretched field carries the same signature. The time from the client's commit to the flip being queued, 63 µs at the median, is one to three milliseconds on those fields, and the client's own drawing cost does not move. It is the compositor that is late.</p>
<p>The bursts line up with the desktop being used, on the same graphics card as the television. Idle, the rate falls to six fields in 3602 and then to none. With somebody working on the machine it was nine in half a second.</p>
<p>So the design target stops being "stretch it less" and becomes a deadline: <strong>no flip may be more than 127 microseconds late</strong>, which the knob that exists today cannot guarantee.</p>
<p>The slack a client is given on top of twice its own drawing time is 1500 µs while the refresh is variable, and the worst overrun seen was three milliseconds. Slack is paid on every frame whether it is needed or not: 3000 µs of it measures 3.52 ms of latency and 5000 µs measures 5.51, which is worse than giving the variable rate up. Widening it is a mitigation.</p>
<p>Three milliseconds is the worst that was seen, and the ceiling is unknown. The distribution of that overrun on a busy machine has not been established, which matters for choosing a number. The deadline holds either way: the 127 microseconds is measured, and so is what each setting of the slack costs.</p>
<p>What has not been isolated is which part of the compositor's own path spends those milliseconds, and what it is not has been measured. The flip call is ruled out: in fourteen thousand samples above the threshold the ioctl never took more than two milliseconds. The client is ruled out, since its drawing cost does not move on the fields that stretch.</p>
<p>It follows the desktop being used, on a card the desktop and the television share. The measurements do not isolate a cause within that shared path.</p>
<p>Two more things came out of the same afternoon. A still picture would be worse than any of this: with no flip at all the field runs to the declared maximum of 328 lines, 37 past the cliff. It does not happen here because the launcher draws every frame, and over 10 125 consecutive intervals the compositor missed a flip three times, each for two or three frames. A client that redraws only when its content changes would need explicit handling of this case.</p>
<p>Turning the variable rate on and off costs no modeset on this chain, despite the driver reporting <code>RequiresModeset</code> for the property. Fourteen toggles, the frames either side of every one of them 16 655 or 16 656 microseconds, not one gap above 20 ms.</p>
<p><strong>No product change was made after these tests.</strong> Choosing between occasional shimmer and the latency increase needs a visual assessment as well as the timing measurements. The overruns come from the desktop competing for the same card, and an idle machine does not produce them. Only somebody watching the screen while the machine is in use can settle it.</p>
<p class="log-code-ref"><a href="https://github.com/stefanomainardi/omacrt/blob/main/docs/data/vrr-frame-length-2026-09-13.txt"><code>docs/data/vrr-frame-length-2026-09-13.txt</code></a> &middot; the fields, the toggles, and the three fixes rejected on the mechanism</p>
<h2 id="how-any-of-this-is-measured" class="log-sec log-sec-4"><a class="log-anchor" href="#how-any-of-this-is-measured" aria-label="Link to this section"></a><span class="log-sec-n">08</span>How any of this is measured</h2>
<p>The next section’s measurements come from one program. Its clock sources and scope determine how to interpret the results.</p>
<p>The problem is that the two ends of the measurement are usually on different clocks. A button press is timestamped by the kernel's input layer. A frame reaching the screen is timestamped by the display hardware's vblank. Comparing them means getting both onto the same clock, and then getting the second one out to the program doing the comparing.</p>
<p><code>shell/src/bin/latency.rs</code> does that, in about 1400 lines, behind a feature flag so that it is absent from every build anybody installs.</p>
<p>The instrument creates a virtual pad. A virtual gamepad through <code>uinput</code>, so a press can be generated at a chosen instant instead of waited for. Then it opens the event node the kernel creates for that pad and sets <code>EVIOCSCLOCKID</code> to <code>CLOCK_MONOTONIC</code>, the clock DRM stamps a vblank with. Both ends of the measurement are then on one clock, and the difference is real rather than approximate.</p>
<p>There is a tenth of a second of waiting between creating the device and opening its node, because the kernel makes the node before udev hands it to the <code>input</code> group, and the first open is otherwise refused.</p>
<p>The other timestamp comes through Wayland. The vblank timestamp reaches it as <code>wp_presentation</code> feedback. That is why the protocol work came first: without it there is nothing to measure with. The instrument also checks the feedback's flags and reports how many of its samples carried <code>HwClock | HwCompletion</code> and not an estimate. In the runs published here that is 300 of 300.</p>
<p>The instrument generates presses at random points in the frame. A test that presses on the frame callback measures the same instant of the frame every time, and does not represent input arriving at arbitrary times.</p>
<p>So the default mode spreads its presses across the frame, and the distribution has the half-frame wait in it because a real one does too. The paced mode, which draws on the frame callback the way a program paces itself, is a different measurement and is labelled as one wherever it appears.</p>
<p>The report also identifies the costs outside the measured interval. The report ends with the measured cost of the instrument's own work, reading the pad and drawing: 0.01 ms. Then a line saying that a real pad adds its own polling in front of everything, one to eight milliseconds by its rate. That belongs to the pad, not to this. It also decomposes its own median against half a frame, so that the part nobody can remove is separated from the part this compositor is responsible for.</p>
<p>What is left outside even that: the converter and the phosphor. The converter has no frame store, and the evidence for that is behaviour and not a datasheet. Under the variable rate it tracks a vertical total that changes from one frame to the next without losing lock, and a frame store could not do that. Its own delay has not been measured here. The phosphor lights within microseconds of the beam arriving. On a set with no panel and no scaler, "the start of scanout" is very nearly "the picture on the glass", and that is the property that makes this chain measurable in software at all. On an LCD the same program would be measuring something else and a photodiode would be compulsory.</p>
<p>No number in this study is click to photon. Every timestamp is a kernel clock, at the press and at the start of scanout, and the step from the start of scanout to the glass is reasoning from what the hardware is. No instrument here has taken it.</p>
<figure class="manual-figure manual-figure-portrait"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/flyback/test-pattern.webp"><img src="https://omacrt.com/assets/media/log/flyback/test-pattern.webp" alt="The measuring instrument&#x27;s own test card on the television, with the clapper it uses to synchronise a film with the timestamps" width="1200" height="2134" loading="lazy" decoding="async" /></a><figcaption>The measuring instrument's own test card on the television, with the clapper it uses to synchronise a film with the timestamps</figcaption></figure>
<p>The instrument also draws a test card. A separate mode puts a self-driving pattern on the tube for filming. A clapper synchronises the film with the log, the steps are counted in frames, and the word END is drawn from a bitmap font, so that a recording carries its own label. The <a href="#where-the-set-stops-following">table of height and brightness against refresh rate</a> was measured from film shot that way.</p>
<p class="log-code-ref"><a href="https://github.com/stefanomainardi/omacrt/blob/main/shell/src/bin/latency.rs"><code>shell/src/bin/latency.rs</code></a> &middot; the instrument, behind a feature flag</p>
<p><code>--dump</code> writes every sample to a file in the order taken, one millisecond value per line. This produces the full set of three hundred samples behind <a href="#what-it-costs">what it costs</a>.</p>
<h2 id="what-it-costs" class="log-sec log-sec-1"><a class="log-anchor" href="#what-it-costs" aria-label="Link to this section"></a><span class="log-sec-n">09</span>What it costs</h2>
<p>Every figure here was taken on the chain in the conditions block at the top, with the launcher mapped underneath, because a program on this television is never the only client on it.</p>
<p><strong>Commit to the start of scanout</strong>, for a client that draws on the frame callback the way a program paces itself:</p>
<div class="table-scroll"><table>
<thead><tr><th>the client takes</th><th class="num">fixed rate</th><th class="num">variable rate</th></tr></thead>
<tbody><tr><td class="rowhead">1 ms</td><td class="num">6.98 ms</td><td class="num"><strong>3.79 ms</strong></td></tr><tr><td class="rowhead">2 ms</td><td class="num">7.90 ms</td><td class="num"><strong>4.61 ms</strong></td></tr><tr><td class="rowhead">4 ms</td><td class="num">9.79 ms</td><td class="num"><strong>6.54 ms</strong></td></tr></tbody>
</table></div>
<p>The launcher end to end reports <strong>2.0 ms</strong>, and 4.5 ms with every core on the machine busy.</p>
<p>The baseline can be reproduced by disabling three features. Turn the three switches off: <code>FLYBACK_LATE_DRAW=off</code>, <code>FLYBACK_MARGIN_US=off</code>, and the variable rate off. Together they reproduce this compositor’s earlier scheduling behaviour. The same client on the same machine then measures <strong>33.36 ms, two frames exactly</strong>, with the launcher's own figure at 33.1. Anybody with this hardware can run both halves.</p>
<figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/flyback/launcher-on-the-tube.webp"><img src="https://omacrt.com/assets/media/log/flyback/launcher-on-the-tube.webp" alt="The launcher photographed on the television rather than captured from the framebuffer, so the phosphor and the bloom are the set&#x27;s own" width="1200" height="985" loading="lazy" decoding="async" /></a><figcaption>The launcher photographed on the television rather than captured from the framebuffer, so the phosphor and the bloom are the set's own</figcaption></figure>
<p><strong>Press to the start of scanout</strong>, from the kernel's timestamp for a button press, 300 presses at random points of the frame: <strong>best 1.46, median 10.19 (0.61 of a frame), 95th 17.52, worst 18.21 ms.</strong> All three hundred samples are published alongside these summary statistics.</p>
<figure class="log-diagram" id="fig-what-it-costs"><div class="dg latency-comparison"><h3 class="dg-headline">From a button press to scanout.</h3><p class="dg-deck">300 button presses from a virtual pad, sent at random points in the frame. The timer stops when scanout begins.</p><div class="latency-main"><div class="latency-median"><p>Median</p><strong>10.19<small> ms</small></strong><p>0.61 of a frame</p></div><div class="latency-plot"><div class="latency-axis"><span>0 ms</span><span>20 ms</span></div><div class="latency-track" aria-hidden="true"><span class="latency-half"></span><span class="latency-own"></span><i class="latency-frame"></i></div><p class="latency-frame-note">Frame reference: 16.655 ms</p></div></div><div class="latency-parts"><section class="latency-part latency-part-half"><h4>Half-frame reference</h4><p class="latency-part-value">8.33 <span>ms</span></p><p>The half-frame waiting time used to interpret the median for randomly timed input.</p></section><section class="latency-part latency-part-own"><h4>Above that reference</h4><p class="latency-part-value">≈ 1.9 <span>ms</span></p><p>The remainder attributed to the compositor in this study. Instrument overhead is 0.01 ms.</p></section></div><section class="latency-distribution"><h4>Best, 95th percentile and worst</h4><p>All intervals use the same 0 to 20 ms scale.</p><div class="latency-stat"><span>Best</span><strong>1.46 <small>ms</small></strong><div class="latency-stat-track" aria-hidden="true"><i class="latency-stat-best"></i></div></div><div class="latency-stat"><span>95th percentile</span><strong>17.52 <small>ms</small></strong><div class="latency-stat-track" aria-hidden="true"><i class="latency-stat-p95"></i></div></div><div class="latency-stat"><span>Worst</span><strong>18.21 <small>ms</small></strong><div class="latency-stat-track" aria-hidden="true"><i class="latency-stat-worst"></i></div></div></section><p class="latency-scope">Physical controller polling and optical delay are outside this measurement. All 300 samples are published with the study.</p></div><figcaption>Kernel timestamps from a virtual pad, measured on the same clock as vblank.</figcaption></figure>
<p>The decomposition matters more than the median. 8.33 ms of it is half a frame, which is what any commit at a random phase waits for, whoever is compositing. The compositor's own contribution is the 1.9 ms above that. The instrument measures its own overhead at 0.01 ms.</p>
<p>The pad is a virtual one, made with <code>uinput</code> and read back through evdev with the clock set to <code>CLOCK_MONOTONIC</code>, so both ends of the measurement are on the clock the vblank uses. A real pad adds its own polling in front, one to eight milliseconds by its rate, and that belongs to the pad.</p>
<p><strong>A mode change, for comparison</strong>, from the request to the first vblank after it:</p>
<div class="table-scroll"><table>
<thead><tr><th>what moved</th><th class="num">first vblank</th></tr></thead>
<tbody><tr><td class="rowhead">nothing: the same timing re-applied</td><td class="num">4.4, 8.8, 9.7, 15.5 ms</td></tr><tr><td class="rowhead">the vertical total alone</td><td class="num">196.5, 196.8, 206.8, 212.2 ms</td></tr><tr><td class="rowhead">the whole standard, NTSC to PAL and back</td><td class="num">182.4, 192.9, 216.7, 226.1 ms</td></tr></tbody>
</table></div>
<p>182 to 229 ms, and it does not matter how little of the timing moves. Those figures are the kernel's share of it, and the television is dark for longer. The converter has nothing to lock to for 280 ms, and filmed at 240 fps the glass shows nothing for about 400 ms, the tube adding its own 120 ms after the picture is back. That is the cost the variable refresh rate avoids, and the first row is what says the cost is the modeset itself.</p>
<p class="log-code-ref"><a href="https://github.com/stefanomainardi/omacrt/blob/main/docs/data/modeset-lock-2026-09-13.txt"><code>docs/data/modeset-lock-2026-09-13.txt</code></a> &middot; the converter&#x27;s lock read over i2c, and the tube filmed at 240 fps</p>
<h3 id="four-of-these-numbers-were-wrong-until-the-day-before-this-was-published">Four of these numbers were wrong until the day before this was published</h3>
<p>Nothing from this project had been published outside its repository when every claim in it was checked again. Four needed correction. The checks also found two defects that normal use had not exposed.</p>
<p>The cost of a mode change was published as 166 to 190 ms. It could not be reproduced from any log, because the clock meant to measure it was declared, zeroed and read, and <strong>never assigned a value</strong>: the line it feeds had never been printed in the life of the project. Armed, it gives the table above.</p>
<p>Press to picture was published as a median of 20.4 ms. That reading predates the scheduler's own fixes, which were made the same day.</p>
<p>The refresh rates the tube was given were published to three decimal places, 59.922, 57.499, 55.000 and 49.999 Hz, by an instrument that does not exist here. One of the four was below this project's own floor and would have been held at it.</p>
<p>And <code>amdgpu.freesync_video=1</code> was published as required. It is not: tested on this chain, with the parameter set and the timing shaped the way the driver's condition wants, the change still cost 222.8 and 229.1 ms.</p>
<p>The second defect: the two explanations printed by the <code>rate</code> command were the wrong way round. They compare periods, not frequencies, so a longer period is a slower rate, and asking for a rate below the floor answered "the mode itself is no faster than that", the opposite of what had happened.</p>
<p>The full record, voice by voice with every kernel claim pinned to a file and a line, is in the repository.<sup class="log-fn"><a id="fn-17" href="#ref-17">17</a></sup></p>
<h2 id="what-it-does-not-do" class="log-sec log-sec-2"><a class="log-anchor" href="#what-it-does-not-do" aria-label="Link to this section"></a><span class="log-sec-n">10</span>What it does not do</h2>
<p>Flyback has a narrow feature set. One unimplemented possibility is beam racing, which would use the control of scanout provided by the lease. I found no report of this being implemented through a leased connector on Linux.</p>
<p>No XWayland. No pointer, no touch, no input devices of any kind. No layer shell, no decorations, no window rules; the stacking order is the whole of the window management. No clipboard, no primary selection, no drag and drop. No multi-output: one leased connector, one CRTC, one mode, and two televisions would need most of this rewritten. No fractional scale, no HDR, no colour management, no explicit sync, and no commit timing or fifo yet. It is a compositor for one television and should not be used as a general-purpose one.</p>
<p>Three absences deserve more than a list.</p>
<p><strong>Tearing.</strong> <code>wp_tearing_control</code> is the other road to low latency and it is not taken. On a 15 kHz set a torn frame is visible across a third of the picture, and the scheduler recovers most of the same time without it.</p>
<p><strong>Interlace.</strong> A 480i modeline on this chain is _accepted_: the modeset returns without error, the converter keeps its lock, and the television shows a narrow strip. The timing is programmed as interlaced and scanned out progressively, at a rate no television locks to. Five separate things in the current upstream tree stand between that modeline and a picture on DCN 3.2.</p>
<ol class="prose-list">
<li>The interlace flag is never copied out of the DRM mode into the timing the display core uses.<sup class="log-fn"><a id="fn-18" href="#ref-18">18</a></sup></li>
<li>The timing validator returns false for any interlaced timing, under the comment _"Temporarily blocking interlacing mode until it's supported"_.<sup class="log-fn"><a id="fn-19" href="#ref-19">19</a></sup></li>
<li>The register that enables interlacing is present in the DCN 1 register list and absent from DCN 3.2's. The code that would write it is generation-independent and already there, and AMD's own headers for this ASIC carry the register and its enable bit.<sup class="log-fn"><a id="fn-20" href="#ref-20">20</a></sup></li>
<li>The mode validation library this generation uses doubles the vertical ratio for an interlaced timing when the ASIC does not claim progressive-to-interlace support, which this one does not.<sup class="log-fn"><a id="fn-21" href="#ref-21">21</a></sup></li>
<li>A line buffer field that exists for exactly this is never set from the timing.<sup class="log-fn"><a id="fn-22" href="#ref-22">22</a></sup></li>
</ol>
<p>This project decided not to require a patched kernel, so it does not have interlace.</p>
<p><strong>Beam racing.</strong> Drawing the frame just ahead of the electron beam is the last real latency win available, and it exists in GroovyMAME and in WinUAE, on Windows. On Linux it never has: RetroArch has had a bounty standing for it since 2018,<sup class="log-fn"><a id="fn-23" href="#ref-23">23</a></sup> and what landed in 2025 is the opposite thing, a shader that _simulates_ a CRT's rolling scan on a high-refresh LCD.<sup class="log-fn"><a id="fn-24" href="#ref-24">24</a></sup></p>
<p>The reason nobody built it is that beam racing needs ownership of the scanout, which a desktop compositor denies, and a leased connector does not. So this is a setup where it could be built, and it has not been built here either. The lease provides the required control, but the implementation is still missing.</p>
<p>It was also decided against on evidence. Beam racing attacks the emulator's own frame production, not the compositor's scanout, which is why it lives inside MAME and inside WinUAE rather than in a window system. The measurement settled it: what remains between a commit and the glass is 0.23 of a frame, and half of what remains between a button and the glass is the wait for the next vblank that nobody can remove.</p>
<h2 id="beside-the-others-and-where-that-leaves-it" class="log-sec log-sec-3"><a class="log-anchor" href="#beside-the-others-and-where-that-leaves-it" aria-label="Link to this section"></a><span class="log-sec-n">11</span>Beside the others, and where that leaves it</h2>
<p>There is a well-populated world of Linux retrogaming on CRTs, and most of it is older, more complete and better tested than this.</p>
<p><strong>If you are willing to dedicate a machine, use <a href="https://github.com/substring/os">GroovyArcade</a> or <a href="https://wiki.batocera.org/batocera-and-crt">Batocera</a>.</strong> They are mature, they have communities, and they solve the problem completely.</p>
<p><strong>If you want the most faithful hardware behaviour money can buy, use a MiSTer.</strong> Its own documentation puts its scaler at four display lines in its fastest mode, about a third of a millisecond, on top of a core that is the console's own timing.<sup class="log-fn"><a id="fn-25" href="#ref-25">25</a></sup> A MiSTer is faster than this and always will be, because there is no operating system in it.</p>
<p>Flyback’s numbers were measured with the instrument in the repository. Figures for other systems come from their documentation; I have not measured those systems on this hardware.</p>
<figure class="log-diagram" id="fig-the-machine"><div class="dg machine-comparison"><h3 class="dg-headline">How each setup uses the machine.</h3><p class="dg-deck">These projects take different approaches to CRT gaming. This comparison groups them by their role in the system.</p><div class="machine-grid"><section class="machine-card"><p class="machine-category">01 / SETUP</p><h4>Dedicated CRT system</h4><p class="machine-description">The machine boots into a gaming system.</p><ul class="machine-projects"><li><h5>GroovyArcade</h5><p>an Arch-based OS with a patched kernel for 15 kHz</p></li><li><h5>Batocera + the CRT script</h5><p>a retro distribution plus a community script</p></li><li><h5>Lakka</h5><p>a RetroArch appliance, CRT-tuned images since 6.1</p></li></ul></section><section class="machine-card"><p class="machine-category">02 / SETUP</p><h4>FPGA console</h4><p class="machine-description">Dedicated hardware reimplements the consoles.</p><ul class="machine-projects"><li><h5>MiSTer</h5><p>an FPGA that reimplements the consoles themselves</p></li></ul></section><section class="machine-card"><p class="machine-category">03 / SETUP</p><h4>Emulator and timing tools</h4><p class="machine-description">Software running on a host system.</p><ul class="machine-projects"><li><h5>GroovyMAME / Switchres</h5><p>a MAME fork and the modeline engine under it</p></li></ul></section><section class="machine-card machine-card-own"><p class="machine-category">04 / SETUP</p><h4>Alongside your desktop</h4><p class="machine-description">Flyback leases one output; the desktop keeps the others.</p><ul class="machine-projects"><li><h5>Flyback</h5><p>a compositor that takes one connector from the desktop</p></li></ul></section></div><aside class="machine-limit"><h4>Switchres has more flexible timings</h4><p>Switchres computes a modeline per game from a monitor preset, across 15, 25 and 31 kHz. Flyback ships five presets and selects between them.</p></aside></div><figcaption>Six projects, grouped by setup. This is a comparison of system roles. Timing flexibility is compared separately above.</figcaption></figure>
<div class="table-scroll"><table>
<thead><tr><th></th><th>what it is</th><th>the machine is</th></tr></thead>
<tbody><tr><td class="rowhead">GroovyMAME / Switchres</td><td>a MAME fork and the modeline engine under it, the origin of most of this craft</td><td>whatever you run it on</td></tr><tr><td class="rowhead">GroovyArcade</td><td>an Arch-based OS built around it, with a patched kernel for 15 kHz</td><td>a CRT machine</td></tr><tr><td class="rowhead">Batocera + the CRT script</td><td>a general retro distribution plus a community script</td><td>a CRT machine</td></tr><tr><td class="rowhead">Lakka</td><td>a RetroArch appliance, with CRT-tuned Raspberry Pi images since 6.1</td><td>a CRT machine</td></tr><tr><td class="rowhead">MiSTer</td><td>an FPGA that reimplements the consoles themselves</td><td>a console</td></tr><tr><td class="rowhead"><strong>Flyback</strong></td><td>a compositor that takes one connector from the desktop</td><td><strong>still your desktop</strong></td></tr></tbody>
</table></div>
<p>Flyback is the only row that is a component: no frontend, no game database, no installer, no community. The last column describes the intended use of each setup. Every other row answers _"how do I build a machine for a CRT?"_; this one answers _"how does a machine I already have grow a CRT?"_</p>
<p>Where Switchres is better than what is here: it computes a modeline per game from a monitor preset, across 15, 25 and 31 kHz and dozens of documented arcade monitor types. This project ships five modelines and picks between them: years of accumulated work, and if per-game timings are ever needed the sensible path is to use Switchres rather than rewrite it.</p>
<p>Flyback is intended for a PC that remains in use as a desktop while driving a 15 kHz television. It controls one leased output and schedules the clients running on it.</p>
<p>It remains experimental and has only been tested on the setup listed above. The instrument and sample data are available for repeating the measurements on another set.<span class="log-end" aria-hidden="true"></span></p><section class="log-refs" aria-labelledby="references"><h2 id="references">References</h2><p>Kernel references are to the upstream tree at tag <code>v7.2.4</code>, which is the
kernel this runs on. Everything else is linked.</p><ol><li id="ref-1"><a class="log-ref-back" href="#fn-1">1</a><span>The compositor is <code>shell/src/bin/flyback/</code> in <a href="https://github.com/stefanomainardi/omacrt">stefanomainardi/omacrt</a>; the measuring instrument is <code>shell/src/bin/latency.rs</code>, behind a feature so that it is absent from any build anybody installs.</span></li><li id="ref-2"><a class="log-ref-back" href="#fn-2">2</a><span><a href="https://wayland.app/protocols/drm-lease-v1">The DRM lease protocol</a>.</span></li><li id="ref-3"><a class="log-ref-back" href="#fn-3">3</a><span><a href="https://indico.freedesktop.org/event/6/contributions/384/attachments/217/296/DRM-lease_Wayland_Monado.pdf">DRM leasing on Wayland, a short history from the XR point of view</a>.</span></li><li id="ref-4"><a class="log-ref-back" href="#fn-4">4</a><span><code>drivers/gpu/drm/drm_edid.c:6418-6432</code>, <code>drm_parse_microsoft_vsdb</code>.</span></li><li id="ref-5b"><a class="log-ref-back" href="#fn-5b">5b</a><span><code>drivers/gpu/drm/drm_edid.c:6418-6432</code>, <code>drm_parse_microsoft_vsdb</code>, which is the same footnote as <sup class="log-fn"><a href="#ref-4">4</a></sup> and is repeated here because it is the mechanism rather than an aside.</span></li><li id="ref-5"><a class="log-ref-back" href="#fn-5">5</a><span><code>drivers/gpu/drm/drm_lease.c:36-66</code>, a lessee is a <code>drm_master</code> holding only the objects named in the lease.</span></li><li id="ref-6"><a class="log-ref-back" href="#fn-6">6</a><span><code>drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c:13851-13865</code> dispatching to the firmware parser, and <code>:13805-13820</code> taking a version and two rates back from it.</span></li><li id="ref-7"><a class="log-ref-back" href="#fn-7">7</a><span><code>amdgpu_dm.c:14079-14081</code>.</span></li><li id="ref-8"><a class="log-ref-back" href="#fn-8">8</a><span><code>amdgpu_dm.c:14042</code>, <code>max_vfreq - min_vfreq &gt; 10</code>.</span></li><li id="ref-9"><a class="log-ref-back" href="#fn-9">9</a><span><code>dc/modules/freesync/freesync.c:1019-1022</code> for the cap at nominal, <code>:33</code> and <code>:1111</code> for <code>MIN_REFRESH_RANGE</code>.</span></li><li id="ref-10"><a class="log-ref-back" href="#fn-10">10</a><span>The only prior report found is on multisync PC monitors and not on televisions. Nothing was found for a consumer set, which says what the search turned up and not what exists.</span></li><li id="ref-11"><a class="log-ref-back" href="#fn-11">11</a><span><code>amdgpu_dm.c:12019</code> and <code>:12032-12040</code>.</span></li><li id="ref-12"><a class="log-ref-back" href="#fn-12">12</a><span><code>amdgpu_dm.c:12196-12199</code> and <code>:12237-12250</code>, both guarded by <code>amdgpu_freesync_vid_mode</code>.</span></li><li id="ref-13"><a class="log-ref-back" href="#fn-13">13</a><span><code>amdgpu_dm.c:12060-12084</code>, <code>is_timing_unchanged_for_freesync</code>.</span></li><li id="ref-14"><a class="log-ref-back" href="#fn-14">14</a><span><code>dc/optc/dcn32/dcn32_optc.c:296</code>, <code>set_vtotal_min_max(optc, vertical_total_min - 1, vertical_total_max - 1)</code>.</span></li><li id="ref-15"><a class="log-ref-back" href="#fn-15">15</a><span><a href="https://wayland.app/protocols/commit-timing-v1">commit-timing-v1</a> and <a href="https://wayland.app/protocols/fifo-v1">fifo-v1</a>, both in wayland-protocols 1.38; smithay 0.7 carries <code>wayland::commit_timing</code> and <code>wayland::fifo</code>.</span></li><li id="ref-16"><a class="log-ref-back" href="#fn-16">16</a><span><a href="https://github.com/libretro/RetroArch/pull/17882">libretro/RetroArch#17882</a>, merged 20 July 2026. mpv has since moved on to wp-presentation v2.</span></li><li id="ref-17"><a class="log-ref-back" href="#fn-17">17</a><span><code>docs/audit-2026-09-12.md</code> in the repository.</span></li><li id="ref-18"><a class="log-ref-back" href="#fn-18">18</a><span><code>amdgpu_dm.c:6966</code>, <code>fill_stream_properties_from_drm_display_mode</code>, with no assignment to the timing's interlace flag anywhere in it; the only mention of <code>DRM_MODE_FLAG_INTERLACE</code> in that file is the rejection at <code>:8581</code>.</span></li><li id="ref-19"><a class="log-ref-back" href="#fn-19">19</a><span><code>dc/optc/dcn10/dcn10_optc.c:617-619</code>.</span></li><li id="ref-20"><a class="log-ref-back" href="#fn-20">20</a><span><code>dc/optc/dcn10/dcn10_optc.h:51</code> has the register; <code>dc/resource/dcn32/dcn32_resource.h</code> contains no occurrence of <code>INTERLACE</code>. The write, guarded by the register's presence, is at <code>dcn10_optc.c:269-276</code>. The hardware has it: <code>include/asic_reg/dcn/dcn_3_2_0_offset.h:7953</code> and <code>dcn_3_2_0_sh_mask.h:24614</code>.</span></li><li id="ref-21"><a class="log-ref-back" href="#fn-21">21</a><span><code>dc/dml/display_mode_vba.c:594-598</code>, with <code>dc/dml/dcn32/dcn32_fpu.c:78</code> setting <code>ptoi_supported</code> false and <code>dc/resource/dcn32/dcn32_resource.c:777</code> selecting DML1.</span></li><li id="ref-22"><a class="log-ref-back" href="#fn-22">22</a><span><code>dc/inc/hw/transform.h:141</code> defines <code>interleave_en</code>; <code>dc/hwss/dcn10/dcn10_hwseq.c:3020-3021</code> and <code>dc/hwss/dcn20/dcn20_hwseq.c:1774-1775</code> set the fields beside it and not that one.</span></li><li id="ref-23"><a class="log-ref-back" href="#fn-23">23</a><span><a href="https://github.com/libretro/RetroArch/issues/6984">libretro/RetroArch#6984</a>.</span></li><li id="ref-24"><a class="log-ref-back" href="#fn-24">24</a><span><a href="https://www.libretro.com/index.php/retroarch-first-program-to-support-blurbusters-crt-beam-racing-simulator-shader/">RetroArch and the Blur Busters CRT beam racing simulator shader</a>.</span></li><li id="ref-25"><a class="log-ref-back" href="#fn-25">25</a><span><a href="https://mister-devel.github.io/MkDocs_MiSTer/advanced/lag/">MiSTer's own lag documentation</a>.</span></li></ol></section>]]></content:encoded>
    </item>
    <item>
      <title>An identity, drawn with an agent</title>
      <link>https://omacrt.com/log/an-identity-drawn-with-an-agent/</link>
      <guid isPermaLink="true">https://omacrt.com/log/an-identity-drawn-with-an-agent/</guid>
      <pubDate>Sat, 12 Sep 2026 00:00:00 +0000</pubDate>
      <category>design</category>
      <description>I used Claude to develop a logo for Flyback from OmaCRT&#x27;s existing design. This is how we went from three proposals to a finished mark, including two rounds of feedback and a crop measurement the agent got wrong.</description>
      <content:encoded><![CDATA[<figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-identity-drawn-with-an-agent/lockup.webp"><img src="https://omacrt.com/assets/media/log/an-identity-drawn-with-an-agent/lockup.webp" alt="The Flyback lockup: the mark beside the word Flyback and the line Wayland compositor" width="1446" height="448" loading="eager" decoding="async" /></a><figcaption>The Flyback lockup: the mark beside the word Flyback and the line Wayland compositor</figcaption></figure>
<p>I asked Claude: _"un logo per Flyback compositor con /design dai un brief adeguato"_. I wanted a logo for the compositor and asked the agent to develop the brief. The repository already contained the project context and the geometry of OmaCRT’s mark, which the new logo needed to share.</p>
<h2 id="the-design-system-was-source-code-before-anybody-called-it-one" class="log-sec log-sec-1"><a class="log-anchor" href="#the-design-system-was-source-code-before-anybody-called-it-one" aria-label="Link to this section"></a><span class="log-sec-n">01</span>Starting from the existing mark</h2>
<p>OmaCRT’s mark has four bars crossed by a dark cut representing the beam’s return between lines. Its geometry is defined by a struct, with the reasoning in the comment above it:</p>
<pre data-lang="rust"><code>pub const RETRACE: Retrace = Retrace {
    grid: 44, bars: 4, thick: 8, gap: 4, cut: 12, minseg: 3, rest: -2,
};</code></pre>
<p>The grid is forty four units square. The bars are eight units thick, with four units between them. A twelve-unit cut steps once per bar to form a 45-degree edge. The accompanying note explains why five bars become unreadable at 24 pixels; the shape needs to survive scaling without a separate small-size version.</p>
<p>The agent read those constraints first. They gave it a grid and a set of proportions for the new mark.</p>
<figure class="manual-figure"><img src="https://omacrt.com/assets/media/log/an-identity-drawn-with-an-agent/family.svg" alt="The two marks side by side: the OmaCRT mark, four bars with the dark cut stepping down and to the left, and the Flyback mark, four bars stepping out to the right with a full height stroke" width="252" height="152" loading="eager" decoding="async" /><figcaption>The two marks side by side: the OmaCRT mark, four bars with the dark cut stepping down and to the left, and the Flyback mark, four bars stepping out to the right with a full height stroke</figcaption></figure>
<p>OmaCRT’s cut moves down and to the left. Flyback’s ramp steps down and to the right. Both use the same grid.</p>
<h2 id="three-directions-each-with-the-case-against-it" class="log-sec log-sec-2"><a class="log-anchor" href="#three-directions-each-with-the-case-against-it" aria-label="Link to this section"></a><span class="log-sec-n">02</span>Three directions, each with the case against it</h2>
<figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-identity-drawn-with-an-agent/directions.webp"><img src="https://omacrt.com/assets/media/log/an-identity-drawn-with-an-agent/directions.webp" alt="Three sketch directions: a filled raster with a staircase return, four bars with the last stopping mid scan, and a stepping ramp with a full height return stroke" width="1200" height="400" loading="eager" decoding="async" /></a><figcaption>Three sketch directions: a filled raster with a staircase return, four bars with the last stopping mid scan, and a stepping ramp with a full height return stroke</figcaption></figure>
<p>The name suggested the subject. Flyback is what a television’s deflection does between two lines: the current ramps across the screen, then collapses back in almost no time. The transformer named after it is the part tuned for one line rate and no other.</p>
<p>The agent proposed three directions: a whole raster with a staircase cut through it; a raster caught mid scan, with the last bar stopping a third of the way across; and a deflection waveform, with four bars stepping out beside a full height return stroke.</p>
<p>Each proposal included a <strong>For</strong> and an <strong>Against</strong>. The drawback listed for the version I chose was:</p>
<blockquote>
<p>the least literal of the three; someone who has never seen a deflection waveform reads it as an abstract monogram, not as a beam.</p>
</blockquote>
<p>I chose C and replied: _"Ok per la C vai avanti"_.</p>
<h2 id="the-refinement-was-a-number" class="log-sec log-sec-3"><a class="log-anchor" href="#the-refinement-was-a-number" aria-label="Link to this section"></a><span class="log-sec-n">03</span>The refinement was a number</h2>
<figure class="manual-figure"><a class="fig-zoom" href="https://omacrt.com/assets/media/log/an-identity-drawn-with-an-agent/refinement.webp"><img src="https://omacrt.com/assets/media/log/an-identity-drawn-with-an-agent/refinement.webp" alt="The sketch mark beside the finished one: the return stroke goes from six units wide to eight" width="800" height="400" loading="eager" decoding="async" /></a><figcaption>The sketch mark beside the finished one: the return stroke goes from six units wide to eight</figcaption></figure>
<p>In the sketch the return stroke was six units wide. In the finished mark it is eight, the same weight as a bar.</p>
<figure class="manual-figure"><img src="https://omacrt.com/assets/media/log/an-identity-drawn-with-an-agent/refinement.svg" alt="The two versions side by side, the six unit stroke and the eight unit one, with the edge that did not sit on the grid marked" width="240" height="132" loading="eager" decoding="async" /><figcaption>The two versions side by side, the six unit stroke and the eight unit one, with the edge that did not sit on the grid marked</figcaption></figure>
<p>The return stroke represents the same beam travelling about a hundred times faster. Giving it the same weight as the bars makes that relationship visible. At six units it introduced a second weight into the eight-thick, four-apart rhythm, which was especially noticeable at small sizes.</p>
<p>The construction uses these dimensions:</p>
<figure class="manual-figure"><img src="https://omacrt.com/assets/media/log/an-identity-drawn-with-an-agent/construction.svg" alt="The mark drawn on its 44 unit grid, every edge landing on a four unit rule" width="440" height="440" loading="eager" decoding="async" /><figcaption>The mark drawn on its 44 unit grid, every edge landing on a four unit rule</figcaption></figure>
<div class="table-scroll"><table>
<thead><tr><th></th><th></th><th></th></tr></thead>
<tbody><tr><td class="rowhead">grid</td><td>44 by 44</td><td>shared with OmaCRT</td></tr><tr><td class="rowhead">bar thickness</td><td>8</td><td>same</td></tr><tr><td class="rowhead">gap</td><td>4</td><td>same</td></tr><tr><td class="rowhead">pitch</td><td>12</td><td>same</td></tr><tr><td class="rowhead">return stroke</td><td>8 by 44</td><td>one bar, stood up</td></tr><tr><td class="rowhead">stroke to bars</td><td>4</td><td>the gap again</td></tr><tr><td class="rowhead">bar widths</td><td>8, 16, 24, 32</td><td>a step of 8</td></tr><tr><td class="rowhead">bars end at</td><td>44</td><td>flush right</td></tr><tr><td class="rowhead">smallest feature</td><td>8 units</td><td>2.9 pixels at 16 px</td></tr></tbody>
</table></div>
<p>The same SVG rendered at 176, 64, 32, 24 and 16 pixels:</p>
<figure class="manual-figure"><img src="https://omacrt.com/assets/media/log/an-identity-drawn-with-an-agent/sizes.svg" alt="The mark drawn at five sizes down to sixteen pixels, the shape unchanged at every one" width="396" height="176" loading="eager" decoding="async" /><figcaption>The mark drawn at five sizes down to sixteen pixels, the shape unchanged at every one</figcaption></figure>
<p>These dimensions also give future variations a consistent starting point.</p>
<h2 id="the-constraint-no-studio-has-ever-been-given" class="log-sec log-sec-4"><a class="log-anchor" href="#the-constraint-no-studio-has-ever-been-given" aria-label="Link to this section"></a><span class="log-sec-n">04</span>Drawing for a 240-line screen</h2>
<p>The mark has to be drawable by an electron beam on 240 scan lines, because this project's marks appear on the television it drives. That is why there are no diagonals, no hairlines and no fractional units anywhere in the system.</p>
<p>The display constraint informed the grid and stroke weight.</p>
<h2 id="the-first-critique-and-the-seventeen-pixels" class="log-sec log-sec-1"><a class="log-anchor" href="#the-first-critique-and-the-seventeen-pixels" aria-label="Link to this section"></a><span class="log-sec-n">05</span>The first critique, and the seventeen pixels</h2>
<p>When I looked at the logo and text together, the second line seemed misaligned and its contrast was too low. I asked the agent to address both.</p>
<p>In a monospaced font, each glyph occupies the same advance width, but the space before its visible ink can differ. That space is its left side bearing. <strong>A capital F at 230 px carries 18 px of bearing, and a capital W at 52 px carries 1.</strong> Set to the same x coordinate, the two lines are aligned on their origins and misaligned on their ink by seventeen pixels. The smaller line's ink hangs out to the left, exactly what the eye was seeing.</p>
<figure class="manual-figure"><img src="https://omacrt.com/assets/media/log/an-identity-drawn-with-an-agent/bearing.svg" alt="Two lines aligned on their origins, the ink of the smaller one starting seventeen pixels to the left of the ink above it, and the same two lines aligned on their ink" width="400" height="202" loading="eager" decoding="async" /><figcaption>Two lines aligned on their origins, the ink of the smaller one starting seventeen pixels to the left of the ink above it, and the same two lines aligned on their ink</figcaption></figure>
<p>The agent aligned the visible ink, then cropped and trimmed both text bands to check the result. Both started at column 442. It also increased the contrast of the second line while keeping it less prominent than the wordmark.</p>
<p>The agent also found a third problem: the descender of the _y_ in Flyback ran into the second line’s cap height. It moved the word up 16 px and the second line down 16 px, keeping the block centred on the mark.</p>
<h2 id="the-second-critique-and-a-measurement-that-was-wrong" class="log-sec log-sec-2"><a class="log-anchor" href="#the-second-critique-and-a-measurement-that-was-wrong" aria-label="Link to this section"></a><span class="log-sec-n">06</span>The second critique, and a measurement that was wrong</h2>
<p>In the next version, _Wayland compositor_ still did not line up with the mark. I suggested aligning it with the bottom bar.</p>
<p>The agent measured the wordmark’s ink at rows 92 to 301 and the second line at 317 to 356, both starting at column 442. It measured the bottom bar at rows 352 to 391 and reported a height of 39 px. Since it also reported the second line as 39 px tall, it proposed moving the line down 35 px to align the two bands.</p>
<p>The bar measurement was wrong. The agent had cropped across its full width, including columns occupied by the bars above it. Measuring only the part where the last bar is isolated gives <strong>rows 331 to 391, 61 px</strong>. The two reported heights of 39 px did not establish an alignment.</p>
<div class="table-scroll"><table>
<thead><tr><th></th><th>first crop</th><th>corrected crop</th></tr></thead>
<tbody><tr><td class="rowhead">columns measured</td><td>148 to 391, the bar's full span</td><td>340 to 380, the bar alone</td></tr><tr><td class="rowhead">what is in them</td><td>the last bar and three above it</td><td>the last bar</td></tr><tr><td class="rowhead">rows reported</td><td>352 to 391</td><td>331 to 391</td></tr><tr><td class="rowhead">bar height</td><td>39 px</td><td>61 px</td></tr></tbody>
</table></div>
<p>Moving the line still made sense. Its band, 317 to 356, crossed the bar’s band, 331 to 391, about a quarter of the way down, without aligning to either edge. The revision aligned the text baseline with the bar’s bottom edge: second line ink at 352 to 391, bar at 331 to 391.</p>
<p>The agent had predicted that moving the line down 35 px would leave a 51 px gap under the wordmark. The wordmark could now move back down the 16 px added earlier, since the second line was 35 px further away. The final file measures:</p>
<div class="table-scroll"><table>
<thead><tr><th></th><th>rows</th><th></th></tr></thead>
<tbody><tr><td class="rowhead">the mark</td><td>56 to 391</td><td>336 px square</td></tr><tr><td class="rowhead">the wordmark's cap height</td><td>108 to 275</td><td>168 px, half the mark exactly</td></tr><tr><td class="rowhead">the descender of the _y_</td><td>to 317</td><td>clears the line below by 35 px</td></tr><tr><td class="rowhead">the second line</td><td>352 to 391</td><td>its baseline on the bar's bottom edge</td></tr><tr><td class="rowhead">the last bar</td><td>331 to 391</td><td></td></tr></tbody>
</table></div>
<p>The second round exposed a limit of the process: a plausible alignment proposal had been supported by an incorrect measurement. Checking the crop changed the explanation and preserved the intended alignment.</p>
<h2 id="the-rules-and-four-ways-to-get-them-wrong" class="log-sec log-sec-3"><a class="log-anchor" href="#the-rules-and-four-ways-to-get-them-wrong" aria-label="Link to this section"></a><span class="log-sec-n">07</span>The rules, and four ways to get them wrong</h2>
<p>The specification has three sheets: construction; colour, clear space and misuse; and the two marks with their wordmarks. The misuse sheet shows four changes to avoid: rotation, gradients, outlines and five bars instead of four.</p>
<figure class="log-diagram" id="fig-space"><div class="dg"><h3 class="dg-headline">Clear space, and four things it is not.</h3><p class="dg-deck">The sheet of the specification that gets skipped. The clear space is the mark’s own gap, and the four wrong ones were all drawn by asking for the mark and getting something else.</p><svg viewBox="0 0 1040 360" class="dg-plates" role="img" aria-label="Four units of clear space around the mark, and the four things it must never be turned into: rotated, gradient filled, outlined, or with five bars"><rect x="24" y="44" width="208" height="208" fill="none" class="dg-space"/><g transform="translate(40 60) scale(4.0000)"><mask id="sp"><rect width="44" height="44" fill="#000"/><rect x="0" y="0" width="44" height="8" fill="#fff"/><rect x="0" y="12" width="44" height="8" fill="#fff"/><rect x="0" y="24" width="44" height="8" fill="#fff"/><rect x="0" y="36" width="44" height="8" fill="#fff"/><rect x="34" y="0" width="12" height="8" fill="#000"/><rect x="22" y="12" width="12" height="8" fill="#000"/><rect x="10" y="24" width="12" height="8" fill="#000"/><rect x="-2" y="36" width="12" height="8" fill="#000"/></mask><rect width="44" height="44" fill="#9ece6a" mask="url(#sp)"/></g><text x="40" y="300" class="dg-sans dg-i" font-size="13" text-anchor="start">Four units on every side: the gap between the bars.</text><text x="40" y="322" class="dg-sans dg-q" font-size="12.5" text-anchor="start">Nothing sits inside the dashed box: no word, no rule,</text><text x="40" y="342" class="dg-sans dg-q" font-size="12.5" text-anchor="start">no second mark.</text><g transform="translate(420 60)"><rect width="104" height="104" fill="none" class="dg-plate"/><g transform="rotate(14 52 52)"><g transform="translate(8 8) scale(2.0000)"><mask id="w1"><rect width="44" height="44" fill="#000"/><rect x="0" y="0" width="44" height="8" fill="#fff"/><rect x="0" y="12" width="44" height="8" fill="#fff"/><rect x="0" y="24" width="44" height="8" fill="#fff"/><rect x="0" y="36" width="44" height="8" fill="#fff"/><rect x="34" y="0" width="12" height="8" fill="#000"/><rect x="22" y="12" width="12" height="8" fill="#000"/><rect x="10" y="24" width="12" height="8" fill="#000"/><rect x="-2" y="36" width="12" height="8" fill="#000"/></mask><rect width="44" height="44" fill="#9ece6a" mask="url(#w1)"/></g></g></g><path class="dg-no" d="M434 154 L510 74"/><text x="420" y="196" class="dg-sans dg-w" font-size="12" text-anchor="start">no rotation</text><g transform="translate(576 60)"><rect width="104" height="104" fill="none" class="dg-plate"/><defs><linearGradient id="wg" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#9ece6a"/><stop offset="1" stop-color="#7aa2f7"/></linearGradient></defs><g transform="translate(8 8) scale(2.0000)"><mask id="w2"><rect width="44" height="44" fill="#000"/><rect x="0" y="0" width="44" height="8" fill="#fff"/><rect x="0" y="12" width="44" height="8" fill="#fff"/><rect x="0" y="24" width="44" height="8" fill="#fff"/><rect x="0" y="36" width="44" height="8" fill="#fff"/><rect x="34" y="0" width="12" height="8" fill="#000"/><rect x="22" y="12" width="12" height="8" fill="#000"/><rect x="10" y="24" width="12" height="8" fill="#000"/><rect x="-2" y="36" width="12" height="8" fill="#000"/></mask><rect width="44" height="44" fill="url(#wg)" mask="url(#w2)"/></g></g><path class="dg-no" d="M590 154 L666 74"/><text x="576" y="196" class="dg-sans dg-w" font-size="12" text-anchor="start">no gradient</text><g transform="translate(732 60)"><rect width="104" height="104" fill="none" class="dg-plate"/><rect x="8" y="8" width="88" height="17" fill="none" stroke="#9ece6a" stroke-width="1.5"/><rect x="8" y="27" width="88" height="17" fill="none" stroke="#9ece6a" stroke-width="1.5"/><rect x="8" y="46" width="88" height="17" fill="none" stroke="#9ece6a" stroke-width="1.5"/><rect x="8" y="66" width="88" height="17" fill="none" stroke="#9ece6a" stroke-width="1.5"/></g><path class="dg-no" d="M746 154 L822 74"/><text x="732" y="196" class="dg-sans dg-w" font-size="12" text-anchor="start">no outline</text><g transform="translate(888 60)"><rect width="104" height="104" fill="none" class="dg-plate"/><rect x="8" y="8" width="88" height="12" fill="#9ece6a"/><rect x="8" y="27" width="88" height="12" fill="#9ece6a"/><rect x="8" y="46" width="88" height="12" fill="#9ece6a"/><rect x="8" y="65" width="88" height="12" fill="#9ece6a"/><rect x="8" y="84" width="88" height="12" fill="#9ece6a"/></g><path class="dg-no" d="M902 154 L978 74"/><text x="888" y="196" class="dg-sans dg-w" font-size="12" text-anchor="start">never five bars</text><text x="420" y="250" class="dg-sans dg-i" font-size="13" text-anchor="start">The mark is a mask over a solid, and the solid is one colour.</text><text x="420" y="272" class="dg-sans dg-q" font-size="12.5" text-anchor="start">Every one of these came out of asking for the mark and getting</text><text x="420" y="292" class="dg-sans dg-q" font-size="12.5" text-anchor="start">something else.</text></svg></div></figure>
<p>Clear space is four units, the gap between the bars again.</p>
<p>The four colour combinations have these measured contrast ratios: Phosphor green on the near black the launcher uses, at 10.6 to one. The paper colour on the same ground, at 12.0. The dark ink on paper, at 16.2, the one print and a light theme get. And the mark knocked out of the green, the same 10.6 the other way round.</p>
<figure class="log-diagram" id="fig-grounds"><div class="dg"><h3 class="dg-headline">Four grounds, and each one measured.</h3><p class="dg-deck">The green is a mark colour with one ground, and it has one ground. Every ratio here is worked out from the two colours.</p><svg viewBox="0 0 1040 356" class="dg-plates" role="img" aria-label="The mark on four grounds with the contrast ratio measured under each: green on dark at 10.6 to one, paper on dark at 12.0, the dark ink on paper at 16.2, and knocked out of the green at 10.6"><rect x="0" y="10" width="240" height="180" fill="#0b0d14" class="dg-plate"/><g transform="translate(78 50) scale(1.9091)"><mask id="gr0"><rect width="44" height="44" fill="#000"/><rect x="0" y="0" width="44" height="8" fill="#fff"/><rect x="0" y="12" width="44" height="8" fill="#fff"/><rect x="0" y="24" width="44" height="8" fill="#fff"/><rect x="0" y="36" width="44" height="8" fill="#fff"/><rect x="34" y="0" width="12" height="8" fill="#000"/><rect x="22" y="12" width="12" height="8" fill="#000"/><rect x="10" y="24" width="12" height="8" fill="#000"/><rect x="-2" y="36" width="12" height="8" fill="#000"/></mask><rect width="44" height="44" fill="#9ece6a" mask="url(#gr0)"/></g><text x="0" y="220" class="dg-sans dg-i" font-size="13" text-anchor="start">phosphor green on the dark ground</text><text x="0" y="242" class="dg-sans dg-q" font-size="12" text-anchor="start">the default</text><text x="0" y="278" class="dg-mono dg-g" font-size="15" font-weight="500" letter-spacing="0.04em" text-anchor="start">10.6 to 1</text><text x="0" y="302" class="dg-mono dg-q" font-size="10" font-weight="400" letter-spacing="0.08em" text-anchor="start">measured</text><rect x="264" y="10" width="240" height="180" fill="#0b0d14" class="dg-plate"/><g transform="translate(342 50) scale(1.9091)"><mask id="gr1"><rect width="44" height="44" fill="#000"/><rect x="0" y="0" width="44" height="8" fill="#fff"/><rect x="0" y="12" width="44" height="8" fill="#fff"/><rect x="0" y="24" width="44" height="8" fill="#fff"/><rect x="0" y="36" width="44" height="8" fill="#fff"/><rect x="34" y="0" width="12" height="8" fill="#000"/><rect x="22" y="12" width="12" height="8" fill="#000"/><rect x="10" y="24" width="12" height="8" fill="#000"/><rect x="-2" y="36" width="12" height="8" fill="#000"/></mask><rect width="44" height="44" fill="#c0caf5" mask="url(#gr1)"/></g><text x="264" y="220" class="dg-sans dg-i" font-size="13" text-anchor="start">paper on the dark ground</text><text x="264" y="242" class="dg-sans dg-q" font-size="12" text-anchor="start">a row of logos</text><text x="264" y="278" class="dg-mono dg-g" font-size="15" font-weight="500" letter-spacing="0.04em" text-anchor="start">12.0 to 1</text><text x="264" y="302" class="dg-mono dg-q" font-size="10" font-weight="400" letter-spacing="0.08em" text-anchor="start">measured</text><rect x="528" y="10" width="240" height="180" fill="#e8eaf2" class="dg-plate"/><g transform="translate(606 50) scale(1.9091)"><mask id="gr2"><rect width="44" height="44" fill="#000"/><rect x="0" y="0" width="44" height="8" fill="#fff"/><rect x="0" y="12" width="44" height="8" fill="#fff"/><rect x="0" y="24" width="44" height="8" fill="#fff"/><rect x="0" y="36" width="44" height="8" fill="#fff"/><rect x="34" y="0" width="12" height="8" fill="#000"/><rect x="22" y="12" width="12" height="8" fill="#000"/><rect x="10" y="24" width="12" height="8" fill="#000"/><rect x="-2" y="36" width="12" height="8" fill="#000"/></mask><rect width="44" height="44" fill="#0b0d14" mask="url(#gr2)"/></g><text x="528" y="220" class="dg-sans dg-i" font-size="13" text-anchor="start">the dark ink on paper</text><text x="528" y="242" class="dg-sans dg-q" font-size="12" text-anchor="start">print, and light themes</text><text x="528" y="278" class="dg-mono dg-g" font-size="15" font-weight="500" letter-spacing="0.04em" text-anchor="start">16.2 to 1</text><text x="528" y="302" class="dg-mono dg-q" font-size="10" font-weight="400" letter-spacing="0.08em" text-anchor="start">measured</text><rect x="792" y="10" width="240" height="180" fill="#9ece6a" class="dg-plate"/><g transform="translate(870 50) scale(1.9091)"><mask id="gr3"><rect width="44" height="44" fill="#000"/><rect x="0" y="0" width="44" height="8" fill="#fff"/><rect x="0" y="12" width="44" height="8" fill="#fff"/><rect x="0" y="24" width="44" height="8" fill="#fff"/><rect x="0" y="36" width="44" height="8" fill="#fff"/><rect x="34" y="0" width="12" height="8" fill="#000"/><rect x="22" y="12" width="12" height="8" fill="#000"/><rect x="10" y="24" width="12" height="8" fill="#000"/><rect x="-2" y="36" width="12" height="8" fill="#000"/></mask><rect width="44" height="44" fill="#0b0d14" mask="url(#gr3)"/></g><text x="792" y="220" class="dg-sans dg-i" font-size="13" text-anchor="start">knocked out of the green</text><text x="792" y="242" class="dg-sans dg-q" font-size="12" text-anchor="start">one colour printing</text><text x="792" y="278" class="dg-mono dg-g" font-size="15" font-weight="500" letter-spacing="0.04em" text-anchor="start">10.6 to 1</text><text x="792" y="302" class="dg-mono dg-q" font-size="10" font-weight="400" letter-spacing="0.08em" text-anchor="start">measured</text><text x="0" y="338" class="dg-mono dg-w" font-size="11" font-weight="400" letter-spacing="0.14em" text-anchor="start">THE GREEN ON WHITE IS 1.8 TO 1, SO IT IS NEVER SET ON ONE</text></svg></div></figure>
<p>The green is a mark colour with one ground. On white it measures 1.8 to one, too little contrast for this use, so a light theme gets the dark ink. There is a second file for that, <code>flyback-mark-mono.svg</code>, which takes <code>currentColor</code> and lets the page decide.</p>
<figure class="log-diagram mk" id="fig-raster" data-mark><div class="dg"><h3 class="dg-headline">The OmaCRT mark, as the tube draws it.</h3><p class="dg-deck">Forty four scan lines, top to bottom, at the speed a television draws them. Every bar is still four lines thick at the bottom of the frame, the whole reason the mark is built out of eights and fours.</p><svg class="mk-art" viewBox="0 0 44 44" role="img" aria-label="The OmaCRT mark drawn as forty four scan lines, one at a time"><rect class="mk-line" data-row="0" x="0" y="0.08" width="34" height="0.84"/><rect class="mk-line" data-row="1" x="0" y="1.08" width="34" height="0.84"/><rect class="mk-line" data-row="2" x="0" y="2.08" width="34" height="0.84"/><rect class="mk-line" data-row="3" x="0" y="3.08" width="34" height="0.84"/><rect class="mk-line" data-row="4" x="0" y="4.08" width="34" height="0.84"/><rect class="mk-line" data-row="5" x="0" y="5.08" width="34" height="0.84"/><rect class="mk-line" data-row="6" x="0" y="6.08" width="34" height="0.84"/><rect class="mk-line" data-row="7" x="0" y="7.08" width="34" height="0.84"/><rect class="mk-line" data-row="12" x="0" y="12.08" width="22" height="0.84"/><rect class="mk-line" data-row="12" x="34" y="12.08" width="10" height="0.84"/><rect class="mk-line" data-row="13" x="0" y="13.08" width="22" height="0.84"/><rect class="mk-line" data-row="13" x="34" y="13.08" width="10" height="0.84"/><rect class="mk-line" data-row="14" x="0" y="14.08" width="22" height="0.84"/><rect class="mk-line" data-row="14" x="34" y="14.08" width="10" height="0.84"/><rect class="mk-line" data-row="15" x="0" y="15.08" width="22" height="0.84"/><rect class="mk-line" data-row="15" x="34" y="15.08" width="10" height="0.84"/><rect class="mk-line" data-row="16" x="0" y="16.08" width="22" height="0.84"/><rect class="mk-line" data-row="16" x="34" y="16.08" width="10" height="0.84"/><rect class="mk-line" data-row="17" x="0" y="17.08" width="22" height="0.84"/><rect class="mk-line" data-row="17" x="34" y="17.08" width="10" height="0.84"/><rect class="mk-line" data-row="18" x="0" y="18.08" width="22" height="0.84"/><rect class="mk-line" data-row="18" x="34" y="18.08" width="10" height="0.84"/><rect class="mk-line" data-row="19" x="0" y="19.08" width="22" height="0.84"/><rect class="mk-line" data-row="19" x="34" y="19.08" width="10" height="0.84"/><rect class="mk-line" data-row="24" x="0" y="24.08" width="10" height="0.84"/><rect class="mk-line" data-row="24" x="22" y="24.08" width="22" height="0.84"/><rect class="mk-line" data-row="25" x="0" y="25.08" width="10" height="0.84"/><rect class="mk-line" data-row="25" x="22" y="25.08" width="22" height="0.84"/><rect class="mk-line" data-row="26" x="0" y="26.08" width="10" height="0.84"/><rect class="mk-line" data-row="26" x="22" y="26.08" width="22" height="0.84"/><rect class="mk-line" data-row="27" x="0" y="27.08" width="10" height="0.84"/><rect class="mk-line" data-row="27" x="22" y="27.08" width="22" height="0.84"/><rect class="mk-line" data-row="28" x="0" y="28.08" width="10" height="0.84"/><rect class="mk-line" data-row="28" x="22" y="28.08" width="22" height="0.84"/><rect class="mk-line" data-row="29" x="0" y="29.08" width="10" height="0.84"/><rect class="mk-line" data-row="29" x="22" y="29.08" width="22" height="0.84"/><rect class="mk-line" data-row="30" x="0" y="30.08" width="10" height="0.84"/><rect class="mk-line" data-row="30" x="22" y="30.08" width="22" height="0.84"/><rect class="mk-line" data-row="31" x="0" y="31.08" width="10" height="0.84"/><rect class="mk-line" data-row="31" x="22" y="31.08" width="22" height="0.84"/><rect class="mk-line" data-row="36" x="10" y="36.08" width="34" height="0.84"/><rect class="mk-line" data-row="37" x="10" y="37.08" width="34" height="0.84"/><rect class="mk-line" data-row="38" x="10" y="38.08" width="34" height="0.84"/><rect class="mk-line" data-row="39" x="10" y="39.08" width="34" height="0.84"/><rect class="mk-line" data-row="40" x="10" y="40.08" width="34" height="0.84"/><rect class="mk-line" data-row="41" x="10" y="41.08" width="34" height="0.84"/><rect class="mk-line" data-row="42" x="10" y="42.08" width="34" height="0.84"/><rect class="mk-line" data-row="43" x="10" y="43.08" width="34" height="0.84"/></svg></div><p class="dg-run"><button type="button" class="dg-button" data-mark-run>Draw it again</button></p><figcaption>It runs once when it comes into view and then stops. Nothing on this site loops.</figcaption></figure>
<h2 id="what-it-does-not-show" class="log-sec log-sec-4"><a class="log-anchor" href="#what-it-does-not-show" aria-label="Link to this section"></a><span class="log-sec-n">08</span>What it does not show</h2>
<p>The agent wrote the SVG as rectangles on a 44 unit grid. No image generator was used.</p>
<p>I reviewed four versions on a canvas on my phone: the three directions, the refined mark and its rules, the first corrections, and the final alignment. The repository contains the working files, including the two unused directions.</p>
<p>The existing geometry gave the agent a useful constraint. It could propose related marks, render them at different sizes and document their construction in the same repository.</p>
<p>That does not make the choice of mark an arithmetic result. All three proposals used the deflection waveform as their subject; a different designer could have explored other ideas. I chose which direction to develop and judged the revisions visually.</p>
<p>The brief, proposals, specification, assets and two feedback rounds took an afternoon. The measurements helped refine the result, but they also needed checking.<span class="log-end" aria-hidden="true"></span></p>]]></content:encoded>
    </item>
    <item>
      <title>How a cathode ray tube works, and why one is fussy about what you send it</title>
      <link>https://omacrt.com/log/how-a-cathode-ray-tube-works/</link>
      <guid isPermaLink="true">https://omacrt.com/log/how-a-cathode-ray-tube-works/</guid>
      <pubDate>Sun, 13 Sep 2026 00:00:00 +0000</pubDate>
      <category>explainer</category>
      <description>A CRT draws its picture with an electron beam, deflection coils and a phosphor coating. This article explains how they work together and why the signal timing matters when driving a television from a PC.</description>
      <content:encoded><![CDATA[<p>A flat panel is a grid of cells and a memory holding what each one should be. Send it a picture and it keeps it until you send another. A cathode ray tube has one moving spot of light, and no grid, no cells and no memory. A picture exists because that spot goes round the whole screen sixty times a second, and because the coating keeps glowing for a few milliseconds after the spot has moved on.</p>
<p>Driving it requires a signal timed to the movement of that spot.</p>
<section class="peel-section" data-peel-section><figure class="peel" data-peel><div class="peel-left"><div class="peel-stage"><canvas class="peel-canvas" width="1000" height="640" aria-label="A professional video monitor drawn in three dimensions and taken apart one layer at a time: the back with its connectors, the two boards on runners, the case, the tube and its aperture grille, the deflection yoke and the neck board, the flyback transformer, the gun inside the glass, and the beam. Scroll to open the monitor and follow the picture to the phosphor, or use the chapter buttons. The component explorer can be turned, zoomed and switched on."></canvas><ul class="peel-labels" data-peel-labels></ul><p class="peel-readout" data-peel-readout>00 · a metal box with a handle on it</p></div><p class="peel-controls"><button type="button" class="dg-button" data-peel-power aria-pressed="false"><svg class="peel-ic" viewBox="0 0 8 8" aria-hidden="true" focusable="false"><rect x="3" y="0" width="1" height="1"/><rect x="4" y="0" width="1" height="1"/><rect x="1" y="1" width="1" height="1"/><rect x="3" y="1" width="1" height="1"/><rect x="4" y="1" width="1" height="1"/><rect x="6" y="1" width="1" height="1"/><rect x="0" y="2" width="1" height="1"/><rect x="3" y="2" width="1" height="1"/><rect x="4" y="2" width="1" height="1"/><rect x="7" y="2" width="1" height="1"/><rect x="0" y="3" width="1" height="1"/><rect x="3" y="3" width="1" height="1"/><rect x="4" y="3" width="1" height="1"/><rect x="7" y="3" width="1" height="1"/><rect x="0" y="4" width="1" height="1"/><rect x="7" y="4" width="1" height="1"/><rect x="0" y="5" width="1" height="1"/><rect x="7" y="5" width="1" height="1"/><rect x="1" y="6" width="1" height="1"/><rect x="6" y="6" width="1" height="1"/><rect x="2" y="7" width="1" height="1"/><rect x="3" y="7" width="1" height="1"/><rect x="4" y="7" width="1" height="1"/><rect x="5" y="7" width="1" height="1"/></svg><span>Switch it on</span></button><button type="button" class="dg-button" data-peel-slow aria-pressed="false"><svg class="peel-ic" viewBox="0 0 8 8" aria-hidden="true" focusable="false"><rect x="2" y="0" width="1" height="1"/><rect x="3" y="0" width="1" height="1"/><rect x="4" y="0" width="1" height="1"/><rect x="5" y="0" width="1" height="1"/><rect x="1" y="1" width="1" height="1"/><rect x="6" y="1" width="1" height="1"/><rect x="0" y="2" width="1" height="1"/><rect x="4" y="2" width="1" height="1"/><rect x="7" y="2" width="1" height="1"/><rect x="0" y="3" width="1" height="1"/><rect x="4" y="3" width="1" height="1"/><rect x="7" y="3" width="1" height="1"/><rect x="0" y="4" width="1" height="1"/><rect x="4" y="4" width="1" height="1"/><rect x="5" y="4" width="1" height="1"/><rect x="7" y="4" width="1" height="1"/><rect x="0" y="5" width="1" height="1"/><rect x="7" y="5" width="1" height="1"/><rect x="1" y="6" width="1" height="1"/><rect x="6" y="6" width="1" height="1"/><rect x="2" y="7" width="1" height="1"/><rect x="3" y="7" width="1" height="1"/><rect x="4" y="7" width="1" height="1"/><rect x="5" y="7" width="1" height="1"/></svg><span>Slow it down</span></button><button type="button" class="dg-button peel-zoom" data-peel-zoom="in" aria-label="Come closer">+</button><button type="button" class="dg-button peel-zoom" data-peel-zoom="out" aria-label="Stand back">−</button><button type="button" class="dg-button" data-peel-reset><svg class="peel-ic" viewBox="0 0 8 8" aria-hidden="true" focusable="false"><rect x="0" y="0" width="1" height="1"/><rect x="1" y="0" width="1" height="1"/><rect x="2" y="0" width="1" height="1"/><rect x="3" y="0" width="1" height="1"/><rect x="4" y="0" width="1" height="1"/><rect x="5" y="0" width="1" height="1"/><rect x="6" y="0" width="1" height="1"/><rect x="7" y="0" width="1" height="1"/><rect x="0" y="1" width="1" height="1"/><rect x="7" y="1" width="1" height="1"/><rect x="0" y="2" width="1" height="1"/><rect x="2" y="2" width="1" height="1"/><rect x="3" y="2" width="1" height="1"/><rect x="4" y="2" width="1" height="1"/><rect x="5" y="2" width="1" height="1"/><rect x="7" y="2" width="1" height="1"/><rect x="0" y="3" width="1" height="1"/><rect x="2" y="3" width="1" height="1"/><rect x="5" y="3" width="1" height="1"/><rect x="7" y="3" width="1" height="1"/><rect x="0" y="4" width="1" height="1"/><rect x="2" y="4" width="1" height="1"/><rect x="5" y="4" width="1" height="1"/><rect x="7" y="4" width="1" height="1"/><rect x="0" y="5" width="1" height="1"/><rect x="2" y="5" width="1" height="1"/><rect x="3" y="5" width="1" height="1"/><rect x="4" y="5" width="1" height="1"/><rect x="5" y="5" width="1" height="1"/><rect x="7" y="5" width="1" height="1"/><rect x="0" y="6" width="1" height="1"/><rect x="7" y="6" width="1" height="1"/><rect x="0" y="7" width="1" height="1"/><rect x="1" y="7" width="1" height="1"/><rect x="2" y="7" width="1" height="1"/><rect x="3" y="7" width="1" height="1"/><rect x="4" y="7" width="1" height="1"/><rect x="5" y="7" width="1" height="1"/><rect x="6" y="7" width="1" height="1"/><rect x="7" y="7" width="1" height="1"/></svg><span>Reset view</span></button><button type="button" class="dg-button peel-expand" data-peel-open><svg class="peel-ic" viewBox="0 0 8 8" aria-hidden="true" focusable="false"><rect x="0" y="0" width="1" height="1"/><rect x="1" y="0" width="1" height="1"/><rect x="2" y="0" width="1" height="1"/><rect x="3" y="0" width="1" height="1"/><rect x="4" y="0" width="1" height="1"/><rect x="5" y="0" width="1" height="1"/><rect x="6" y="0" width="1" height="1"/><rect x="7" y="0" width="1" height="1"/><rect x="0" y="1" width="1" height="1"/><rect x="7" y="1" width="1" height="1"/><rect x="0" y="2" width="1" height="1"/><rect x="7" y="2" width="1" height="1"/><rect x="0" y="3" width="1" height="1"/><rect x="7" y="3" width="1" height="1"/><rect x="0" y="4" width="1" height="1"/><rect x="7" y="4" width="1" height="1"/><rect x="0" y="5" width="1" height="1"/><rect x="1" y="5" width="1" height="1"/><rect x="2" y="5" width="1" height="1"/><rect x="3" y="5" width="1" height="1"/><rect x="4" y="5" width="1" height="1"/><rect x="5" y="5" width="1" height="1"/><rect x="6" y="5" width="1" height="1"/><rect x="7" y="5" width="1" height="1"/><rect x="2" y="6" width="1" height="1"/><rect x="5" y="6" width="1" height="1"/><rect x="1" y="7" width="1" height="1"/><rect x="2" y="7" width="1" height="1"/><rect x="3" y="7" width="1" height="1"/><rect x="4" y="7" width="1" height="1"/><rect x="5" y="7" width="1" height="1"/><rect x="6" y="7" width="1" height="1"/></svg><span>Full screen</span></button></p><p class="peel-stops" data-peel-stops></p></div><div class="peel-side"><p class="peel-eyebrow">Eight layers, in the order they come off</p><h3 class="peel-title" data-peel-title>The set</h3><p class="peel-body" data-peel-body>Scroll to take it apart. Drag to turn it, pinch or hold control and scroll to come closer, and pick any part to read what it is. Everything that comes off stays in view, where it was lifted to.</p><ol class="peel-rail" data-peel-rail></ol><p class="peel-note">A monitor is a box of air around one glass bottle, and every other part in it is there to bend the beam inside that bottle or to feed it. Switched on, the beam draws the way the set does: 48.9 microseconds of picture, 2.4 off at the right, 4.7 flung back to the left, 7.6 waiting there. Slowed to one line a second, all four are things you can watch.</p></div></figure></section><details class="peel-illustrations" data-peel-illustrations open><summary>Explore the eight illustrated layers</summary><div class="peel-stills" data-peel-stills><figure class="peel-still"><svg viewBox="0 0 518 584" role="img" aria-label="The set: a television in a room"><rect width="518" height="584" fill="#0b0d14"/><g transform="translate(0 0) scale(1.0)"><g transform="translate(44 132)"><g transform="translate(0 0)" opacity="1.0"><path d="M78 70 H400 C430 70 448 96 452 130 L456 200 L452 270 C448 304 430 330 400 330 H78 Z" fill="none" stroke="#c0caf5" stroke-width="2.2" stroke-linejoin="round"/><path d="M392 300 H436" stroke="#c0caf5" stroke-width="1" opacity="0.6"/><circle cx="400" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><circle cx="413" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><circle cx="426" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><path d="M150 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M159 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M168 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M177 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M186 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M195 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M204 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M213 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M222 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M231 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M240 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M249 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M258 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M267 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/></g><g transform="translate(0 0)" opacity="0.9"><path d="M78 70 H126 V330 H78 Z" fill="#2b3350" fill-opacity="0.55" stroke="#7f88ad" stroke-width="1.5"/><path d="M88 96 H116" stroke="#7f88ad" stroke-width="1.4" opacity="0.5"/><path d="M88 111 H116" stroke="#7f88ad" stroke-width="1.4" opacity="0.5"/><path d="M88 126 H116" stroke="#7f88ad" stroke-width="1.4" opacity="0.5"/><path d="M88 141 H116" stroke="#7f88ad" stroke-width="1.4" opacity="0.5"/><path d="M88 156 H116" stroke="#7f88ad" stroke-width="1.4" opacity="0.5"/><path d="M88 171 H116" stroke="#7f88ad" stroke-width="1.4" opacity="0.5"/><path d="M88 186 H116" stroke="#7f88ad" stroke-width="1.4" opacity="0.5"/><path d="M88 201 H116" stroke="#7f88ad" stroke-width="1.4" opacity="0.5"/><path d="M88 216 H116" stroke="#7f88ad" stroke-width="1.4" opacity="0.5"/><path d="M88 231 H116" stroke="#7f88ad" stroke-width="1.4" opacity="0.5"/><path d="M88 246 H116" stroke="#7f88ad" stroke-width="1.4" opacity="0.5"/><path d="M88 261 H116" stroke="#7f88ad" stroke-width="1.4" opacity="0.5"/><path d="M88 276 H116" stroke="#7f88ad" stroke-width="1.4" opacity="0.5"/></g></g></g></svg><h4>00 &middot; The set</h4><p>70 cm of moulded plastic, 24 kg, and a picture 34 cm across.</p></figure><figure class="peel-still"><svg viewBox="0 0 518 584" role="img" aria-label="The back: six screws, and dust"><rect width="518" height="584" fill="#0b0d14"/><g transform="translate(0 0) scale(1.0)"><g transform="translate(44 132)"><g transform="translate(0 0)" opacity="0.9"><path d="M78 70 H400 C430 70 448 96 452 130 L456 200 L452 270 C448 304 430 330 400 330 H78 Z" fill="none" stroke="#7f88ad" stroke-width="1.5" stroke-linejoin="round"/><path d="M392 300 H436" stroke="#7f88ad" stroke-width="1" opacity="0.6"/><circle cx="400" cy="313" r="3.4" fill="none" stroke="#7f88ad" stroke-width="1" opacity="0.75"/><circle cx="413" cy="313" r="3.4" fill="none" stroke="#7f88ad" stroke-width="1" opacity="0.75"/><circle cx="426" cy="313" r="3.4" fill="none" stroke="#7f88ad" stroke-width="1" opacity="0.75"/><path d="M150 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M159 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M168 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M177 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M186 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M195 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M204 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M213 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M222 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M231 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M240 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M249 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M258 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M267 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/></g><g transform="translate(-116 -6)" opacity="1.0"><path d="M78 70 H126 V330 H78 Z" fill="#2b3350" fill-opacity="0.55" stroke="#c0caf5" stroke-width="2.2"/><path d="M88 96 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 111 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 126 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 141 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 156 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 171 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 186 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 201 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 216 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 231 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 246 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 261 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 276 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/></g><g transform="translate(0 0)" opacity="0.9"><path d="M130 300 H418" stroke="#7f88ad" stroke-width="1.5"/><rect x="138" y="272" width="28" height="28" fill="none" stroke="#7f88ad" stroke-width="1.6"/><path d="M142 272 V300" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M147 272 V300" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M152 272 V300" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M157 272 V300" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M162 272 V300" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><rect x="180" y="276" width="36" height="24" fill="none" stroke="#7f88ad" stroke-width="1.6"/><circle cx="232" cy="289" r="5.4" fill="none" stroke="#7f88ad" stroke-width="1.4"/><circle cx="245" cy="289" r="5.4" fill="none" stroke="#7f88ad" stroke-width="1.4"/><rect x="276" y="274" width="16" height="26" fill="none" stroke="#7f88ad" stroke-width="1.6"/><path d="M296 272 V300" stroke="#7f88ad" stroke-width="1.2" opacity="0.6"/><path d="M300 272 V300" stroke="#7f88ad" stroke-width="1.2" opacity="0.6"/><path d="M304 272 V300" stroke="#7f88ad" stroke-width="1.2" opacity="0.6"/><path d="M308 272 V300" stroke="#7f88ad" stroke-width="1.2" opacity="0.6"/></g><g transform="translate(0 0)" opacity="0.9"><path d="M132 188 H222 L330 114 H420 C430 114 436 120 436 130 L438 200 L436 270 C436 280 430 286 420 286 H330 L222 212 H132 Z" fill="none" stroke="#7f88ad" stroke-width="1.5" stroke-linejoin="round" opacity="1.0"/><path d="M132 188 V212" stroke="#7f88ad" stroke-width="1.5" opacity="1.0"/><path d="M356 108 L364 108" stroke="#c0caf5" stroke-width="4" opacity="0.85"/><path d="M356 292 L364 292" stroke="#c0caf5" stroke-width="4" opacity="0.85"/><path d="M431 142 L433 200 L431 258" fill="none" stroke="#7f88ad" stroke-width="3.6" opacity="0.55"/></g><g transform="translate(0 0)" opacity="0.9"><path d="M222 186 L268 154 L280 170 L234 200 Z" fill="none" stroke="#7f88ad" stroke-width="1.5"/><path d="M222 214 L268 246 L280 230 L234 200 Z" fill="none" stroke="#7f88ad" stroke-width="1.5"/><path d="M234 184 L246 175" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M243 184 L255 175" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M252 184 L264 175" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M261 184 L273 175" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M234 216 L246 225" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M243 216 L255 225" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M252 216 L264 225" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M261 216 L273 225" stroke="#7f88ad" stroke-width="1" opacity="0.55"/></g><g transform="translate(0 0)" opacity="0.9"><ellipse cx="212" cy="200" rx="4.5" ry="15" fill="none" stroke="#7f88ad" stroke-width="1.5"/><ellipse cx="202" cy="200" rx="4.5" ry="15" fill="none" stroke="#7f88ad" stroke-width="1.5" opacity="0.7"/></g><g transform="translate(0 0)" opacity="0.9"><rect x="330" y="258" width="30" height="42" rx="2" fill="none" stroke="#7f88ad" stroke-width="1.5"/><path d="M330 268 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M330 277 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M330 286 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M330 295 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M345 258 C360 216 352 158 346 130" stroke="#f7768e" stroke-width="2.2" fill="none"/><path d="M338 122 a9 9 0 0 1 14 10" fill="none" stroke="#f7768e" stroke-width="2.4"/></g></g></g></svg><h4>01 &middot; The back</h4><p>Behind it, air. A set is mostly the space the bottle needs.</p></figure><figure class="peel-still"><svg viewBox="0 0 518 584" role="img" aria-label="The chassis: one board does everything"><rect width="518" height="584" fill="#0b0d14"/><g transform="translate(0 0) scale(1.0)"><g transform="translate(44 132)"><g transform="translate(0 0)" opacity="0.9"><path d="M78 70 H400 C430 70 448 96 452 130 L456 200 L452 270 C448 304 430 330 400 330 H78 Z" fill="none" stroke="#7f88ad" stroke-width="1.5" stroke-linejoin="round"/><path d="M392 300 H436" stroke="#7f88ad" stroke-width="1" opacity="0.6"/><circle cx="400" cy="313" r="3.4" fill="none" stroke="#7f88ad" stroke-width="1" opacity="0.75"/><circle cx="413" cy="313" r="3.4" fill="none" stroke="#7f88ad" stroke-width="1" opacity="0.75"/><circle cx="426" cy="313" r="3.4" fill="none" stroke="#7f88ad" stroke-width="1" opacity="0.75"/><path d="M150 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M159 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M168 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M177 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M186 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M195 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M204 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M213 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M222 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M231 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M240 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M249 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M258 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/><path d="M267 314 V324" stroke="#7f88ad" stroke-width="1" opacity="0.34"/></g><g transform="translate(-116 -6)" opacity="0.5"><path d="M78 70 H126 V330 H78 Z" fill="#2b3350" fill-opacity="0.55" stroke="#c0caf5" stroke-width="1.5"/><path d="M88 96 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 111 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 126 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 141 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 156 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 171 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 186 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 201 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 216 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 231 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 246 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 261 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 276 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/></g><g transform="translate(0 118)" opacity="1.0"><path d="M130 300 H418" stroke="#b98a4e" stroke-width="2.2"/><rect x="138" y="272" width="28" height="28" fill="none" stroke="#b98a4e" stroke-width="1.6"/><path d="M142 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M147 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M152 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M157 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M162 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><rect x="180" y="276" width="36" height="24" fill="none" stroke="#b98a4e" stroke-width="1.6"/><circle cx="232" cy="289" r="5.4" fill="none" stroke="#b98a4e" stroke-width="1.4"/><circle cx="245" cy="289" r="5.4" fill="none" stroke="#b98a4e" stroke-width="1.4"/><rect x="276" y="274" width="16" height="26" fill="none" stroke="#b98a4e" stroke-width="1.6"/><path d="M296 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M300 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M304 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M308 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/></g><g transform="translate(0 0)" opacity="0.9"><path d="M132 188 H222 L330 114 H420 C430 114 436 120 436 130 L438 200 L436 270 C436 280 430 286 420 286 H330 L222 212 H132 Z" fill="none" stroke="#7f88ad" stroke-width="1.5" stroke-linejoin="round" opacity="1.0"/><path d="M132 188 V212" stroke="#7f88ad" stroke-width="1.5" opacity="1.0"/><path d="M356 108 L364 108" stroke="#c0caf5" stroke-width="4" opacity="0.85"/><path d="M356 292 L364 292" stroke="#c0caf5" stroke-width="4" opacity="0.85"/><path d="M431 142 L433 200 L431 258" fill="none" stroke="#7f88ad" stroke-width="3.6" opacity="0.55"/></g><g transform="translate(0 0)" opacity="0.9"><path d="M222 186 L268 154 L280 170 L234 200 Z" fill="none" stroke="#7f88ad" stroke-width="1.5"/><path d="M222 214 L268 246 L280 230 L234 200 Z" fill="none" stroke="#7f88ad" stroke-width="1.5"/><path d="M234 184 L246 175" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M243 184 L255 175" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M252 184 L264 175" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M261 184 L273 175" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M234 216 L246 225" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M243 216 L255 225" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M252 216 L264 225" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M261 216 L273 225" stroke="#7f88ad" stroke-width="1" opacity="0.55"/></g><g transform="translate(0 0)" opacity="0.9"><ellipse cx="212" cy="200" rx="4.5" ry="15" fill="none" stroke="#7f88ad" stroke-width="1.5"/><ellipse cx="202" cy="200" rx="4.5" ry="15" fill="none" stroke="#7f88ad" stroke-width="1.5" opacity="0.7"/></g><g transform="translate(0 118)" opacity="0.9"><rect x="330" y="258" width="30" height="42" rx="2" fill="none" stroke="#7f88ad" stroke-width="1.5"/><path d="M330 268 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M330 277 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M330 286 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M330 295 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M345 258 C360 216 352 158 346 130" stroke="#f7768e" stroke-width="2.2" fill="none"/><path d="M338 122 a9 9 0 0 1 14 10" fill="none" stroke="#f7768e" stroke-width="2.4"/></g></g></g></svg><h4>02 &middot; The chassis</h4><p>Mains, supply, tuner, audio, and the line output stage.</p></figure><figure class="peel-still"><svg viewBox="0 0 518 584" role="img" aria-label="The tube: the largest single object"><rect width="518" height="584" fill="#0b0d14"/><g transform="translate(0 0) scale(1.0)"><g transform="translate(44 132)"><g transform="translate(0 -196)" opacity="0.5"><path d="M78 70 H400 C430 70 448 96 452 130 L456 200 L452 270 C448 304 430 330 400 330 H78 Z" fill="none" stroke="#c0caf5" stroke-width="1.5" stroke-linejoin="round"/><path d="M392 300 H436" stroke="#c0caf5" stroke-width="1" opacity="0.6"/><circle cx="400" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><circle cx="413" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><circle cx="426" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><path d="M150 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M159 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M168 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M177 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M186 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M195 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M204 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M213 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M222 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M231 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M240 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M249 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M258 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M267 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/></g><g transform="translate(-116 -6)" opacity="0.5"><path d="M78 70 H126 V330 H78 Z" fill="#2b3350" fill-opacity="0.55" stroke="#c0caf5" stroke-width="1.5"/><path d="M88 96 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 111 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 126 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 141 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 156 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 171 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 186 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 201 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 216 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 231 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 246 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 261 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 276 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/></g><g transform="translate(0 118)" opacity="0.5"><path d="M130 300 H418" stroke="#b98a4e" stroke-width="1.5"/><rect x="138" y="272" width="28" height="28" fill="none" stroke="#b98a4e" stroke-width="1.6"/><path d="M142 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M147 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M152 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M157 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M162 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><rect x="180" y="276" width="36" height="24" fill="none" stroke="#b98a4e" stroke-width="1.6"/><circle cx="232" cy="289" r="5.4" fill="none" stroke="#b98a4e" stroke-width="1.4"/><circle cx="245" cy="289" r="5.4" fill="none" stroke="#b98a4e" stroke-width="1.4"/><rect x="276" y="274" width="16" height="26" fill="none" stroke="#b98a4e" stroke-width="1.6"/><path d="M296 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M300 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M304 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M308 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/></g><g transform="translate(0 0)" opacity="1.0"><path d="M132 188 H222 L330 114 H420 C430 114 436 120 436 130 L438 200 L436 270 C436 280 430 286 420 286 H330 L222 212 H132 Z" fill="none" stroke="#9ece6a" stroke-width="2.2" stroke-linejoin="round" opacity="1.0"/><path d="M132 188 V212" stroke="#9ece6a" stroke-width="2.2" opacity="1.0"/><path d="M356 108 L364 108" stroke="#c0caf5" stroke-width="4" opacity="0.85"/><path d="M356 292 L364 292" stroke="#c0caf5" stroke-width="4" opacity="0.85"/><path d="M431 142 L433 200 L431 258" fill="none" stroke="#9ece6a" stroke-width="3.6" opacity="0.55"/></g><g transform="translate(0 0)" opacity="0.9"><path d="M222 186 L268 154 L280 170 L234 200 Z" fill="none" stroke="#7f88ad" stroke-width="1.5"/><path d="M222 214 L268 246 L280 230 L234 200 Z" fill="none" stroke="#7f88ad" stroke-width="1.5"/><path d="M234 184 L246 175" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M243 184 L255 175" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M252 184 L264 175" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M261 184 L273 175" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M234 216 L246 225" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M243 216 L255 225" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M252 216 L264 225" stroke="#7f88ad" stroke-width="1" opacity="0.55"/><path d="M261 216 L273 225" stroke="#7f88ad" stroke-width="1" opacity="0.55"/></g><g transform="translate(0 0)" opacity="0.9"><ellipse cx="212" cy="200" rx="4.5" ry="15" fill="none" stroke="#7f88ad" stroke-width="1.5"/><ellipse cx="202" cy="200" rx="4.5" ry="15" fill="none" stroke="#7f88ad" stroke-width="1.5" opacity="0.7"/></g><g transform="translate(0 118)" opacity="0.9"><rect x="330" y="258" width="30" height="42" rx="2" fill="none" stroke="#7f88ad" stroke-width="1.5"/><path d="M330 268 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M330 277 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M330 286 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M330 295 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M345 258 C360 216 352 158 346 130" stroke="#f7768e" stroke-width="2.2" fill="none"/><path d="M338 122 a9 9 0 0 1 14 10" fill="none" stroke="#f7768e" stroke-width="2.4"/></g></g></g></svg><h4>03 &middot; The tube</h4><p>Held by four lugs at the corners, with a steel band round the rim.</p></figure><figure class="peel-still"><svg viewBox="0 0 518 584" role="img" aria-label="The neck: the yoke, and what aims it"><rect width="518" height="584" fill="#0b0d14"/><g transform="translate(0 0) scale(1.0)"><g transform="translate(44 132)"><g transform="translate(0 -196)" opacity="0.5"><path d="M78 70 H400 C430 70 448 96 452 130 L456 200 L452 270 C448 304 430 330 400 330 H78 Z" fill="none" stroke="#c0caf5" stroke-width="1.5" stroke-linejoin="round"/><path d="M392 300 H436" stroke="#c0caf5" stroke-width="1" opacity="0.6"/><circle cx="400" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><circle cx="413" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><circle cx="426" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><path d="M150 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M159 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M168 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M177 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M186 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M195 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M204 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M213 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M222 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M231 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M240 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M249 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M258 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M267 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/></g><g transform="translate(-116 -6)" opacity="0.5"><path d="M78 70 H126 V330 H78 Z" fill="#2b3350" fill-opacity="0.55" stroke="#c0caf5" stroke-width="1.5"/><path d="M88 96 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 111 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 126 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 141 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 156 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 171 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 186 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 201 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 216 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 231 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 246 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 261 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 276 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/></g><g transform="translate(0 118)" opacity="0.5"><path d="M130 300 H418" stroke="#b98a4e" stroke-width="1.5"/><rect x="138" y="272" width="28" height="28" fill="none" stroke="#b98a4e" stroke-width="1.6"/><path d="M142 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M147 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M152 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M157 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M162 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><rect x="180" y="276" width="36" height="24" fill="none" stroke="#b98a4e" stroke-width="1.6"/><circle cx="232" cy="289" r="5.4" fill="none" stroke="#b98a4e" stroke-width="1.4"/><circle cx="245" cy="289" r="5.4" fill="none" stroke="#b98a4e" stroke-width="1.4"/><rect x="276" y="274" width="16" height="26" fill="none" stroke="#b98a4e" stroke-width="1.6"/><path d="M296 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M300 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M304 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M308 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/></g><g transform="translate(0 0)" opacity="0.9"><path d="M132 188 H222 L330 114 H420 C430 114 436 120 436 130 L438 200 L436 270 C436 280 430 286 420 286 H330 L222 212 H132 Z" fill="none" stroke="#7f88ad" stroke-width="1.5" stroke-linejoin="round" opacity="1.0"/><path d="M132 188 V212" stroke="#7f88ad" stroke-width="1.5" opacity="1.0"/><path d="M356 108 L364 108" stroke="#c0caf5" stroke-width="4" opacity="0.85"/><path d="M356 292 L364 292" stroke="#c0caf5" stroke-width="4" opacity="0.85"/><path d="M431 142 L433 200 L431 258" fill="none" stroke="#7f88ad" stroke-width="3.6" opacity="0.55"/></g><g transform="translate(-86 -104)" opacity="1.0"><path d="M222 186 L268 154 L280 170 L234 200 Z" fill="none" stroke="#b98a4e" stroke-width="2.2"/><path d="M222 214 L268 246 L280 230 L234 200 Z" fill="none" stroke="#b98a4e" stroke-width="2.2"/><path d="M234 184 L246 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M243 184 L255 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M252 184 L264 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M261 184 L273 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M234 216 L246 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M243 216 L255 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M252 216 L264 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M261 216 L273 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/></g><g transform="translate(-120 -70)" opacity="0.5"><ellipse cx="212" cy="200" rx="4.5" ry="15" fill="none" stroke="#b98a4e" stroke-width="1.5"/><ellipse cx="202" cy="200" rx="4.5" ry="15" fill="none" stroke="#b98a4e" stroke-width="1.5" opacity="0.7"/></g><g transform="translate(0 118)" opacity="0.9"><rect x="330" y="258" width="30" height="42" rx="2" fill="none" stroke="#7f88ad" stroke-width="1.5"/><path d="M330 268 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M330 277 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M330 286 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M330 295 H360" stroke="#7f88ad" stroke-width="1" opacity="0.45"/><path d="M345 258 C360 216 352 158 346 130" stroke="#f7768e" stroke-width="2.2" fill="none"/><path d="M338 122 a9 9 0 0 1 14 10" fill="none" stroke="#f7768e" stroke-width="2.4"/></g></g></g></svg><h4>04 &middot; The neck</h4><p>Two pairs of coils. One bends left and right, the other down.</p></figure><figure class="peel-still"><svg viewBox="0 0 518 584" role="img" aria-label="The high voltage: a matchbox with a red wire"><rect width="518" height="584" fill="#0b0d14"/><g transform="translate(0 0) scale(1.0)"><g transform="translate(44 132)"><g transform="translate(0 -196)" opacity="0.5"><path d="M78 70 H400 C430 70 448 96 452 130 L456 200 L452 270 C448 304 430 330 400 330 H78 Z" fill="none" stroke="#c0caf5" stroke-width="1.5" stroke-linejoin="round"/><path d="M392 300 H436" stroke="#c0caf5" stroke-width="1" opacity="0.6"/><circle cx="400" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><circle cx="413" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><circle cx="426" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><path d="M150 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M159 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M168 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M177 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M186 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M195 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M204 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M213 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M222 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M231 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M240 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M249 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M258 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M267 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/></g><g transform="translate(-116 -6)" opacity="0.5"><path d="M78 70 H126 V330 H78 Z" fill="#2b3350" fill-opacity="0.55" stroke="#c0caf5" stroke-width="1.5"/><path d="M88 96 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 111 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 126 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 141 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 156 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 171 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 186 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 201 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 216 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 231 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 246 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 261 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 276 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/></g><g transform="translate(0 118)" opacity="0.5"><path d="M130 300 H418" stroke="#b98a4e" stroke-width="1.5"/><rect x="138" y="272" width="28" height="28" fill="none" stroke="#b98a4e" stroke-width="1.6"/><path d="M142 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M147 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M152 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M157 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M162 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><rect x="180" y="276" width="36" height="24" fill="none" stroke="#b98a4e" stroke-width="1.6"/><circle cx="232" cy="289" r="5.4" fill="none" stroke="#b98a4e" stroke-width="1.4"/><circle cx="245" cy="289" r="5.4" fill="none" stroke="#b98a4e" stroke-width="1.4"/><rect x="276" y="274" width="16" height="26" fill="none" stroke="#b98a4e" stroke-width="1.6"/><path d="M296 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M300 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M304 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M308 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/></g><g transform="translate(0 0)" opacity="0.9"><path d="M132 188 H222 L330 114 H420 C430 114 436 120 436 130 L438 200 L436 270 C436 280 430 286 420 286 H330 L222 212 H132 Z" fill="none" stroke="#7f88ad" stroke-width="1.5" stroke-linejoin="round" opacity="1.0"/><path d="M132 188 V212" stroke="#7f88ad" stroke-width="1.5" opacity="1.0"/><path d="M356 108 L364 108" stroke="#c0caf5" stroke-width="4" opacity="0.85"/><path d="M356 292 L364 292" stroke="#c0caf5" stroke-width="4" opacity="0.85"/><path d="M431 142 L433 200 L431 258" fill="none" stroke="#7f88ad" stroke-width="3.6" opacity="0.55"/></g><g transform="translate(-86 -104)" opacity="0.5"><path d="M222 186 L268 154 L280 170 L234 200 Z" fill="none" stroke="#b98a4e" stroke-width="1.5"/><path d="M222 214 L268 246 L280 230 L234 200 Z" fill="none" stroke="#b98a4e" stroke-width="1.5"/><path d="M234 184 L246 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M243 184 L255 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M252 184 L264 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M261 184 L273 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M234 216 L246 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M243 216 L255 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M252 216 L264 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M261 216 L273 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/></g><g transform="translate(-120 -70)" opacity="0.5"><ellipse cx="212" cy="200" rx="4.5" ry="15" fill="none" stroke="#b98a4e" stroke-width="1.5"/><ellipse cx="202" cy="200" rx="4.5" ry="15" fill="none" stroke="#b98a4e" stroke-width="1.5" opacity="0.7"/></g><g transform="translate(118 62)" opacity="1.0"><rect x="330" y="258" width="30" height="42" rx="2" fill="none" stroke="#ff9e64" stroke-width="2.2"/><path d="M330 268 H360" stroke="#ff9e64" stroke-width="1" opacity="0.45"/><path d="M330 277 H360" stroke="#ff9e64" stroke-width="1" opacity="0.45"/><path d="M330 286 H360" stroke="#ff9e64" stroke-width="1" opacity="0.45"/><path d="M330 295 H360" stroke="#ff9e64" stroke-width="1" opacity="0.45"/><path d="M345 258 C360 216 352 158 346 130" stroke="#f7768e" stroke-width="2.2" fill="none"/><path d="M338 122 a9 9 0 0 1 14 10" fill="none" stroke="#f7768e" stroke-width="2.4"/></g></g></g></svg><h4>05 &middot; The high voltage</h4><p>The part that decides what signal the set will take. 15.7 kHz, no other.</p></figure><figure class="peel-still"><svg viewBox="0 0 518 584" role="img" aria-label="Inside the glass: the gun stack, in the neck"><rect width="518" height="584" fill="#0b0d14"/><g transform="translate(0 0) scale(1.0)"><g transform="translate(44 132)"><g transform="translate(0 -196)" opacity="0.5"><path d="M78 70 H400 C430 70 448 96 452 130 L456 200 L452 270 C448 304 430 330 400 330 H78 Z" fill="none" stroke="#c0caf5" stroke-width="1.5" stroke-linejoin="round"/><path d="M392 300 H436" stroke="#c0caf5" stroke-width="1" opacity="0.6"/><circle cx="400" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><circle cx="413" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><circle cx="426" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><path d="M150 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M159 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M168 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M177 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M186 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M195 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M204 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M213 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M222 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M231 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M240 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M249 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M258 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M267 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/></g><g transform="translate(-116 -6)" opacity="0.5"><path d="M78 70 H126 V330 H78 Z" fill="#2b3350" fill-opacity="0.55" stroke="#c0caf5" stroke-width="1.5"/><path d="M88 96 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 111 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 126 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 141 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 156 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 171 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 186 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 201 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 216 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 231 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 246 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 261 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 276 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/></g><g transform="translate(0 118)" opacity="0.5"><path d="M130 300 H418" stroke="#b98a4e" stroke-width="1.5"/><rect x="138" y="272" width="28" height="28" fill="none" stroke="#b98a4e" stroke-width="1.6"/><path d="M142 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M147 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M152 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M157 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M162 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><rect x="180" y="276" width="36" height="24" fill="none" stroke="#b98a4e" stroke-width="1.6"/><circle cx="232" cy="289" r="5.4" fill="none" stroke="#b98a4e" stroke-width="1.4"/><circle cx="245" cy="289" r="5.4" fill="none" stroke="#b98a4e" stroke-width="1.4"/><rect x="276" y="274" width="16" height="26" fill="none" stroke="#b98a4e" stroke-width="1.6"/><path d="M296 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M300 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M304 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M308 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/></g><g transform="translate(0 0)" opacity="0.9"><path d="M132 188 H222 L330 114 H420 C430 114 436 120 436 130 L438 200 L436 270 C436 280 430 286 420 286 H330 L222 212 H132 Z" fill="none" stroke="#7f88ad" stroke-width="1.5" stroke-linejoin="round" opacity="0.45"/><path d="M132 188 V212" stroke="#7f88ad" stroke-width="1.5" opacity="0.45"/><path d="M356 108 L364 108" stroke="#c0caf5" stroke-width="4" opacity="0.85"/><path d="M356 292 L364 292" stroke="#c0caf5" stroke-width="4" opacity="0.85"/><path d="M431 142 L433 200 L431 258" fill="none" stroke="#7f88ad" stroke-width="3.6" opacity="0.55"/></g><g transform="translate(-86 -104)" opacity="0.5"><path d="M222 186 L268 154 L280 170 L234 200 Z" fill="none" stroke="#b98a4e" stroke-width="1.5"/><path d="M222 214 L268 246 L280 230 L234 200 Z" fill="none" stroke="#b98a4e" stroke-width="1.5"/><path d="M234 184 L246 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M243 184 L255 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M252 184 L264 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M261 184 L273 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M234 216 L246 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M243 216 L255 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M252 216 L264 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M261 216 L273 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/></g><g transform="translate(-120 -70)" opacity="0.5"><ellipse cx="212" cy="200" rx="4.5" ry="15" fill="none" stroke="#b98a4e" stroke-width="1.5"/><ellipse cx="202" cy="200" rx="4.5" ry="15" fill="none" stroke="#b98a4e" stroke-width="1.5" opacity="0.7"/></g><g transform="translate(118 62)" opacity="0.5"><rect x="330" y="258" width="30" height="42" rx="2" fill="none" stroke="#ff9e64" stroke-width="1.5"/><path d="M330 268 H360" stroke="#ff9e64" stroke-width="1" opacity="0.45"/><path d="M330 277 H360" stroke="#ff9e64" stroke-width="1" opacity="0.45"/><path d="M330 286 H360" stroke="#ff9e64" stroke-width="1" opacity="0.45"/><path d="M330 295 H360" stroke="#ff9e64" stroke-width="1" opacity="0.45"/><path d="M345 258 C360 216 352 158 346 130" stroke="#f7768e" stroke-width="2.2" fill="none"/><path d="M338 122 a9 9 0 0 1 14 10" fill="none" stroke="#f7768e" stroke-width="2.4"/></g><rect x="140" y="193" width="9" height="14" fill="none" stroke="#c0caf5" stroke-width="1.8"/><path d="M132 196 H140 M132 204 H140" stroke="#f7768e" stroke-width="1.4"/><path d="M156 190 V210" stroke="#c0caf5" stroke-width="1.8"/><path d="M164 190 V210" stroke="#c0caf5" stroke-width="1.8"/><path d="M172 190 V210" stroke="#c0caf5" stroke-width="1.8"/><rect x="182" y="192" width="12" height="16" fill="none" stroke="#c0caf5" stroke-width="1.8"/></g></g></svg><h4>06 &middot; Inside the glass</h4><p>A cathode at 800 degrees and a stack of grids. The first one is the picture.</p></figure><figure class="peel-still"><svg viewBox="0 0 518 584" role="img" aria-label="The beam: the only part that is not an object"><rect width="518" height="584" fill="#0b0d14"/><g transform="translate(0 0) scale(1.0)"><g transform="translate(44 132)"><g transform="translate(0 -196)" opacity="0.5"><path d="M78 70 H400 C430 70 448 96 452 130 L456 200 L452 270 C448 304 430 330 400 330 H78 Z" fill="none" stroke="#c0caf5" stroke-width="1.5" stroke-linejoin="round"/><path d="M392 300 H436" stroke="#c0caf5" stroke-width="1" opacity="0.6"/><circle cx="400" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><circle cx="413" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><circle cx="426" cy="313" r="3.4" fill="none" stroke="#c0caf5" stroke-width="1" opacity="0.75"/><path d="M150 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M159 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M168 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M177 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M186 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M195 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M204 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M213 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M222 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M231 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M240 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M249 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M258 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/><path d="M267 314 V324" stroke="#c0caf5" stroke-width="1" opacity="0.34"/></g><g transform="translate(-116 -6)" opacity="0.5"><path d="M78 70 H126 V330 H78 Z" fill="#2b3350" fill-opacity="0.55" stroke="#c0caf5" stroke-width="1.5"/><path d="M88 96 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 111 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 126 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 141 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 156 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 171 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 186 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 201 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 216 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 231 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 246 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 261 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/><path d="M88 276 H116" stroke="#c0caf5" stroke-width="1.4" opacity="0.5"/></g><g transform="translate(0 118)" opacity="0.5"><path d="M130 300 H418" stroke="#b98a4e" stroke-width="1.5"/><rect x="138" y="272" width="28" height="28" fill="none" stroke="#b98a4e" stroke-width="1.6"/><path d="M142 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M147 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M152 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M157 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><path d="M162 272 V300" stroke="#b98a4e" stroke-width="1" opacity="0.45"/><rect x="180" y="276" width="36" height="24" fill="none" stroke="#b98a4e" stroke-width="1.6"/><circle cx="232" cy="289" r="5.4" fill="none" stroke="#b98a4e" stroke-width="1.4"/><circle cx="245" cy="289" r="5.4" fill="none" stroke="#b98a4e" stroke-width="1.4"/><rect x="276" y="274" width="16" height="26" fill="none" stroke="#b98a4e" stroke-width="1.6"/><path d="M296 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M300 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M304 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/><path d="M308 272 V300" stroke="#b98a4e" stroke-width="1.2" opacity="0.6"/></g><g transform="translate(0 0)" opacity="0.9"><path d="M132 188 H222 L330 114 H420 C430 114 436 120 436 130 L438 200 L436 270 C436 280 430 286 420 286 H330 L222 212 H132 Z" fill="none" stroke="#7f88ad" stroke-width="1.5" stroke-linejoin="round" opacity="0.45"/><path d="M132 188 V212" stroke="#7f88ad" stroke-width="1.5" opacity="0.45"/><path d="M356 108 L364 108" stroke="#c0caf5" stroke-width="4" opacity="0.85"/><path d="M356 292 L364 292" stroke="#c0caf5" stroke-width="4" opacity="0.85"/><path d="M431 142 L433 200 L431 258" fill="none" stroke="#7f88ad" stroke-width="3.6" opacity="0.55"/></g><g transform="translate(-86 -104)" opacity="0.5"><path d="M222 186 L268 154 L280 170 L234 200 Z" fill="none" stroke="#b98a4e" stroke-width="1.5"/><path d="M222 214 L268 246 L280 230 L234 200 Z" fill="none" stroke="#b98a4e" stroke-width="1.5"/><path d="M234 184 L246 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M243 184 L255 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M252 184 L264 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M261 184 L273 175" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M234 216 L246 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M243 216 L255 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M252 216 L264 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/><path d="M261 216 L273 225" stroke="#b98a4e" stroke-width="1" opacity="0.55"/></g><g transform="translate(-120 -70)" opacity="0.5"><ellipse cx="212" cy="200" rx="4.5" ry="15" fill="none" stroke="#b98a4e" stroke-width="1.5"/><ellipse cx="202" cy="200" rx="4.5" ry="15" fill="none" stroke="#b98a4e" stroke-width="1.5" opacity="0.7"/></g><g transform="translate(118 62)" opacity="0.5"><rect x="330" y="258" width="30" height="42" rx="2" fill="none" stroke="#ff9e64" stroke-width="1.5"/><path d="M330 268 H360" stroke="#ff9e64" stroke-width="1" opacity="0.45"/><path d="M330 277 H360" stroke="#ff9e64" stroke-width="1" opacity="0.45"/><path d="M330 286 H360" stroke="#ff9e64" stroke-width="1" opacity="0.45"/><path d="M330 295 H360" stroke="#ff9e64" stroke-width="1" opacity="0.45"/><path d="M345 258 C360 216 352 158 346 130" stroke="#f7768e" stroke-width="2.2" fill="none"/><path d="M338 122 a9 9 0 0 1 14 10" fill="none" stroke="#f7768e" stroke-width="2.4"/></g><rect x="140" y="193" width="9" height="14" fill="none" stroke="#7f88ad" stroke-width="1.4"/><path d="M132 196 H140 M132 204 H140" stroke="#f7768e" stroke-width="1.4"/><path d="M156 190 V210" stroke="#7f88ad" stroke-width="1.4"/><path d="M164 190 V210" stroke="#7f88ad" stroke-width="1.4"/><path d="M172 190 V210" stroke="#7f88ad" stroke-width="1.4"/><rect x="182" y="192" width="12" height="16" fill="none" stroke="#7f88ad" stroke-width="1.4"/><path d="M150 200 H244 L430 168" stroke="#9ece6a" stroke-width="1.8" fill="none"/><circle cx="430" cy="168" r="11" fill="#9ece6a" opacity="0.16"/><circle cx="430" cy="168" r="5.6" fill="#9ece6a" opacity="0.35"/><circle cx="430" cy="168" r="2.6" fill="#9ece6a"/></g></g></svg><h4>07 &middot; The beam</h4><p>48.9 microseconds across, 4.7 back. 15,730 times a second.</p></figure></div></details>
<h2 id="the-beam" class="log-sec log-sec-1"><a class="log-anchor" href="#the-beam" aria-label="Link to this section"></a><span class="log-sec-n">01</span>The beam</h2>
<p>At the back of the neck is a cathode, heated to about 800 degrees, with a coating that gives up electrons easily when it is hot. Around it sit a stack of metal grids at different voltages. The first one is the important one: make it more negative and fewer electrons get past it, make it less negative and more do. That grid is where the picture signal goes. A line of video is a voltage on it, changing a few million times a second. What this set resolves is nearer four to six megahertz, measured on the chain rather than assumed.</p>
<p>The rest of the grids focus what gets through into a beam a fraction of a millimetre across. The coating on the inside of the funnel, held at twenty odd kilovolts, then accelerates it the length of the tube. The electrons leave the gun slowly and arrive fast. That is why the glass at the front has to be thick, and why a tube of any size is heavy.</p>
<h2 id="bending-it" class="log-sec log-sec-2"><a class="log-anchor" href="#bending-it" aria-label="Link to this section"></a><span class="log-sec-n">02</span>Bending it</h2>
<p>The beam would land in the middle of the screen and stay there. What moves it is the yoke: two pairs of coils clamped around the neck, one pair bending it left and right, the other pulling it down the screen.</p>
<p>The horizontal pair determines the line rate this project must respect. To draw a line, the current through those coils ramps from one end to the other and then collapses back. That has to happen <strong>15,731 times a second</strong> on this set. A coil resists a change in the current through it, so what drives it is a tuned circuit instead of a plain amplifier, built to resonate at one frequency with the coil's own inductance as part of the tuning.</p>
<p>This tuned circuit limits the frequencies a television can accept. Sending a different timing asks the deflection hardware to operate outside the conditions it was built for.</p>
<h2 id="the-flyback" class="log-sec log-sec-3"><a class="log-anchor" href="#the-flyback" aria-label="Link to this section"></a><span class="log-sec-n">03</span>The flyback</h2>
<p>At the end of every line the current in the horizontal coils is at one extreme and has to get back to the other. It is not eased back. The drive is cut, the circuit is allowed to swing, and the beam is flung back across the screen in 4.7 microseconds, against the 48.9 it spent crossing. About a tenth of the time, going the other way.</p>
<p>Blanking and retrace are not the same thing. A line on this project's television is 63.6 microseconds long and only 48.9 of them carry picture. The other 14.7 are blanking, and the beam is not flying back for all of it. A front porch of 2.4 microseconds holds the beam off and still at the right. Then the sync pulse of 4.7, where it actually flies back. Then a back porch of 7.6, where it is off, at the left, and waiting.</p>
<p>The retrace is the sync pulse. The porches are the set being given time to settle and to clamp its black level, and a signal that skips them is one a television will not lock to.</p>
<p>That swing is a large, fast change of current in a coil, producing a large voltage across it. The transformer wound around the same core steps up that voltage, which is then rectified to supply the anode’s twenty odd kilovolts. <strong>One component draws the picture sideways and supplies the accelerating voltage</strong>, out of the same collapse, once a line.</p>
<p>That return motion gives the flyback transformer its name. I used the same name for this project’s compositor.</p>
<p>Switch the model on above and slow it to one line a second. The sweep is the part where the picture is, the flick back to the left is the flyback, and the two pauses either side of it are the porches. The same thing happens at the bottom of the frame, more slowly: 22 blanked lines, 1.4 milliseconds, to get the beam back to the top.</p>
<h2 id="the-screen" class="log-sec log-sec-4"><a class="log-anchor" href="#the-screen" aria-label="Link to this section"></a><span class="log-sec-n">04</span>The screen</h2>
<p>The inside of the faceplate is coated in a phosphor. An electron lands, gives up its energy, and the coating emits light for a few milliseconds and then stops.</p>
<p>The tube has no frame buffer, scaler or panel timing controller. At any instant, the viewer sees the bright spot and the fading glow of the lines it has just drawn. The picture is an artefact of persistence, partly in the phosphor and partly in the eye.</p>
<p>This matters to the <a href="https://omacrt.com/log/flyback/">Flyback study</a>, which measures latency up to the start of scanout. A flat panel can buffer the frame after that point. The CRT draws from the incoming signal; the study discusses the remaining unmeasured delay through the converter and phosphor.</p>
<h2 id="why-15-khz-and-not-31" class="log-sec log-sec-1"><a class="log-anchor" href="#why-15-khz-and-not-31" aria-label="Link to this section"></a><span class="log-sec-n">05</span>Why 15 kHz and not 31</h2>
<p>A computer monitor of the same era runs its line rate at 31.5 kHz or higher, and draws 480 or more lines progressively. A television runs at 15.7 and draws 240 lines in a field.</p>
<p>The number is what a broadcast standard settled on in the 1940s with the parts available then, and everything downstream inherited it: the yoke's inductance, the transformer's core, the capacitor tuned with it.</p>
<p>Send a set built for 15.7 kHz a signal at 31.5, and the horizontal output transistor is switched twice as often as it was designed for, into a circuit that is no longer resonant at the driving frequency. The transistor dissipates what it cannot pass on.</p>
<p>Flyback checks timings before passing them to the kernel, to avoid driving that circuit outside the configured frequency range.</p>
<p>The response depends on the set. Some lose sync, blank or shut down; others have little protection. This project has not tested out-of-range signals on a television.</p>
<h2 id="what-the-model-above-does-not-have" class="log-sec log-sec-2"><a class="log-anchor" href="#what-the-model-above-does-not-have" aria-label="Link to this section"></a><span class="log-sec-n">06</span>What the model above does not have</h2>
<p>The model simplifies or omits four components.</p>
<p>The <strong>shadow mask or aperture grille</strong> sits just behind the phosphor on a colour set. It makes sure the beam for one colour can only land on that colour's phosphor. A colour tube is dim next to a monochrome one for that reason. Drawing it needs three guns.</p>
<p>Convergence is the business of making three beams land in the same place at the corners as well as in the middle, the fiddliest thing about a colour tube. One beam has no convergence problem.</p>
<p>The <strong>degauss coil</strong> is the loop around the front of the tube that fires for a second at switch-on to demagnetise the mask, and it does nothing a model can show.</p>
<p>And <strong>the high voltage supply</strong> is drawn as one component here. On a real chassis it is a multiplier, a regulator and a focus divider, all of it hanging off the flyback.</p>
<h2 id="the-imperfections-were-the-medium" class="log-sec log-sec-3"><a class="log-anchor" href="#the-imperfections-were-the-medium" aria-label="Link to this section"></a><span class="log-sec-n">07</span>The imperfections were the medium</h2>
<p>For about twenty five years, game artists and programmers worked with these display characteristics. Some effects depended on the tube; others depended on the composite signal used to reach it.</p>
<p>There is no grid of pixels. The spot is a fuzzy round thing with a brightness that falls off from the middle, and it does not aim at a particular phosphor: whatever timing arrives is native, and there is nothing to resample onto anything.<sup class="log-fn"><a id="fn-1" href="#ref-1">1</a></sup> A modern panel has a fixed number of cells and a scaler in front of them, so a 240 line picture is 240 lines interpolated onto a thousand and something. Here it is 240 lines.</p>
<p>It is an impulse and not a hold. A panel lights a frame and keeps it lit until the next one. A tube flashes a line and lets it die. The blur you see when something moves across a flat panel comes from that holding rather than from the refresh rate. It is why panels sold for games strobe their backlight or insert black frames, which imitates a tube.<sup class="log-fn"><a id="fn-2" href="#ref-2">2</a></sup></p>
<p>The tube emits light as the signal is scanned out. The <a href="https://omacrt.com/log/flyback/">Flyback study</a> uses this relationship to interpret its software measurements, while distinguishing them from an optical latency measurement.</p>
<p>Bright spots spread into neighbouring areas, softening edges. Artists could account for that when drawing sprites and backgrounds.</p>
<h3 id="what-that-was-worth-to-somebody-drawing-what-it-was-worth">What that was worth to somebody drawing | what it was worth</h3>
<p><strong>Colour, out of timing.</strong> The Apple II has no colour hardware. Wozniak clocked its black and white dot stream at a multiple of the television's colour subcarrier, so certain alternations of on and off arrive at the decoder as colour. Two pixels give purple or green depending on the phase, and the eighth bit of every byte delays the clock by half a cycle to give blue and orange as well.<sup class="log-fn"><a id="fn-3" href="#ref-3">3</a></sup></p>
<p>The IBM PC’s CGA uses the same effect on the same pixel clock.<sup class="log-fn"><a id="fn-4" href="#ref-4">4</a></sup> These colours arise when the television decodes the deliberately timed signal.</p>
<p>Transparency, out of a narrow channel. Composite video carries colour on a channel narrower than the one it carries brightness on, so two colours in alternating columns arrive averaged into one. The waterfalls in the first _Sonic the Hedgehog_ are a checkerboard of one blue over the rock behind it, and on the screen they were drawn for they are water.<sup class="log-fn"><a id="fn-5" href="#ref-5">5</a></sup> An RGB connection preserves that checkerboard, as does emulation without composite filtering.<sup class="log-fn"><a id="fn-6" href="#ref-6">6</a></sup></p>
<figure class="log-diagram" id="fig-dither"><div class="dg"><h3 class="dg-headline">Half a colour, on a screen that averages.</h3><p class="dg-deck">Composite video carries colour on a channel narrower than the one it carries brightness on, so two colours in alternating columns arrive as one. <b>The transparency is not in the picture. It is in the screen the picture was drawn for.</b></p><div class="dg-scroll"><svg viewBox="0 0 1040 380" class="dg-art" role="img" aria-label="The same block of chequered rectangles drawn twice: on the left as it is, a checkerboard of one blue over dark rock, and on the right smeared along each line until the two colours average into a single sheet of water."><defs><filter id="dg-composite" x="-10%" y="-10%" width="120%" height="120%"><feGaussianBlur stdDeviation="7 0"/></filter></defs><text x="40" y="66" class="dg-mono dg-q" font-size="12" font-weight="500" letter-spacing="0.18em" text-anchor="start">AS RGB, OR IN AN EMULATOR</text><text x="580" y="66" class="dg-mono dg-g" font-size="12" font-weight="500" letter-spacing="0.18em" text-anchor="start">AS COMPOSITE, THE SCREEN IT WAS DRAWN FOR</text><rect class="dg-rock" x="40" y="96" width="220" height="198"/><rect class="dg-water" x="40" y="96" width="11" height="11"/><rect class="dg-water" x="51" y="96" width="11" height="11"/><rect class="dg-water" x="62" y="96" width="11" height="11"/><rect class="dg-water" x="73" y="96" width="11" height="11"/><rect class="dg-water" x="84" y="96" width="11" height="11"/><rect class="dg-water" x="95" y="96" width="11" height="11"/><rect class="dg-water" x="106" y="96" width="11" height="11"/><rect class="dg-water" x="117" y="96" width="11" height="11"/><rect class="dg-water" x="128" y="96" width="11" height="11"/><rect class="dg-water" x="139" y="96" width="11" height="11"/><rect class="dg-water" x="150" y="96" width="11" height="11"/><rect class="dg-water" x="161" y="96" width="11" height="11"/><rect class="dg-water" x="172" y="96" width="11" height="11"/><rect class="dg-water" x="183" y="96" width="11" height="11"/><rect class="dg-water" x="194" y="96" width="11" height="11"/><rect class="dg-water" x="205" y="96" width="11" height="11"/><rect class="dg-water" x="216" y="96" width="11" height="11"/><rect class="dg-water" x="227" y="96" width="11" height="11"/><rect class="dg-water" x="238" y="96" width="11" height="11"/><rect class="dg-water" x="249" y="96" width="11" height="11"/><rect class="dg-water" x="40" y="107" width="11" height="11"/><rect class="dg-water" x="51" y="107" width="11" height="11"/><rect class="dg-water" x="62" y="107" width="11" height="11"/><rect class="dg-water" x="73" y="107" width="11" height="11"/><rect class="dg-water" x="84" y="107" width="11" height="11"/><rect class="dg-water" x="95" y="107" width="11" height="11"/><rect class="dg-water" x="106" y="107" width="11" height="11"/><rect class="dg-water" x="117" y="107" width="11" height="11"/><rect class="dg-water" x="128" y="107" width="11" height="11"/><rect class="dg-water" x="139" y="107" width="11" height="11"/><rect class="dg-water" x="150" y="107" width="11" height="11"/><rect class="dg-water" x="161" y="107" width="11" height="11"/><rect class="dg-water" x="172" y="107" width="11" height="11"/><rect class="dg-water" x="183" y="107" width="11" height="11"/><rect class="dg-water" x="194" y="107" width="11" height="11"/><rect class="dg-water" x="205" y="107" width="11" height="11"/><rect class="dg-water" x="216" y="107" width="11" height="11"/><rect class="dg-water" x="227" y="107" width="11" height="11"/><rect class="dg-water" x="238" y="107" width="11" height="11"/><rect class="dg-water" x="249" y="107" width="11" height="11"/><rect class="dg-water" x="40" y="118" width="11" height="11"/><rect class="dg-water" x="51" y="118" width="11" height="11"/><rect class="dg-water" x="62" y="118" width="11" height="11"/><rect class="dg-water" x="73" y="118" width="11" height="11"/><rect class="dg-water" x="84" y="118" width="11" height="11"/><rect class="dg-water" x="95" y="118" width="11" height="11"/><rect class="dg-water" x="106" y="118" width="11" height="11"/><rect class="dg-water" x="117" y="118" width="11" height="11"/><rect class="dg-water" x="128" y="118" width="11" height="11"/><rect class="dg-water" x="139" y="118" width="11" height="11"/><rect class="dg-water" x="150" y="118" width="11" height="11"/><rect class="dg-water" x="161" y="118" width="11" height="11"/><rect class="dg-water" x="172" y="118" width="11" height="11"/><rect class="dg-water" x="183" y="118" width="11" height="11"/><rect class="dg-water" x="194" y="118" width="11" height="11"/><rect class="dg-water" x="205" y="118" width="11" height="11"/><rect class="dg-water" x="216" y="118" width="11" height="11"/><rect class="dg-water" x="227" y="118" width="11" height="11"/><rect class="dg-water" x="238" y="118" width="11" height="11"/><rect class="dg-water" x="249" y="118" width="11" height="11"/><rect class="dg-water" x="40" y="129" width="11" height="11"/><rect class="dg-water" x="51" y="129" width="11" height="11"/><rect class="dg-water" x="62" y="129" width="11" height="11"/><rect class="dg-water" x="73" y="129" width="11" height="11"/><rect class="dg-water" x="84" y="129" width="11" height="11"/><rect class="dg-water" x="95" y="129" width="11" height="11"/><rect class="dg-water" x="106" y="129" width="11" height="11"/><rect class="dg-water" x="117" y="129" width="11" height="11"/><rect class="dg-water" x="128" y="129" width="11" height="11"/><rect class="dg-water" x="139" y="129" width="11" height="11"/><rect class="dg-water" x="150" y="129" width="11" height="11"/><rect class="dg-water" x="161" y="129" width="11" height="11"/><rect class="dg-water" x="172" y="129" width="11" height="11"/><rect class="dg-water" x="183" y="129" width="11" height="11"/><rect class="dg-water" x="194" y="129" width="11" height="11"/><rect class="dg-water" x="205" y="129" width="11" height="11"/><rect class="dg-water" x="216" y="129" width="11" height="11"/><rect class="dg-water" x="227" y="129" width="11" height="11"/><rect class="dg-water" x="238" y="129" width="11" height="11"/><rect class="dg-water" x="249" y="129" width="11" height="11"/><rect class="dg-water" x="40" y="140" width="11" height="11"/><rect class="dg-water" x="62" y="140" width="11" height="11"/><rect class="dg-water" x="84" y="140" width="11" height="11"/><rect class="dg-water" x="106" y="140" width="11" height="11"/><rect class="dg-water" x="128" y="140" width="11" height="11"/><rect class="dg-water" x="150" y="140" width="11" height="11"/><rect class="dg-water" x="172" y="140" width="11" height="11"/><rect class="dg-water" x="194" y="140" width="11" height="11"/><rect class="dg-water" x="216" y="140" width="11" height="11"/><rect class="dg-water" x="238" y="140" width="11" height="11"/><rect class="dg-water" x="51" y="151" width="11" height="11"/><rect class="dg-water" x="73" y="151" width="11" height="11"/><rect class="dg-water" x="95" y="151" width="11" height="11"/><rect class="dg-water" x="117" y="151" width="11" height="11"/><rect class="dg-water" x="139" y="151" width="11" height="11"/><rect class="dg-water" x="161" y="151" width="11" height="11"/><rect class="dg-water" x="183" y="151" width="11" height="11"/><rect class="dg-water" x="205" y="151" width="11" height="11"/><rect class="dg-water" x="227" y="151" width="11" height="11"/><rect class="dg-water" x="249" y="151" width="11" height="11"/><rect class="dg-water" x="40" y="162" width="11" height="11"/><rect class="dg-water" x="62" y="162" width="11" height="11"/><rect class="dg-water" x="84" y="162" width="11" height="11"/><rect class="dg-water" x="106" y="162" width="11" height="11"/><rect class="dg-water" x="128" y="162" width="11" height="11"/><rect class="dg-water" x="150" y="162" width="11" height="11"/><rect class="dg-water" x="172" y="162" width="11" height="11"/><rect class="dg-water" x="194" y="162" width="11" height="11"/><rect class="dg-water" x="216" y="162" width="11" height="11"/><rect class="dg-water" x="238" y="162" width="11" height="11"/><rect class="dg-water" x="51" y="173" width="11" height="11"/><rect class="dg-water" x="73" y="173" width="11" height="11"/><rect class="dg-water" x="95" y="173" width="11" height="11"/><rect class="dg-water" x="117" y="173" width="11" height="11"/><rect class="dg-water" x="139" y="173" width="11" height="11"/><rect class="dg-water" x="161" y="173" width="11" height="11"/><rect class="dg-water" x="183" y="173" width="11" height="11"/><rect class="dg-water" x="205" y="173" width="11" height="11"/><rect class="dg-water" x="227" y="173" width="11" height="11"/><rect class="dg-water" x="249" y="173" width="11" height="11"/><rect class="dg-water" x="40" y="184" width="11" height="11"/><rect class="dg-water" x="62" y="184" width="11" height="11"/><rect class="dg-water" x="84" y="184" width="11" height="11"/><rect class="dg-water" x="106" y="184" width="11" height="11"/><rect class="dg-water" x="128" y="184" width="11" height="11"/><rect class="dg-water" x="150" y="184" width="11" height="11"/><rect class="dg-water" x="172" y="184" width="11" height="11"/><rect class="dg-water" x="194" y="184" width="11" height="11"/><rect class="dg-water" x="216" y="184" width="11" height="11"/><rect class="dg-water" x="238" y="184" width="11" height="11"/><rect class="dg-water" x="51" y="195" width="11" height="11"/><rect class="dg-water" x="73" y="195" width="11" height="11"/><rect class="dg-water" x="95" y="195" width="11" height="11"/><rect class="dg-water" x="117" y="195" width="11" height="11"/><rect class="dg-water" x="139" y="195" width="11" height="11"/><rect class="dg-water" x="161" y="195" width="11" height="11"/><rect class="dg-water" x="183" y="195" width="11" height="11"/><rect class="dg-water" x="205" y="195" width="11" height="11"/><rect class="dg-water" x="227" y="195" width="11" height="11"/><rect class="dg-water" x="249" y="195" width="11" height="11"/><rect class="dg-water" x="40" y="206" width="11" height="11"/><rect class="dg-water" x="62" y="206" width="11" height="11"/><rect class="dg-water" x="84" y="206" width="11" height="11"/><rect class="dg-water" x="106" y="206" width="11" height="11"/><rect class="dg-water" x="128" y="206" width="11" height="11"/><rect class="dg-water" x="150" y="206" width="11" height="11"/><rect class="dg-water" x="172" y="206" width="11" height="11"/><rect class="dg-water" x="194" y="206" width="11" height="11"/><rect class="dg-water" x="216" y="206" width="11" height="11"/><rect class="dg-water" x="238" y="206" width="11" height="11"/><rect class="dg-water" x="51" y="217" width="11" height="11"/><rect class="dg-water" x="73" y="217" width="11" height="11"/><rect class="dg-water" x="95" y="217" width="11" height="11"/><rect class="dg-water" x="117" y="217" width="11" height="11"/><rect class="dg-water" x="139" y="217" width="11" height="11"/><rect class="dg-water" x="161" y="217" width="11" height="11"/><rect class="dg-water" x="183" y="217" width="11" height="11"/><rect class="dg-water" x="205" y="217" width="11" height="11"/><rect class="dg-water" x="227" y="217" width="11" height="11"/><rect class="dg-water" x="249" y="217" width="11" height="11"/><rect class="dg-water" x="40" y="228" width="11" height="11"/><rect class="dg-water" x="62" y="228" width="11" height="11"/><rect class="dg-water" x="84" y="228" width="11" height="11"/><rect class="dg-water" x="106" y="228" width="11" height="11"/><rect class="dg-water" x="128" y="228" width="11" height="11"/><rect class="dg-water" x="150" y="228" width="11" height="11"/><rect class="dg-water" x="172" y="228" width="11" height="11"/><rect class="dg-water" x="194" y="228" width="11" height="11"/><rect class="dg-water" x="216" y="228" width="11" height="11"/><rect class="dg-water" x="238" y="228" width="11" height="11"/><rect class="dg-water" x="51" y="239" width="11" height="11"/><rect class="dg-water" x="95" y="239" width="11" height="11"/><rect class="dg-water" x="139" y="239" width="11" height="11"/><rect class="dg-water" x="183" y="239" width="11" height="11"/><rect class="dg-water" x="227" y="239" width="11" height="11"/><rect class="dg-water" x="40" y="250" width="11" height="11"/><rect class="dg-water" x="84" y="250" width="11" height="11"/><rect class="dg-water" x="128" y="250" width="11" height="11"/><rect class="dg-water" x="172" y="250" width="11" height="11"/><rect class="dg-water" x="216" y="250" width="11" height="11"/><rect class="dg-water" x="73" y="261" width="11" height="11"/><rect class="dg-water" x="117" y="261" width="11" height="11"/><rect class="dg-water" x="161" y="261" width="11" height="11"/><rect class="dg-water" x="205" y="261" width="11" height="11"/><rect class="dg-water" x="249" y="261" width="11" height="11"/><rect class="dg-water" x="62" y="272" width="11" height="11"/><rect class="dg-water" x="106" y="272" width="11" height="11"/><rect class="dg-water" x="150" y="272" width="11" height="11"/><rect class="dg-water" x="194" y="272" width="11" height="11"/><rect class="dg-water" x="238" y="272" width="11" height="11"/><rect class="dg-water" x="51" y="283" width="11" height="11"/><rect class="dg-water" x="95" y="283" width="11" height="11"/><rect class="dg-water" x="139" y="283" width="11" height="11"/><rect class="dg-water" x="183" y="283" width="11" height="11"/><rect class="dg-water" x="227" y="283" width="11" height="11"/><g filter="url(#dg-composite)"><rect class="dg-rock" x="580" y="96" width="220" height="198"/><rect class="dg-water" x="580" y="96" width="11" height="11"/><rect class="dg-water" x="591" y="96" width="11" height="11"/><rect class="dg-water" x="602" y="96" width="11" height="11"/><rect class="dg-water" x="613" y="96" width="11" height="11"/><rect class="dg-water" x="624" y="96" width="11" height="11"/><rect class="dg-water" x="635" y="96" width="11" height="11"/><rect class="dg-water" x="646" y="96" width="11" height="11"/><rect class="dg-water" x="657" y="96" width="11" height="11"/><rect class="dg-water" x="668" y="96" width="11" height="11"/><rect class="dg-water" x="679" y="96" width="11" height="11"/><rect class="dg-water" x="690" y="96" width="11" height="11"/><rect class="dg-water" x="701" y="96" width="11" height="11"/><rect class="dg-water" x="712" y="96" width="11" height="11"/><rect class="dg-water" x="723" y="96" width="11" height="11"/><rect class="dg-water" x="734" y="96" width="11" height="11"/><rect class="dg-water" x="745" y="96" width="11" height="11"/><rect class="dg-water" x="756" y="96" width="11" height="11"/><rect class="dg-water" x="767" y="96" width="11" height="11"/><rect class="dg-water" x="778" y="96" width="11" height="11"/><rect class="dg-water" x="789" y="96" width="11" height="11"/><rect class="dg-water" x="580" y="107" width="11" height="11"/><rect class="dg-water" x="591" y="107" width="11" height="11"/><rect class="dg-water" x="602" y="107" width="11" height="11"/><rect class="dg-water" x="613" y="107" width="11" height="11"/><rect class="dg-water" x="624" y="107" width="11" height="11"/><rect class="dg-water" x="635" y="107" width="11" height="11"/><rect class="dg-water" x="646" y="107" width="11" height="11"/><rect class="dg-water" x="657" y="107" width="11" height="11"/><rect class="dg-water" x="668" y="107" width="11" height="11"/><rect class="dg-water" x="679" y="107" width="11" height="11"/><rect class="dg-water" x="690" y="107" width="11" height="11"/><rect class="dg-water" x="701" y="107" width="11" height="11"/><rect class="dg-water" x="712" y="107" width="11" height="11"/><rect class="dg-water" x="723" y="107" width="11" height="11"/><rect class="dg-water" x="734" y="107" width="11" height="11"/><rect class="dg-water" x="745" y="107" width="11" height="11"/><rect class="dg-water" x="756" y="107" width="11" height="11"/><rect class="dg-water" x="767" y="107" width="11" height="11"/><rect class="dg-water" x="778" y="107" width="11" height="11"/><rect class="dg-water" x="789" y="107" width="11" height="11"/><rect class="dg-water" x="580" y="118" width="11" height="11"/><rect class="dg-water" x="591" y="118" width="11" height="11"/><rect class="dg-water" x="602" y="118" width="11" height="11"/><rect class="dg-water" x="613" y="118" width="11" height="11"/><rect class="dg-water" x="624" y="118" width="11" height="11"/><rect class="dg-water" x="635" y="118" width="11" height="11"/><rect class="dg-water" x="646" y="118" width="11" height="11"/><rect class="dg-water" x="657" y="118" width="11" height="11"/><rect class="dg-water" x="668" y="118" width="11" height="11"/><rect class="dg-water" x="679" y="118" width="11" height="11"/><rect class="dg-water" x="690" y="118" width="11" height="11"/><rect class="dg-water" x="701" y="118" width="11" height="11"/><rect class="dg-water" x="712" y="118" width="11" height="11"/><rect class="dg-water" x="723" y="118" width="11" height="11"/><rect class="dg-water" x="734" y="118" width="11" height="11"/><rect class="dg-water" x="745" y="118" width="11" height="11"/><rect class="dg-water" x="756" y="118" width="11" height="11"/><rect class="dg-water" x="767" y="118" width="11" height="11"/><rect class="dg-water" x="778" y="118" width="11" height="11"/><rect class="dg-water" x="789" y="118" width="11" height="11"/><rect class="dg-water" x="580" y="129" width="11" height="11"/><rect class="dg-water" x="591" y="129" width="11" height="11"/><rect class="dg-water" x="602" y="129" width="11" height="11"/><rect class="dg-water" x="613" y="129" width="11" height="11"/><rect class="dg-water" x="624" y="129" width="11" height="11"/><rect class="dg-water" x="635" y="129" width="11" height="11"/><rect class="dg-water" x="646" y="129" width="11" height="11"/><rect class="dg-water" x="657" y="129" width="11" height="11"/><rect class="dg-water" x="668" y="129" width="11" height="11"/><rect class="dg-water" x="679" y="129" width="11" height="11"/><rect class="dg-water" x="690" y="129" width="11" height="11"/><rect class="dg-water" x="701" y="129" width="11" height="11"/><rect class="dg-water" x="712" y="129" width="11" height="11"/><rect class="dg-water" x="723" y="129" width="11" height="11"/><rect class="dg-water" x="734" y="129" width="11" height="11"/><rect class="dg-water" x="745" y="129" width="11" height="11"/><rect class="dg-water" x="756" y="129" width="11" height="11"/><rect class="dg-water" x="767" y="129" width="11" height="11"/><rect class="dg-water" x="778" y="129" width="11" height="11"/><rect class="dg-water" x="789" y="129" width="11" height="11"/><rect class="dg-water" x="580" y="140" width="11" height="11"/><rect class="dg-water" x="602" y="140" width="11" height="11"/><rect class="dg-water" x="624" y="140" width="11" height="11"/><rect class="dg-water" x="646" y="140" width="11" height="11"/><rect class="dg-water" x="668" y="140" width="11" height="11"/><rect class="dg-water" x="690" y="140" width="11" height="11"/><rect class="dg-water" x="712" y="140" width="11" height="11"/><rect class="dg-water" x="734" y="140" width="11" height="11"/><rect class="dg-water" x="756" y="140" width="11" height="11"/><rect class="dg-water" x="778" y="140" width="11" height="11"/><rect class="dg-water" x="591" y="151" width="11" height="11"/><rect class="dg-water" x="613" y="151" width="11" height="11"/><rect class="dg-water" x="635" y="151" width="11" height="11"/><rect class="dg-water" x="657" y="151" width="11" height="11"/><rect class="dg-water" x="679" y="151" width="11" height="11"/><rect class="dg-water" x="701" y="151" width="11" height="11"/><rect class="dg-water" x="723" y="151" width="11" height="11"/><rect class="dg-water" x="745" y="151" width="11" height="11"/><rect class="dg-water" x="767" y="151" width="11" height="11"/><rect class="dg-water" x="789" y="151" width="11" height="11"/><rect class="dg-water" x="580" y="162" width="11" height="11"/><rect class="dg-water" x="602" y="162" width="11" height="11"/><rect class="dg-water" x="624" y="162" width="11" height="11"/><rect class="dg-water" x="646" y="162" width="11" height="11"/><rect class="dg-water" x="668" y="162" width="11" height="11"/><rect class="dg-water" x="690" y="162" width="11" height="11"/><rect class="dg-water" x="712" y="162" width="11" height="11"/><rect class="dg-water" x="734" y="162" width="11" height="11"/><rect class="dg-water" x="756" y="162" width="11" height="11"/><rect class="dg-water" x="778" y="162" width="11" height="11"/><rect class="dg-water" x="591" y="173" width="11" height="11"/><rect class="dg-water" x="613" y="173" width="11" height="11"/><rect class="dg-water" x="635" y="173" width="11" height="11"/><rect class="dg-water" x="657" y="173" width="11" height="11"/><rect class="dg-water" x="679" y="173" width="11" height="11"/><rect class="dg-water" x="701" y="173" width="11" height="11"/><rect class="dg-water" x="723" y="173" width="11" height="11"/><rect class="dg-water" x="745" y="173" width="11" height="11"/><rect class="dg-water" x="767" y="173" width="11" height="11"/><rect class="dg-water" x="789" y="173" width="11" height="11"/><rect class="dg-water" x="580" y="184" width="11" height="11"/><rect class="dg-water" x="602" y="184" width="11" height="11"/><rect class="dg-water" x="624" y="184" width="11" height="11"/><rect class="dg-water" x="646" y="184" width="11" height="11"/><rect class="dg-water" x="668" y="184" width="11" height="11"/><rect class="dg-water" x="690" y="184" width="11" height="11"/><rect class="dg-water" x="712" y="184" width="11" height="11"/><rect class="dg-water" x="734" y="184" width="11" height="11"/><rect class="dg-water" x="756" y="184" width="11" height="11"/><rect class="dg-water" x="778" y="184" width="11" height="11"/><rect class="dg-water" x="591" y="195" width="11" height="11"/><rect class="dg-water" x="613" y="195" width="11" height="11"/><rect class="dg-water" x="635" y="195" width="11" height="11"/><rect class="dg-water" x="657" y="195" width="11" height="11"/><rect class="dg-water" x="679" y="195" width="11" height="11"/><rect class="dg-water" x="701" y="195" width="11" height="11"/><rect class="dg-water" x="723" y="195" width="11" height="11"/><rect class="dg-water" x="745" y="195" width="11" height="11"/><rect class="dg-water" x="767" y="195" width="11" height="11"/><rect class="dg-water" x="789" y="195" width="11" height="11"/><rect class="dg-water" x="580" y="206" width="11" height="11"/><rect class="dg-water" x="602" y="206" width="11" height="11"/><rect class="dg-water" x="624" y="206" width="11" height="11"/><rect class="dg-water" x="646" y="206" width="11" height="11"/><rect class="dg-water" x="668" y="206" width="11" height="11"/><rect class="dg-water" x="690" y="206" width="11" height="11"/><rect class="dg-water" x="712" y="206" width="11" height="11"/><rect class="dg-water" x="734" y="206" width="11" height="11"/><rect class="dg-water" x="756" y="206" width="11" height="11"/><rect class="dg-water" x="778" y="206" width="11" height="11"/><rect class="dg-water" x="591" y="217" width="11" height="11"/><rect class="dg-water" x="613" y="217" width="11" height="11"/><rect class="dg-water" x="635" y="217" width="11" height="11"/><rect class="dg-water" x="657" y="217" width="11" height="11"/><rect class="dg-water" x="679" y="217" width="11" height="11"/><rect class="dg-water" x="701" y="217" width="11" height="11"/><rect class="dg-water" x="723" y="217" width="11" height="11"/><rect class="dg-water" x="745" y="217" width="11" height="11"/><rect class="dg-water" x="767" y="217" width="11" height="11"/><rect class="dg-water" x="789" y="217" width="11" height="11"/><rect class="dg-water" x="580" y="228" width="11" height="11"/><rect class="dg-water" x="602" y="228" width="11" height="11"/><rect class="dg-water" x="624" y="228" width="11" height="11"/><rect class="dg-water" x="646" y="228" width="11" height="11"/><rect class="dg-water" x="668" y="228" width="11" height="11"/><rect class="dg-water" x="690" y="228" width="11" height="11"/><rect class="dg-water" x="712" y="228" width="11" height="11"/><rect class="dg-water" x="734" y="228" width="11" height="11"/><rect class="dg-water" x="756" y="228" width="11" height="11"/><rect class="dg-water" x="778" y="228" width="11" height="11"/><rect class="dg-water" x="591" y="239" width="11" height="11"/><rect class="dg-water" x="635" y="239" width="11" height="11"/><rect class="dg-water" x="679" y="239" width="11" height="11"/><rect class="dg-water" x="723" y="239" width="11" height="11"/><rect class="dg-water" x="767" y="239" width="11" height="11"/><rect class="dg-water" x="580" y="250" width="11" height="11"/><rect class="dg-water" x="624" y="250" width="11" height="11"/><rect class="dg-water" x="668" y="250" width="11" height="11"/><rect class="dg-water" x="712" y="250" width="11" height="11"/><rect class="dg-water" x="756" y="250" width="11" height="11"/><rect class="dg-water" x="613" y="261" width="11" height="11"/><rect class="dg-water" x="657" y="261" width="11" height="11"/><rect class="dg-water" x="701" y="261" width="11" height="11"/><rect class="dg-water" x="745" y="261" width="11" height="11"/><rect class="dg-water" x="789" y="261" width="11" height="11"/><rect class="dg-water" x="602" y="272" width="11" height="11"/><rect class="dg-water" x="646" y="272" width="11" height="11"/><rect class="dg-water" x="690" y="272" width="11" height="11"/><rect class="dg-water" x="734" y="272" width="11" height="11"/><rect class="dg-water" x="778" y="272" width="11" height="11"/><rect class="dg-water" x="591" y="283" width="11" height="11"/><rect class="dg-water" x="635" y="283" width="11" height="11"/><rect class="dg-water" x="679" y="283" width="11" height="11"/><rect class="dg-water" x="723" y="283" width="11" height="11"/><rect class="dg-water" x="767" y="283" width="11" height="11"/></g><text x="40" y="328" class="dg-sans dg-q" font-size="13" text-anchor="start">A checkerboard of one blue over the rock behind it.</text><text x="580" y="328" class="dg-sans dg-i" font-size="13" text-anchor="start">The same rectangles, averaged along the line into water.</text></svg></div></div><figcaption>Drawn here, not photographed. The smear is a horizontal blur, which shows the direction the averaging goes, along the line.</figcaption></figure>
<p>A program with no picture in it. The Atari 2600 has no frame buffer at all. There are 76 processor cycles in a line, and the program spends them changing the video chip's registers while the beam moves across the screen.<sup class="log-fn"><a id="fn-7" href="#ref-7">7</a></sup> Miss the timing and there is no picture at all. The beam is not where the program's output goes; the beam is the program's clock.<sup class="log-fn"><a id="fn-8" href="#ref-8">8</a></sup></p>
<p>The frame taken off. The Commodore 64's video chip decides to draw its border by comparing where it has got to against where the picture ends. Switch the machine to a shorter screen at exactly the right moment and it never starts, so the border is not there any more. On the sides it has to be done inside every line, to the cycle.<sup class="log-fn"><a id="fn-9" href="#ref-9">9</a></sup> Programmers used the timing of that comparison to keep the border open.</p>
<p><strong>A gun that sees the beam.</strong> A light gun does not know where it is pointing. The console blanks the screen and flashes one target at a time while the gun reports whether it can see light, and the gun's filter is tuned to the fifteen kilohertz flicker of the beam itself.<sup class="log-fn"><a id="fn-10" href="#ref-10">10</a></sup> It works because the picture is being drawn rather than held, and it stopped working when screens stopped drawing: there is nothing at fifteen kilohertz on a panel to see.<sup class="log-fn"><a id="fn-11" href="#ref-11">11</a></sup></p>
<p>These techniques used properties of the original display and video signal, which helps explain why the same image can look different on another screen.</p>
<h2 id="what-this-has-to-do-with-a-compositor" class="log-sec log-sec-4"><a class="log-anchor" href="#what-this-has-to-do-with-a-compositor" aria-label="Link to this section"></a><span class="log-sec-n">08</span>What this has to do with a compositor</h2>
<p>These properties inform three parts of Flyback’s design.</p>
<p><strong>The refusal guard.</strong> The tuned horizontal circuit needs a line rate within its supported band. Flyback checks the timing before sending it to the kernel: the line rate inside the band the configuration allows for that set, 15 to 16.5 kHz as shipped, the field rate between 40 and 90 Hz, and every timing in order.</p>
<p><strong>The variable refresh rate.</strong> The horizontal rate must not move and the vertical one can. The vertical oscillator re-triggers on sync, so a longer frame is simply a longer wait before the beam is sent back to the top. The refresh rate can change without the mode changing, and it stops at about 55 Hz on this set, which is <a href="https://omacrt.com/log/flyback/#a-variable-refresh-rate-on-a-television-from-1998">where the picture starts losing height</a>.</p>
<p><strong>The latency.</strong> The compositor controls when a frame starts scanning out. The study measures from a client’s commit to the start of scanout. It does not measure the converter’s delay or the time until light reaches the viewer.<span class="log-end" aria-hidden="true"></span></p><section class="log-refs" aria-labelledby="references"><h2 id="references">References</h2><p>The historical examples in section 7 come from the sources below; they were not measured for this project.</p><ol><li id="ref-1"><a class="log-ref-back" href="#fn-1">1</a><span>A tube draws with a round spot whose brightness falls off from the middle, and it never aims at an individual phosphor, so a non-native resolution costs it nothing: <a href="https://forums.blurbusters.com/viewtopic.php?t=427">Display scaling, analog against digital</a>, Blur Busters forums.</span></li><li id="ref-2"><a class="log-ref-back" href="#fn-2">2</a><span>Impulse against sample and hold, and why strobing and black frame insertion exist: <a href="https://blurbusters.com/faq/oled-motion-blur/">Why do some OLEDs have motion blur?</a>, Blur Busters.</span></li><li id="ref-3"><a class="log-ref-back" href="#fn-3">3</a><span><a href="https://paleotronic.com/2018/10/03/apple-ii-colour-computer-graphics/">Colour without colour: Apple II computer graphics</a>, Paleotronic, and <a href="http://nerdlypleasures.blogspot.com/2021/10/apple-ii-composite-artifact-color-ntsc.html">Apple II composite artifact colour</a>, Nerdly Pleasures, for the half cycle delay on the eighth bit.</span></li><li id="ref-4"><a class="log-ref-back" href="#fn-4">4</a><span><a href="https://en.wikipedia.org/wiki/Composite_artifact_colors">Composite artifact colors</a>, Wikipedia, for the shared 14.318 MHz pixel clock.</span></li><li id="ref-5"><a class="log-ref-back" href="#fn-5">5</a><span><a href="https://www.timeextension.com/news/2024/10/what-do-you-see-in-sonic-the-hedgehogs-waterfalls">What do you see in Sonic the Hedgehog's waterfalls?</a>, Time Extension.</span></li><li id="ref-6"><a class="log-ref-back" href="#fn-6">6</a><span><a href="https://rasterscroll.com/mdgraphics/graphical-effects/transparency/">Transparency</a>, Raster Scroll, on what the Mega Drive's artists were drawing for.</span></li><li id="ref-7"><a class="log-ref-back" href="#fn-7">7</a><span><a href="https://en.wikipedia.org/wiki/Television_Interface_Adaptor">Television Interface Adaptor</a>, Wikipedia, for the registers holding one line at a time.</span></li><li id="ref-8"><a class="log-ref-back" href="#fn-8">8</a><span><a href="https://www.bigmessowires.com/2023/01/11/atari-2600-hardware-design-making-something-out-of-almost-nothing/">Atari 2600 hardware design: making something out of (almost) nothing</a>, Big Mess o' Wires, for the 76 cycles.</span></li><li id="ref-9"><a class="log-ref-back" href="#fn-9">9</a><span><a href="https://aartbik.blogspot.com/2019/09/opening-top-and-bottom-borders-on.html">Opening top and bottom borders on the Commodore 64</a>, and the cycle exact version for the sides.</span></li><li id="ref-10"><a class="log-ref-back" href="#fn-10">10</a><span><a href="https://www.nesdev.org/wiki/Zapper">Zapper</a>, NESdev wiki, for the high pass filter around the line rate and the one target at a time.</span></li><li id="ref-11"><a class="log-ref-back" href="#fn-11">11</a><span><a href="https://nicole.express/2023/bang-bang-youre-dead.html">Yes, you can use lightguns on LCDs, sometimes</a>, which is also where the filter frequencies come from.</span></li></ol></section>]]></content:encoded>
    </item>
  </channel>
</rss>
