<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Gaming | AkitaOnRails.com</title><link>https://akitaonrails.github.io/en/tags/gaming/</link><description>Fabio Akita's blog — tech, career, and assorted geek topics. English edition.</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 19 Oct 2026 23:20:51 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/en/tags/gaming/index.xml" rel="self" type="application/rss+xml"/><item><title>AI Challenge: Converting NES ROMs to the Master System/SMS</title><link>https://akitaonrails.github.io/en/2026/09/07/ai-challenge-converting-nes-roms-to-master-system-sms/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/09/07/ai-challenge-converting-nes-roms-to-master-system-sms/</guid><pubDate>Mon, 07 Sep 2026 14:00:00 -0300</pubDate><description>&lt;p&gt;For many years I had a fixed idea in my head, and I always thought it wasn&amp;rsquo;t practical: converting NES games to run on the Master System.&lt;/p&gt;
&lt;p&gt;Let me be precise. I always knew it could be done. Two Turing-complete machines can, in theory, run each other&amp;rsquo;s code. You can always translate a program from one architecture to another. The problem was never possibility, it was cost. The NES runs a 6502, the Master System runs a Z80, and over the years I read in several places that a direct 6502-to-Z80 translation always ended up running much slower, because of the architecture and hardware differences. Slow enough to never pay off in practice.&lt;/p&gt;</description><content:encoded><![CDATA[<p>For many years I had a fixed idea in my head, and I always thought it wasn&rsquo;t practical: converting NES games to run on the Master System.</p>
<p>Let me be precise. I always knew it could be done. Two Turing-complete machines can, in theory, run each other&rsquo;s code. You can always translate a program from one architecture to another. The problem was never possibility, it was cost. The NES runs a 6502, the Master System runs a Z80, and over the years I read in several places that a direct 6502-to-Z80 translation always ended up running much slower, because of the architecture and hardware differences. Slow enough to never pay off in practice.</p>
<p>And that memory has a basis. If you dig into retro-dev forums, like <a href="https://forums.nesdev.org/viewtopic.php?t=17339"target="_blank" rel="noopener">NESdev</a>, the consensus is that translating instruction by instruction is exactly the worst path: &ldquo;each instruction in the source code becomes multiple instructions of object code, and the resulting program runs much slower and takes much more memory.&rdquo; It&rsquo;s not just the CPU. The 6502 has the zero page, a dirt-cheap addressing mode the Z80 has no equivalent for, so you trade a cheap access for an expensive sequence with <code>IX</code>/<code>IY</code>. And the graphics part is even worse: the NES PPU and the Master System VDP are different beasts, with scroll, sprites, and screen mirroring that don&rsquo;t fit into each other without expensive workarounds.</p>
<p>In other words, everyone who ever thought about this reached the same conclusion: it can be done, but it runs too badly to be worth it.</p>
<p>Except now we have frontier LLMs. And I got curious: could a brute-force approach, with a good LLM in the middle of the process, get a better result than the usual naive translation? I started the <a href="https://github.com/akitaonrails/nes-to-sms"target="_blank" rel="noopener">nes-to-sms</a> project in late May, about three months ago, to find out.</p>
<p>The first result was exactly what the theory predicted: bad. A more or less direct translation generated code that runs, but way too slow. I managed to convert the entire Super Mario Bros, with sprites, levels, everything, but to make it minimally playable I had to run the Mednafen emulator at 500% overclock. After banging my head against Claude, GPT, and other models for a while, I got stuck. I worked hard through June and July, and in August I paused the project to let the dust settle.</p>
<h2>Why the Master System, and not the NES?<span class="hx:absolute hx:-mt-20" id="why-the-master-system-and-not-the-nes"></span>
    <a href="#why-the-master-system-and-not-the-nes" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Before explaining the technical part, I need to explain the obsession, because it is the reason for everything.</p>
<p>I always thought the Master System was a superior console to the NES. Better CPU, better video chip, a much richer color palette. The NES works with 4 colors per background tile and 3 per sprite. The Master System works with 16-color palettes. That is a huge difference in practice.</p>
<p>The problem is that Nintendo had a brutal monopoly back then and would not let third parties release for other consoles. Anyone who wanted to make a game for the NES signed an exclusivity contract. The result is that the Master System, technically better, ended up starved of games. Basically only Sega itself released for it, and Sega&rsquo;s library never had the weight of a Castlevania, a Mega Man, a Final Fantasy.</p>
<p>Imagine what we lost. We could have had those same NES classics, but with the Master System&rsquo;s superior capability underneath. A Castlevania with more color, better sprites, better sound. We never had that, and it is exactly this historical frustration that made me want to convert NES to SMS in the first place.</p>
<h2>The port that proved the thesis<span class="hx:absolute hx:-mt-20" id="the-port-that-proved-the-thesis"></span>
    <a href="#the-port-that-proved-the-thesis" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>While my project was paused, another developer, <a href="https://github.com/lackoftrack27/Super-Mario-Bros.-SMS"target="_blank" rel="noopener">lackoftrack27</a>, published a Super Mario Bros port for the Master System that is on another level. It runs at full speed, 60fps, and on top of that with improved sprite art that takes advantage of the console&rsquo;s superior palette. It ended up looking more like the Super Mario All-Stars remaster from the Super Nintendo than the somewhat crude original NES version. The retro community was in awe.</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/igOu1NQL5Ww"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>That proved my thesis in practice: the Master System is capable of running superior versions of NES games. It always was.</p>
<p>Now, two honest points about this port, because they matter for the rest of the story.</p>
<p>First: lackoftrack27&rsquo;s work is a hand reimplementation, built from the game&rsquo;s disassembly, optimized specifically for Super Mario. It&rsquo;s not a general-purpose automatic translation like the one I&rsquo;m trying to do. He solved one game, beautifully, by hand. Curiously, that reinforces what the forums already said: the path that works is reimplementing by hand, not machine-translating.</p>
<p>Second: as impressive as it is to see Super Mario on the Master System, it&rsquo;s worth remembering it is still a first-generation game, around 40kb. Meaning it is one of the simpler games on the NES. Later-generation stuff, like Super Mario Bros 3 or Kirby, nobody has tried to convert yet, and for good reasons I explain further down.</p>
<p>But the most important thing was the side effect: with the map his port gave me, I finally got my automatic conversion running properly. I came back to the project in early September, loaded lackoftrack27&rsquo;s code into Claude Fable and then the new GPT Astra, and put both of them to study what he did that I wasn&rsquo;t doing.</p>
<h2>The architecture of the solution<span class="hx:absolute hx:-mt-20" id="the-architecture-of-the-solution"></span>
    <a href="#the-architecture-of-the-solution" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Since this article is for programmers, let me open the black box.</p>
<p>The first important decision is that <code>nes-to-sms</code> is not an emulator, nor a text-to-text translator of 6502 to Z80. It is a <strong>static recompiler</strong>. It reads the NES ROM, lifts the 6502 code into an intermediate representation with explicit semantics, and from that IR it generates Z80 code. The output is a WLA-DX assembly project that compiles into a real <code>.sms</code> ROM. The core is a Rust workspace with thirteen crates, plus a hand-written Z80 runtime shared across games.</p>
<p>The flow, end to end, goes roughly like this:</p>
<ol>
<li>Parse the ROM header, split the code banks (PRG) from the tile banks (CHR), and read the reset and interrupt vectors.</li>
<li>Load a per-game profile (a TOML with labels, data regions, jump tables, mapper type, and replacements).</li>
<li>Discover the functions, build the control-flow graph, and classify every byte as code or data. A key detail enters here: every memory access is tagged with the region it belongs to (zero page, stack, RAM, PPU registers, sprite DMA, sound, mapper).</li>
<li>Lift each routine into IR, with the 6502 flags explicit. A write to the PPU register <code>$2006</code> doesn&rsquo;t become just some <code>mem[]=</code>, it becomes a first-class &ldquo;PPU write&rdquo; primitive.</li>
<li>Lower the IR to Z80. The 6502 flags are kept in a shadow status byte in RAM, and every hardware access becomes a call into the runtime.</li>
<li>Emit the Z80 bytes and also the readable WLA-DX assembly.</li>
<li>Convert the assets: the NES 2bpp tiles become SMS mode-4 4bpp tiles, the NES palette becomes SMS CRAM, and so on.</li>
<li>Write the whole SMS project, with a Makefile, runtime, and data, ready to compile.</li>
</ol>
<p>The CPU mapping is conservative on purpose. The 6502 accumulator <code>A</code> becomes the Z80 <code>A</code>, the <code>X</code> and <code>Y</code> registers live in RAM or registers, the zero page becomes a fixed block in SMS RAM, and every operation that touches a flag calls a helper. It&rsquo;s safe and correct, but this is exactly where the cost lives.</p>
<h2>Where NES and SMS really diverge<span class="hx:absolute hx:-mt-20" id="where-nes-and-sms-really-diverge"></span>
    <a href="#where-nes-and-sms-really-diverge" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The project&rsquo;s central finding is about speed, and it&rsquo;s counterintuitive.</p>
<p>The NES 6502 runs at 1.79 MHz. The Master System Z80 runs at 3.58 MHz, double the clock. You look at that and think &ldquo;so there&rsquo;s plenty of headroom.&rdquo; Except no. The Z80 spends on average about 13 cycles per instruction, while the 6502 spends about 4. In real numbers, a 3.5 MHz Z80 is roughly equivalent to a 1 MHz 6502 for general work. Meaning the SMS Z80 is actually <strong>slower</strong> than the NES 6502 for the same work. The headroom is negative.</p>
<p>Worse: the cheapest 6502 idiom, an <code>LDA table,X</code>, is one of the most expensive to emulate on the Z80. So a faithful translation of a game that already squeezed the NES to the limit simply cannot hit 60fps without overclock. The blame is on the physics of the problem, not on my translator.</p>
<p>And the video is another story. The NES sees video memory in a mapped way, with nametables, hardware scroll, and a sprite table accessed via DMA. The SMS Z80 does not address VRAM as memory: it sets an address latch and streams bytes through VDP ports. So every NES PPU write has to be captured, queued in a buffer, and flushed to the VDP during vblank. The NES flips a sprite for free, just by setting an attribute bit. The Master System has no hardware sprite flipping, so the program has to mirror the tile data in VRAM by hand, burning memory. The mid-frame screen split that NES games do, changing the scroll each scanline, also has no direct equivalent, because the VDP latches the vertical scroll at the top of the frame.</p>
<p>And there&rsquo;s still the VRAM ceiling. Super Mario&rsquo;s 8 KB of tiles become 16 KB in the SMS 4bpp format, which is the console&rsquo;s entire VRAM. Background and sprites can&rsquo;t both stay resident at once. And the code balloons 3 to 6 times in size, which is why the SMS output is already banked from the very first game, even the simplest ones.</p>
<h2>Leveraging what the SMS does best<span class="hx:absolute hx:-mt-20" id="leveraging-what-the-sms-does-best"></span>
    <a href="#leveraging-what-the-sms-does-best" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The trick to recover speed is to stop emulating NES behavior and start spending the Master System&rsquo;s strengths. The project&rsquo;s motto became &ldquo;spend ROM to buy CPU.&rdquo;</p>
<p>The SMS accepts cartridges of several megabytes. So instead of mirroring a sprite by hand at runtime, the build already generates pre-flipped variants of each sprite tile, and the flip becomes a table lookup. Instead of emulating the NES attribute table, the assembler already writes the final SMS nametable word, with the flip, palette, and priority bits baked in. The NES attribute table simply stops existing in the program. The copies to the VDP use unrolled <code>OUTI</code> blocks, faster than the generic loop. And the status-bar split, which on the NES depends on sprite-zero, becomes an SMS line interrupt, which is the native way to do it.</p>
<p>None of this is magic. The negative CPU headroom is still there, so these optimizations reduce the cost but don&rsquo;t work miracles on their own. But it&rsquo;s the difference between unplayable and playable.</p>
<h2>The real trick: making the AI run the emulator<span class="hx:absolute hx:-mt-20" id="the-real-trick-making-the-ai-run-the-emulator"></span>
    <a href="#the-real-trick-making-the-ai-run-the-emulator" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Here&rsquo;s the part I consider the most important of the whole project, and it&rsquo;s what separated the failed attempt from May from the version that finally moves.</p>
<p>Blind translation doesn&rsquo;t work. The model needs real feedback to know what&rsquo;s broken and what&rsquo;s slow. So the heart of the project is the <strong>differential oracle</strong>, the infrastructure that runs the emulators and measures reality. The translator is just the easy part.</p>
<p>It works in two layers. The first compares instruction by instruction: for each routine, the system generates random initial states, runs the original 6502 in a reference interpreter and runs the generated Z80, and compares the result (accumulator, registers, flags, touched RAM). Any divergence raises a flag. The second layer compares frame by frame: it runs the NES ROM as absolute ground truth and the SMS build side by side, with the same button script, and compares RAM byte by byte on every frame. That was later extended with an oracle that checks the SMS VRAM and palette, and with a visual oracle that compares the output against the real NES frame.</p>
<p>This measurement loop is what unlocked the recent fixes. It wasn&rsquo;t guesswork. Two concrete examples:</p>
<p>In Super Mario, every optimization step was measured in cycles per frame, with the emulator actually running. A frame&rsquo;s budget is 59,736 cycles. The baseline sat at around 370 thousand cycles per frame, something like 6 times over budget, which gave about 10fps and explained the need for that brutal 500% overclock. Measuring step by step, each change validated by the oracle, it dropped to 118,296 cycles per frame, or 1.98 times the budget. It went from 6 times to under 2 times. It now runs at full speed already in the emulator&rsquo;s standard overclock, and it&rsquo;s comfortable in the 200 to 300% range, versus the 500 to 700% from the start.</p>
<p>In Castlevania, the biggest speed win came from a profile only the emulator could reveal: a sprite-zero wait loop, at address <code>$F8C7</code>, was doing 255 status reads and exiting by timeout every single time, consuming almost 23% of the entire CPU. Without running the emulator and measuring, nobody would find this by eye. With the measurement, the fix was surgical.</p>
<p>This is the bigger argument I&rsquo;ve been hammering for a while about agents: an LLM that only generates code in the dark makes big mistakes. An LLM that can actually run the target, collect real data, and react to what it measured plays in another league.</p>
<h2>What I learned studying lackoftrack27&rsquo;s port<span class="hx:absolute hx:-mt-20" id="what-i-learned-studying-lackoftrack27s-port"></span>
    <a href="#what-i-learned-studying-lackoftrack27s-port" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>With that oracle in place, I put Claude Fable and GPT Astra to dissect lackoftrack27&rsquo;s port, which is a reimplementation of the same game from the same disassembly I translate. That settled once and for all a doubt that was blocking me: Super Mario fits on the Master System. My 6-times overrun on the budget was 100% translation overhead, not a limitation of the game or the hardware.</p>
<p>His wins, in order of impact, were roughly these:</p>
<ul>
<li><strong>The biggest of all: the data representation.</strong> He reorganized the game&rsquo;s object arrays into RAM pages, one per slot, with the same field offsets. Then the 6502&rsquo;s <code>X</code> becomes the register <code>H</code> and the field name becomes <code>L</code>, and an access that cost about 70 time units in my translator now costs 14. The cruel detail is that this only works because the programmer knows, from memory, that this particular <code>X</code> is always an object index between 0 and 6. A static translator has no way to safely discover that invariant. This is the most powerful optimization, and it&rsquo;s precisely the one automatic translation can&rsquo;t prove on its own. It&rsquo;s the residual that holds back the last 2 times in speed.</li>
<li><strong>Flags audited, not emulated.</strong> He checked the code and found only about 10 places that genuinely depend on some 6502 carry quirk. The rest uses the Z80&rsquo;s native flags. That confirmed a measurement of mine: spending energy emulating flags yields almost nothing.</li>
<li><strong>Native calls.</strong> <code>JSR</code> becoming a real <code>CALL</code>, without the expensive emulated-stack bookkeeping. That was the biggest generic win I ported over to my pipeline.</li>
<li><strong>PPU deleted at build time</strong>, not emulated at runtime, exactly as I described in the section on leveraging the SMS.</li>
<li><strong>Frame architected to overrun gracefully</strong>, turning an overrun into a clean lag frame instead of corrupting the screen.</li>
</ul>
<p>I adopted what could be generalized, and Super Mario dropped from 6 times to under 2 times the budget. This is the result running today:</p>
<div class="embed-container">
  <video controls preload="metadata" playsinline style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000;">
    <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260907152243_smb-sms-conversion.mp4" type="video/mp4">
  </video>
</div>
<p>The most honest lesson from this study is twofold. The real wins are in the data representation and the calling convention, not in what I had attacked first. And the deepest win of all is exactly what the automatic translator can&rsquo;t infer. That draws the ceiling of what can be automated pretty clearly.</p>
<h2>Castlevania and the mapper problem<span class="hx:absolute hx:-mt-20" id="castlevania-and-the-mapper-problem"></span>
    <a href="#castlevania-and-the-mapper-problem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>With Super Mario moving, I went back to my old target: the first Castlevania. And here we hit the NES&rsquo;s most serious limitation.</p>
<p>The NES is a simple console, with a bunch of limits. One of the main ones is that it doesn&rsquo;t support games bigger than about 40kb of ROM, because the 6502 only sees a 32 KB window of program in its address space. The &ldquo;solution&rdquo; from back then was brilliant and a bit crazy: parts of the console were being upgraded by the cartridge itself.</p>
<p>Many people think a cartridge is just a ROM chip with the game code. In the NES case, it&rsquo;s not. The cartridges came with a variety of extra chips that increased the console&rsquo;s capability, be it a better sound chip or, much more commonly, the <strong>mappers</strong>. There are several different mappers, from Nintendo itself and from third parties like Capcom and Konami. They use a technique called <strong>bank switching</strong>: since the 6502 can&rsquo;t see enough address space, the mapper swaps which pieces of ROM are visible in that 32 KB window, as the game asks. That&rsquo;s how big games fit into a console that, on paper, couldn&rsquo;t handle them.</p>
<p>I already explained this in detail in an old video from my channel, <a href="/2020/06/18/akitando-81-aprendendo-sobre-computadores-com-super-mario-do-jeito-hardcore/">Akitando #81, about learning computing with Super Mario the hardcore way</a>:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/hYJ3dvHjeOE"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Castlevania uses one of the earlier mappers, the UxROM. And here is the scale problem of my project: if I want to be able to translate the majority of NES games, I have to map every mapper, one by one. Today the project implements only two: the NROM, which is Super Mario&rsquo;s bare cartridge, and Castlevania&rsquo;s UxROM. All the others, the MMC1, MMC3, and company, are only planned. Each mapper is quite a bit of work to implement, because it&rsquo;s not enough to understand the NES bank switching, you have to translate that behavior into the Master System&rsquo;s own mapper.</p>
<p>The good news is that the Master System is also a banked system, so the mapping is &ldquo;the same shape one level up.&rdquo; A NES PRG bank becomes a set of SMS banks, a NES bank-switch write becomes a write to the SMS mapper through a shim, and a call that crosses banks uses the &ldquo;gate&rdquo; machinery that already exists. A routine&rsquo;s identity becomes the pair (bank, address), and a dispatch table resolves that at runtime. When it doesn&rsquo;t find it, it fails closed, with a trap, instead of running garbage.</p>
<p>This is where bigger games will hit the wall. The tile bank switching of the more advanced mappers, the scanline interrupt timing of the MMC3 and MMC5, and the SMS&rsquo;s own bank ceiling for a really big NES game, all of that is still unsolved. That&rsquo;s why I still can&rsquo;t say whether a Super Mario Bros 3 is viable or not. It&rsquo;s quite possible the bigger games die exactly at this step.</p>
<p>Either way, I worked a good bit more on Castlevania in this comeback. I fixed a pile of things using the oracle: the sprite-zero loop that ate CPU, the weapon and projectile logic that lost call returns, a scroll glitch in the status bar, the VRAM collision between 8x16 and 8x8 sprites, and the coherence between background, palette, HUD, and sprites. Today Castlevania boots, accepts start, draws a recognizable first stage, responds to the controller, and runs for a good while without crashing. But it&rsquo;s honest to say it still runs slowly, and the path I use to play it is Mednafen at 500% overclock, and even then it&rsquo;s still 2 to 3 times below its own speed target. It&rsquo;s far from pixel-perfect and real-time.</p>
<div class="embed-container">
  <video controls preload="metadata" playsinline style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000;">
    <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260907152243_castlevania-sms-conversion.mp4" type="video/mp4">
  </video>
</div>
<h2>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>After so many weeks and so many attempts, I still don&rsquo;t know how much more can be squeezed out of an automatic translation. The ceiling might be close, because the deepest optimizations are precisely the ones a machine can&rsquo;t infer on its own.</p>
<p>But there&rsquo;s a path that excites me. The automatic translation can become a baseline. An SMS conversion that already runs, correct, even if slow, and that can later be optimized by hand, game by game, exactly like lackoftrack27 did with Super Mario and like so many people have been doing with decompilations and ports out there. The oracle guarantees the base is correct, and the human hand steps in to do what the machine can&rsquo;t.</p>
<p>Porting a game to much better hardware, like a PC port, is relatively easy, because there&rsquo;s capacity to spare. The truly hard part is fitting the game into a same-generation console, where nothing is to spare. And it&rsquo;s exactly that difficulty that makes the idea of making better versions of NES games for the Master System so appealing. It&rsquo;s the console that deserved to have had those games, and never did.</p>
<p>I hope more people get excited about this possibility and contribute to the project. It&rsquo;s all open at <a href="https://github.com/akitaonrails/nes-to-sms"target="_blank" rel="noopener">nes-to-sms</a>. If you enjoy 6502, Z80, VDP, and the challenge of squeezing cycles out of 40-year-old hardware, come by. There&rsquo;s plenty of room.</p>
]]></content:encoded><category>retrocomputing</category><category>coding-agents</category><category>gaming</category><category>emulation</category></item><item><title>Frank GO: Playing Go with AI</title><link>https://akitaonrails.github.io/en/2026/07/06/frank-go-playing-go-with-ai/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/07/06/frank-go-playing-go-with-ai/</guid><pubDate>Mon, 06 Jul 2026 12:00:00 -0300</pubDate><description>&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/06/frank-go/key-art-hikaru-no-go.webp" alt="Official Hikaru no Go anime key art: Hikaru at the center holding a Go stone out to the camera, with the ghost Sai in Heian-era robes and fan behind him, surrounded by the cast (Akira, Waya, Isumi, Akari and company) and the Shonen Jump logo." loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;I got into the ancient game of Go because of the manga &lt;strong&gt;Hikaru no Go&lt;/strong&gt;. I even wrote about it &lt;a href="https://akitaonrails.github.io/en/2008/07/15/off-topic-por-que-programadores-devem-jogar-go/"&gt;back in 2008, in a post that&amp;rsquo;s still up&lt;/a&gt;, arguing why programmers should play Go. Since then, my use case has barely moved: I own a physical goban at home, but I never had the slightest intention of joining a study group, a federation, or a tournament. I just like playing solo, solving tsumego (Go&amp;rsquo;s life-and-death puzzles), and loading famous games to study, like the legendary Lee Sedol vs AlphaGo games from 2016.&lt;/p&gt;</description><content:encoded><![CDATA[<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/06/frank-go/key-art-hikaru-no-go.webp" alt="Official Hikaru no Go anime key art: Hikaru at the center holding a Go stone out to the camera, with the ghost Sai in Heian-era robes and fan behind him, surrounded by the cast (Akira, Waya, Isumi, Akari and company) and the Shonen Jump logo."  loading="lazy" /></p>
<p>I got into the ancient game of Go because of the manga <strong>Hikaru no Go</strong>. I even wrote about it <a href="/en/2008/07/15/off-topic-por-que-programadores-devem-jogar-go/">back in 2008, in a post that&rsquo;s still up</a>, arguing why programmers should play Go. Since then, my use case has barely moved: I own a physical goban at home, but I never had the slightest intention of joining a study group, a federation, or a tournament. I just like playing solo, solving tsumego (Go&rsquo;s life-and-death puzzles), and loading famous games to study, like the legendary Lee Sedol vs AlphaGo games from 2016.</p>
<p>For anyone who doesn&rsquo;t know the manga: Hikaru no Go (written by Yumi Hotta, drawn by Takeshi Obata, the same artist as Death Note) ran in Shonen Jump from 1998 to 2003 and set off a Go boom across all of Japan. And here&rsquo;s a detail few people know: practically every game shown in the manga is based on a real historical game, recreated panel by panel. Hold onto that, because it comes back in a minute.</p>
<h2>SGF and the scattered-material problem<span class="hx:absolute hx:-mt-20" id="sgf-and-the-scattered-material-problem"></span>
    <a href="#sgf-and-the-scattered-material-problem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>There are several very good open source programs for loading Go games or even playing online. Go games are recorded in a format called <strong>SGF</strong> (Smart Game Format), a plain-text format that&rsquo;s been around since the late 80s: each move becomes a coordinate (<code>;B[pd]</code> is a black stone on the 4-4 point), and the format supports variation trees, comments, and game metadata. It&rsquo;s the de facto standard of the Go world: every client reads SGF, every server exports SGF, and there&rsquo;s a gigantic archive of historical games and problems in this format accumulated over decades.</p>
<p>And that&rsquo;s where the problem lives: that archive is <strong>scattered across the entire internet</strong>. One site has the classic tsumego collections, another has the historical games, one GitHub repo has the commented problems, another has the professional kifu (game records). I was in no mood to download everything by hand, organize it folder by folder, and configure client by client. I wanted an app good enough, pretty enough, with everything built in.</p>
<h2>2008: the GNU Go era<span class="hx:absolute hx:-mt-20" id="2008-the-gnu-go-era"></span>
    <a href="#2008-the-gnu-go-era" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Here&rsquo;s where the historical context earns its keep, because it&rsquo;s the fun part. In 2008, when I wrote that post, the only accessible Go &ldquo;AI&rdquo; was <a href="https://www.gnu.org/software/gnugo/"target="_blank" rel="noopener">GNU Go</a>. And look, you couldn&rsquo;t even call that thing an AI: it was a pile of stacked heuristics. And it was terrible. Even an amateur like me could see it made moves with no flow, no sense of direction, loose stones that didn&rsquo;t talk to each other. It beat beginners on pure local tactics and nothing else.</p>
<p>What happened over the next 18 years is one of the most beautiful stories in computing. First came the Monte Carlo Tree Search methods, which already raised the level considerably.</p>
<p>Then in March 2016, DeepMind&rsquo;s <strong>AlphaGo</strong> beat Lee Sedol, one of the greatest players in history, 4 to 1. That was the series of the famous move 37 in game 2 (the shoulder hit no human would play) and Lee Sedol&rsquo;s answer in kind: move 78 in game 4, the &ldquo;hand of God&rdquo; that earned him his only win. If you&rsquo;ve never seen it, there&rsquo;s an <a href="https://www.netflix.com/title/80190844"target="_blank" rel="noopener">excellent documentary about that series on Netflix</a>, worth watching even if you know nothing about Go.</p>
<p>And after DeepMind published the papers, the open source community chased it down: first Leela Zero reimplementing AlphaZero, then KataGo going beyond it. Today anyone has a superhuman Go player running for free on their own computer. I wanted to see up close how much these AIs matured in 20 years.</p>
<p>That itch is what my new toy project was born from: <strong><a href="https://github.com/akitaonrails/frank_go"target="_blank" rel="noopener">Frank GO</a></strong>.</p>
<h2>Sabaki + KataGo<span class="hx:absolute hx:-mt-20" id="sabaki--katago"></span>
    <a href="#sabaki--katago" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I asked Claude Code to research the most popular open source Go apps and engines (the whole survey is documented <a href="https://github.com/akitaonrails/frank_go/tree/main/docs/research"target="_blank" rel="noopener">in the repository itself</a>, as it should be). In the end I picked two projects as the foundation:</p>
<p><strong><a href="https://sabaki.yichuanshen.de/"target="_blank" rel="noopener">Sabaki</a></strong>, by Yichuan Shen, is the front-end. It&rsquo;s probably the most beautiful, best-kept open source SGF editor and Go board out there, built on Electron, with variation trees, analysis, and engine support via the GTP protocol. Elegant and minimalist.</p>
<p><strong><a href="https://github.com/lightvector/KataGo"target="_blank" rel="noopener">KataGo</a></strong>, by David Wu, is the AI. It&rsquo;s the strongest open source engine in the world, superhuman, heir to the AlphaZero lineage but with one difference that matters enormously for my case: beyond win rate, KataGo estimates <strong>score and territory ownership point by point</strong>. It answers &ldquo;who&rsquo;s winning, by how many points, and which areas of the board belong to whom.&rdquo; That&rsquo;s exactly the kind of information a beginner needs and that the old engines kept hidden.</p>
<p>Frank GO is a fork of Sabaki that wires in KataGo for solo play sessions, and bundles everything I wanted to have in one place:</p>
<ul>
<li><strong>Over 4,700 tsumego</strong> from the classic collections (Cho Chikun&rsquo;s Encyclopedia, the Gokyo Shumyo, the Xuanxuan Qijing), transcribed by Vít Brunner at <a href="https://tsumego.tasuki.org/"target="_blank" rel="noopener">tsumego.tasuki.org</a> and converted to SGF by the <a href="https://github.com/Seon82/tasuki2sgf"target="_blank" rel="noopener">tasuki2sgf</a> project, plus the 420 commented problems from <a href="https://github.com/gogameguru/go-problems"target="_blank" rel="noopener">Go Game Guru&rsquo;s go-problems</a> (by An Younggil, 8-dan professional), organized into 10 difficulty levels. Solve 5 in a row and you level up. A focus picker filters by life &amp; death, tesuji, ko, or semeai (capturing races).</li>
<li><strong>The puzzles fight back</strong>: with KataGo installed, it answers your moves inside the problem, and when it gives up the area the app judges the result and marks the problem solved automatically.</li>
<li><strong>Famous historical games to study</strong>, from the Ear-Reddening Game (1846, by the legendary Honinbo Shusaku) to AlphaGo vs Lee Sedol, curated from <a href="https://homepages.cwi.nl/~aeb/go/games/"target="_blank" rel="noopener">Andries Brouwer&rsquo;s game database at CWI</a>, each with the story behind it, and the AlphaGo ones with move-by-move commentary. You can watch them in auto-play or turn on &ldquo;guess the moves&rdquo; and try to find the pro&rsquo;s play.</li>
<li><strong>The Hikaru no Go games</strong>: remember how the manga&rsquo;s games are real? Well, 15 of them are bundled with chapter references and trivia, using the <a href="https://senseis.xmp.net/?HikaruNoGoGames"target="_blank" rel="noopener">chapter-to-game mapping the community compiled on Sensei&rsquo;s Library</a>. Sai&rsquo;s internet game against Toya Meijin, for instance, is a real game decided by half a point.</li>
<li><strong>A joseki dictionary</strong> (<a href="https://waterfire.us/joseki.htm"target="_blank" rel="noopener">Kogo&rsquo;s Joseki Dictionary</a>, the classic corner-opening reference), ladder drills, a &ldquo;who is winning?&rdquo; drill, and a ten-problem rank test that estimates your level and drops you straight into the right practice.</li>
</ul>
<p>Remember the scattered-material problem? Those collections above are exactly it: each one lived in its own corner of the internet. The provenance and licensing of every set is documented in the repo&rsquo;s <a href="https://github.com/akitaonrails/frank_go/blob/main/data/SOURCES.md"target="_blank" rel="noopener">data/SOURCES.md</a>, because you use other people&rsquo;s archives by giving proper credit.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/06/frank-go/tsumego-practice.jpg" alt="Tsumego practice in Frank GO: a corner problem on the board with the area painting overlay on, and the side panel showing level 1, focus, and the note that KataGo answers your moves."  loading="lazy" /></p>
<h2>Overlays for people just starting out<span class="hx:absolute hx:-mt-20" id="overlays-for-people-just-starting-out"></span>
    <a href="#overlays-for-people-just-starting-out" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The part of Sabaki I changed the most was building beginner-friendly visual overlays. The main one is <strong>area painting</strong>: an overlay that paints each player&rsquo;s influence as a soft gradient and settled territory in solid color, so you literally <strong>see</strong> what the stones are doing, which areas are dead and which are contested.</p>
<p>Alongside it comes the <strong>real-time score</strong> accounting for komi (White&rsquo;s point compensation for playing second), which warns you when you&rsquo;re hopelessly behind or when the game is already settled. And hovering over the board shows the move names (&ldquo;Hane&rdquo;, &ldquo;One-Point Jump&rdquo;), the vocabulary the books use and that beginners take forever to absorb.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/06/frank-go/study-mode.jpg" alt="Frank GO’s study mode: Honinbo Shusai’s retirement game (1938) loaded on the board, with area painting dimming the dead stones and the side panel telling the story of the game immortalized in Kawabata’s novel “The Master of Go”."  loading="lazy" /></p>
<p>That was my problem with every Go software I ever tried: they&rsquo;re tools built by strong players for strong players. A winrate percentage says nothing to someone who&rsquo;s learning. &ldquo;You&rsquo;re 12 points behind and the lower left corner just died&rdquo; says everything. KataGo always knew how to compute this; someone just had to put it on screen in a way a beginner understands.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/06/frank-go/practice-panel.jpg" alt="Frank GO’s practice panel greeting you and suggesting the ten-problem rank test to find your level."  loading="lazy" /></p>
<p>The idea is to be a one-stop shop, zero configuration, for beginners and students to learn Go at their own pace. And there&rsquo;s a build for everywhere. On Arch Linux it&rsquo;s <code>yay -S frank-go</code> via AUR. On <strong>Windows</strong> there&rsquo;s an installer and a portable build on the <a href="https://github.com/akitaonrails/frank_go/releases"target="_blank" rel="noopener">Releases page</a>; the installer has no code signing, so SmartScreen complains on first run — just click &ldquo;More info → Run anyway&rdquo; (<a href="/en/2026/05/30/open-source-best-practices-llm-the-minimum/">I explain why I don&rsquo;t sign Windows builds in the best-practices post</a>). On <strong>macOS</strong> it&rsquo;s <code>brew install --cask akitaonrails/tap/frank-go</code>, or the <code>.dmg</code> for Apple Silicon and Intel; that build is signed with a Developer ID and notarized by Apple, opens with zero drama. And on any platform you can run from source with <code>npm install</code>.</p>
<p>In every case the app downloads a small CPU engine with one click, no GPU, no account, no server, fully offline. The exception is macOS, where KataGo has no official binary: a <code>brew install katago</code> unlocks play-vs-AI, and everything else (tsumego, study, drills, area painting) works without it. Your progress stays on your machine.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/06/frank-go/study-and-drills.jpg" alt="Frank GO’s study and drills menu: study a famous game, Hikaru no Go games, joseki dictionary, who-is-winning drill, ladder drill, and rank test."  loading="lazy" /></p>
<h2>And when you want to play against people<span class="hx:absolute hx:-mt-20" id="and-when-you-want-to-play-against-people"></span>
    <a href="#and-when-you-want-to-play-against-people" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Frank GO is deliberately a solo study app. When you&rsquo;ve learned and want to play against humans, the natural path is the online servers, and there the more advanced apps serve you better. <a href="https://online-go.com/"target="_blank" rel="noopener">OGS (Online Go Server)</a> is the friendliest today, runs right in the browser. <a href="https://www.gokgs.com/"target="_blank" rel="noopener">KGS</a> and <a href="https://www.pandanet-igs.com/"target="_blank" rel="noopener">Pandanet IGS</a> (the original Internet Go Server, running since the early 90s) are the veterans, and the Asian servers like Fox and Tygem concentrate the strongest players. Organized Go revolves around the <a href="https://www.intergofed.org/"target="_blank" rel="noopener">IGF (International Go Federation)</a> and the national associations, with rankings and tournaments for anyone who wants to take it seriously.</p>
<p>I don&rsquo;t. And that&rsquo;s the whole point of the project: like I said up top, I have zero desire to compete. What I wanted, for literally years, was a way to play and have fun with Go at my own pace, with decent study material in one place, against an AI that plays beautifully instead of spitting out senseless stones like the GNU Go of 2008. I think I finally pulled it off.</p>
<p>The code is MIT, the repo is <a href="https://github.com/akitaonrails/frank_go"target="_blank" rel="noopener">github.com/akitaonrails/frank_go</a>, and contributions are welcome. There&rsquo;s even an <a href="https://github.com/akitaonrails/frank_go/issues/6"target="_blank" rel="noopener">open issue asking for character portraits</a> for the Hikaru no Go mode, if anyone feels like drawing. Sai thanks you.</p>
]]></content:encoded><category>frank</category><category>llms</category><category>artificial-intelligence</category><category>gaming</category></item><item><title>How DriveClub and shadPS4 Almost Defeated AI and Me: How to Learn</title><link>https://akitaonrails.github.io/en/2026/04/23/driveclub-shadps4-e-ia-como-aprender/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/04/23/driveclub-shadps4-e-ia-como-aprender/</guid><pubDate>Thu, 23 Apr 2026 12:00:00 -0300</pubDate><description>&lt;h2&gt;Where we were&lt;span class="hx:absolute hx:-mt-20" id="where-we-were"&gt;&lt;/span&gt;
&lt;a href="#where-we-were" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Last week I &lt;a href="https://akitaonrails.github.io/en/2026/04/19/retrogames-de-corrida-favoritos-no-distrobox/"&gt;posted about my distrobox-gaming setup&lt;/a&gt;, with more than ten retro racing games running on Linux, from Gran Turismo 1 on DuckStation to Forza Motorsport 4 on Xenia. At the end of that piece, I described Driveclub on shadPS4 as the &amp;ldquo;final boss&amp;rdquo;: the game boots, the menu loads, the race starts, the controller responds.&lt;/p&gt;
&lt;p&gt;Except I mentioned two problems in passing and treated them as &amp;ldquo;accepted limitations&amp;rdquo;:&lt;/p&gt;</description><content:encoded><![CDATA[<h2>Where we were<span class="hx:absolute hx:-mt-20" id="where-we-were"></span>
    <a href="#where-we-were" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Last week I <a href="/en/2026/04/19/retrogames-de-corrida-favoritos-no-distrobox/">posted about my distrobox-gaming setup</a>, with more than ten retro racing games running on Linux, from Gran Turismo 1 on DuckStation to Forza Motorsport 4 on Xenia. At the end of that piece, I described Driveclub on shadPS4 as the &ldquo;final boss&rdquo;: the game boots, the menu loads, the race starts, the controller responds.</p>
<p>Except I mentioned two problems in passing and treated them as &ldquo;accepted limitations&rdquo;:</p>
<ol>
<li><strong>During daytime, the image is a bit darker than on a reference PS4.</strong> Not alarming, but noticeable.</li>
<li><strong>At night, the race start in Canada or Munnar went pitch black for 10 to 30 seconds</strong> before the game &ldquo;recovered on its own&rdquo; around the 1:30 mark. During those seconds, only the HUD was visible. Track, car, headlights — all absolute black.</li>
</ol>
<p>In the previous article I pushed the explanation to &ldquo;it&rsquo;s just not fixed in the emulator yet, I&rsquo;ll live with it&rdquo;. But inside I couldn&rsquo;t make myself play the game in a visually unplayable state. I wanted to play DriveClub at its best, and I didn&rsquo;t want to sit around waiting for the project to fix it for me. I decided to take matters into my own hands. Thirty seconds of absolute black isn&rsquo;t &ldquo;a bit darker&rdquo;. It&rsquo;s a serious bug, the kind that makes a game unplayable in practice.</p>
<p>This article is what happened between Sunday night and Thursday morning.</p>
<h2>The blackout, live<span class="hx:absolute hx:-mt-20" id="the-blackout-live"></span>
    <a href="#the-blackout-live" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/original-blackout.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>This video is the behavior I wanted to fix. Race start in India (Munnar), night race. You see the track, headlights turn on, the first few seconds run. Then it darkens progressively. Within about 15 seconds everything is black. Stays that way for another 60-90 seconds. Then the game &ldquo;recalibrates&rdquo; on its own and the track reappears.</p>
<p>If you&rsquo;re a player, you don&rsquo;t care why. You just want to drive.</p>
<h2>Why shadPS4 is hard to debug<span class="hx:absolute hx:-mt-20" id="why-shadps4-is-hard-to-debug"></span>
    <a href="#why-shadps4-is-hard-to-debug" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><a href="https://shadps4.net/"target="_blank" rel="noopener">shadPS4</a> <strong>still has no stable release</strong>. The project is under active development. The main branch changes several times a week, configs migrate from TOML to JSON without notice, settings hide behind &ldquo;Advanced&rdquo; so users with incompatible hardware don&rsquo;t file issues, open PRs compete over different approaches to the same problem.</p>
<p>Anyone trying to configure DriveClub today will find:</p>
<ul>
<li>September/2025 Reddit guides using <code>readbacks = true</code> (it was a boolean back then) saying &ldquo;for DriveClub always enable readbacks&rdquo;.</li>
<li>November/2025 guides saying &ldquo;disable readbacks, it kills performance&rdquo; (because in 2025-07 readbacks became an enum <code>Disabled | Relaxed | Precise</code> with different performance profiles, and Bloodborne started hanging with it on).</li>
<li>January/2026 YouTube videos using some custom fork nobody documented.</li>
<li>A thread on shadps4.net with three different, mutually exclusive explanations for the blackout.</li>
<li>My own notes from last week in <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/docs/driveclub-shadps4.md"target="_blank" rel="noopener"><code>distrobox-gaming/docs/driveclub-shadps4.md</code></a> saying <code>readbacks_mode: 0</code>. The exact opposite of what I found this week.</li>
</ul>
<p>So the information exists, but it&rsquo;s scattered, dated, contradictory, and most of it depends on version details that changed between the original post and today. Reproducing a setup from a YouTube video is like trying to solve a Rubik&rsquo;s cube blindfolded.</p>
<h2>Spoiler: the solution is one integer<span class="hx:absolute hx:-mt-20" id="spoiler-the-solution-is-one-integer"></span>
    <a href="#spoiler-the-solution-is-one-integer" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>After 31 phases of investigation, 44 commits on my shadPS4 fork, 15,668 lines of instrumental code added, and three days of nearly uninterrupted debugging, the answer is <strong>one line in the per-game config</strong>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;GPU&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;readbacks_mode&#34;</span><span class="p">:</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><code>readbacks_mode: 2</code> is the <code>Precise</code> mode. Explaining what it does requires understanding how DriveClub implements auto-exposure, and that&rsquo;s where the journey starts. To know WHY this toggle fixes things, you have to understand the whole chain.</p>
<p>The short version: <strong>DriveClub implements auto-exposure as a GPU→CPU feedback loop</strong>.</p>
<ol>
<li>The scene renders to an HDR target.</li>
<li>A compute shader calculates a luminance histogram into an SSBO.</li>
<li>The CPU <strong>reads</strong> that SSBO on the next frame and derives a target exposure.</li>
<li>The CPU writes that exposure back into a 1936-byte lighting UBO (at slots <code>[38] [48] [50]</code>).</li>
<li>Fragment shaders read that UBO and multiply scene luminance by it.</li>
</ol>
<p>Without <code>readbacks_mode: Precise</code>, step 3 reads stale zeros. The memory page where the GPU wrote the histogram is never synchronized to the CPU side. The CPU exposure integrator concludes &ldquo;scene is dark, open the aperture all the way&rdquo; and ramps its output value monotonically: <code>2.59 → 7.84 → 24 → 90 → 179 → 255</code>. Within 60-90 seconds the scale saturates so high that everything clips to zero. That&rsquo;s the pitch black.</p>
<p>With <code>readbacks_mode: Precise</code> on, the emulator marks the pages the GPU just wrote as kernel-protected (via <code>mprotect</code> on Linux). When the CPU tries to read one of those pages for the first time, a page fault fires. The emulator intercepts it, issues a <code>vkCmdCopyBuffer</code> download from the GPU buffer to the host staging area, waits on the scheduler, and the CPU finally sees fresh data. The real histogram enters the integrator, exposure converges normally, and the race start opens bright as it should.</p>
<p>Why isn&rsquo;t it the default? Three documented reasons. First, per-page <code>mprotect</code> + per-fault GPU stall + compute dispatch + scheduler wait has a real cost: on NVIDIA&rsquo;s warm path it&rsquo;s tolerable, on AMD it tanks below 12 FPS, with an <a href="https://github.com/shadps4-emu/shadPS4/issues/3322"target="_blank" rel="noopener">open issue</a> about it. Second, <a href="https://github.com/shadps4-emu/shadPS4/issues/3826"target="_blank" rel="noopener">Bloodborne hangs</a> on loading screens with Precise enabled. Third, the maintainer tucked the option behind &ldquo;Advanced&rdquo; in the UI precisely to keep users with sensitive hardware from enabling it by mistake. The mode has existed since <code>v0.15.0</code> (September/2025), but <strong>nobody had publicly connected <code>Precise</code> + DriveClub + auto-exposure feedback loop</strong>. The <a href="https://github.com/shadps4-emu/shadPS4/issues/3346"target="_blank" rel="noopener">compat tracker already admits</a> that DriveClub &ldquo;requires readbacks enabled to function properly&rdquo;, but doesn&rsquo;t specify the mode.</p>
<p>In my setup the performance cost is irrelevant. I have an RTX 5090 on my desktop, plenty of hardware to swallow the mprotect, the page fault, the copy and the stall per frame. The call is trivial: I flip Precise on, pay the price, the game runs. On more modest hardware or on AMD the conversation is different, which is exactly why this setting isn&rsquo;t default and hides behind &ldquo;Advanced&rdquo;.</p>
<p>One thing I want to make clear: this whole explanation above (<code>Precise</code> vs <code>Relaxed</code> vs <code>Disabled</code>, GPU→CPU feedback loop, per-page mprotect syscall) is not knowledge I had on Sunday night. I didn&rsquo;t know what a buffer readback was, didn&rsquo;t know DriveClub implemented auto-exposure as a GPU→CPU feedback loop, didn&rsquo;t know <code>readbacks_mode</code> had become an enum in 2025 with three values. To arrive at the right answer, I had to learn every piece from scratch. I couldn&rsquo;t guess. Had I tried to &ldquo;enable readbacks&rdquo; last week without understanding the chain, I&rsquo;d have picked the wrong value (Relaxed isn&rsquo;t enough for this feedback loop, as I&rsquo;ll explain below) and given up thinking &ldquo;it doesn&rsquo;t work&rdquo;.</p>
<p>Two more things need to be in place for this to work, both covered in the <a href="/en/2026/04/19/retrogames-de-corrida-favoritos-no-distrobox/#driveclub-the-impossible-finally-possible">previous article</a>:</p>
<ul>
<li><strong>v1.28 patch applied</strong> on top of the v1.00 base install (without it, content stays locked with &ldquo;not yet released&rdquo;).</li>
<li><strong>60fps XML patch disabled</strong> (it raises the render rate but the logic tickrate is fixed; with it on, the game runs in slow motion).</li>
</ul>
<h2>The result<span class="hx:absolute hx:-mt-20" id="the-result"></span>
    <a href="#the-result" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/canada-fixed.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Same race in Canada, night, with <code>readbacks_mode: 2</code> on. Race start opens bright, auto-exposure converges immediately, the night transition unfolds naturally as the game&rsquo;s TOD (time of day) advances. No blackout, no &ldquo;recalibration&rdquo; at 1:30, no 30-second window of absolute black. This is DriveClub running like it does on PS4.</p>
<p>Now the interesting question is not &ldquo;which setting&rdquo;. It&rsquo;s: <strong>how did we get here?</strong></p>
<h2>How does a junior learn today?<span class="hx:absolute hx:-mt-20" id="how-does-a-junior-learn-today"></span>
    <a href="#how-does-a-junior-learn-today" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>There&rsquo;s a question I hear often: &ldquo;with AI doing everything, how does a junior learn?&rdquo;</p>
<p>The premise is wrong. <strong>AI doesn&rsquo;t do everything.</strong> AI does what you ask it to. It doesn&rsquo;t discover on its own. It has no domain initiative. It&rsquo;s not capable of telling you &ldquo;hey, this looks like a broken GPU→CPU readback feedback loop&rdquo; unless you already know there&rsquo;s a thing called a readback and that broken feedback loops are a class of bug.</p>
<p>Worse: if you go to an AI agent today and ask &ldquo;fix this black screen in DriveClub on shadPS4&rdquo;, it will give you a list of possible answers based on old forum posts, with a high probability of steering you wrong. It will suggest the 60fps XML patch (wrong — causes slow motion). It will suggest a tonemap override (wrong — the game already writes correct SDR). It will suggest a vblank_frequency tweak (useless for the real problem). It will suggest manual gamma (treats the symptom, not the cause).</p>
<p>The way to discover is to <strong>dive into the chain</strong>, phase by phase, ruling out wrong hypotheses until the terrain is familiar enough that you recognize the right answer when it appears.</p>
<p>That&rsquo;s where a lot of people misread me. &ldquo;Akita is already a senior, he has 25+ years of experience, of course he knows.&rdquo; <strong>False.</strong> A senior is someone who has been a junior in every topic they master. And they keep becoming a junior every time they pick up a new domain. I have 25 years of web programming, distributed systems, Ruby, Erlang, Go. But <strong>I&rsquo;ve never programmed for PS4</strong>. I&rsquo;d never looked at the shadPS4 codebase. I didn&rsquo;t know PS4&rsquo;s graphics architecture. On Sunday I was an absolute junior in this domain.</p>
<p>But junior doesn&rsquo;t mean totally green. A while back I had explored on my YouTube channel the low-level architecture of older consoles (from the NES&rsquo;s 6502 onward) and how emulators work internally in general. The two videos below helped build the basic vocabulary for how a game console works under the hood, and made sure I wouldn&rsquo;t trip on concepts like fetch-decode-execute, opcode, interrupt, or HLE vs LLE.</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/hYJ3dvHjeOE"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>



<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/vUqLLpUJ47s"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>I had also made an older video on the evolution of CPUs, GPUs, DirectX, and Vulkan, so at least the vocabulary of &ldquo;modern graphics pipeline&rdquo; and &ldquo;shading language&rdquo; wasn&rsquo;t alien.</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/JEp7ozWqIps"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Knowing that <code>VkCommandBuffer</code> exists is very different from knowing where in shadPS4 a readback <code>vkCmdCopyBuffer</code> fires. But it gives you a base. That kind of old curiosity paid interest now.</p>
<h2>The rotation: Claude Code and Codex<span class="hx:absolute hx:-mt-20" id="the-rotation-claude-code-and-codex"></span>
    <a href="#the-rotation-claude-code-and-codex" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>AI (in my case <a href="https://claude.com/claude-code"target="_blank" rel="noopener">Claude Code</a>) didn&rsquo;t &ldquo;know&rdquo; more than I did. It has the same dated forum text you&rsquo;d find on Reddit. What it did well was <strong>aggregate</strong>: read shadPS4 source code in parallel with me, index comments, cross-reference between phase docs, compile, run probes, parse 61 MB logs, compare decompiled binary against UBO diffs, shine light on corners of the system faster than I could alone.</p>
<p>But the decision to &ldquo;keep investigating&rdquo; was mine. The perseverance of &ldquo;don&rsquo;t accept a mitigation, want the root cause&rdquo; was mine. And when the AI suggested &ldquo;honestly, we should just accept the current result and document it&rdquo; (and it suggested this <strong>several times</strong> over the four days), I was the one who had to say &ldquo;no, we keep going&rdquo;.</p>
<p>The central point about the rotation: <strong>no single LLM managed to close this problem alone.</strong></p>
<p>Over the four days, Claude Code repeatedly got stuck looping on the same hypotheses with no new direction. When that happened, I&rsquo;d switch to Codex for a few hours to bring in outside ideas, different probes, a re-read of the codebase from a fresh angle. Then back to Claude Code to integrate what Codex had surfaced. And so on, alternating.</p>
<p>The final resolution happened on Claude Code, but the journey switched models several times. Each LLM had its biases and blind spots. Left alone, each one would have given up sooner.</p>
<p>What broke the impasse was me continuing to bring in new ideas to try, forcing the model to reassess, switching models whenever one started to repeat itself.</p>
<p>At two points things went past &ldquo;just repeating hypotheses&rdquo;. Codex literally had an agentic-loop glitch: it started repeating the exact same &ldquo;solution&rdquo; it had just tried, over and over, in sequence, without noticing what it was doing. It was the first time I&rsquo;d ever seen that happen in any AI agent. I had to kill the session and start a fresh one from zero to break the cycle.</p>
<p>That says something about context size and session duration. The complexity and pace of this investigation were enough to bring both LLMs to their knees, each in their own way.</p>
<p>The whole journey below is documented in 33 phase docs on the <a href="https://github.com/akitaonrails/shadPS4/tree/gamma-debug/docs/driveclub-investigation"target="_blank" rel="noopener"><code>gamma-debug</code> branch of my shadPS4 fork</a>. Each section here links to the original doc for anyone who wants the complete detail. Here I summarize the essentials: the hypothesis we had, what we tried, what new concept we had to learn, and why the phase didn&rsquo;t close the case.</p>
<h2>Session zero: baseline and reproducibility<span class="hx:absolute hx:-mt-20" id="session-zero-baseline-and-reproducibility"></span>
    <a href="#session-zero-baseline-and-reproducibility" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Every serious investigation starts before Phase 1. The first thing, for any project, is to make sure the <strong>baseline works as expected</strong> and that the situation you want to research is <strong>reproducible every time</strong>. If you can&rsquo;t reproduce the bug on demand, you&rsquo;re chasing ghosts. Every probe after that will be inconclusive because the bug &ldquo;appeared and vanished&rdquo; without control. And if the baseline is broken in some way you haven&rsquo;t noticed, every experiment measures the wrong problem. Without those two guarantees, you can&rsquo;t make progress.</p>
<p>In my case, on Sunday night I confirmed:</p>
<ul>
<li>The game boots all the way to the race screen without crashing, using the <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/docs/driveclub-shadps4.md"target="_blank" rel="noopener"><code>distrobox-gaming</code></a> config.</li>
<li><a href="https://github.com/Nenkai/DriveClubFS"target="_blank" rel="noopener">DriveClubFS</a> extracts v1.28 cleanly, 8018 files, ~47 GB.</li>
<li>MSAA depth resolve applied on the fork; night tracks render shapes instead of uniform absolute black (covered in the <a href="/en/2026/04/19/retrogames-de-corrida-favoritos-no-distrobox/">previous article</a>).</li>
<li>8BitDo controller detected.</li>
<li>Night race in India 19:30 <strong>reproduces the blackout on 100% of attempts</strong>, with the same temporal pattern: progressive fade in the first ~15s, pitch black between ~20s and ~90s, &ldquo;recalibration&rdquo; at 1:30.</li>
</ul>
<p>That baseline is the only solid ground that lets me compare each experiment reliably. Without it, a probe that &ldquo;worked&rdquo; might just be a different cache state, not the real fix. With it, every change has a before and an after that are measurable.</p>
<p>Beyond that, zero specific knowledge of:</p>
<ul>
<li>PS4 PKG format (PFS, param.sfo, disc_info, keystone).</li>
<li>shadPS4 codebase (src/core, src/video_core, shader_recompiler, buffer_cache).</li>
<li>PS4 graphics pipeline (GCN ISA, forward+ lighting, MSAA depth resolve).</li>
<li>Vulkan beyond the surface (descriptor sets, UBO binding, push constants, pipeline cache).</li>
<li>SPIR-V disassembly and patching.</li>
<li>PS4 OELF loader, Itanium RTTI, SCE dynamic relocations.</li>
<li>mprotect-based page-fault tracking for CPU-GPU synchronization.</li>
</ul>
<h2>The journey, phase by phase<span class="hx:absolute hx:-mt-20" id="the-journey-phase-by-phase"></span>
    <a href="#the-journey-phase-by-phase" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I&rsquo;ll group the 30+ phases into buckets that make sense as a narrative. Each phase links to the full doc for anyone who wants technical detail.</p>
<blockquote>
  <p><strong>Note for anyone who doesn&rsquo;t want to read all 30+ phases:</strong> after the phase sequence there are reflection and methodology sections you can read on their own. <a href="#the-shotgun-technique">The shotgun technique</a> explains the debugging methodology I used throughout the investigation. <a href="#what-i-knew-on-sunday-vs-thursday">What I knew on Sunday vs Thursday</a> sums up what I actually learned. <a href="#the-ai-suggested-accepting-the-perseverance-was-mine">The AI suggested accepting. The perseverance was mine</a> discusses AI&rsquo;s real role in this process. <a href="#the-real-cost-of-this-journey">The real cost of this journey</a> catalogs what got produced. If you want to jump straight to the argument, those are the shortcuts.</p>

</blockquote>
<h3>Phase 01: pipeline cache, the warm-up<span class="hx:absolute hx:-mt-20" id="phase-01-pipeline-cache-the-warm-up"></span>
    <a href="#phase-01-pipeline-cache-the-warm-up" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-01-shader-compile-stalls.md"target="_blank" rel="noopener"><code>phase-01-shader-compile-stalls.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> menus run at 2-3 fps on first launch. Must be Vulkan pipeline compilation happening in real time.</li>
<li><strong>Action:</strong> enable <code>pipeline_cache_enabled: true</code> in the global config.</li>
<li><strong>New concept:</strong> Vulkan pipeline caching. When the emulator translates a PS4 GCN shader into SPIR-V and then into driver-specific Vulkan binary, it can cache that binary. Without the cache, it recompiles every cold launch (~864 shaders + ~590 pipelines).</li>
<li><strong>Outcome:</strong> solved. First session pays the cost; subsequent sessions read from disk and launch immediately. Easy. Also my first lesson about the shadPS4 codebase.</li>
</ul>
<h3>Phase 02: gamma dim, starting in the wrong direction<span class="hx:absolute hx:-mt-20" id="phase-02-gamma-dim-starting-in-the-wrong-direction"></span>
    <a href="#phase-02-gamma-dim-starting-in-the-wrong-direction" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-02-gamma-dim-image.md"target="_blank" rel="noopener"><code>phase-02-gamma-dim-image.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> DriveClub&rsquo;s image is dim compared to reference. Must be wrong sRGB encoding or an HDR tonemap applied badly by the emulator.</li>
<li><strong>Action:</strong> instrumented the swapchain to log format, added <code>SHADPS4_PP_*</code> env vars to experiment, built a post-processing pipeline with three knobs (exposure, ACES tonemap, gamma curve).</li>
<li><strong>New concept:</strong> sRGB OETF, HDR tonemap (ACES vs Reinhard), Bayer dither as anti-banding.</li>
<li><strong>Outcome:</strong> via bypass path (<code>SHADPS4_PP_BYPASS=1</code>, writes raw game output with no post-processing) I discovered the <strong>game writes correct SDR already</strong> into the framebuffer. The emulator&rsquo;s post-processing pipeline was mangling the signal. Final shader became simply sRGB encode + Bayer dither. Dim persists, but it&rsquo;s not a gamma problem.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/wrong-gamma.png" alt="Gamma adjustments going wrong everywhere"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/wrong-gamma-blown.png" alt="Blown out: gamma too high"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/wrong-gamma-sky-dark.png" alt="Dark sky: gamma too low"  loading="lazy" /></p>
<p>Three hours on this phase. The most important lesson: <strong>when &ldquo;the image looks wrong&rdquo;, the first diagnostic should be a bypass path that shows raw game output</strong>. If bypass looks right, the emulator is the one mangling the signal. Stop touching the tonemap.</p>
<h3>Phase 03: v1.28 content access<span class="hx:absolute hx:-mt-20" id="phase-03-v128-content-access"></span>
    <a href="#phase-03-v128-content-access" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-03-v128-content-access.md"target="_blank" rel="noopener"><code>phase-03-v128-content-access.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> v1.28 extracts, but the game says &ldquo;content not released yet, download required&rdquo;.</li>
<li><strong>Action:</strong> merge v1.28 on top of v1.00, restore <code>param.sfo</code>, <code>disc_info.dat</code>, <code>keystone</code>.</li>
<li><strong>New concept:</strong> PS4 cumulative patches, package metadata.</li>
<li><strong>Outcome:</strong> solved. Detailed in the <a href="/en/2026/04/19/retrogames-de-corrida-favoritos-no-distrobox/">previous article</a>.</li>
</ul>
<h3>Phase 04: MSAA depth + slow motion<span class="hx:absolute hx:-mt-20" id="phase-04-msaa-depth--slow-motion"></span>
    <a href="#phase-04-msaa-depth--slow-motion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-04-slowness.md"target="_blank" rel="noopener"><code>phase-04-slowness.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> night tracks are pitch black and the game &ldquo;feels slow&rdquo;.</li>
<li><strong>Action:</strong> two separate things. Disable the 60fps XML patch (which was decoupling render rate from logic tickrate). Implement <code>ReinterpretMsDepthAsColor</code> on my fork (to let the emulator resolve 4x MSAA depth to 1-sample color, which DriveClub needs for forward+ lighting on night tracks).</li>
<li><strong>New concept:</strong> forward+ lighting (a renderer that writes the scene to an MSAA depth target and then reads it as color for SSAO and volumetrics), MSAA depth resolve.</li>
<li><strong>Outcome:</strong> both fixed. Detailed in the <a href="/en/2026/04/19/retrogames-de-corrida-favoritos-no-distrobox/">previous article</a>.</li>
</ul>
<h3>Phase 05: second pass on brightness<span class="hx:absolute hx:-mt-20" id="phase-05-second-pass-on-brightness"></span>
    <a href="#phase-05-second-pass-on-brightness" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-05-brightness-followup.md"target="_blank" rel="noopener"><code>phase-05-brightness-followup.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> since the game writes correct SDR, maybe a static boost in post-processing helps without touching the UBO feedback loop.</li>
<li><strong>Action:</strong> tried linear boost (1.5x, 2.0x), gamma pre-encode (pow curve), scene-aware auto-exposure with peak+mean sampling.</li>
<li><strong>Outcome:</strong> all rolled back. Linear boost clips highlights. Gamma introduces banding. My auto-exposure layer cancels the user&rsquo;s manual brightness slider. There&rsquo;s no free lunch in post-processing. Dim stays dim without real HDR or shader patches.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/wrong-gamma-sky-dark.png" alt="Sky too dark"  loading="lazy" /></p>
<h3>Narrowing the blackout: the shared gate<span class="hx:absolute hx:-mt-20" id="narrowing-the-blackout-the-shared-gate"></span>
    <a href="#narrowing-the-blackout-the-shared-gate" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/race-start-blackout-040420.md"target="_blank" rel="noopener"><code>race-start-blackout-040420.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> the blackout has a curious signature. Main view goes dim/black, rearview mirror goes absolute black, HUD stays bright. Three layers with different behaviors suggest a shared &ldquo;gate&rdquo;, not a common fade.</li>
<li><strong>Action:</strong> instrumented texture-cache aliasing, probed with live fragment shader substitution on suspected inputs.</li>
<li><strong>New concept:</strong> texture-cache aliasing (multiple images sharing a memory address, which the emulator cache reuses), render-target lifetime.</li>
<li><strong>Outcome:</strong> points to a shared &ldquo;presentation permission&rdquo; between the main scene and the mirror, not a scripted fade.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/screenshot-2026-04-20_21-22-45.png" alt="Blackout baseline"  loading="lazy" /></p>
<h3>Phases 07-09: texture torture, UI surgery, binary patch plan<span class="hx:absolute hx:-mt-20" id="phases-07-09-texture-torture-ui-surgery-binary-patch-plan"></span>
    <a href="#phases-07-09-texture-torture-ui-surgery-binary-patch-plan" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-07-aggressive-torture-probe.md"target="_blank" rel="noopener"><code>phase-07-aggressive-torture-probe.md</code></a> · <a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-08-ui-asset-surgery.md"target="_blank" rel="noopener"><code>phase-08-ui-asset-surgery.md</code></a> · <a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-09-eboot-binary-patch-plan.md"target="_blank" rel="noopener"><code>phase-09-eboot-binary-patch-plan.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> the race-start dim overlay is an asset. Small texture, UI panel, or a method of the <code>FreeplayGetInCar</code> controller in the eboot.</li>
<li><strong>Action:</strong> null-substituted BC-compressed materials, small texture candidates, four-vector weight masks during critical draws. Disabled UI panels (<code>loading_freeplay.txt</code>, <code>get_in_car_animation.txt</code>, <code>vehicle_select_background</code>). Extracted the OELF, located <code>FreeplayGetInCar</code> ASCII strings, identified the constructor at VA 0xf89b00 and the vtable at 0x15dc3e0.</li>
<li><strong>New concept:</strong> PS4 OELF loader (Sony&rsquo;s variant of ELF), Itanium C++ RTTI (how C++ encodes type info in vtables), SCE dynamic relocations (placeholders that only the dynamic loader resolves at runtime).</li>
<li><strong>Outcome:</strong> nulls work but the fade persists. The overlay renders directly into HDR scene targets, not through the panel system. Binary patching has relocation placeholders that aren&rsquo;t statically resolvable without a Ghidra + PS4 plugin. <strong>Asset-side surface exhausted.</strong> Six hours of debugging, final conclusion: the bug lives in state, not in an asset.</li>
</ul>
<h3>Phase 10: rethink + animlib<span class="hx:absolute hx:-mt-20" id="phase-10-rethink--animlib"></span>
    <a href="#phase-10-rethink--animlib" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-10a-rethink-040422.md"target="_blank" rel="noopener"><code>phase-10a-rethink-040422.md</code></a> · <a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-10b-video-diagnostic-animlib.md"target="_blank" rel="noopener"><code>phase-10b-video-diagnostic-animlib.md</code></a></p>

</blockquote>
<p>Tuesday morning. Sat down, rebooted mentally. New model: the blackout is a <strong>state-machine gate</strong>. It&rsquo;s not &ldquo;scene not ready&rdquo;, it&rsquo;s &ldquo;permission transition&rdquo;. The gate closes after the starting grid appears. Next probe: runtime dispatch trace, enough of the blind asset surgery.</p>
<p>In parallel: frame extraction from the recording at 10 Hz, cross-referenced against keyframes of the animation library (<code>animlib</code>) in <code>india_posteffects.lvl</code>. Found a smoking gun. The <code>MasterBrightness</code> track has keyframes 0.003 and 0.0007 (0.63% brightness) where the default would be 1.0. Animlib beats inline defaults every frame.</p>
<ul>
<li><strong>New concept:</strong> animation library, keyframe encoding, binary float-scanning.</li>
</ul>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/debugging-blackout-fade.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Video above: the process of probing the fade in real time.</p>
<h3>Phase 11: multi-scalar patch<span class="hx:absolute hx:-mt-20" id="phase-11-multi-scalar-patch"></span>
    <a href="#phase-11-multi-scalar-patch" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-11-multi-scalar-patch.md"target="_blank" rel="noopener"><code>phase-11-multi-scalar-patch.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> patching <code>MasterBrightness</code> + <code>AutoTargetLuminance</code> + <code>ManualAutoMix</code> simultaneously will lift the blackout.</li>
<li><strong>Action:</strong> extended <code>PatchAnimlibFloatValues</code> to rewrite three scalars. Instrumented with <code>SHADPS4_DC_DRAWLOG</code> + <code>SHADPS4_DC_UBOLOG</code>.</li>
<li><strong>Outcome:</strong> the patch produces a real <strong>numeric</strong> lift (1.2 → 6.5 in 255, a 5×), but still sub-perceptual black. And the crucial finding: the mirror works normally after the patch. It&rsquo;s not gated, just reflecting a dark scene. <strong>At least 285× of additional attenuation lives downstream of the scalars we can patch.</strong></li>
</ul>
<h3>Phase 12: bisect closes the asset side<span class="hx:absolute hx:-mt-20" id="phase-12-bisect-closes-the-asset-side"></span>
    <a href="#phase-12-bisect-closes-the-asset-side" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-12-bisect-closes-asset-side.md"target="_blank" rel="noopener"><code>phase-12-bisect-closes-asset-side.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> one of the 60 accumulated patches broke the mirror independently from the blackout. Bisect.</li>
<li><strong>Action:</strong> binary bisect across 60 overlay files over 5 rounds.</li>
<li><strong>Outcome:</strong> <code>x_live_lobby_pre_race.txt</code> (rerouting loading-spinner transition from <code>ZoomInAndFade</code> to <code>NoFade</code>) was the mirror killer. Rolled back. <strong>Asset side completely closed.</strong> The driver lives in compiled code or post-process state, not in a game asset.</li>
</ul>
<h3>Asset sweeps 01-09 (consolidated)<span class="hx:absolute hx:-mt-20" id="asset-sweeps-01-09-consolidated"></span>
    <a href="#asset-sweeps-01-09-consolidated" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/asset-sweep-09-conclusions.md"target="_blank" rel="noopener"><code>asset-sweep-09-conclusions.md</code></a></p>

</blockquote>
<p>In parallel with the phases above, I swept 12 RPK substitutions: <code>FreeplayGetInCar</code> page swaps, postfx edits in <code>india_landscape_gui.rpk</code>, <code>globaldata</code> RTT-only edits, prerace state-string rewrites. <strong>All clean misses.</strong> No asset family touches the blackout. Lesson: <strong>stop chasing by names; chase by state transitions.</strong></p>
<h3>Phase 13: UBO dump breakthrough<span class="hx:absolute hx:-mt-20" id="phase-13-ubo-dump-breakthrough"></span>
    <a href="#phase-13-ubo-dump-breakthrough" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-13-ubo-dump-breakthrough.md"target="_blank" rel="noopener"><code>phase-13-ubo-dump-breakthrough.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> eye-adaptation feedback loop with stale input is the culprit. A luminance scalar growing without converging.</li>
<li><strong>Action:</strong> dump 128 bytes of UBO in hex during the race window across six hand-picked pipelines. Look for monotonically changing values.</li>
<li><strong>New concept:</strong> <strong>UBO (Uniform Buffer Object)</strong>, a shared memory region the CPU writes and shaders read. <strong>Auto-exposure integrator</strong>, a classic eye-adaptation component that takes time to converge toward a target.</li>
<li><strong>Outcome:</strong> found it. <strong>UBO offset 3 climbs monotonically from 2.59 → 7.84 → 24 → 90 → 179 → 255</strong> over 2 seconds. Textbook feedback loop with broken input. First real hit in 12 phases.</li>
</ul>
<h3>Phase 14: scene-pipeline UBOs are the read side<span class="hx:absolute hx:-mt-20" id="phase-14-scene-pipeline-ubos-are-the-read-side"></span>
    <a href="#phase-14-scene-pipeline-ubos-are-the-read-side" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-14-scene-pipeline-ubos-wrong.md"target="_blank" rel="noopener"><code>phase-14-scene-pipeline-ubos-wrong.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> clamping the climbing scalar will stop the blackout.</li>
<li><strong>Action:</strong> <code>SHADPS4_DC_LUM_CLAMP=2.0</code> forces offset 3 to a fixed value during the race window.</li>
<li><strong>Outcome:</strong> the clamp fires 1093 times, blackout unchanged. Scene-pipeline UBOs <strong>consume</strong> the dim, they don&rsquo;t produce it. The dim is upstream, in a fullscreen tonemap or post-fx compute.</li>
</ul>
<h3>Phases 15-16: runtime texture dump + mutation<span class="hx:absolute hx:-mt-20" id="phases-15-16-runtime-texture-dump--mutation"></span>
    <a href="#phases-15-16-runtime-texture-dump--mutation" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-15-runtime-texture-dump.md"target="_blank" rel="noopener"><code>phase-15-runtime-texture-dump.md</code></a> · <a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-16-runtime-texture-mutation.md"target="_blank" rel="noopener"><code>phase-16-runtime-texture-mutation.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> the blackout is a fullscreen texture composited over the scene.</li>
<li><strong>Action:</strong> <code>SHADPS4_DC_TEX_DUMP=1</code> dumps every bound texture during the race window (1079 textures captured). Convert tiled format to PNG for visual triage. Then, deterministically mutate each texture via hash-based tint + <code>InvalidateMemory</code> to force re-upload.</li>
<li><strong>New concept:</strong> <strong>GCN tile format</strong> (Sony interleaves bytes a specific way for cache locality), texture de-swizzle, buffer cache invalidation.</li>
<li><strong>Outcome:</strong> 14 rounds of mutation (BC textures, float data, render targets, UI atlases, post-fx buffers, LUTs). Blackout <strong>unchanged across all of them</strong>. Texture content 100% ruled out as the source.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/blue-ish-overlay.png" alt="Blue overlay during mutation"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/green-ish-overlay.png" alt="Green overlay during mutation"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/reflections-glitching-blown.png" alt="Reflections glitched"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/reflections-glitched-blown-white.png" alt="Reflections blown white"  loading="lazy" /></p>
<h3>Phases 17-19: UBO smashing, push-constant smashing, compute dispatch probe<span class="hx:absolute hx:-mt-20" id="phases-17-19-ubo-smashing-push-constant-smashing-compute-dispatch-probe"></span>
    <a href="#phases-17-19-ubo-smashing-push-constant-smashing-compute-dispatch-probe" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-17-ubo-batch-smashing.md"target="_blank" rel="noopener"><code>phase-17-ubo-batch-smashing.md</code></a> · <a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-18-push-constant-smashing.md"target="_blank" rel="noopener"><code>phase-18-push-constant-smashing.md</code></a> · <a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-19-compute-dispatch-probe.md"target="_blank" rel="noopener"><code>phase-19-compute-dispatch-probe.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> random-fill on UBOs, push constants, and compute input buffers can bracket and isolate the dim field without crashing.</li>
<li><strong>Action:</strong> <code>SHADPS4_DC_UBO_SMASH=1</code>, <code>SHADPS4_DC_PC_SMASH=1</code>, <code>SHADPS4_DC_DISPATCH_SMASH=1</code> with cb-index and size filters.</li>
<li><strong>New concept:</strong> <strong>push constants</strong> (128 bytes of fast params the driver pushes straight into the pipeline, no descriptor), <strong>compute dispatches</strong> (fires compute shaders with a grid of threads), <strong>ud_regs</strong> (GCN user data registers — how shadPS4 maps user data to push constants).</li>
<li><strong>Outcome:</strong> safe brackets produce overlay bokeh + blinks, but the dim stays put. Wide brackets that reach the dim also hit matrices/transforms and crash. <strong>Random-fill is the wrong instrument.</strong> Needs diff methodology.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/all-bege.png" alt="Everything beige after smash"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/all-green.png" alt="Everything green"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/all-cyan.png" alt="Everything cyan"  loading="lazy" /></p>
<h3>Phase 19b-c: dim-vs-lift dispatch diff<span class="hx:absolute hx:-mt-20" id="phase-19b-c-dim-vs-lift-dispatch-diff"></span>
    <a href="#phase-19b-c-dim-vs-lift-dispatch-diff" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-19b-dim-vs-lift-diff.md"target="_blank" rel="noopener"><code>phase-19b-dim-vs-lift-diff.md</code></a> · <a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-19c-dispatch-draw-skip-null.md"target="_blank" rel="noopener"><code>phase-19c-dispatch-draw-skip-null.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> compute dispatches that only fire during dim (and stop once it lifts) are part of the chain.</li>
<li><strong>Action:</strong> split the clean log into pre / dim / bright windows (via user wall-clock), count dispatch frequency per pipeline.</li>
<li><strong>Outcome:</strong> identified <strong>8 compute pipelines</strong> (histogram 256→downsamples, tile-grid reductions, auto-exposure 1x1 scalars) that stop exactly when dim lifts. Shapes strongly suggest the eye-adaptation chain. <strong>First genuine diagnostic hit.</strong> Skip test on those 8 + 101 correlated draws: blackout <strong>unchanged</strong>. They&rsquo;re <strong>downstream effects</strong>, not drivers.</li>
</ul>
<h3>Phase 20-21: tonemap inline, fragment → flip buffer<span class="hx:absolute hx:-mt-20" id="phase-20-21-tonemap-inline-fragment--flip-buffer"></span>
    <a href="#phase-20-21-tonemap-inline-fragment--flip-buffer" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-20-shader-dump-tonemap-inline.md"target="_blank" rel="noopener"><code>phase-20-shader-dump-tonemap-inline.md</code></a> · <a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-21-fragment-to-flip-buffer.md"target="_blank" rel="noopener"><code>phase-21-fragment-to-flip-buffer.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> the auto-exposure compute shader is broken, or the tonemap compute has the wrong math.</li>
<li><strong>Action:</strong> recompile all shaders, dump SPIR-V, disassemble the histogram auto-exposure. Patch its output to constant 1.0 and test.</li>
<li><strong>New concept:</strong> <strong>SPIR-V disassembly</strong> (Khronos intermediate format with readable assembly). <strong>Compute shader verification.</strong> Later, <strong>pipeline-to-shader mapping</strong>.</li>
<li><strong>Outcome:</strong> patch <strong>has no effect</strong>. Discovery: <strong>DriveClub has no separate tonemap pass</strong>. Scene geometry writes directly to the flip buffer, with exposure inline in material shaders. Each material shader bakes the dim scalar from a shared UBO. Pipeline cache wipe + <code>SHADPS4_DC_PIPEMAP=1</code> dumps every pipeline→shader mapping. Filtering drawlog for pipelines writing to <code>0x5000900000</code> / <code>0x5000108000</code> (the actual swapchain buffers) yields <strong>14 unique pipelines</strong>. None with Exp2/Log2 tonemap ops. Exposure is applied upstream.</li>
</ul>
<h3>Phase 22: tonemap compute identified<span class="hx:absolute hx:-mt-20" id="phase-22-tonemap-compute-identified"></span>
    <a href="#phase-22-tonemap-compute-identified" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-22-tonemap-compute-identified.md"target="_blank" rel="noopener"><code>phase-22-tonemap-compute-identified.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> among the game&rsquo;s 510 compute shaders, one has a tonemap signature (many Exp2/Log2).</li>
<li><strong>Action:</strong> automated scan, cross-reference with dispatchlog.</li>
<li><strong>Outcome:</strong> <strong><code>cs_0x2c918c06</code></strong> is the strong candidate, with 8 Exp2 + 8 Log2, 5 SSBOs (scene params, TAA, bloom, camera, exposure), 11 images. Skip test on that specific pipeline: <strong>stops the blackout cycle</strong>, removes color grading, introduces temporal ghosting. <strong>That&rsquo;s the final scene→swapchain tonemap path.</strong></li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/temporal-glitching.png" alt="Temporal ghosting after skip"  loading="lazy" /></p>
<h3>Phase 22+: tonemap patches → playable<span class="hx:absolute hx:-mt-20" id="phase-22-tonemap-patches--playable"></span>
    <a href="#phase-22-tonemap-patches--playable" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-22plus-tonemap-compute-patches.md"target="_blank" rel="noopener"><code>phase-22plus-tonemap-compute-patches.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> two surgical SPIR-V edits (histogram no-op + tonemap exposure clamp/boost) make the game playable.</li>
<li><strong>Action:</strong> histogram patch to never update; tonemap patch to clamp exposure floor at 0.5 and boost blend by ×10. Both via SPIR-V patching + <code>shader/patch/</code> drop-in.</li>
<li><strong>Outcome:</strong> <strong>playable!</strong> Dim shrinks from pitch black to mild. A <strong>real mitigation, not a root-cause fix.</strong> The fade curve still drags exposure, and each track needs per-threshold tuning.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/blown-just-track-lights.png" alt="Track lights blowing out after ×10 boost"  loading="lazy" /></p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/india-gamma-correction-attempt.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Here the AI suggested stopping for the first time. <strong>&ldquo;The game is playable, this is a decent mitigation, we should document and accept.&rdquo;</strong> I pushed back: &ldquo;it&rsquo;s playable but it&rsquo;s wrong. Don&rsquo;t accept. We keep going.&rdquo;</p>
<h3>Phase 23: CPU-side exposure intercept<span class="hx:absolute hx:-mt-20" id="phase-23-cpu-side-exposure-intercept"></span>
    <a href="#phase-23-cpu-side-exposure-intercept" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-23-cpu-exposure-intercept.md"target="_blank" rel="noopener"><code>phase-23-cpu-exposure-intercept.md</code></a></p>

</blockquote>
<ul>
<li><strong>Hypothesis:</strong> clamping the exposure scalar at the tonemap compute&rsquo;s <code>BindBuffers</code> point prevents over-dimming.</li>
<li><strong>Action:</strong> <code>SHADPS4_DC_EXPOSURE_PIN=1</code> intercepts <code>BindBuffers</code>, overwrites exposure offsets with a clamp-min threshold, invalidates buffer_cache.</li>
<li><strong>New concept:</strong> <strong>CPU-GPU buffer binding</strong> (the moment when the CPU hands a descriptor to the GPU). <strong>Buffer cache invalidation</strong> (how the emulator keeps consistency between a buffer&rsquo;s CPU-side and GPU-side state).</li>
<li><strong>Outcome:</strong> the scalar oscillates at race start, then climbs monotonically. Per-track tuning is needed. India wants 3.0, Canada blows out at the same threshold. <strong>A single-scalar solution doesn&rsquo;t converge cross-track.</strong></li>
</ul>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/debugging-cli-gamma-correction.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<h3>Phase 24: dim upstream of tonemap<span class="hx:absolute hx:-mt-20" id="phase-24-dim-upstream-of-tonemap"></span>
    <a href="#phase-24-dim-upstream-of-tonemap" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-24-dim-upstream-of-tonemap.md"target="_blank" rel="noopener"><code>phase-24-dim-upstream-of-tonemap.md</code></a></p>

</blockquote>
<ul>
<li><strong>Conclusion:</strong> after the whole probe chain (identity tonemap, exposure pin, compute skip), the dim <strong>is in the HDR target before</strong> the tonemap reads it. Three upstream suspects: (1) atmospheric scattering / volumetric fog, (2) reflection-probe / envmap update, (3) pre-tonemap exposure apply.</li>
<li><strong>New concept:</strong> <strong>atmospheric scattering</strong> (simulating how light travels through atmosphere, Rayleigh/Mie-style), <strong>volumetric fog</strong> (fog rendered as a froxel volume).</li>
<li><strong>Outcome:</strong> plan set. Frame-order dump, walk backwards from tonemap through the whole chain.</li>
</ul>
<h3>Phase 25: frame-order trace<span class="hx:absolute hx:-mt-20" id="phase-25-frame-order-trace"></span>
    <a href="#phase-25-frame-order-trace" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-25-frameorder-trace.md"target="_blank" rel="noopener"><code>phase-25-frameorder-trace.md</code></a></p>

</blockquote>
<ul>
<li><strong>Action:</strong> <code>SHADPS4_DC_FRAMEORDER=&lt;N&gt;</code> logs strict per-submit pipeline/shader chain over N submits during race window.</li>
<li><strong>New concept:</strong> <strong>frame-order reconstruction</strong> (listing every GPU submission in exact order), <strong>froxel volumetrics</strong> (frustum volume voxels — a 3D grid aligned to the camera&rsquo;s frustum that stores light info).</li>
<li><strong>Outcome:</strong> three suspects: (A) half-res compute <code>(60,34,1)</code>, probably SSAO; (B) progressive dispatches <code>(256,1,1)→(2560,1,1)→(10240,1,1)</code>, froxel volumetric; (C) fullscreen apply-fog-to-HDR draw. <strong>Suspect B</strong> is the strongest fit (sunset best-fit, per-pixel non-uniform dim).</li>
</ul>
<h3>Phase 26: The UBO fade pin, the &ldquo;aha moment&rdquo;<span class="hx:absolute hx:-mt-20" id="phase-26-the-ubo-fade-pin-the-aha-moment"></span>
    <a href="#phase-26-the-ubo-fade-pin-the-aha-moment" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-26-ubo-fade-pin.md"target="_blank" rel="noopener"><code>phase-26-ubo-fade-pin.md</code></a></p>

</blockquote>
<p>This is where everything changed.</p>
<ul>
<li><strong>Hypothesis:</strong> a direct memory diff (bright vs dim vs recovered) identifies the exact bytes the game writes to control the blackout.</li>
<li><strong>Action:</strong> capture periodic UBO snapshots across visual state transitions. Diff three snapshots byte by byte. Look for fade signatures.</li>
<li><strong>New concept:</strong> <strong>state diff methodology</strong>. Instead of &ldquo;which pass looks suspicious?&rdquo;, ask &ldquo;which bytes actually change with the visual state?&rdquo;</li>
<li><strong>Outcome:</strong> <strong>breakthrough.</strong> Two UBOs control the fade: a 1936-byte one with slots <code>[38/48/50]</code> (light intensity) being multiplied by 0.094 (fade factor), and a 224-byte sun UBO with slot <code>[50]</code> dropping 97%. Pinning both: race start opens bright. TOD animation intact. Content-aware three-state lifecycle (idle → engaged → expired).</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/touching-sun-light.png" alt="First touch on the sun-light UBO"  loading="lazy" /></p>
<p>Wednesday night. Claude suggests stopping again. <strong>&ldquo;We have the pin working, three-state lifecycle is robust, Canada + Munnar are running. This is upstream quality, we should PR and close it.&rdquo;</strong> I insist: &ldquo;there&rsquo;s still residual dim on other tracks, it&rsquo;s not closed. We keep going.&rdquo;</p>
<h3>Phase 28: pivot to emulator code<span class="hx:absolute hx:-mt-20" id="phase-28-pivot-to-emulator-code"></span>
    <a href="#phase-28-pivot-to-emulator-code" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-28-pivot-to-emulator-code.md"target="_blank" rel="noopener"><code>phase-28-pivot-to-emulator-code.md</code></a></p>

</blockquote>
<ul>
<li><strong>Realization:</strong> the residual dim in Canada dusk/night isn&rsquo;t a wrong UBO value. It&rsquo;s an <strong>emulator translation gap</strong>. The game runs correctly on a real PS4, so shadPS4 has some path that translates or synchronizes incorrectly.</li>
<li><strong>Action:</strong> ranked 5 candidate emulator bugs: HLE shader misidentification, image storage classification, layout transitions, IMAGE_STORE_MIP fallback, OpImageFetch LOD restriction.</li>
<li><strong>New concept:</strong> <strong>HLE (High-Level Emulation)</strong>. Replacing complex PS4 OS functions with host-side implementations. Different from LLE (low-level) which emulates bit by bit. HLE is faster but can diverge from real behavior whenever the host implementation doesn&rsquo;t cover every case.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/canada-dim.png" alt="Canada dusk/night still dim"  loading="lazy" /></p>
<h3>Phase 29: calibrated state + recording harness<span class="hx:absolute hx:-mt-20" id="phase-29-calibrated-state--recording-harness"></span>
    <a href="#phase-29-calibrated-state--recording-harness" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-29-calibrated-state.md"target="_blank" rel="noopener"><code>phase-29-calibrated-state.md</code></a></p>

</blockquote>
<ul>
<li><strong>Action:</strong> <code>SHADPS4_DC_RECORD=1</code>. Recording harness that dumps lighting UBOs periodically + cross-correlates with wall-clock screenshots.</li>
<li><strong>Outcome:</strong> important discovery. <strong>Slots <code>[144..295]</code> of the 1936-byte UBO</strong> (30+ fields) stay <strong>denormal/uninitialized</strong> for the first ~90 seconds of a race. At the 1:30 &ldquo;auto-recalibration&rdquo;, the game writes the real values. <strong>It&rsquo;s not magic recovery, it&rsquo;s the CPU finally writing values that should have been there since frame 1.</strong> On PS4 they&rsquo;re ready before the first frame; on shadPS4 they&rsquo;re delayed by 90s. <strong>Root cause lives in an emulator init gap.</strong></li>
</ul>
<h3>Phase 30: UBO writer audit<span class="hx:absolute hx:-mt-20" id="phase-30-ubo-writer-audit"></span>
    <a href="#phase-30-ubo-writer-audit" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-30-ubo-writer-audit.md"target="_blank" rel="noopener"><code>phase-30-ubo-writer-audit.md</code></a></p>

</blockquote>
<ul>
<li><strong>Action:</strong> exhaustive audit of every path that writes into slot <code>[38]</code> of the 1936-byte UBO. 7 suspects ranked: (1) ObtainBuffer stream-copy race, (2) lazy RegionManager in memory_tracker, (3) histogram compute skip, (4) page-fault delayed invalidation, (5) readback gating, (6) buffer-coherency race, (7) push-constant.</li>
<li><strong>Status:</strong> audit incomplete. Before I could verify each suspect, Phase 31 short-circuited the whole thing.</li>
</ul>
<h3>Phase 31: readbacks_mode = Precise, the turning point<span class="hx:absolute hx:-mt-20" id="phase-31-readbacks_mode--precise-the-turning-point"></span>
    <a href="#phase-31-readbacks_mode--precise-the-turning-point" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-31-readbacks-mode-fix.md"target="_blank" rel="noopener"><code>phase-31-readbacks-mode-fix.md</code></a></p>

</blockquote>
<p>Thursday morning. Tired, thinking about suspect <strong>#5, readback gating</strong>. That one itched at me because it was the only one of the 7 writers that touched the <em>timing</em> of the read rather than the <em>correctness</em> of the write.</p>
<ul>
<li><strong>Hypothesis:</strong> flipping <code>readbacks_mode: 2 (Precise)</code> in per-game config makes the feedback loop work by synchronizing the CPU&rsquo;s read to fresh GPU output.</li>
<li><strong>Action:</strong> one edit in <code>custom_configs/CUSA00003.json</code>. Boot. Canada night.</li>
<li><strong>Outcome:</strong> <strong>race start opens bright. Auto-exposure converges normally. Zero blackout. Zero 1:30 recalibration. Night TOD natural.</strong></li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/23/driveclub-learning/almost-fixed.png" alt="Almost there, the test before the final one"  loading="lazy" /></p>
<p>Tested on Munnar. Same result. One integer. <strong>31 phases. 44 commits. 15,668 lines. Answer: one integer in the config.</strong></p>
<p>Why did this slip past 30 phases? The Phase 31 doc has the honest analysis:</p>
<ol>
<li><strong>Symptom disguise.</strong> Progressive darkening looks exactly like a broken tonemap, bad shader, or miscompiled exposure scalar. Every dump-driven probe found <strong>wrong values in the UBO</strong>, which is true but describes the downstream read, not the upstream gap.</li>
<li><strong><code>readback_linear_images</code> red herring.</strong> I tested that flag in Phase 29, saw no effect, and concluded &ldquo;the readback surface is audited&rdquo;. I forgot that flag is for <em>image</em> readbacks (linear images via <code>TextureCache::DownloadImageMemory</code>). The histogram SSBO is a <strong>buffer</strong>, a separate path.</li>
<li><strong>Pinning making it worse, not better.</strong> Overwriting slots [38/48/50] looked promising but never converged. The game rewrote on top of the pin every frame. I interpreted that as &ldquo;the pin mechanism lands too late in the pipeline&rdquo;. I spent phases 27-29 moving the pin earlier. The real fix was to <strong>stop clobbering the GPU-side input the integrator reads</strong>.</li>
<li><strong>Never audited the buffer readback path.</strong> The Phase 30 audit listed 7 writer candidates, but all of them were about the correctness of the <em>write</em>. None asked &ldquo;what is the CPU reading, expecting the GPU to have filled?&rdquo;</li>
</ol>
<h2>The shotgun technique<span class="hx:absolute hx:-mt-20" id="the-shotgun-technique"></span>
    <a href="#the-shotgun-technique" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>One thing I want to put on record: <strong>torture probes aren&rsquo;t waste, they&rsquo;re learning infrastructure.</strong> And the methodology behind them has a name: shotgun.</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/HkxVhFg81fs?start=40"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>If you have a suspicion (say, &ldquo;the dim comes from some texture&rdquo;), the worst thing you can do is start investigating one texture at a time, in depth. DriveClub has 1079 bound textures during the race window. If a detailed triage on one texture takes 20 minutes, you spend three days on a single branch of that tree and still don&rsquo;t know if the dim even comes from a texture. It might not be in the whole tree.</p>
<p>Shotgun fixes that. Instead of investigating one at a time, you fire at all of them simultaneously. Mutate every bound texture with a hash-based tint, force re-upload, run the game. In 30 seconds you know whether any of them affects the dim. If it does, the dim is texture-side and only then is it worth starting to narrow down. If nothing changes, congratulations: you just ruled out the entire &ldquo;texture&rdquo; category in 30 seconds. Switch categories, shotgun UBOs. No hit? Shotgun push constants. No hit? Shotgun compute dispatches. Keep going until something reacts.</p>
<p><strong>Don&rsquo;t start by narrowing down a tree that might be huge.</strong> You&rsquo;ll spend weeks in irrelevant leaves. Shotgun the trunk first. See which branch reacts. Then go deep inside that branch.</p>
<p>That&rsquo;s what I did, without knowing I was doing it, for the first 30 phases. 32 probe env vars (<code>SHADPS4_DC_UBO_SMASH</code>, <code>SHADPS4_DC_PC_SMASH</code>, <code>SHADPS4_DC_DISPATCH_SMASH</code>, <code>SHADPS4_DC_TEX_NUKE_*</code>, <code>SHADPS4_DC_UBO_NUKE</code>, and more). Random batches, hash-based tints, null substitutions. Most of them never pointed at the real fix. But every shotgun round ruled out an entire category of mental model. &ldquo;Not texture.&rdquo; &ldquo;Not push-constant.&rdquo; &ldquo;Not those 8 compute pipelines.&rdquo; &ldquo;Not the UBO write side anywhere in that region.&rdquo; Every ruling-out narrows the problem.</p>
<p><strong>And while I was doing it, I was learning the system.</strong> Every smash that crashed told me &ldquo;there&rsquo;s a critical descriptor pointer here&rdquo;. Every color tint that appeared told me &ldquo;this UBO feeds that composition pass&rdquo;. Every dispatch that fired differently between dim/bright showed me where the interesting boundary lives.</p>
<p><strong>When you don&rsquo;t know the system, shotgun probes are a map.</strong> You don&rsquo;t use them to find the answer. You use them to learn the terrain.</p>
<h2>What I knew on Sunday vs Thursday<span class="hx:absolute hx:-mt-20" id="what-i-knew-on-sunday-vs-thursday"></span>
    <a href="#what-i-knew-on-sunday-vs-thursday" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><strong>Sunday:</strong> zero everything. All the areas below were black boxes to me.</p>
<p><strong>Thursday:</strong></p>
<ul>
<li>PS4 package format end-to-end (PKG → PFS → param.sfo → disc_info → keystone → npbind). How v1.28 cumulative patches layer on top of a v1.00 base install.</li>
<li>shadPS4 architecture in outline: <code>src/common</code>, <code>src/core</code> (OS emulation, HLE libraries), <code>src/shader_recompiler</code> (GCN→SPIR-V), <code>src/video_core</code> (Vulkan renderer, buffer cache, texture cache, page manager).</li>
<li>Full graphics pipeline: G-buffer → forward+ lighting (writes MSAA depth) → read depth as color for SSAO / volumetric froxel → post-fx compute → tonemap → composite → swapchain.</li>
<li>UBO vs push constant vs descriptor set. When to use each.</li>
<li>SPIR-V disassembly and re-assembly for surgical patching. How to identify a tonemap compute by its Exp2/Log2 signature.</li>
<li>Forward+ lighting + MSAA depth resolve + how to implement ReinterpretMsDepthAsColor.</li>
<li>shadPS4 buffer cache: <code>MemoryTracker</code>, <code>RegionManager</code>, <code>FaultManager</code>. How page faults turn into GPU→CPU sync.</li>
<li><code>readbacks_mode</code>: Disabled / Relaxed / Precise. What each tradeoff looks like. Why Relaxed isn&rsquo;t enough for feedback loops (it only write-protects, not read-protects).</li>
<li>PS4 OELF → Itanium RTTI → SCE relocation → dynamic linker reasoning, even without actually doing the binary patch (the plan is documented).</li>
</ul>
<p>I didn&rsquo;t become an expert in any of it. But I went from <strong>total outsider</strong> to <strong>reasonably oriented operator</strong>. And that jump, as I&rsquo;ll argue below, was the single most valuable thing of the four days.</p>
<h2>The AI suggested accepting. The perseverance was mine<span class="hx:absolute hx:-mt-20" id="the-ai-suggested-accepting-the-perseverance-was-mine"></span>
    <a href="#the-ai-suggested-accepting-the-perseverance-was-mine" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I&rsquo;ve said a few times in this article that &ldquo;Claude suggested stopping&rdquo;. I want to be fair and specific: it wasn&rsquo;t an AI failure. It was exactly the correct behavior of an intelligent tool: <strong>when you have something that works reasonably well, stopping and documenting is frequently the right call.</strong> The Phase 22+ tonemap patch made the game playable. The Phase 26 UBO pin made the image pretty. The Phase 29 recording harness covered the critical window. All were legitimate stopping points.</p>
<p>If you read <a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/codex-conclusion.md"target="_blank" rel="noopener"><code>codex-conclusion.md</code></a>, the document the AI produced after Phase 26 (still not knowing about Phase 31 yet), it describes the then-current solution as &ldquo;<strong>a mitigation, not a root-cause fix</strong>&rdquo;. It honestly admits what we knew: the UBO pin was a countermeasure, not an explanation. That&rsquo;s good behavior.</p>
<p>The difference is that <strong>I had a stubborn hunch that something was still wrong</strong>. It worked in the playable sense. But the cost (DriveClub-specific pin + tonemap SPIR-V patch + per-track threshold tuning + doesn&rsquo;t generalize) was too high for a supposedly engineering-quality fix. A senior professional recognizes when the asymmetry between cost and understanding points to something deeper. You don&rsquo;t always get it right. But that hunch deserves to be heard.</p>
<p>Four days without interruption, 8 a.m. to midnight, Sunday, Monday, Tuesday, Wednesday, resolved Thursday morning. How many times did the AI suggest &ldquo;honestly, we should just accept the current result and document&rdquo;? Four or five. Each technically defensible. <strong>Human perseverance is the difference between &ldquo;the game works well enough&rdquo; and &ldquo;the game works, and I know why&rdquo;.</strong></p>
<h2>The real cost of this journey<span class="hx:absolute hx:-mt-20" id="the-real-cost-of-this-journey"></span>
    <a href="#the-real-cost-of-this-journey" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/investigation-effort-accounting.md"target="_blank" rel="noopener"><code>investigation-effort-accounting.md</code></a> catalogs everything before cleanup. Some highlights:</p>
<ul>
<li><strong>3 days</strong> of active work (Apr 21-23, 2026).</li>
<li><strong>33 numbered phases</strong> + sweeps + side threads = 54 docs, 412 KB, <strong>6,867 lines of prose</strong>.</li>
<li><strong>44 commits</strong> on the gamma-debug fork. <strong>15,668 lines added</strong>, 1,204 removed.</li>
<li><code>src/video_core/renderer_vulkan/vk_rasterizer.cpp</code> grew from 1,364 to <strong>4,680 lines</strong> (3.4×) with instrumentation, pin lifecycles, recording harness.</li>
<li><strong>32 probe env vars</strong> defined. <strong>1 effectively used to find the bug</strong> (<code>SHADPS4_DC_LOG_STREAMCOPY</code>, in Phase 30).</li>
<li><strong>61 MB</strong> of shadPS4 log accumulated. <strong>57 MB</strong> of shader dumps (5,803 files). ~<strong>470 MB</strong> of total reclaimable artifacts once closed.</li>
</ul>
<p>Resolution: <strong>1 integer in the config.</strong></p>
<p>The ratio of <strong>6,867 lines of prose : 1 integer</strong> amuses me. But it isn&rsquo;t a joke about inefficiency. It&rsquo;s the real cost of learning a system you didn&rsquo;t know. Every line of prose ruled out a hypothesis. Every commit taught a layer of the stack. The integer was the end; the path was the product.</p>
<p>An honest note: despite the 15,668 lines added on the <code>gamma-debug</code> fork, <strong>almost none of it is useful to contribute upstream to shadPS4</strong>. What&rsquo;s there is instrumentation: torture probes, recording harness, pin lifecycles, calibrate-at-arm, UBO snapshots, dozens of debug env vars. Diagnostic code, not production code. Diagnostics that only make sense for DriveClub under those specific conditions.</p>
<p>The fork is going to stay public, but <strong>not as a contribution branch to the shadPS4 project</strong>. Unfortunately. It stays as a study document: 54 md files covering the full process, 44 commits showing the sequence of hypotheses, rule-outs and failures. It&rsquo;s material for anyone who wants to understand how the debugging process unfolded, or to reuse some of the techniques in similar investigations. No upstream-ready PR is coming out of that tree.</p>
<h2>Closing<span class="hx:absolute hx:-mt-20" id="closing"></span>
    <a href="#closing" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Yes, the final solution was a config toggle that already existed. It&rsquo;s been in shadPS4&rsquo;s code for seven months. But on Sunday night, with zero PS4 knowledge, zero emulator knowledge, zero notion that auto-exposure was a GPU→CPU feedback loop, <strong>I had no technical authority to say &ldquo;flip readbacks Precise, done&rdquo;</strong>.</p>
<p>I had to see the whole chain to understand <em>why</em> it works. Why Relaxed isn&rsquo;t enough (it only write-protects, and DriveClub reads, it doesn&rsquo;t write into the histogram SSBO). Why Precise is expensive (per-page mprotect + per-fault GPU stall). Why the maintainer hid it behind &ldquo;Advanced&rdquo; (Bloodborne hangs, AMD tanks to 12 FPS). Why nobody in the community had connected Precise to DriveClub before: the compat tracker admits &ldquo;readbacks enabled&rdquo; but doesn&rsquo;t cite the mode, and nobody had seen the monotonic-drift signature through the GPU→CPU lens.</p>
<p>That&rsquo;s the difference between <strong>knowing the answer</strong> and <strong>knowing the terrain well enough to recognize the answer</strong>. To the junior asking me &ldquo;how do I learn today, in the AI era&rdquo;: that&rsquo;s my answer. Don&rsquo;t ask. Dive in. Use AI to speed up the search, to read code in parallel, to index old forums, to compile, to parse logs. But <strong>insist on understanding the why</strong>. When the AI suggests &ldquo;let&rsquo;s accept and document&rdquo; (and it will, because that&rsquo;s often the right behavior), ask yourself whether you actually understood the chain. If the itch &ldquo;this is wrong but I don&rsquo;t know why&rdquo; is still there, keep going.</p>
<p>You&rsquo;re probably going to spend four days on something that has a one-integer solution. You&rsquo;re probably going to discover five wrong paths before the right one. Probably half your probes will be torture shotguns that never pointed at the fix.</p>
<p>And you&rsquo;ll probably, when you finish, know a domain you didn&rsquo;t know before.</p>
<ul>
<li><strong>Fork gamma-debug:</strong> <a href="https://github.com/akitaonrails/shadPS4/tree/gamma-debug"target="_blank" rel="noopener">github.com/akitaonrails/shadPS4/tree/gamma-debug</a></li>
<li><strong>54 investigation docs:</strong> <a href="https://github.com/akitaonrails/shadPS4/tree/gamma-debug/docs/driveclub-investigation"target="_blank" rel="noopener">docs/driveclub-investigation/</a></li>
<li><strong>Operational runbook (recipe):</strong> <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/docs/driveclub-shadps4.md"target="_blank" rel="noopener">distrobox-gaming/docs/driveclub-shadps4.md</a></li>
<li><strong>Phase 31 resolution:</strong> <a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-investigation/phase-31-readbacks-mode-fix.md"target="_blank" rel="noopener"><code>readbacks_mode: 2</code></a></li>
</ul>
<p>Now I&rsquo;m going to go play DriveClub. Night race in Canada, natural brightness, no blackout. Good Thursday.</p>
]]></content:encoded><category>emulation</category><category>gaming</category><category>learning</category></item><item><title>My Favorite Retro Racing Games Running on My Distrobox</title><link>https://akitaonrails.github.io/en/2026/04/19/my-favorite-retro-racing-games-on-distrobox/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/04/19/my-favorite-retro-racing-games-on-distrobox/</guid><pubDate>Sun, 19 Apr 2026 20:00:00 -0300</pubDate><description>&lt;p&gt;I spent the whole Sunday on this, and it was one of the most productive Sundays I&amp;rsquo;ve had in a while. The mission was specific: close the loop on the simcade racing games that are hardest to emulate, the ones I&amp;rsquo;ve wanted to run properly for years, and only now got to a reliable state.&lt;/p&gt;
&lt;p&gt;Two monsters sit at the top of that list. First, &lt;strong&gt;&lt;a href="#driveclub-the-impossible-finally-possible"&gt;Driveclub&lt;/a&gt;&lt;/strong&gt; on shadPS4. It&amp;rsquo;s a PS4 exclusive, never got a port, Evolution Studios was shut down, no remaster. To play it outside the original PS4, emulation is the only option, and PS4 emulation is still the most immature part of the ecosystem. This is &lt;strong&gt;by far the hardest on the list&lt;/strong&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I spent the whole Sunday on this, and it was one of the most productive Sundays I&rsquo;ve had in a while. The mission was specific: close the loop on the simcade racing games that are hardest to emulate, the ones I&rsquo;ve wanted to run properly for years, and only now got to a reliable state.</p>
<p>Two monsters sit at the top of that list. First, <strong><a href="#driveclub-the-impossible-finally-possible">Driveclub</a></strong> on shadPS4. It&rsquo;s a PS4 exclusive, never got a port, Evolution Studios was shut down, no remaster. To play it outside the original PS4, emulation is the only option, and PS4 emulation is still the most immature part of the ecosystem. This is <strong>by far the hardest on the list</strong>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/19/distrobox-gaming/shadps4-launcher.png" alt="shadPS4 Qt Launcher showing DRIVECLUB CUSA00003 ready to run"  loading="lazy" /></p>
<p>Second, <strong><a href="#forza-motorsport-4-the-goat">Forza Motorsport 4 with Project Forza Plus</a></strong> on Xenia Canary. FM4 is the GOAT of the Xbox 360 era, Project Forza Plus is the community mod that consolidates patches and content, and getting the two to run together on Xenia with decent graphics, no audio crashes and no shadow bugs took serious hours of trial and error.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/19/distrobox-gaming/xenia-manager.png" alt="Xenia Manager with the Xbox 360 Forza collection installed"  loading="lazy" /></p>
<p>Around those two I organized the rest of what I already knew worked at some level, just needed consolidating into a reproducible setup: <strong><a href="#gran-turismo-4-spec-ii-mod">Gran Turismo 4 with Retexture 3.0 and HD HUD</a></strong>, <strong><a href="#gran-turismo-3-a-spec">Gran Turismo 3 with HD textures and widescreen</a></strong>, <strong><a href="#gran-turismo-2-spec-ii-mod">Gran Turismo 2 with the Spec II mod</a></strong>, the PS3 Gran Turismos (<a href="#gran-turismo-5">GT5</a> and <a href="#gran-turismo-6">GT6</a>), Forza Horizon <a href="#forza-horizon-1-with-xe-mod">1</a> and <a href="#forza-horizon-2">2</a>, the <a href="#project-gotham-racing-3-and-4">PGRs</a>, <a href="#ridge-racer-v">Ridge Racer</a>, <a href="#enthusia-professional-racing">Enthusia</a>, <a href="#colin-mcrae-rally-1-and-2">Colin McRae</a>. I had an idea how to make these work, I just needed a setup that doesn&rsquo;t break the next time I reinstall the system.</p>
<p>Before the details, two things to clarify.</p>
<p>First, on infra: I&rsquo;m not going to repeat here how the setup was built. I wrote that in detail in <a href="/en/2026/04/11/emulation-distrobox-with-claude-code/">An Emulation Distrobox with Claude Code</a>. Arch Linux in a distrobox with <code>--nvidia</code>, 17 Ansible roles, every emulator from the AUR, ES-DE as frontend, automated per-game configs, Python scripts for PS3 update checking and Xbox 360 title updates, Xenia via Wine. Go read that one. The repo is at <a href="https://github.com/akitaonrails/distrobox-gaming"target="_blank" rel="noopener">akitaonrails/distrobox-gaming</a> if you want to reproduce. Here I&rsquo;m going to talk about the games.</p>
<p>Second, on taste: I like simcade racing. Gran Turismo has been my declared addiction since PS1, and I wrote about the rest in the <a href="/en/2026/04/01/my-sim-racing-cockpit-formula-fx1/">Formula FX1 cockpit article</a>. Direct-drive wheel, load-cell pedal, triple monitor when I&rsquo;m feeling masochistic. That&rsquo;s the kind of racing I like.</p>
<h2>The warning for the &ldquo;well, actually&rdquo; crowd<span class="hx:absolute hx:-mt-20" id="the-warning-for-the-well-actually-crowd"></span>
    <a href="#the-warning-for-the-well-actually-crowd" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Yes, I know iRacing and the rest of the current sims and simcades. I have 12 TB of ISOs and ROMs on the NAS, I probably have one you&rsquo;d remember to mention. It&rsquo;s not for lack of options that I&rsquo;m focusing on emulation today.</p>
<p>On the new side, the games I&rsquo;m genuinely anticipating: <strong><a href="https://forza.net/"target="_blank" rel="noopener">Forza Horizon 6</a> in Tokyo</strong>, <strong><a href="https://www.assettocorsa.it/"target="_blank" rel="noopener">Assetto Corsa EVO</a></strong>, and <strong><a href="https://www.assettocorsa.it/"target="_blank" rel="noopener">Assetto Corsa Rally</a></strong>. I&rsquo;ve been playing the demos of both Assetto Corsa titles and enjoying their single-player campaigns a lot. When those ship, they&rsquo;ll become my main sim rotation. This article covers a different side of the collection, pulling classics out of oblivion that still don&rsquo;t have a remaster.</p>
<p>Today, here, I&rsquo;m interested in these specific games I listed above. Period. You do what you want, I do what I want. If you want iRacing, fire up Steam and go in peace. This article is about messing with emulators, preserving old games, and pulling these gems out of oblivion on a Linux setup. If you&rsquo;re here for current-sim reviews, you won&rsquo;t find them.</p>
<p>Now to what matters.</p>
<p><strong>A note on the videos:</strong> most of them have no audio because I forgot to turn on sound capture in OBS before recording and I was too lazy to re-record. Audio works perfectly on every emulator here; this is my OBS mistake, not a setup issue.</p>
<h2>Global settings per emulator<span class="hx:absolute hx:-mt-20" id="global-settings-per-emulator"></span>
    <a href="#global-settings-per-emulator" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Before the games, a quick consolidation of the global configs that apply across each emulator. This is automated in the repo, but if you&rsquo;re setting up by hand, here&rsquo;s the summary.</p>
<h3>DuckStation (PS1)<span class="hx:absolute hx:-mt-20" id="duckstation-ps1"></span>
    <a href="#duckstation-ps1" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>Renderer:</strong> Vulkan</li>
<li><strong>Internal Resolution Scale:</strong> 8x (8K internal, downscaled to the monitor)</li>
<li><strong>Texture Filtering:</strong> JINC2 (preserves pixel art but smooths)</li>
<li><strong>PGXP:</strong> on (fixes the typical PS1 vertex wobble)</li>
<li><strong>Widescreen Hack:</strong> on as fallback, but I prefer per-game widescreen cheats</li>
<li><strong>Aspect Ratio:</strong> 16:9 with widescreen cheat, 4:3 without</li>
</ul>
<p>PS1 runs smoothly on DuckStation these days. It&rsquo;s the most mature emulator on the list. The attention goes to per-game choices (some games need a specific widescreen cheat).</p>
<h3>PCSX2 (PS2)<span class="hx:absolute hx:-mt-20" id="pcsx2-ps2"></span>
    <a href="#pcsx2-ps2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>Renderer:</strong> Vulkan</li>
<li><strong>Upscale:</strong> 4x SSAA (1440p native → 4K internal)</li>
<li><strong>Anisotropic Filtering:</strong> 16x</li>
<li><strong>Post Processing:</strong> FXAA on, PCRTC antiblur on</li>
<li><strong>Deinterlacing:</strong> Automatic (some games need override)</li>
<li><strong>Controller:</strong> Xbox-style bindings (I use an 8BitDo)</li>
</ul>
<p>PCSX2 2.7.x is much better than the 2.6.3 still in Arch&rsquo;s official repo. The AUR&rsquo;s <code>pcsx2-latest-bin</code> tracks 2.7.x and that&rsquo;s where texture replacement, modern FXAA and PCRTC antiblur live. If you&rsquo;re on a different distro stuck on 2.6.3, get the official AppImage.</p>
<h3>RPCS3 (PS3)<span class="hx:absolute hx:-mt-20" id="rpcs3-ps3"></span>
    <a href="#rpcs3-ps3" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>Renderer:</strong> Vulkan</li>
<li><strong>Resolution Scale:</strong> 300% (generally) or native (for games with upscale issues)</li>
<li><strong>Shader Precision:</strong> Ultra</li>
<li><strong>Force High Precision Z:</strong> on</li>
<li><strong>SPU XFloat:</strong> Accurate</li>
<li><strong>Multithreaded RSX:</strong> on</li>
<li><strong>Write Color Buffers (WCB) / Read Color Buffers (RCB):</strong> off by default (GT-series safety)</li>
</ul>
<p>RPCS3 is where the most traps are. Each game has a recommended preset in the <a href="https://rpcs3.net/compatibility"target="_blank" rel="noopener">official compatibility DB</a>. And a heads-up: per-game configs live in <code>~/.config/rpcs3/custom_configs/config_&lt;SERIAL&gt;.yml</code>. The <code>config_</code> prefix is mandatory. It cost me time to figure out. RPCS3 silently accepts the YAML and then ignores it if the name is wrong.</p>
<h3>Xenia Canary (Xbox 360)<span class="hx:absolute hx:-mt-20" id="xenia-canary-xbox-360"></span>
    <a href="#xenia-canary-xbox-360" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>Build:</strong> Canary (<code>master</code> has been stagnant for months, Canary is where development lives)</li>
<li><strong>Renderer:</strong> Vulkan</li>
<li><strong>Render Target Path:</strong> <code>rtv</code> by default, <code>fsi</code> for specific games (PGR4)</li>
<li><strong>User Profile:</strong> created once, persists</li>
<li><strong>Wine Prefix:</strong> dedicated, managed by Xenia Manager</li>
</ul>
<p>Xenia on Linux runs via Wine, managed by <a href="https://github.com/xenia-manager/xenia-manager"target="_blank" rel="noopener">Xenia Manager</a>. There&rsquo;s no decent native build. It works well, but each game needs specific tuning, and Title Updates have to be pulled manually from archive.org (I wrote a script for that, details in the previous article).</p>
<h3>shadPS4 (PS4)<span class="hx:absolute hx:-mt-20" id="shadps4-ps4"></span>
    <a href="#shadps4-ps4" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Gets its own section at the end. TL;DR: works for some simple games, Driveclub is still a moving target. Still a shot in the dark.</p>
<h2>PS1 on DuckStation<span class="hx:absolute hx:-mt-20" id="ps1-on-duckstation"></span>
    <a href="#ps1-on-duckstation" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Gran Turismo (the first, from 1997)<span class="hx:absolute hx:-mt-20" id="gran-turismo-the-first-from-1997"></span>
    <a href="#gran-turismo-the-first-from-1997" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%201.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>This one is pure sentiment. GT1 was one of the first games I bought for the PS1 and I played it until the CD cracked. At the time, it was on another level. Real simulation on a machine with 128K of video RAM, decent physics, 140 cars when everybody else had 15, memorable soundtrack.</p>
<p>But let&rsquo;s be honest: you can&rsquo;t go back today. GT1&rsquo;s physics has exaggerated drift, the car slides more than it should on any corner above 80 km/h. It&rsquo;s part of the 1997 charm but gets annoying in 2026. The <strong>handling model was refined starting in GT2</strong>, and everything after only got better. Between GT1 and GT2, GT2 wins on content (650+ cars vs 140) and especially on drivability.</p>
<p>I keep GT1 in ES-DE to open in nostalgic moments, drive 10 minutes at Trial Mountain, and close it. For serious PS1-era gameplay, it&rsquo;s GT2.</p>
<h3>Gran Turismo 2 (Spec II Mod)<span class="hx:absolute hx:-mt-20" id="gran-turismo-2-spec-ii-mod"></span>
    <a href="#gran-turismo-2-spec-ii-mod" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%202.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>The original PS1 Gran Turismo is a historical milestone. I played a lot of it back then. To replay today? No. GT2 exists, and GT2 is superior in everything. More cars (650+), more tracks, more modes, and most importantly, <strong>a much better handling model</strong>. GT1 has that exaggerated drift where the car slides more than it should, GT2 tightened that up to something closer to what the series would become starting on PS2. Between playing GT1 or GT2, always GT2.</p>
<p>And the GT2 I run today is the <strong>GT2 Spec II Mod</strong> by <a href="https://x.com/projectaspec?lang=en"target="_blank" rel="noopener">Project A-Spec</a>, a community project that merges the two regional variants (Arcade + Simulation) into a single game, brings back events that were cut from the final release, fixes physics bugs, adds native widescreen support, and ships quality-of-life menu updates. It&rsquo;s the definitive way to play GT2 in 2026. The project doesn&rsquo;t maintain its own website anymore, updates come through the author&rsquo;s Twitter.</p>
<p>To run it:</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disc</td>
          <td>GT2 Spec II Mod (patch applied over Simulation USA ISO)</td>
      </tr>
      <tr>
          <td>Serial</td>
          <td>SCUS-94455</td>
      </tr>
      <tr>
          <td>Widescreen cheat</td>
          <td>On</td>
      </tr>
      <tr>
          <td>8 MB RAM cheat</td>
          <td>On (fixes audio on crowded tracks)</td>
      </tr>
      <tr>
          <td>Texture Filter</td>
          <td>JINC2</td>
      </tr>
      <tr>
          <td>Resolution Scale</td>
          <td>8x</td>
      </tr>
  </tbody>
</table>
<p>The 8 MB RAM cheat matters for crowded tracks (Seattle in populated races for example) where audio starts clipping. It simulates the memory expansion of the Japanese version that never made it West.</p>
<h3>Colin McRae Rally 1 and 2<span class="hx:absolute hx:-mt-20" id="colin-mcrae-rally-1-and-2"></span>
    <a href="#colin-mcrae-rally-1-and-2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Iconic PS1 games. CMR1 has that rough first-gen rally feel that has its charm. CMR2 is technically superior, better physics, more rallies, better graphics. But honestly: <strong>to play CMR2 today, it&rsquo;s worth looking for the original PC version</strong>. Higher resolution, native 60fps (the PS1 version is locked to 30), direct keyboard/wheel support. Codemasters repacks exist on the internet, a 40-second search will find them. I&rsquo;m not going to link them, you know where to look.</p>
<p>On PS1 via DuckStation they run smoothly, nostalgia guaranteed, but it&rsquo;s not where I spend my time.</p>
<h2>PS2 on PCSX2<span class="hx:absolute hx:-mt-20" id="ps2-on-pcsx2"></span>
    <a href="#ps2-on-pcsx2" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Gran Turismo 3 A-Spec<span class="hx:absolute hx:-mt-20" id="gran-turismo-3-a-spec"></span>
    <a href="#gran-turismo-3-a-spec" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%203.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>GT3 was a generational leap. Out of the blocky PS1 world into shaders, decent environment mapping, serious polygon car models, physics that finally started to feel like actual physics. The soundtrack with Feeder&rsquo;s &ldquo;Just a Day&rdquo; opening the game still gives me goosebumps.</p>
<p>The problem is that GT3 has LESS content than GT2. Polyphony was more cautious, cut modes to focus on polish. The A/B/S license tree is smaller, fewer cars, fewer tracks. But what&rsquo;s there is polished.</p>
<p>In 2026 on PCSX2 2.7.x, GT3 looks great. Community HD textures, widescreen pnach, anti-aliasing via SSAA, 16x AF. Feels like a re-release.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disc</td>
          <td>GT3 A-spec (SCUS-97102) or Bundle (PBPX-95503)</td>
      </tr>
      <tr>
          <td>Widescreen pnach</td>
          <td>On</td>
      </tr>
      <tr>
          <td>Retexture pack</td>
          <td>Optional (off by default, enable if you want full HD)</td>
      </tr>
      <tr>
          <td>Upscale</td>
          <td>4x SSAA</td>
      </tr>
      <tr>
          <td>Deinterlacing</td>
          <td>Automatic</td>
      </tr>
  </tbody>
</table>
<p>Caveat on the retexture pack: when enabled, some car-showcase cutscenes in the garage have momentary flicker due to NFS streaming. Doesn&rsquo;t bother in-race. If it bothers you in menus, turn it off.</p>
<p><strong>Reminder:</strong> for the widescreen pnach to take effect, you still need to go into the <strong>in-game</strong> options and switch the aspect ratio to 16:9. The pnach unlocks the option in the menu, it doesn&rsquo;t apply automatically.</p>
<h3>Gran Turismo 4 (Spec II Mod)<span class="hx:absolute hx:-mt-20" id="gran-turismo-4-spec-ii-mod"></span>
    <a href="#gran-turismo-4-spec-ii-mod" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%204.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>GT4 is my GOAT in the series. 700+ cars, 50+ tracks, physics still respectable by 2026 standards, and that soundtrack mixing alternative rock, Japanese lounge, and Moby&rsquo;s infamous &ldquo;Sail Away&rdquo; on the main menu. It&rsquo;s the game I poured the most hours into back in 2004, and it&rsquo;s what I play the most today.</p>
<p>Like GT2, the GT4 I run today <strong>is not the original disc</strong>. It&rsquo;s the <a href="https://www.theadmiester.co.uk/specii/"target="_blank" rel="noopener"><strong>Gran Turismo 4: Spec II</strong></a> maintained by TheAdmiester — a massive community mod that&rsquo;s objectively the best way to play GT4 in 2026. It combines the final USA disc, the 2003 Beta prototype, and the original Japanese release into one game, restoring cut cars (including several that never left Japan), cancelled events, dozens of missing soundtrack tracks, adding 16:9 widescreen support, over 30 built-in cheats exposed as menu options (pro tuning, top speed, fuel economy), the option to switch between English and Japanese, and original bug fixes. The project is active, still gets patches, and ships its own installer that applies over a clean USA ISO.</p>
<p>In practice, Spec II is what GT4 should have been if Polyphony had six more months to polish. Playing it today feels like GT4 with all the content that got left out, plus modern quality-of-life. For anyone who enjoyed the original, it&rsquo;s mandatory.</p>
<p>On top of that goes the community&rsquo;s <strong>Retexture 3.0</strong> (HD texture pack) and the <strong>HD HUD</strong> by <a href="https://github.com/Silentwarior112/GT4-HD-HUD-Pack"target="_blank" rel="noopener">Silentwarior112</a>. With those three pieces together (Spec II Mod + Retexture + HD HUD), GT4 runs at 1440p SSAA on PCSX2 2.7.x with 16x AF, FXAA, and a look that&rsquo;s honestly indistinguishable from a new low-poly-style release. It&rsquo;s my current favorite to sit down and play long.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disc</td>
          <td>GT4 USA (SCUS-97328) or Spec II (SCUS-97436, CRC <code>4CE521F2</code>)</td>
      </tr>
      <tr>
          <td>HD HUD pack</td>
          <td>Installed via symlink (Silentwarior112)</td>
      </tr>
      <tr>
          <td>Retexture 3.0</td>
          <td>Installed</td>
      </tr>
      <tr>
          <td>Widescreen pnach</td>
          <td>On (renamed to Spec II CRC if using Spec II)</td>
      </tr>
      <tr>
          <td>Silent&rsquo;s trigger/camera patches</td>
          <td>On</td>
      </tr>
      <tr>
          <td>Deinterlace (Spec II)</td>
          <td>Mode 8 Adaptive TFF</td>
      </tr>
      <tr>
          <td>ShadeBoost (Spec II)</td>
          <td>Saturation +10, Brightness +3, Contrast +2</td>
      </tr>
      <tr>
          <td>FXAA + PCRTC antiblur</td>
          <td>Global</td>
      </tr>
      <tr>
          <td>Upscale</td>
          <td>4x SSAA</td>
      </tr>
  </tbody>
</table>
<p>Spec II users need to pay attention to the CRC: the vanilla GT4 HD packs were built for <code>77E61C8A</code> (USA), so on Spec II with CRC <code>4CE521F2</code> I symlink the assets into the CRC-suffixed folder that PCSX2 looks for. The widescreen pnach was also renamed to match the Spec II CRC. Without that, the game runs but the mods don&rsquo;t load.</p>
<p><strong>In-game reminders:</strong> same as GT3, the widescreen pnach only unlocks the option, you still need to enter Options in-game and switch to 16:9. And important: <strong>change the video mode to progressive 480p</strong> (also in Options). The default is interlaced 480i, which looks terrible on a modern screen. Spec II Mod has native progressive support, just flip it on.</p>
<p>If I could recommend a single GT in the series for someone starting today, it would be this one. If you like racing and never played GT4 in full, you&rsquo;re missing out.</p>
<h3>Enthusia Professional Racing<span class="hx:absolute hx:-mt-20" id="enthusia-professional-racing"></span>
    <a href="#enthusia-professional-racing" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/enthusia.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Enthusia is the forgotten Konami game from 2005. Launched in GT4&rsquo;s shadow and died on the shelves. It&rsquo;s a shame, because <strong>Enthusia&rsquo;s physics was more realistic than GT4&rsquo;s</strong>. Car weight, mass transfer, limit behavior, all closer to real-world behavior. The sim-racing community of the 2000s knew this and the game has a cult following today.</p>
<p>Enthusia&rsquo;s problem was the Enthusia Points system: you earned points for driving clean, lost them for crashing, and the game&rsquo;s progression depended on those points. A lot of people found it punitive. I found it perfect. It forced you to drive with your head.</p>
<p>On PCSX2 with retexture and widescreen, the game looks great. Strong recommendation for anyone who wants something different from Gran Turismo.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disc</td>
          <td>Enthusia Professional Racing (SLUS-20967)</td>
      </tr>
      <tr>
          <td>Widescreen pnach</td>
          <td>On</td>
      </tr>
      <tr>
          <td>Retexture</td>
          <td>On</td>
      </tr>
      <tr>
          <td>Upscale</td>
          <td>4x SSAA</td>
      </tr>
  </tbody>
</table>
<h3>Ridge Racer V<span class="hx:absolute hx:-mt-20" id="ridge-racer-v"></span>
    <a href="#ridge-racer-v" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/ridge%20racer%20v.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Ridge Racer is its own thing. Not simcade, pure arcade. Drift everything, cartoony grip, electronic soundtrack, saturated colors. Ridge Racer V was the Japanese PS2 launch title and it carries that &ldquo;show what the console can do&rdquo; vibe, with massive scenery and a sense of speed GT never attempted.</p>
<p>On PCSX2 it runs well, with a catch: car textures sometimes flicker on the hardware renderer (PCSX2 issues <a href="https://github.com/PCSX2/pcsx2/issues/3639"target="_blank" rel="noopener">#3639</a> and <a href="https://github.com/PCSX2/pcsx2/issues/13729"target="_blank" rel="noopener">#13729</a>). Software renderer fixes it but kills performance. I stay on hardware and accept the occasional flicker.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disc</td>
          <td>Ridge Racer V (SLUS-20002)</td>
      </tr>
      <tr>
          <td>Widescreen pnach</td>
          <td>On</td>
      </tr>
      <tr>
          <td>No-interlace pnach</td>
          <td>On</td>
      </tr>
      <tr>
          <td>Upscale</td>
          <td>4x SSAA</td>
      </tr>
  </tbody>
</table>
<h3>Colin McRae Rally 3, 4, 5 (PS2)<span class="hx:absolute hx:-mt-20" id="colin-mcrae-rally-3-4-5-ps2"></span>
    <a href="#colin-mcrae-rally-3-4-5-ps2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>PS2 Colin McRae versions run well on PCSX2 with default configs. CMR3 and CMR04 are good. CMR05 (the last before the rebrand to Dirt) is the best of the three technically. But again: <strong>if you&rsquo;re on PC, look for the native version</strong>. The PS2 ports were relatively inferior to PC of the era, ran at lower resolution, and a modern PC with a repack will hit 144Hz easily.</p>
<p>I keep them on PCSX2 just for collection completeness.</p>
<h2>PS3 on RPCS3<span class="hx:absolute hx:-mt-20" id="ps3-on-rpcs3"></span>
    <a href="#ps3-on-rpcs3" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Gran Turismo 5<span class="hx:absolute hx:-mt-20" id="gran-turismo-5"></span>
    <a href="#gran-turismo-5" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%205.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>GT5 is controversial. Polyphony promised the world and delivered a somewhat fragmented game: gorgeous Premium models next to Standards that were basically HD GT4 cars, a level system some loved and others hated, online that went to mud when PSN service was cut, a Top Gear mode that was weird. The contemporary critical reception was mixed, and I remember entire communities fighting over whether the game was a disappointment or a masterpiece.</p>
<p>For me, it&rsquo;s a masterpiece. GT5 has the Course Maker (design your own track from real sections), real 24h Nurburgring with dynamic weather, the Moon Rover (yes, a drive-on-the-moon mode), Red Bull X-Challenge with Vettel, and the single-player endurance experience is ridiculously big. Anyone who went deep into the game knows there&rsquo;s content for a whole year.</p>
<p>On RPCS3 in 2026, GT5 runs <strong>really well</strong>. Stable, without the serious bugs from years past. It&rsquo;s the most stable GT I have emulated today.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Serial</td>
          <td>BCUS98114 (US) or BCES00569 (EU)</td>
      </tr>
      <tr>
          <td>Resolution Scale</td>
          <td>300%</td>
      </tr>
      <tr>
          <td>Shader Precision</td>
          <td>Ultra</td>
      </tr>
      <tr>
          <td>Force High Precision Z</td>
          <td>On</td>
      </tr>
      <tr>
          <td>SPU XFloat</td>
          <td>Accurate</td>
      </tr>
      <tr>
          <td>Multithreaded RSX</td>
          <td>On</td>
      </tr>
      <tr>
          <td>WCB / RCB</td>
          <td>Off</td>
      </tr>
  </tbody>
</table>
<p>The Shader Precision Ultra + Force High Precision Z combo kills the typical RSX dithering that made GT5 look grainy on RPCS3 in years past. Without those two, asphalt looks permanently noisy. With them, the game looks clean.</p>
<h3>Gran Turismo 6<span class="hx:absolute hx:-mt-20" id="gran-turismo-6"></span>
    <a href="#gran-turismo-6" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%206.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>GT6 is one of my favorites in the series. It came out in 2013 for an end-of-life PS3 while the competition was already looking at PS4. Sold poorly. A lot of people didn&rsquo;t play it. That&rsquo;s a shame, because GT6 took the GT5 base, cut the fat (Standards were reduced), added more Premiums, better Moon circuit sessions, dynamic weather on more tracks, physics refinement. It&rsquo;s basically GT5 polished.</p>
<p>On content, GT6 is comparable to GT4. A lot of old-guard players put GT4 above it because of the PS2-era charm. I stay between the two and go GT6 on a normal day, GT4 on a nostalgic day.</p>
<p>On RPCS3, GT6 has a serious catch: <strong>patches 1.06 and beyond regress visually</strong>. Black surfaces on cars in the garage menu, flicker in cockpit view, full-screen flashing on certain tracks. The version that works well in 2026 is <strong>pinned at v1.05</strong>. The repo&rsquo;s <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/scripts/extract_ps3_dlc.py"target="_blank" rel="noopener"><code>extract_ps3_dlc.py</code></a> script has a per-title version ceiling specifically to pin GT6 at 1.05 even when PSN serves 1.22 as &ldquo;most recent&rdquo;.</p>
<p>Also, Force CPU Blit is mandatory. Without it, full-screen flicker in the menu. The trade-off is the rear-view mirror stays permanently black. I prefer losing the mirror to having the flicker. Anyone who wants the mirror can enable Write Color Buffers, but then resolution drops to 720p native.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Serial</td>
          <td>BCES01893 + regional variants</td>
      </tr>
      <tr>
          <td>Version pinned</td>
          <td>v1.05 (patches 1.06+ cause black surfaces)</td>
      </tr>
      <tr>
          <td>Resolution Scale</td>
          <td>300% (menus) or 200% (heavy gameplay)</td>
      </tr>
      <tr>
          <td>Force CPU Blit</td>
          <td>On (mandatory, kills flicker)</td>
      </tr>
      <tr>
          <td>WCB</td>
          <td>Off (trade-off: black rear mirror)</td>
      </tr>
      <tr>
          <td>Shader Precision</td>
          <td>Ultra</td>
      </tr>
  </tbody>
</table>
<p>In 2026, with these settings, GT6 is in my rotation. Not perfect, but playable enough that I can spend an afternoon doing endurance.</p>
<h3>Ridge Racer 7<span class="hx:absolute hx:-mt-20" id="ridge-racer-7"></span>
    <a href="#ridge-racer-7" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Ridge Racer 7 was a PS3 launch title in 2006 and the last mainline in the franchise. Arcade like Ridge Racer V, drift-first, electronic soundtrack, cinematic camera. For a PS3 of that era it was a pretty tech demo.</p>
<p>On RPCS3 it runs, but with caveats: requires Write Color Buffers on to avoid lighting issues, and the recommended preset in the compat DB is different from my GT preset. I use a dedicated per-game config. Not my favorite, but it&rsquo;s there for completeness.</p>
<p>No video for this one, I&rsquo;ll record later.</p>
<h2>Xbox 360 on Xenia Canary<span class="hx:absolute hx:-mt-20" id="xbox-360-on-xenia-canary"></span>
    <a href="#xbox-360-on-xenia-canary" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>This is the part that caused me the most headaches this Sunday. Xbox 360 on Linux is hostile territory. Xenia runs via Wine, each game needs specific tweaks, title updates have to come from archive.org, and Forza Motorsport 4 specifically had serious problems until recently.</p>
<h3>Forza Motorsport 4 (the GOAT)<span class="hx:absolute hx:-mt-20" id="forza-motorsport-4-the-goat"></span>
    <a href="#forza-motorsport-4-the-goat" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20motorsport%204.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>FM4 is, for a lot of people, the best Forza Motorsport ever released. Polyphony made Gran Turismo, Turn 10 made Forza, and FM4 is the peak of the 360 era. Beautiful car models for their time, well-designed tracks, solid physics without being a hard sim, Autovista mode with Jeremy Clarkson narration, epic soundtrack, and that feeling of the Xbox 360 being pushed to its limit.</p>
<p>FM4 on Xenia <strong>was a nightmare until recently</strong>. Severe shadow artifacts, constant texture pop-in on the car, grainy pixelated track textures, sky brightness completely wrong (it came out white-blown-out instead of the deep blue of the original), and worst: frequent audio crashes where XMA (the Xbox 360&rsquo;s proprietary codec) would freeze and the audio would turn into a high-pitched noise until the game hung. I spent hours this Sunday messing with configs, testing builds, searching the xenia-canary issue tracker, testing different title updates.</p>
<p>What unlocked it:</p>
<ul>
<li><strong>Latest Xenia Canary build</strong> (<code>master</code> is stagnant, Canary tracks active development)</li>
<li><strong>Render Target Path:</strong> <code>rtv</code> (the default works fine for FM4)</li>
<li><strong>Title Update 1.0.17.0</strong> installed via Xenia Manager (version matters, older ones regress shadows)</li>
<li><strong>Vulkan GPU with NVIDIA ICD forced</strong> (on my hybrid setup with AMD iGPU, I had to hardforce the 5090)</li>
</ul>
<p>After that, FM4 ran. Not perfectly. Still gets the occasional audio crash (<a href="https://github.com/xenia-canary/xenia-canary/issues/161"target="_blank" rel="noopener">xenia-canary issue #161</a> open). But it&rsquo;s playable, and I spent two hours of the Sunday at Top Gear Test Track just admiring the Koenigsegg CCX. Worth every hour of debugging.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build</td>
          <td>Latest Xenia Canary</td>
      </tr>
      <tr>
          <td>Render Target Path</td>
          <td><code>rtv</code> (default)</td>
      </tr>
      <tr>
          <td>Title Update</td>
          <td>1.0.17.0</td>
      </tr>
      <tr>
          <td>Wine Prefix</td>
          <td>dedicated via Xenia Manager</td>
      </tr>
      <tr>
          <td>GPU</td>
          <td>NVIDIA via <code>VK_ICD_FILENAMES</code> hardforce</td>
      </tr>
  </tbody>
</table>
<h3>Forza Motorsport 3<span class="hx:absolute hx:-mt-20" id="forza-motorsport-3"></span>
    <a href="#forza-motorsport-3" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20motorsport%203.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>FM3 is one of the solid entries in the series, sitting between FM2&rsquo;s rougher charm and FM4&rsquo;s technical peak. Getting it to emulate was surprisingly annoying, because I was using the wrong ISO.</p>
<p>There are two releases of FM3: the <strong>retail</strong> (original disc sold in stores) and the <strong>Ultimate Edition</strong> (all DLCs packaged on a second disc). I was trying to run the Ultimate, which combines both discs into a hybrid ISO that Xenia can&rsquo;t parse correctly. I swapped to the standard retail + DLCs installed separately via Title Update, and bang, the game ran first try.</p>
<p>Moral: if your FM3 isn&rsquo;t running on Xenia, check that it&rsquo;s retail. Ultimate breaks.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Version</td>
          <td>Retail (NOT Ultimate Edition)</td>
      </tr>
      <tr>
          <td>Build</td>
          <td>Latest Xenia Canary</td>
      </tr>
      <tr>
          <td>Render Target Path</td>
          <td><code>rtv</code></td>
      </tr>
      <tr>
          <td>DLCs</td>
          <td>Installed via Xenia Manager separately</td>
      </tr>
  </tbody>
</table>
<h3>Forza Motorsport 2<span class="hx:absolute hx:-mt-20" id="forza-motorsport-2"></span>
    <a href="#forza-motorsport-2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20motorsport%202.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>FM2 is from 2007, first-gen 360. Less refined systems, more basic modeling, but that original Forza charm some prefer to FM4&rsquo;s sophistication. On Xenia it ran practically out of the box. No special tweak.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build</td>
          <td>Xenia Canary</td>
      </tr>
      <tr>
          <td>Render Target Path</td>
          <td>default</td>
      </tr>
  </tbody>
</table>
<h3>Forza Horizon 2<span class="hx:absolute hx:-mt-20" id="forza-horizon-2"></span>
    <a href="#forza-horizon-2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20horizon%202.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>FH2 was the most polished Forza Horizon of the 360 generation (there&rsquo;s also the Xbox One version, which is technically better, but the 360 has its charm because of the smaller map and more focused progression). Open world in southern Europe, iconic soundtrack, Horizon festival at its peak.</p>
<p>On Xenia it ran without asking for anything extra. Same as FM2.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build</td>
          <td>Xenia Canary</td>
      </tr>
      <tr>
          <td>Render Target Path</td>
          <td>default</td>
      </tr>
  </tbody>
</table>
<h3>Forza Horizon 1 (with XE Mod)<span class="hx:absolute hx:-mt-20" id="forza-horizon-1-with-xe-mod"></span>
    <a href="#forza-horizon-1-with-xe-mod" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20horizon%201%20xe%20mod.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Original FH1 runs OK on Xenia, but there&rsquo;s a community maintaining the <strong>Forza Horizon 1 XE Mod</strong>, which improves traffic AI, rebalances progression, adds cars that were cut, fixes known bugs, and brings quality-of-life tweaks to the UI. I tested the XE Mod this Sunday and it&rsquo;s worth it. The game feels more alive and progression feels fairer.</p>
<p>Install details for the XE Mod are on the project site. Basically it&rsquo;s a patch over the original ISO + a few custom title updates.</p>
<p>For comparison, here&rsquo;s FH1 <strong>without</strong> the mod, running vanilla:</p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20horizon%201.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>The most visible difference is traffic and the variety of situations that come up, but the XE Mod has a lot of stuff you only notice after a few hours.</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build</td>
          <td>Xenia Canary</td>
      </tr>
      <tr>
          <td>XE Mod</td>
          <td>Applied over original ISO</td>
      </tr>
      <tr>
          <td>Title Updates</td>
          <td>Version pinned by XE Mod</td>
      </tr>
  </tbody>
</table>
<h3>Project Gotham Racing 3 and 4<span class="hx:absolute hx:-mt-20" id="project-gotham-racing-3-and-4"></span>
    <a href="#project-gotham-racing-3-and-4" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>PGR3 was a 360 launch title in 2005 and still looks gorgeous today. Urban tracks (New York, Tokyo, Nurburgring, Las Vegas), kudos system, cinematic camera. PGR4 added dynamic weather (rain and snow) and motorcycles (some people love them, I prefer to pretend they don&rsquo;t exist).</p>
<p>PGR3 on Xenia Canary runs reasonably well with default configs. PGR4 needs a specific tweak: <strong><code>render_target_path_vulkan = &quot;fsi&quot;</code></strong> in the config. Without it, some tracks break with severe visual artifacts (green bars across the screen in snow races).</p>
<p>And PGR4 on NVIDIA has a known audio bug: XMA decoding produces intermittent garbage (open xenia-canary issue, no resolution). Not game-breaking, but annoying.</p>
<table>
  <thead>
      <tr>
          <th>Game</th>
          <th>Relevant setting</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>PGR3</td>
          <td>Default</td>
      </tr>
      <tr>
          <td>PGR4</td>
          <td><code>render_target_path_vulkan = &quot;fsi&quot;</code>, accept the XMA audio bug</td>
      </tr>
  </tbody>
</table>
<h3>Ridge Racer 6<span class="hx:absolute hx:-mt-20" id="ridge-racer-6"></span>
    <a href="#ridge-racer-6" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Ridge Racer 6 was the Ridge Racer of the 360, skipping the 7 that stayed on PS3. Pure arcade, like Ridge Racer V of the PS2. On Xenia Canary it runs with default config, no special tweak. Fun for short sessions. No video for this one in the article, but it&rsquo;s in the catalog running.</p>
<h2>PS4 on shadPS4: the state of the emulator<span class="hx:absolute hx:-mt-20" id="ps4-on-shadps4-the-state-of-the-emulator"></span>
    <a href="#ps4-on-shadps4-the-state-of-the-emulator" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><a href="https://shadps4.net/"target="_blank" rel="noopener">shadPS4</a> <strong>still doesn&rsquo;t have a stable release</strong>. The project is under active development, the main branch changes several times a week, and &ldquo;what works today&rdquo; can regress on a rebase tomorrow. That said, <strong>many games already boot and run well enough</strong>. The community list is on the <a href="https://github.com/shadps4-compatibility/shadps4-game-compatibility/issues"target="_blank" rel="noopener">compatibility tracker</a> and grows every week.</p>
<p>The difference between shadPS4 and the other emulators in this article is the kind of effort. PCSX2, RPCS3, Xenia are mature projects where each game has a reasonably up-to-date wiki page. shadPS4 is the opposite: you read a Reddit guide, try to reproduce, discover the guide uses a specific fork (not the official main), or a two-month-old commit, or a firmware with <code>sys_modules</code> symlinked in a particular way, or XML patches applied in a specific order that the author didn&rsquo;t document. Reproducing a shadPS4 setup from a YouTube video is like trying to solve a Rubik&rsquo;s Cube blindfolded.</p>
<p>But that doesn&rsquo;t mean you shouldn&rsquo;t try. It means you need to treat shadPS4 as a hobby, not a workflow. And if you have the patience, you can pull off impressive results.</p>
<h2>Driveclub: the impossible, finally possible<span class="hx:absolute hx:-mt-20" id="driveclub-the-impossible-finally-possible"></span>
    <a href="#driveclub-the-impossible-finally-possible" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/driveclub.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Driveclub is my Moby Dick of emulation. It&rsquo;s the only PS4 game I really want to run. Released in 2014 by Evolution Studios, had a catastrophic launch (online servers collapsed on day one), got patched over two years until it became one of the best racing games of the generation, and was discontinued by Sony when Evolution was closed in 2016. No PC port. No remaster. No sequel. Only exists on PS4.</p>
<p>And today, after many hours of debugging, <strong>it&rsquo;s running here</strong>. Main menu loads, intro plays, races start, controller responds, audio works, daytime tracks visible, night tracks with headlights lighting the road. The video above is real gameplay, stable 30 FPS, v1.28 with DLC content accessible.</p>
<p>Getting here required dismantling several traps. Worth documenting, because most online guides don&rsquo;t mention any of them.</p>
<h3>Trap 1: the FMOD loop error that wasn&rsquo;t FMOD<span class="hx:absolute hx:-mt-20" id="trap-1-the-fmod-loop-error-that-wasnt-fmod"></span>
    <a href="#trap-1-the-fmod-loop-error-that-wasnt-fmod" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>When you try to boot Driveclub on shadPS4 straight from the retail PKG, the log hits an infinite loop of:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>/app0/audio/fmodstudio/masterbank.bank failed, file does not exist</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The first reaction is to investigate FMOD. Some people online spent hours messing with sceFont, libtbb, sceNgs2, convinced it was an audio or HLE lib problem. <strong>None of that is the cause</strong>. The <code>masterbank.bank</code> file literally doesn&rsquo;t exist on disk, because it&rsquo;s packed inside the <code>gameNNN.dat</code> files in a custom Evolution Studios archive format that shadPS4&rsquo;s VFS can&rsquo;t read. Same story for fonts, models, shaders. All the game&rsquo;s content lives inside the <code>.dat</code> files.</p>
<p>The only public tool that parses that format is <strong><a href="https://github.com/Nenkai/DriveClubFS"target="_blank" rel="noopener">Nenkai&rsquo;s DriveClubFS</a></strong>. It&rsquo;s a .NET app that reads <code>game.ndx</code> (the index) and decompresses the <code>.dat</code> files into loose files. Without that unpack, shadPS4 can&rsquo;t open anything.</p>
<p>Build detail: the DriveClubFS project targets <code>net9.0</code>, and Arch has <code>net8</code> and <code>net10</code>. You need to retarget the csproj to <code>net10.0</code> before building. The exact command is documented in the repo&rsquo;s <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/docs/driveclub-shadps4.md"target="_blank" rel="noopener"><code>docs/driveclub-shadps4.md</code></a>.</p>
<h3>Trap 2: v1.28 is a patch, not a base installer<span class="hx:absolute hx:-mt-20" id="trap-2-v128-is-a-patch-not-a-base-installer"></span>
    <a href="#trap-2-v128-is-a-patch-not-a-base-installer" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Like every PS4 update, v1.28 is a patch that layers on top of a base install. The v1.28 PKG ships the updated eboot, new-content <code>.dat</code> files, and patch content (<code>sce_sys/about/right.sprx</code>), but <strong>no <code>param.sfo</code>, no <code>disc_info.dat</code>, no <code>keystone</code></strong>. Those metadata files live in the v1.00 base — on a real PS4 they&rsquo;d already be on disk from the original install.</p>
<p>And there&rsquo;s more: v1.28&rsquo;s <code>game.ndx</code> references both the base <code>.dat</code> files and the new ones. Running DriveClubFS against v1.28 alone makes the index point at files that aren&rsquo;t there.</p>
<p>The cruelest symptom hits later: the game boots, the Qt Launcher shows the tile, you click, the loading screen comes up. Then the game decides no content &ldquo;has been released yet&rdquo; and asks you to download. Download obviously isn&rsquo;t happening because PSN for PS4 isn&rsquo;t accessible. You get stuck thinking it&rsquo;s a network bug, when actually it&rsquo;s missing metadata.</p>
<p>The correct flow is to treat v1.28 as an overlay on top of the base. First, extract the v1.00 PKG into a dedicated dir (<code>CUSA00003.v100-working-backup/</code> works well, and doubles as a rollback snapshot if some experiment breaks the live install). Then extract v1.28 into staging and symlink the base&rsquo;s low-index <code>.dat</code> files into the staging dir so DriveClubFS sees the full set. Run the unpack. You get 8018 loose files, ~47 GB.</p>
<p>Move that into the live <code>CUSA00003/</code> dir, copy v1.28&rsquo;s updated eboot and new patch content on top, then pull <code>param.sfo</code>, <code>disc_info.dat</code>, and <code>keystone</code> from the v1.00 base. The base&rsquo;s <code>param.sfo</code> already has <code>APP_VER = 01.28</code> recorded (because it was the version that received the patch originally), so there&rsquo;s no version conflict. The v1.28 eboot reads those files and unlocks the content.</p>
<h3>Trap 3: the 60fps XML patch produces slow-motion<span class="hx:absolute hx:-mt-20" id="trap-3-the-60fps-xml-patch-produces-slow-motion"></span>
    <a href="#trap-3-the-60fps-xml-patch-produces-slow-motion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>There&rsquo;s a community <code>Driveclub.xml</code> patch that makes the game render at 60fps. Intuitively, it looks like pure upside. In practice, the patch rewrites the render rate but doesn&rsquo;t touch the game&rsquo;s fixed logic tickrate, which is locked at 30fps. On a real PS4 Pro, the hardware reconciles the mismatch. On shadPS4, it doesn&rsquo;t.</p>
<p>Result: the game renders smoothly at 60fps, but the physics, AI, audio, race timing all run in slow motion. You press the throttle and the car takes a perceptible two seconds to launch. Looks like a random bug until you understand the mismatch.</p>
<p>Disabling the XML patch is the move (just rename the file to something like <code>.disabled</code>). Without the patch, the game runs at native 30 FPS, same speed as a stock PS4 base. Which is what we want. If someone in the future produces a patch that moves both rates (render + logic), we&rsquo;ll flip it back on.</p>
<h3>Trap 4: pipeline cache has to be on<span class="hx:absolute hx:-mt-20" id="trap-4-pipeline-cache-has-to-be-on"></span>
    <a href="#trap-4-pipeline-cache-has-to-be-on" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>shadPS4&rsquo;s default ships with <code>pipeline_cache_enabled = false</code>. On a large game like Driveclub, which has hundreds of Vulkan shaders and pipelines, that means every cold launch recompiles everything. My first launch counted ~864 shaders and ~590 pipelines being compiled in real time. The menu animated at 2-3 fps. A minute later, with everything compiled, it snapped to normal fluidity. Next launch, same from-scratch compilation cycle.</p>
<p>Just enable <code>&quot;pipeline_cache_enabled&quot;: true</code> in the global <code>config.json</code>. The first session pays the one-time compilation cost, subsequent sessions read from cache and start up instantly.</p>
<h3>Trap 5: TOML silently ignored<span class="hx:absolute hx:-mt-20" id="trap-5-toml-silently-ignored"></span>
    <a href="#trap-5-toml-silently-ignored" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>shadPS4 migrated from TOML to JSON for per-game configs in late 2025. If you have an old <code>CUSA00003.toml</code> in the <code>custom_configs/</code> folder, it&rsquo;s silently ignored, no warning in the log. You think your config is active, in reality you&rsquo;re running all defaults.</p>
<p>Delete any TOML and use <code>CUSA00003.json</code>. The per-game configs that work for Driveclub are specific and detailed, all documented in the <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/docs/driveclub-shadps4.md"target="_blank" rel="noopener"><code>docs/driveclub-shadps4.md</code></a> of the repo.</p>
<h3>Trap 6: controller not detected without hidapi hints<span class="hx:absolute hx:-mt-20" id="trap-6-controller-not-detected-without-hidapi-hints"></span>
    <a href="#trap-6-controller-not-detected-without-hidapi-hints" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>shadPS4&rsquo;s Qt Launcher calls an internal <code>Shadps4-sdl.AppImage</code>. Without specific SDL hidapi env vars, the controller (8BitDo Ultimate 2 in my case) isn&rsquo;t detected, even though it works in every other emulator. The fix is to wrap the AppImage in a shell that exports <code>SDL_JOYSTICK_HIDAPI=1</code> and platform-specific variants (PS4, PS5, Xbox), before executing the original.</p>
<p>Plugging the controller in <strong>before</strong> opening the Qt Launcher also helps. Hot-plug works during the game, not before.</p>
<h3>Trap 7: night tracks are pitch black on upstream<span class="hx:absolute hx:-mt-20" id="trap-7-night-tracks-are-pitch-black-on-upstream"></span>
    <a href="#trap-7-night-tracks-are-pitch-black-on-upstream" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>This one needed a code patch, not just config. On upstream shadPS4, Driveclub night tracks came out pitch black. The HUD rendered on top, but the track, the car, everything hidden in absolute black. Not even the car&rsquo;s own headlights lit anything up.</p>
<p>Investigation pointed at the log repeating 1325 times the message <code>ResolveDepthOverlap: Unimplemented depth overlap copy</code>. The cause: Driveclub uses a forward+ renderer, which writes scene depth into a 4x MSAA D32Sfloat buffer, then reads that same buffer as <code>R32G32B32A32Sfloat</code> 1x for effects like SSAO, volumetrics, and dynamic lighting (headlights). shadPS4 upstream has a path for MSAA → MSAA and for color → color as MSAA depth, but <strong>it didn&rsquo;t have a path for &ldquo;MSAA depth 4x becomes color 1x&rdquo;</strong> in that specific format. It falls into the final <code>else</code>, frees the buffer, and the shader reads uninitialized memory — i.e., black.</p>
<p>I implemented in my local shadPS4 fork a <code>ReinterpretMsDepthAsColor</code> symmetric to the existing <code>ReinterpretColorAsMsDepth</code>. It&rsquo;s a tiny fragment shader (<code>ms_depth_to_color.frag</code>) that does <code>texelFetch</code> of sample 0&rsquo;s depth and writes it as <code>vec4(depth, 0, 0, 1)</code> to the color attachment. Plus a <code>BlitHelper</code> helper and a new <code>else if</code> branch in <code>TextureCache::ResolveDepthOverlap</code>. With that, night tracks light up properly, opponent headlights appear, AO and volumetrics on day scenes get more precise too.</p>
<p>The fix lives on a <code>gamma-debug</code> branch of my fork, ready to become an upstream PR. Until it merges, anyone who wants to race at night on Driveclub on shadPS4 needs to build from that branch. Anyone sticking to daytime can use upstream nightly directly.</p>
<h3>What finally worked<span class="hx:absolute hx:-mt-20" id="what-finally-worked"></span>
    <a href="#what-finally-worked" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Component</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>shadPS4</td>
          <td>Upstream Pre-release (nightly) for daytime; <code>gamma-debug</code> fork for night (until MSAA depth fix lands upstream)</td>
      </tr>
      <tr>
          <td>Driveclub</td>
          <td><strong>v1.28 base</strong> (cumulative patch, ~47 GB, 8018 files)</td>
      </tr>
      <tr>
          <td>Extraction</td>
          <td><a href="https://github.com/shadps4-emu/ShadPKG"target="_blank" rel="noopener">ShadPKG</a> → <a href="https://github.com/Nenkai/DriveClubFS"target="_blank" rel="noopener">DriveClubFS</a> (retargeted to net10.0)</td>
      </tr>
      <tr>
          <td>Metadata</td>
          <td>Restore <code>param.sfo</code>, <code>disc_info.dat</code>, <code>keystone</code> from the v1.00 PKG</td>
      </tr>
      <tr>
          <td>Global config</td>
          <td><code>pipeline_cache_enabled: true</code>, <code>readbacks_mode: 0</code></td>
      </tr>
      <tr>
          <td>Per-game config</td>
          <td>JSON at <code>custom_configs/CUSA00003.json</code></td>
      </tr>
      <tr>
          <td><code>vblank_frequency</code></td>
          <td>60</td>
      </tr>
      <tr>
          <td><code>gpu_id</code></td>
          <td>0 (NVIDIA dGPU hardforced)</td>
      </tr>
      <tr>
          <td><code>Driveclub.xml</code> patch</td>
          <td><strong>Disabled</strong> (60fps produces slow-motion, root cause: fixed logic tickrate)</td>
      </tr>
      <tr>
          <td>Controller</td>
          <td>AppImage wrapper with <code>SDL_JOYSTICK_HIDAPI=1</code> + variants</td>
      </tr>
      <tr>
          <td>Qt Launcher</td>
          <td><code>checkForUpdates=false</code> to avoid GitHub rate-limit dialog</td>
      </tr>
  </tbody>
</table>
<p>All of this is encapsulated in <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/docs/driveclub-shadps4.md"target="_blank" rel="noopener"><code>docs/driveclub-shadps4.md</code></a> in the repo, with the exact step-by-step for extraction, config, and wrapper. The full technical investigation (5 phases, detailed engineering log) lives in the fork&rsquo;s <a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-v128-investigation.md"target="_blank" rel="noopener"><code>driveclub-v128-investigation.md</code></a>.</p>
<h3>How playable it is today<span class="hx:absolute hx:-mt-20" id="how-playable-it-is-today"></span>
    <a href="#how-playable-it-is-today" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Good. The video above is real gameplay, stable 30 FPS, physics responding, audio in sync, HUD correct, v1.28 with DLC accessible. Day races and night races both work (with the MSAA depth fork for night). What&rsquo;s left as limitation:</p>
<ul>
<li><strong>Daytime brightness sits a bit below what the PS4 delivered.</strong> Not an emulator bug. I spent hours testing tonemap, auto-exposure, gamma curves, ACES, various post-processing shaders, and in the end discovered the game <strong>writes already-correct SDR</strong> to the framebuffer. What was missing was the display-calibration pipeline that PS4 firmware 4.0+ applied on the outside, and that Linux doesn&rsquo;t have. The in-game brightness slider (which on shadPS4 maps to <code>sceVideoOutAdjustColor</code>) works, but only up to the point where the game expected the firmware to pick up the rest. The 5-second dim at the start of a race is also the game&rsquo;s own cinematic fade-in VFX, not an emulator fault. Accepted.</li>
<li><strong>Native 30 FPS.</strong> No viable 60fps patch (due to the fixed logic tickrate explained above). Stock PS4 speed.</li>
<li><strong>MSAA depth fix still on the fork.</strong> PR candidate ready, awaiting upstream merge.</li>
</ul>
<p><strong>&ldquo;Achievement Unlocked&rdquo;.</strong> The game runs, with sound, controller, gameplay, day and night, and I complete a whole race without crashing. That&rsquo;s a personal milestone. I spent years trying to get this game running on Linux and would always hit a point where I just gave up. Now it&rsquo;s part of my ES-DE, shortcut on the desktop, sitting there waiting for me to play.</p>
<p>If you want to replicate, the repo is public. Good luck. You&rsquo;re going to need patience.</p>
<h2>&ldquo;Achievement Unlocked&rdquo;<span class="hx:absolute hx:-mt-20" id="achievement-unlocked"></span>
    <a href="#achievement-unlocked" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I&rsquo;ve been trying to make these games work, at some level, since each respective emulator came out of alpha. I&rsquo;ve been running GT2 since ePSXe. I&rsquo;ve been messing with GT4 since PCSX2 0.9.6. I tried to get FM4 running on Xenia twice in past years and gave up both times. And I looked at shadPS4&rsquo;s face three times before this Sunday and swallowed hard.</p>
<p>Today it closed. The two monsters that opened this article (Driveclub on shadPS4 and FM4 with Project Forza Plus on Xenia) are running, along with the rest of the Gran Turismo, Forza, Ridge Racer, Colin McRae, and Enthusia collection. All in a single setup, reproducible, automated. I&rsquo;m now part of the <strong>very small group of people who brute-forced their way into emulating Driveclub on Linux</strong>, and that alone made the Sunday unforgettable.</p>
<p>The frustration of years wasn&rsquo;t lack of information. It was the OPPOSITE. Too much information, poorly indexed, scattered across dead forums, YouTube videos that went stale, Reddit threads with solutions for two-year-old emulator versions that don&rsquo;t apply today, wikis that are out of date on half the pages, and mods with VERY specific requirements of CRC, version, patch, path, setting. Every time I sat down to get one of these games running right, I spent three hours reading conflicting material before writing the first line of config. Worse still when the good material was buried under wrong diagnoses, like the Driveclub FMOD loop everyone investigated as if it was an audio problem when in reality it was Evolution Studios&rsquo; proprietary archive format.</p>
<p>What changed in the last year is that Claude Code can be my aggregator. I tell Claude to hit the Xenia issue tracker, the PCSX2 wiki, the RPCS3 subreddit, shadPS4 PRs, cross-reference all that info with the emulator version I have installed, cross-reference with the logs I&rsquo;m looking at, and give me back the combination that works today. It reads source code when needed, understands why <code>Force CPU Blit</code> changes behavior on GT6, finds which FM4 title update was the one that fixed the shadow bug, discovers that DriveClubFS needs a retarget to .NET 10 before compiling. What used to take three weekends of research now takes an afternoon of assisted execution.</p>
<p>This Sunday is the consolidated result of all that work. <a href="https://github.com/akitaonrails/distrobox-gaming"target="_blank" rel="noopener">distrobox-gaming</a> is that knowledge turned into code, reproducible, automated. Starting on a fresh machine, one <code>ansible-playbook site.yml</code> puts me back in this state in under two hours. Fewer than 10 commands separate &ldquo;vanilla Arch Linux&rdquo; from &ldquo;Driveclub running&rdquo;.</p>
<p>If anyone wants to contribute, test on different hardware, report a bug, or send a config that&rsquo;s missing, the repo is open. The more people test on different setups, the more resilient the project gets. If you improve the Driveclub situation (get the MSAA depth fix merged upstream, a 60fps patch that also fixes the logic tickrate, font dumps that don&rsquo;t need jailbroken hardware), send a PR and I&rsquo;ll embrace it.</p>
<p>For now, I&rsquo;m going to do a race in Iceland on Driveclub. Good Sunday night.</p>
]]></content:encoded><category>gaming</category><category>emulation</category><category>linux</category></item><item><title>My Sim Racing Cockpit - Formula FX1</title><link>https://akitaonrails.github.io/en/2026/04/01/my-sim-racing-cockpit-formula-fx1/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/04/01/my-sim-racing-cockpit-formula-fx1/</guid><pubDate>Wed, 01 Apr 2026 17:00:00 -0300</pubDate><description>&lt;p&gt;I&amp;rsquo;ve loved cars for as long as I can remember. My first real contact with racing games was at the arcades of the 80s and 90s. And when I say &amp;ldquo;real,&amp;rdquo; I mean sitting at a cabinet with a wheel, pedals, a hard plastic seat and the screen wrapping in front of you. &lt;a href="https://en.wikipedia.org/wiki/Out_Run"target="_blank" rel="noopener"&gt;OutRun&lt;/a&gt; (1986), &lt;a href="https://en.wikipedia.org/wiki/Rad_Mobile"target="_blank" rel="noopener"&gt;Rad Mobile&lt;/a&gt; (1991), &lt;a href="https://en.wikipedia.org/wiki/Virtua_Racing"target="_blank" rel="noopener"&gt;Virtua Racing&lt;/a&gt; (1992), &lt;a href="https://en.wikipedia.org/wiki/Ridge_Racer"target="_blank" rel="noopener"&gt;Ridge Racer&lt;/a&gt; (1993), &lt;a href="https://en.wikipedia.org/wiki/Daytona_USA_%28video_game%29"target="_blank" rel="noopener"&gt;Daytona USA&lt;/a&gt; (1994), &lt;a href="https://en.wikipedia.org/wiki/Scud_Race"target="_blank" rel="noopener"&gt;Scud Race&lt;/a&gt; (1996). Every one of those games left a mark on me. But Daytona USA stuck in a different way. That twin cabinet, two machines side by side, the &amp;ldquo;DAYTONAAA, let&amp;rsquo;s go away&amp;rdquo; track blasting through the arcade, the wheel rumbling in your hand. I still remember it.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;ve loved cars for as long as I can remember. My first real contact with racing games was at the arcades of the 80s and 90s. And when I say &ldquo;real,&rdquo; I mean sitting at a cabinet with a wheel, pedals, a hard plastic seat and the screen wrapping in front of you. <a href="https://en.wikipedia.org/wiki/Out_Run"target="_blank" rel="noopener">OutRun</a> (1986), <a href="https://en.wikipedia.org/wiki/Rad_Mobile"target="_blank" rel="noopener">Rad Mobile</a> (1991), <a href="https://en.wikipedia.org/wiki/Virtua_Racing"target="_blank" rel="noopener">Virtua Racing</a> (1992), <a href="https://en.wikipedia.org/wiki/Ridge_Racer"target="_blank" rel="noopener">Ridge Racer</a> (1993), <a href="https://en.wikipedia.org/wiki/Daytona_USA_%28video_game%29"target="_blank" rel="noopener">Daytona USA</a> (1994), <a href="https://en.wikipedia.org/wiki/Scud_Race"target="_blank" rel="noopener">Scud Race</a> (1996). Every one of those games left a mark on me. But Daytona USA stuck in a different way. That twin cabinet, two machines side by side, the &ldquo;DAYTONAAA, let&rsquo;s go away&rdquo; track blasting through the arcade, the wheel rumbling in your hand. I still remember it.</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/2/24/DaytonaUSA_arcade_SaoPaulo.jpg" alt="Daytona USA machines at a São Paulo mall — exactly the kind of twin cabinet that’s burned into my memory"  loading="lazy" /></p>
<p>But the game that really got me hooked on the simcade genre was the original <a href="https://en.wikipedia.org/wiki/Gran_Turismo_%28video_game%29"target="_blank" rel="noopener">Gran Turismo</a>, in 1997, on the PlayStation 1. &ldquo;The Real Driving Simulator&rdquo; on the cover. I played that game obsessively. Around the same time I was watching the <a href="https://en.wikipedia.org/wiki/Initial_D"target="_blank" rel="noopener">Initial D</a> anime, which premiered in 1998 in Japan. I bought every manga volume and read all of it from start to finish. The story of Takumi Fujiwara going down Mount Akina at dawn delivering tofu in his father&rsquo;s AE86 is, to me, one of the best motorsport stories ever told in any medium.</p>
<p>I still follow Shuichi Shigeno&rsquo;s work today. After Initial D came <a href="https://kodansha.us/series/mf-ghost/"target="_blank" rel="noopener">MF Ghost</a> (2017-2025), set in the same universe but in a near future where combustion cars have become museum pieces. And now, since July 2025, I&rsquo;m reading <a href="https://kmanga.kodansha.com/title/10664/episode/360584"target="_blank" rel="noopener">Subaru and Subaru</a>, the direct sequel that ties the Initial D and MF Ghost universes together with two protagonists named Subaru — one from Gunma, one from Kanagawa — competing in a new racing series. It&rsquo;s Shigeno at his best.</p>
<p><a href="https://kmanga.kodansha.com/title/10664/episode/360584"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/subaru-x-subaru-cover.png" alt="Subaru and Subaru — Shigeno’s new manga, the direct sequel to Initial D and MF Ghost"  loading="lazy" /></a></p>
<p>Two years ago I traveled to Japan with my girlfriend and made a point of going to <a href="https://en.wikipedia.org/wiki/Daikoku_Parking_Area"target="_blank" rel="noopener">Daikoku PA</a>, the famous parking area on the Shuto Expressway in Yokohama where the JDM culture concentrates. As an old fan of <a href="https://store.steampowered.com/app/2634950/Tokyo_Xtreme_Racer/"target="_blank" rel="noopener">Tokyo Xtreme Racer</a>, by Genki, I needed to see Daikoku with my own eyes at least once. And it didn&rsquo;t disappoint. Instead of renting a car, we booked a tour with a local guide in his prepped Nissan GT-R. Better that way. On the drive there he explained the history of the Wangan, how the scene works, what&rsquo;s YouTube exaggeration and what&rsquo;s real. When we got there on a Friday night and I saw the whole thing in person — Skyline R34, RX-7, Supra, GT-R, tuned kei trucks, insane bosozoku — the feeling was strange in the best possible way. It looked like Tokyo Xtreme Racer, except with the smell of fuel in the air and the sound of real exhaust pipes.</p>
<p>And there&rsquo;s another detail: I&rsquo;m playing the new <a href="https://store.steampowered.com/app/2634950/Tokyo_Xtreme_Racer/"target="_blank" rel="noopener">Tokyo Xtreme Racer</a> reboot on PC, and it&rsquo;s exactly the kind of game that understands its own audience. Strong single-player campaign, addictive progression, the right vibe, and none of the loot box nonsense. I&rsquo;d recommend it without hesitation. For the same reason, I&rsquo;m also really looking forward to <a href="https://forza.net/forzahorizon6"target="_blank" rel="noopener">Forza Horizon 6</a>, which this time is going to be set in Japan. I&rsquo;ve already pre-ordered it and I can&rsquo;t wait to play it on the new cockpit.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/daikoku---nissan.jpg" alt="At Daikoku PA with a modified GT-R"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/daikoku---trueno.jpg" alt="With an AE86 Trueno at Daikoku PA — Takumi’s car"  loading="lazy" /></p>
<h2>Driving for real<span class="hx:absolute hx:-mt-20" id="driving-for-real"></span>
    <a href="#driving-for-real" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Now that I&rsquo;m semi-retired, I&rsquo;ve had the chance to take my Mercedes to track days. I&rsquo;ve driven at <a href="https://en.wikipedia.org/wiki/Aut%C3%B3dromo_Jos%C3%A9_Carlos_Pace"target="_blank" rel="noopener">Autódromo de Interlagos</a> (the Autódromo José Carlos Pace), the 4.309 km circuit in São Paulo that&rsquo;s been hosting the Brazilian F1 GP since 1973, famous for the S do Senna corner complex and the circuit&rsquo;s wild elevation changes. I&rsquo;ve also driven at <a href="https://www.velocittapark.com.br/"target="_blank" rel="noopener">Autódromo Velocitta</a>, a modern 3.443 km circuit opened in 2014 in Mogi Guaçu in the interior of São Paulo, which hosts Stock Car Brasil and Porsche Cup.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/velocitta.jpg" alt="At Velocitta with my Mercedes during an AMG track day"  loading="lazy" /></p>
<p>In Las Vegas I&rsquo;ve driven supercars on those track-day experiences. And when I traveled with my girlfriend to Gramado, in Rio Grande do Sul, we went to <a href="https://supercarros.cc/"target="_blank" rel="noopener">Super Carros</a>, which is on Av. das Hortênsias 4635. They have a 2,400 m² hangar with more than 50 cars — Ferraris, Lamborghinis, Porsches, GT-Rs, Corvettes, American muscle cars. You pick a car, head out with an instructor, and drive a roughly 17 km route between Gramado and Canela. I took out a Nissan GT-R and a Ferrari California.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/gramado---gtr.jpg" alt="With a GT-R in Gramado"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/gramado---ferrari.jpg" alt="With a Ferrari California in Gramado"  loading="lazy" /></p>
<p>Three years ago I also went to Abu Dhabi with my girlfriend and we went to Ferrari World, which has some of the best racing simulators I&rsquo;ve ever tried. Hydraulic platform with 6 degrees of freedom, F1 cockpit, the works. I&rsquo;ve always loved testing simulators wherever I go.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/ferrari-park.jpg" alt="F1 simulator with a hydraulic platform at Ferrari World in Abu Dhabi"  loading="lazy" /></p>
<p>But driving real cars on real tracks is a very expensive hobby. Tires, fuel, insurance, maintenance, registration. And more important: I&rsquo;m an introvert. I prefer being alone. My simulator cockpit is perfect for when I want to drive without having to deal with anyone. That&rsquo;s why I love rally so much — it&rsquo;s me, the virtual co-driver, and the road. Nothing else.</p>
<h2>The games I play<span class="hx:absolute hx:-mt-20" id="the-games-i-play"></span>
    <a href="#the-games-i-play" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I know most people who build a cockpit like this do it to play serious sims — iRacing, Assetto Corsa Competizione, Automobilista 2. I respect that, but it isn&rsquo;t my thing. I don&rsquo;t like playing online with other people. I have zero intention of starting a live streaming career. This is purely for my own enjoyment.</p>
<p>These days I play Gran Turismo 7 on the PS5, <a href="https://store.steampowered.com/app/2440510/Forza_Motorsport/"target="_blank" rel="noopener">Forza Motorsport</a> (the 8, from 2023) on PC, but where I have the most fun is in rally games: <a href="https://store.steampowered.com/app/1849250/EA_SPORTS_WRC/"target="_blank" rel="noopener">EA SPORTS WRC</a>, <a href="https://store.steampowered.com/app/1462810/WRC_10_FIA_World_Rally_Championship/"target="_blank" rel="noopener">WRC 10</a> and <a href="https://store.steampowered.com/app/690790/DiRT_Rally_20/"target="_blank" rel="noopener">DiRT Rally 2.0</a>. My first experience with Forza was on the Xbox One with Forza Motorsport 5 and then Forza Horizon 4, which kept me hooked for hundreds of hours.</p>
<p>And I have a huge soft spot for retro games. The original Colin McRae Rally from 1998 on the PS1 was my first rally game. But my favorite of all time is Colin McRae Rally 2.0 (2000), also on the PS1. I recently played through the entire campaign again on the PC version — you can find repacks that run in high resolution and widescreen, much better than the original PlayStation versions. I&rsquo;d recommend that for any of the titles in the series.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/colin-mcrae-rally-2-gameplay.jpg" alt="Colin McRae Rally 2.0 running in widescreen on PC — snow in Sweden with the Ford Focus"  loading="lazy" /></p>
<p>After that came Colin McRae 3 (2002), Colin McRae Rally 04 (2003) and Colin McRae 2005 (2004). Other arcades I revisit often: OutRun 2 SP (2004) and OutRun 2006: Coast 2 Coast (2006) — the best OutRun ever made, in my opinion.</p>
<p>But my game of the year, by a long shot, is <a href="https://store.steampowered.com/app/3218630/Super_Woden_Rally_Edge/"target="_blank" rel="noopener">Super Woden: Rally Edge</a>. An indie made by a solo developer (ViJuDa, from Spain) that launched in January 2026 for less than R$ 60. Eight countries, more than 80 cars, a career mode, local split-screen multiplayer for up to 4 players, online leaderboards. The behind-the-car camera instead of the top-down view of the previous Super Woden GP made all the difference. 96% positive reviews on Steam with more than 1,300 ratings. It&rsquo;s the kind of game that proves you don&rsquo;t need a million-dollar budget to make something amazing.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/super-woden-rally-edge.jpg" alt="Super Woden: Rally Edge — indie made by a solo dev that competes with the big ones"  loading="lazy" /></p>
<h2>The evolution of my wheel setup<span class="hx:absolute hx:-mt-20" id="the-evolution-of-my-wheel-setup"></span>
    <a href="#the-evolution-of-my-wheel-setup" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Logitech G29 era (~2015-2021)<span class="hx:absolute hx:-mt-20" id="logitech-g29-era-2015-2021"></span>
    <a href="#logitech-g29-era-2015-2021" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I always wanted a racing wheel. I started over 15 years ago with something equivalent to Logitech&rsquo;s entry-level wheel, a G29. The G29 is a fine wheel to start with — gear-driven force feedback, pedals with a clutch, 900 degrees of rotation. But its force feedback is noisy and a bit crude. You can feel the gears turning.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/logitech-g29-and-myself.jpeg" alt="Me playing with the Logitech G29 on the couch — the beginning of it all"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/logitech-g29-with-support.jpg" alt="The G29 with a simple stand in front of the couch"  loading="lazy" /></p>
<h3>Thrustmaster T300RS + SXT V2 stand era (~2021-2024)<span class="hx:absolute hx:-mt-20" id="thrustmaster-t300rs--sxt-v2-stand-era-2021-2024"></span>
    <a href="#thrustmaster-t300rs--sxt-v2-stand-era-2021-2024" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Around 2021 I upgraded to the <a href="https://www.thrustmaster.com/products/t300rs-gt-edition/"target="_blank" rel="noopener">Thrustmaster T300RS</a>, a belt-driven wheel that&rsquo;s a huge jump up from the Logitech. The force feedback is much smoother and more precise. And I bought the <a href="https://loja.cockpitextremeracing.com.br/products/suporte-para-volantes-sxt-v2?variant=51111119454489"target="_blank" rel="noopener">Extreme Sim Racing SXT V2</a> stand, which is much sturdier than those generic desk clamps.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/thrustmaster-with-support.jpg" alt="The Thrustmaster T300RS mounted on the SXT V2 stand"  loading="lazy" /></p>
<p>First I set it up in front of my desktop PC, which at the time had an RTX 3090. It worked, but it was a hassle to keep mounting and unmounting the stand and the cables every time I wanted to play.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/thrustmaster-with-pc.jpg" alt="The Thrustmaster in front of the desktop PC — functional but annoying"  loading="lazy" /></p>
<p>Then I built a setup with a long fiber-optic HDMI cable to connect my 60&quot; TV to the PC at the back of the room. I moved the stand in front of the couch. Less of a hassle, but I still had to take it down whenever I wanted to watch a movie with my girlfriend.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/thrustmaster-with-big-tv.jpg" alt="Setup with the big TV — better, but still a hassle"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/thrustmaster-in-the-living-room.jpg" alt="The stand in the living room — always in the way"  loading="lazy" /></p>
<p>Around 2024 or 2025 I swapped my couch for one of those VIP cinema couches from <a href="https://www.starseat.com.br/sofa-cinema-sofa-cinema"target="_blank" rel="noopener">Star Seat</a>, which reclines and the whole nine yards. The problem: it was way taller than the previous couch. I had to do all kinds of workarounds to make the stand work at that height. I even 3D-printed mounts and sent them to PCBWay to machine steel plates so I could attach big wheels under the stand and gain a few centimeters of height. But that left the setup way too wobbly to drive comfortably.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/fanatec-support-sofa-too-high.jpg" alt="The problem: VIP couch too tall for the stand — total kludge"  loading="lazy" /></p>
<h3>Fanatec CSL DD + Direct Drive era (~2024-2025)<span class="hx:absolute hx:-mt-20" id="fanatec-csl-dd--direct-drive-era-2024-2025"></span>
    <a href="#fanatec-csl-dd--direct-drive-era-2024-2025" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>In the meantime I gave the T300 to my brother and upgraded to a <a href="https://fanatec.com/eu-en/racing-wheels-wheel-bases/racing-wheels/gran-turismo-dd-pro-5-nm-wheel-base"target="_blank" rel="noopener">Fanatec CSL DD Gran Turismo Edition</a>. The CSL DD&rsquo;s direct drive motor delivers 5 Nm of torque at the base, but the Gran Turismo DD Pro kit comes with the Boost Kit 180 that brings it up to a sustained 8 Nm with no active cooling. Direct drive means there&rsquo;s no gear or belt between the motor and the wheel — the motor&rsquo;s shaft IS the wheel&rsquo;s shaft. The difference is absurd. The T300 was already great, much better than the Logitech. But the Fanatec is on another level. You feel every asphalt texture, every bump, every incipient slide. There&rsquo;s no going back once you try it.</p>
<p>I bought it together with the <a href="https://fanatec.com/eu-en/pedals/csl-pedals"target="_blank" rel="noopener">CSL Pedals with Load Cell</a> kit, which measures pressure on the brake instead of displacement. Makes all the difference in braking — you learn to modulate by foot pressure, not by how far the pedal moves. Way more natural.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/fanatec-direct-drive-close-up.jpg" alt="Close-up of the Fanatec CSL DD direct drive motor"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/fanatec-csl-pedals.jpg" alt="Fanatec CSL Pedals with Load Cell Kit"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/fanatec---shifter.jpg" alt="The Fanatec shifter — manual H-pattern with chrome knob"  loading="lazy" /></p>
<p>I also wanted to try the H-pattern manual shifter with the <a href="https://fanatec.com/us/en/p/add-ons/crd-9040002-ww/clubsport-shifter-sq"target="_blank" rel="noopener">ClubSport Shifter SQ V1.5</a> from Fanatec and a separate handbrake. It&rsquo;s fun to try out old cars with a clutch and an H-pattern, but in practice I never adapted. The SXT V2 stand was already shaking a lot with the direct drive, and using the shifter on that unstable setup was frustrating. And I know there are people who want to do heel-toe, but in the simulator I prefer to keep my left foot on the brake and my right on the gas and modulate both at the same time. Works better for me. Now that I have the McLaren wheel with the analog handbrake and clutch paddles right on the wheel, the H-pattern shifter and the external handbrake have been retired. For rally, an analog handbrake on the wheel is much more natural.</p>
<p>I also bought the PS5 with Gran Turismo 7 around this time. I put on the <a href="https://dbrand.com/shop/ps5"target="_blank" rel="noopener">dbrand Darkplates</a> matte black faceplates to replace the original white plates — it looks much better and more discreet.</p>
<p>But the setup was still the SXT V2 stand in front of the VIP couch. The same kludge. The same wobble. I obviously wasn&rsquo;t going to give up the cinema couch. The situation became unsustainable.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/fanatec-with-support.jpg" alt="The Fanatec CSL DD on the SXT V2 stand — too powerful for the stand to handle"  loading="lazy" /></p>
<h2>The computer and the hardware setup<span class="hx:absolute hx:-mt-20" id="the-computer-and-the-hardware-setup"></span>
    <a href="#the-computer-and-the-hardware-setup" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A note on my gaming hardware. I bought a <a href="https://www.minisforum.com/products/UX790-Pro.html"target="_blank" rel="noopener">Minisforum UX790 Pro</a> to be my dedicated Steam machine. It&rsquo;s a mini-PC with an Intel Core Ultra 9 285H processor, fits in the palm of your hand. Together I bought the <a href="https://www.minisforum.com/products/minisforum-deg1-egpu-dock"target="_blank" rel="noopener">Minisforum DEG1</a>, an external GPU dock that connects via OCuLink (PCIe 4.0 x4, 64 GT/s). It&rsquo;s an open design — basically a board with a PCIe x16 slot and room for an ATX or SFX power supply. There&rsquo;s no card size limit, so an RTX 4090 fits comfortably. The performance loss compared to a native PCIe slot is minimal. I put the RTX 4090 in it. The 4090 came from my desktop — at the start of 2025 I went to Miami and took the chance to buy an RTX 5090 because I was using more and more local AI and LLMs. I gave my old 3090 to my girlfriend to use for video editing. The 4090 went into the mini-PC.</p>
<p>So my gaming setup today is: Minisforum UX790 Pro + eGPU with RTX 4090 for Steam and PC games, and a PlayStation 5 with matte black Darkplates for Gran Turismo 7 and exclusives.</p>
<h2>The cockpit: Formula FX1<span class="hx:absolute hx:-mt-20" id="the-cockpit-formula-fx1"></span>
    <a href="#the-cockpit-formula-fx1" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>To round out the setup I also needed a decent monitor. I was already used to my 80&quot; Samsung OLED TV in the living room and didn&rsquo;t want to downgrade picture quality. So I invested in the <a href="https://www.samsung.com/br/monitors/gaming/odyssey-oled-g8-g81sf-32-inch-240hz-oled-uhd-ls32fg810snxzd/"target="_blank" rel="noopener">Samsung Odyssey OLED G8 32&quot;</a>. It&rsquo;s a 4K (3840x2160) OLED monitor with a 240 Hz refresh rate, 0.03 ms response time (GTG), HDR True Black 400, HDR10+, 99% DCI-P3 coverage, 1,000,000:1 contrast, and FreeSync Premium Pro. It has 2 HDMI 2.1 inputs and 1 DisplayPort 1.4.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/closed-delivery-box.jpg" alt="The box of the Samsung Odyssey OLED G8 32&#34; when it arrived"  loading="lazy" /></p>
<p>In practice: the colors pop, black is real black (it&rsquo;s OLED, no backlight), and with the RTX 4090 I run most games at 4K and 120 fps with no problem. On lighter titles like Super Woden: Rally Edge, it easily hits 240 Hz. The smoothness is absurd. For a cockpit where you&rsquo;re 60-70 cm from the screen, 32&quot; OLED in 4K is the sweet spot. Bigger and you start to see pixels. Smaller and you lose the immersion.</p>
<p>In January 2026, after years of kludges, I finally ordered a dedicated cockpit. I researched a lot. I considered the <a href="https://loja.cockpitextremeracing.com.br/products/cockpit-ax160-horizontal?variant=52008751431961"target="_blank" rel="noopener">Cockpit AX160</a>, made of aluminum profile and very modular, and the <a href="https://loja.cockpitextremeracing.com.br/products/cockpit-4-0-horizontal?variant=52004294852889"target="_blank" rel="noopener">Cockpit 4.0</a>, which is the more traditional tubular steel kind. But neither was available at the time of purchase. And then I found the <a href="https://loja.cockpitextremeracing.com.br/products/cockpit-formula-fx1-preto-e-verde?variant=51700876509465"target="_blank" rel="noopener">Formula FX1 in black and green</a> from Extreme Racing — Petronas colors, F1-styled.</p>
<p>The FX1 is very different from traditional cockpits. The whole structure is welded thick steel tubing. When I say it doesn&rsquo;t shake, I mean it doesn&rsquo;t shake at all. Zero wobble. It&rsquo;s a brutal difference compared to a stand in front of the couch. The driving position is reclined, F1-style — your feet are at the same height or higher than your hips. You&rsquo;d think it would be uncomfortable, but it isn&rsquo;t. You can sit there for hours without complaining. It comes with a padded adjustable seat, an articulating monitor mount, a tilt-adjustable pedal mount, and a height-adjustable wheel mount.</p>
<p>I had to wait about a month for delivery. In the meantime, as anyone who follows my blog knows, I dove into a 16-hour-a-day marathon testing the new AI agents from Anthropic and OpenAI — check the <a href="/en/en/tags/vibe-coding/">#vibecoding</a> and <a href="/en/en/tags/ai/">#agents</a> tags to see everything I built. After about 30 days of that insane marathon, my lower back gave out and I started developing what looks like a herniated disc. I had to see a doctor and take heavy anti-inflammatories.</p>
<p>And right that week, the cockpit decided to arrive.</p>
<h3>The build<span class="hx:absolute hx:-mt-20" id="the-build"></span>
    <a href="#the-build" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I was in absurd pain, but I built the cockpit anyway. It took an entire day to unbox and assemble the heavy steel pieces with my back screaming, but I did it.</p>
<p>The official assembly video I followed:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/WnocqqhmZas"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/first-pieces-mounted.jpg" alt="First pieces mounted — the base and the seat structure"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/mostly-mounted.jpg" alt="Almost complete structure — seat, wheel mount, pedals"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/mostly-mounted-from-front.jpg" alt="Front view of the almost-finished structure"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/almost-mounted,-with-monitor.jpg" alt="Almost complete assembly with the monitor mount"  loading="lazy" /></p>
<h3>The McLaren GT3 V2 wheel<span class="hx:absolute hx:-mt-20" id="the-mclaren-gt3-v2-wheel"></span>
    <a href="#the-mclaren-gt3-v2-wheel" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>After assembling the cockpit, I decided that the standard wheel that comes with the CSL GT kit wasn&rsquo;t enough. I upgraded to the <a href="https://www.racingwheelbrasil.com.br/produtos/volante-fanatec-csl-elite-mclaren-gt3-v2-pc-xbox-ps4-ps5-ready/"target="_blank" rel="noopener">Fanatec CSL Elite McLaren GT3 V2</a> (~R$ 4,990). It&rsquo;s a 1:1 scale replica of the McLaren GT3 wheel, with carbon fiber, an OLED display, and compatibility with PC, Xbox, PS4 and PS5.</p>
<p>What I like most about it: it has the normal shift paddles behind it (shift up/down), but it also has two additional analog paddles that can be configured in four different modes. In mode B, which is what I use, the left paddle works as an analog handbrake and the right one as an analog clutch. That&rsquo;s perfect for rally — I can pull the handbrake mid-corner without taking my hand off the wheel. It also has two 2-position toggles, two 12-position rotaries, 7 standard buttons with interchangeable caps, and Fanatec&rsquo;s 7-direction FunkySwitch. It&rsquo;s a complete racing controller.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/mclaren-wheel.jpg" alt="The McLaren GT3 V2 wheel mounted on the cockpit — carbon fiber and OLED display"  loading="lazy" /></p>
<h2>The final setup<span class="hx:absolute hx:-mt-20" id="the-final-setup"></span>
    <a href="#the-final-setup" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The cockpit ended up in a corner of my bedroom, between the manga shelves (you can spot Akira, Initial D, and 500-something other volumes in the background). I mounted the mini-PC and the PS5 on the cockpit&rsquo;s side structure, together with the eGPU and the RTX 4090. Everything stays permanently connected. That&rsquo;s what makes the difference: I no longer have to set anything up or take it down. I sit, turn it on, and I&rsquo;m driving in 30 seconds.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/fully-mounted-from-side.jpg" alt="Side view of the complete cockpit — between the manga shelves"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/all-consoles-from-front.jpg" alt="The consoles mounted on the structure: PS5 with Darkplates, Minisforum UX790 Pro, eGPU with RTX 4090"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/close-up-of-the-consoles-from-front.jpg" alt="Close-up of the consoles and cabling"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/sitting-angle.jpg" alt="The driver’s perspective — this is what I see when I sit down"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/monitor-on-view-from-front.jpg" alt="PS5 showing the game list — Gran Turismo 7 at the top"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/another-fully-mounted-shot.jpg" alt="The complete cockpit seen from behind"  loading="lazy" /></p>
<div style="max-width: 100%; margin: 1em 0;">
  <video controls playsinline style="width: 100%; border-radius: 8px;">
    <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/from-front-gran-turismo-h264.mp4" type="video/mp4">
  </video>
  <em>Gran Turismo 7 running on the final cockpit</em>
</div>
<h2>The audio system<span class="hx:absolute hx:-mt-20" id="the-audio-system"></span>
    <a href="#the-audio-system" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>To round out the setup I needed dedicated audio. I didn&rsquo;t want to use the monitor&rsquo;s audio (terrible) and I didn&rsquo;t want to be on a headset all the time. The fix was to build a separate audio system with HDMI audio extraction.</p>
<p>The centerpiece is an <a href="https://www.amazon.com.br/dp/B00MNGIP2Y"target="_blank" rel="noopener">HDMI 2.1 switcher from OREI</a> with audio extraction. It has 2 inputs and 1 HDMI output, supports 4K at 120Hz (48 Gbps of bandwidth), and extracts the audio through optical TOSLINK and 3.5mm. I connect the HDMI output of the RTX 4090 to one input and the PS5 to the other. Video goes to the monitor. Audio goes out through the optical port.</p>
<p>The optical audio goes to an <a href="https://www.mercadolivre.com.br/amplificador-de-potncia-aiyima-d03-bluetooth-50-150-watts-cor-preto/p/MLB46172770"target="_blank" rel="noopener">Aiyima D03 amplifier</a>, a compact 2.1 channel amp with 150W per channel, integrated DAC (PCM1808 chip), and Bluetooth 5.0 with aptX HD. It has optical, coaxial, USB, RCA and Bluetooth inputs. It even has a dedicated subwoofer output for when I get around to adding one. It uses Texas Instruments&rsquo; TAS5624 amplifier chip and has bass and treble control through the remote. For a cockpit setup where you&rsquo;re 1 meter from the speakers, 150W is more than enough.</p>
<p>In practice, I keep the amp at 50% and Windows volume at 50%, and that&rsquo;s already loud as hell. Which is to say: this isn&rsquo;t a &ldquo;good enough&rdquo; little system. It&rsquo;s set up to actually go loud if I want.</p>
<p>The speakers are <a href="https://edifier.com.br/caixa-de-som-passiva-p12-madeira-edifier.html"target="_blank" rel="noopener">Edifier P12</a>, passive, with a 4-inch woofer and a 19mm tweeter. Frequency response from 55Hz to 20kHz, 6 ohm impedance, 20W RMS each. The MDF cabinet with wood finish has a rear bass-reflex port that helps the lows. For passive speakers this size, they deliver well. The mid-range is clean and the highs don&rsquo;t distort even at high volume.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/audio-equipment.jpg" alt="The audio gear — Aiyima D03 box and Edifier P12"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/audio-speakers-next-to-other-stuff.jpg" alt="The Edifier P12s positioned on the shelves next to the cockpit, alongside the Akira collection and car miniatures"  loading="lazy" /></p>
<p>The setup logic is: HDMI switcher handles the switching between PS5 and PC, extracts audio to optical, the amp converts and amplifies, and the passive speakers deliver the sound. All without having to touch the monitor or swap cables. I press a button on the switcher and switch consoles.</p>
<p>When I want to play without bothering anyone, I plug my <a href="https://www.mercadolivre.com.br/fones-de-ouvido-meze-audio-109-pro-com-fio-de-madeira-com-en/p/MLB42456685"target="_blank" rel="noopener">Meze 109 Pro</a> directly into the 3.5mm output of the HDMI switcher. The Meze 109 Pro is an open-back headphone with 50mm dynamic drivers, 40 ohm impedance, 112 dB SPL/1mW sensitivity, and 5Hz to 30kHz response. The ear cups are walnut wood with handcrafted finish. It&rsquo;s an audiophile headphone that works perfectly without a dedicated amplifier thanks to the low impedance. The sound is warm, with full bass and rich mids. You can hear every detail of the engines.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/meze-headset.jpg" alt="Meze 109 Pro — walnut wood, audiophile sound"  loading="lazy" /></p>
<p>I haven&rsquo;t decided about a subwoofer yet, but it&rsquo;ll be my next upgrade. A dedicated sub is going to add that low-end weight that makes you feel the engine in your chest.</p>
<h2>The verdict<span class="hx:absolute hx:-mt-20" id="the-verdict"></span>
    <a href="#the-verdict" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The couch with a stand works. The PC desk with a stand works. But neither comes anywhere close to a dedicated cockpit. The FX1&rsquo;s steel structure doesn&rsquo;t move a millimeter, even with the Fanatec direct drive at max torque. The reclined F1 position is comfortable for sessions of hours. The load cell pedals stay firm on the base. The monitor is exactly at the right height and distance. And best of all: it&rsquo;s always ready. I don&rsquo;t need to assemble anything, take anything down, run cables, none of it. I sit and I drive.</p>
<p>For anyone who&rsquo;s wondering whether it&rsquo;s worth investing in a dedicated cockpit instead of staying with a desk or couch stand: it is. If you already have a direct drive wheel, the cockpit is the missing piece. I spent years thinking &ldquo;this is fine&rdquo; with the stand on the couch. It wasn&rsquo;t fine. The difference in driveability is something else entirely. And for my case — introvert, single-player only, simcade — I couldn&rsquo;t have built it any sooner. To be honest, I think I&rsquo;ve finally landed on the simulator setup that&rsquo;s perfect for my taste.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/monitor-on-view.jpg" alt="The monitor on with the driver’s view"  loading="lazy" /></p>
<div style="max-width: 100%; margin: 1em 0;">
  <video controls playsinline style="width: 100%; border-radius: 8px;">
    <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/cockpit/video-testing-h264.mp4" type="video/mp4">
  </video>
  <em>Me driving on the final cockpit</em>
</div>
<h2>Shopping list: how much it all cost<span class="hx:absolute hx:-mt-20" id="shopping-list-how-much-it-all-cost"></span>
    <a href="#shopping-list-how-much-it-all-cost" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Here&rsquo;s the consolidated list of everything in my current setup, with approximate prices (some were bought in dollars and converted to reais at the time&rsquo;s exchange rate):</p>
<table>
  <thead>
      <tr>
          <th>Item</th>
          <th style="text-align: right">Estimated Price (R$)</th>
          <th>Link</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://loja.cockpitextremeracing.com.br/products/cockpit-formula-fx1-preto-e-verde?variant=51700876509465"target="_blank" rel="noopener">Cockpit Formula FX1 Black and Green</a></td>
          <td style="text-align: right">~6,290</td>
          <td>Extreme Racing</td>
      </tr>
      <tr>
          <td><a href="https://fanatec.com/us/en/p/sim-racing-bundles/crd-9020007-8nm-us/gran-turismo-dd-pro-8nm-qr2l-us"target="_blank" rel="noopener">Fanatec Gran Turismo DD Pro 8Nm (motor + wheel + pedals + Boost Kit)</a></td>
          <td style="text-align: right">~9,590</td>
          <td>Fanatec / Racing Wheel Brasil</td>
      </tr>
      <tr>
          <td><a href="https://fanatec.com/us/en/p/pedals/csl_p_lc/csl_pedals_lc"target="_blank" rel="noopener">Fanatec CSL Pedals LC (with Load Cell)</a></td>
          <td style="text-align: right">~1,500</td>
          <td>Fanatec</td>
      </tr>
      <tr>
          <td><a href="https://www.racingwheelbrasil.com.br/produtos/volante-fanatec-csl-elite-mclaren-gt3-v2-pc-xbox-ps4-ps5-ready/"target="_blank" rel="noopener">Fanatec CSL Elite McLaren GT3 V2</a></td>
          <td style="text-align: right">~4,990</td>
          <td>Racing Wheel Brasil</td>
      </tr>
      <tr>
          <td><a href="https://fanatec.com/us/en/p/add-ons/crd-9040002-ww/clubsport-shifter-sq"target="_blank" rel="noopener">Fanatec ClubSport Shifter SQ V1.5</a></td>
          <td style="text-align: right">~2,500</td>
          <td>Fanatec</td>
      </tr>
      <tr>
          <td><a href="https://www.minisforum.com/products/UX790-Pro.html"target="_blank" rel="noopener">Minisforum UX790 Pro</a></td>
          <td style="text-align: right">~5,000</td>
          <td>Minisforum</td>
      </tr>
      <tr>
          <td><a href="https://www.minisforum.com/products/minisforum-deg1-egpu-dock"target="_blank" rel="noopener">Minisforum DEG1 eGPU Dock</a> + RTX 4090</td>
          <td style="text-align: right">~12,000</td>
          <td>Minisforum / bought separately</td>
      </tr>
      <tr>
          <td>PlayStation 5 + <a href="https://dbrand.com/shop/limited-edition/ps5"target="_blank" rel="noopener">dbrand Darkplates</a></td>
          <td style="text-align: right">~4,500</td>
          <td>Sony / dbrand</td>
      </tr>
      <tr>
          <td><a href="https://www.samsung.com/br/monitors/gaming/odyssey-oled-g8-g81sf-32-inch-240hz-oled-uhd-ls32fg810snxzd/"target="_blank" rel="noopener">Samsung Odyssey OLED G8 32&quot;</a></td>
          <td style="text-align: right">~2,500</td>
          <td>Samsung</td>
      </tr>
      <tr>
          <td><a href="https://www.amazon.com.br/dp/B00MNGIP2Y"target="_blank" rel="noopener">OREI BK-21A HDMI 2.1 Switcher 2x1 with audio extraction</a></td>
          <td style="text-align: right">~450</td>
          <td>Amazon</td>
      </tr>
      <tr>
          <td><a href="https://www.mercadolivre.com.br/amplificador-de-potncia-aiyima-d03-bluetooth-50-150-watts-cor-preto/p/MLB46172770"target="_blank" rel="noopener">Aiyima D03 Amplifier</a></td>
          <td style="text-align: right">~900</td>
          <td>Mercado Livre</td>
      </tr>
      <tr>
          <td><a href="https://edifier.com.br/caixa-de-som-passiva-p12-madeira-edifier.html"target="_blank" rel="noopener">Edifier P12 (pair)</a></td>
          <td style="text-align: right">~799</td>
          <td>Edifier</td>
      </tr>
      <tr>
          <td><a href="https://www.mercadolivre.com.br/fones-de-ouvido-meze-audio-109-pro-com-fio-de-madeira-com-en/p/MLB42456685"target="_blank" rel="noopener">Meze 109 Pro</a></td>
          <td style="text-align: right">~5,390</td>
          <td>Mercado Livre / Heinrich Audio</td>
      </tr>
      <tr>
          <td>Cables (HDMI 2.1, optical, 3.5mm, power)</td>
          <td style="text-align: right">~300</td>
          <td>Various</td>
      </tr>
      <tr>
          <td><strong>TOTAL ESTIMATED</strong></td>
          <td style="text-align: right"><strong>~56,709</strong></td>
          <td></td>
      </tr>
  </tbody>
</table>
<p>Yes, almost R$ 57k is a lot of money. I worked like a dog for decades. Now that I&rsquo;ve managed to retire honestly, my family is well taken care of, I have no debts, and I can finally give myself something I always wanted as a kid but couldn&rsquo;t afford. When I sat at those OutRun and Daytona USA cabinets at the arcade, I dreamed of having something like this at home. It took 30-something years, but I got there.</p>
<p>And if you add up the years of kludges, stands that didn&rsquo;t work, 15-meter HDMI cables, 3D prints, machined steel plates, and the frustration of mounting and unmounting everything — a dedicated cockpit saves your sanity. Unlike a PC that depreciates fast, a steel cockpit lasts decades.</p>
]]></content:encoded><category>gaming</category><category>hardware</category><category>off-topic</category></item><item><title>Using A.I. (ComfyUI) to Generate NPCs in Game Development</title><link>https://akitaonrails.github.io/en/2025/04/23/using-ai-comfyui-to-generate-npcs-for-game-development/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/04/23/using-ai-comfyui-to-generate-npcs-for-game-development/</guid><pubDate>Wed, 23 Apr 2025 16:25:00 -0300</pubDate><description>&lt;p&gt;I have been updating my little ComfyUI with Docker Compose project A LOT. I think it is the most complete and tested setup out there, with the advantage of being in Docker, so it is repeatable with zero setup. Read &lt;a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded"target="_blank" rel="noopener"&gt;my post&lt;/a&gt; for more details on how it works.&lt;/p&gt;
&lt;p&gt;I decided to document in this post one of the most complex workflows I have tested that runs on my setup: how to develop Character Sheets of random characters, in any style, whether realistic, Pixar style, cartoon style, whatever you want. The result is a sheet like the example below. In several poses, several lighting setups, several emotional expressions, with a color index, all generated from absolute scratch by &amp;ldquo;A.I.&amp;rdquo; In half an hour of processing on my RTX 4090.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I have been updating my little ComfyUI with Docker Compose project A LOT. I think it is the most complete and tested setup out there, with the advantage of being in Docker, so it is repeatable with zero setup. Read <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded"target="_blank" rel="noopener">my post</a> for more details on how it works.</p>
<p>I decided to document in this post one of the most complex workflows I have tested that runs on my setup: how to develop Character Sheets of random characters, in any style, whether realistic, Pixar style, cartoon style, whatever you want. The result is a sheet like the example below. In several poses, several lighting setups, several emotional expressions, with a color index, all generated from absolute scratch by &ldquo;A.I.&rdquo; In half an hour of processing on my RTX 4090.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/469eyg4a4pl3ry0z12twg74nlbc8" alt="NPC Design Sheet"  loading="lazy" /></p>
<p>Imagine you are an intern at a game studio. Your job: create 100 background characters (NPCs). In the end you have to deliver 100 of them. But just because they are secondary characters does not mean you can do them any old way, they all have to be coherent, in every pose. Imagine how many days you would take. What if you could automate it? This is the kind of scenario where A.I. is interesting. Spend more time on the main characters, do them manually, but do not waste time on the secondary ones and do not lower the final quality of everything.</p>
<p>I did not make this workflow, it was Mickmumpitz. Watch his entire video to learn how this workflow works, I will not repeat what he already explains:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/grtmiWbmvv0"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>I will also not share his workflow, because he has a paid Patreon (it is super cheap, only USD 10 per month), which gives you access to download his entire workflow pack <a href="https://www.patreon.com/posts/free-workflows-120405048"target="_blank" rel="noopener">at this link</a></p>
<p>But that alone is not enough. The requirements you need:</p>
<ul>
<li>a beefy machine with at least an RTX 3090 with 24GB of VRAM. The important thing here is VRAM, the models used easily exceed 20GB of usage. CPU is less important, I use a 7950X3D but a 7800 would already handle it.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/730safwxqkrsgawu4liw42scoul3" alt="GPU VRAM"  loading="lazy" /></p>
<ul>
<li>if you do not have such a machine, it takes more work but you can rent one on <strong>RUNPOD</strong>. They have several GPU configurations, but you will need to know how to configure Linux a bit or choose Windows and do Mickmumpitz&rsquo;s tutorial all by hand (trust me, you will suffer if you do it that way).</li>
<li>you need to have at least <strong>500GB</strong> of available space and good internet to download. The first time my Docker runs, it automatically downloads all ComfyUI extensions and all models for everything. You can edit the <code>models.conf</code> file beforehand and remove models that you know you will never use. But just this workflow alone requires at least about 30GB of models, there is no way around it.</li>
<li>this workflow is particularly <strong>HEAVY</strong> because it has several features (parts of which we can disable, to customize for our workflow). But for 1 character, the entire flow can take <strong>30 MINUTES</strong>. Even on an RTX 4090 like mine.</li>
</ul>
<p>If you have done everything right up to this point, you have ComfyUI running with all dependencies already downloaded and you bought the Workflow. When you load it you will have to fix some things manually.</p>
<p>The first thing is that you will see an error in several Nodes in red, all of them <strong>Ultimate SD Upscale</strong>. Just right-click on each one and choose &ldquo;Fix Recreate&rdquo; and it will fix automatically:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/djblur5g36mw9denng0ht0vvksym" alt="Fix Upscale"  loading="lazy" /></p>
<p>ComfyUI requires that each output slot of a Node connects to the input slot of another compatible Node, but often the workflows get so big that there are little wires connecting things scattered all over the place and it gets really hard to know what connects to what. It literally becomes <strong>spaghetti</strong>.</p>
<p>But there is an extension (which I have already pre-installed) called <a href="https://github.com/chrisgoringe/cg-use-everywhere"target="_blank" rel="noopener">cg-use-everywhere</a>. Every extension is a little GitHub project, cloned into the <code>custom_nodes</code> subdirectory. It lets you connect a Node&rsquo;s output to this <strong>Anything Elsewhere</strong> node, say the &ldquo;clip&rdquo; output of the &ldquo;Load Clip&rdquo; Node. Now, every other Node that has &ldquo;clip&rdquo; as an input field and does not have anything explicitly connected to it will use this Anything that was created. It works super well.</p>
<p>Only in this Mick workflow, who knows why, all the Anywheres are broken. But it is easy to fix. Just find these Nodes, delete them, create new ones and reconnect. There are several right at the beginning, write down who connects to each one, delete them and create a new one to reconnect:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/kjjo7u6g58drww1v5f8v88h1iis1" alt="anywhere initial"  loading="lazy" /></p>
<p>There are also some in the Upscale group 02:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qrkdxs6qjdtmdjs3c463oxetbgyy" alt="anywhere upscale"  loading="lazy" /></p>
<p>Small Nodes, with only the title showing, are COLLAPSED. Just right-click and choose EXPAND to see the details. It is another way to organize the mess.</p>
<p>Lastly, you have to recheck the correct name of the models in all the &ldquo;Load&rdquo; something Nodes right at the beginning of the workflow (luckily he organized these nodes all together):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/y1u93ujz3kq4hz3pik5q4wcgapoa" alt="model path"  loading="lazy" /></p>
<p>In his workflow, the Node comes looking for &ldquo;FLUX/flux1-dev-fp8.safetensors&rdquo; but then you go to the list (which pulls from what is actually in the &ldquo;models/checkpoints&rdquo; directory on your machine) and choose the one with the same name. This happens often because different people organize different subdirectories for the models. But after configuring, just save and you are done. You have to do this for the checkpoint, controlnet, ic-light, etc.</p>
<p>ComfyUI errors are not friendly. They are Python stacktraces, which are not friendly because they show which line of code blew up, but they do not show what was in the variables passed to the function that blew up. So there is no way to know where it went wrong. But normally on the web interface, the Node turns red, then you check it and usually it is because you forgot to download the model or pick the right one from the list.</p>
<p>The last thing in this workflow is to download the OpenPose image from his Patreon page, which I linked at the beginning of the post, and load it:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/uajcc6ocnr5llzv8wzoowl85c5o3" alt="openpose image"  loading="lazy" /></p>
<p><strong>OpenPose</strong> is a standard file format that describes poses, an open format that can be edited in several tools. And there are several pre-trained models to extract poses in this format from images, so we do not even need to edit it by hand. We can download several pre-made open poses from this site:</p>
<p><a href="https://openposes.com/"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ntvaf9vba1rugg11qn3n14u2t69e" alt="Poses Sites"  loading="lazy" /></a>
I am going to repeat this several times, but <strong>watch Mick&rsquo;s video tutorial</strong> where he explains how to generate characters from scratch or use <strong>your own photos</strong> with your own poses to continue the flow from there. His workflow, after running, looks like this (with zoom way down below):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/jqu4o2c5cenci6au2g8bbny064mm" alt="complete workflow"  loading="lazy" /></p>
<p>In the first part, there is the manual configuration and checking of the models. You only have to fix it once and save. After that it will always work the same way. In this first menu you can also turn on and off some of the Node groups, for example, to skip the first stage of generating a character from scratch and be able to use your own photos:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/s72mrc1ib5o173c9l74vmpsn3js3" alt="menu and setup"  loading="lazy" /></p>
<p>If you choose to generate a character from scratch, there is a green prompt box to type how you want your character to be, in what style. Text Prompts are decoded by <strong>CLIP</strong> (Contrastive Language Image Pre-Training) models, which are specific to certain UNET categories (like SDXL for Stable Diffusion or the now best and most popular FLUX). Part of the learning is starting to figure out which sub-models (clips, clip_vision, vaes, loras, etc.) are compatible with which unets (unet, checkpoints, diffusion_models). You cannot mix just anything. Roughly speaking, it is a bit like, if you choose to do a text in German, all plugins have to be in German.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/37oe4dyqr5t0in9ad98bocli6u3w" alt="Character Generation"  loading="lazy" /></p>
<p>I will not show the complete workflow because it does not even fit, but you can see the prompts in green, in the middle where we choose which pose we want (it is on his Patreon), and on top the image generated by the <strong>flux1-dev-fp8</strong> model, one of the best and most popular for having excellent quality and not being so heavy (fp8 instead of fp32, for example).</p>
<p>This is one of the advantages of using tools outside the Dalle or Midjourney of the world: we can control all aspects of image generation not only with text prompts, but also with image prompts, like OpenPose.</p>
<p>Another detail. I explained in my previous post about KSampler or Samplers in general, that they are the engine that does all the work of joining all the models, images, prompts, controlnet, vaes and produces the final image. KSampler parameters like steps, cfg, denoise, scheduler and more <strong>DEPEND</strong> on the chosen model. There are well-documented models, there are poorly documented models.</p>
<p>For example, say you read on a Reddit thread about this other more optimized model called <a href="https://civitai.com/models/215418?modelVersionId=273102"target="_blank" rel="noopener">&ldquo;TurboVisionXL&rdquo;</a>, which is available on the Civit.ai site:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/hpcj5e8g0hxvefelvszwf48vsnpf" alt="TurboVisionXL"  loading="lazy" /></p>
<p>It really is a good model, but now what? What do I configure in the KSampler for it? Luckily if you scroll the page (always read the original documentation!!) you will find these recommendations:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/57f9vyfh4w9j8bduaomc3aow2sks" alt="TurboVisionXL documentation"  loading="lazy" /></p>
<p>See? He explains what to configure and what the known limitations are. Further down there will be comments from other people who can give more tips or share errors that you have already seen. It is a huge community, you need to learn how to participate in it.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/7avk9tl1pop1eexkuu3h8epgs2t5" alt="MultiView"  loading="lazy" /></p>
<p>But back from the tangent, in Mick&rsquo;s second group of nodes, he takes the first image in &ldquo;Pose T&rdquo; and runs it through a &ldquo;Multi View&rdquo; process using Diffuser MV which does this automatically. It is the same pose, but as if the camera went 360 degrees around it, capturing different angles and, most importantly, generating new images coherent with the original (see how the model is even used to invent the character&rsquo;s back).</p>
<p>In this step he also extracts only the face from the first image to do other treatments in parallel. Face recognition and extraction is super common. Every security system uses it, Google Photos uses it to organize dynamic albums. And it is worth learning to use in your own products. It is a reasonably light and fast process these days and there are several pre-made algorithms and models to use. In this case I do not think it even uses A.I. as such, it is just an algorithm.</p>
<p>Another detail: image generation models normally work in <strong>LOW RESOLUTION</strong>. Usually below 1024x1024. Do not expect to generate 4K images right off the bat. But that is no problem, because the solution is to first generate a low resolution image and then run it through another A.I. model specialized in <strong>UPSCALING</strong> like the RealESRGAN that I already talked about in my other post about <a href="https://www.akitaonrails.com/en/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a"target="_blank" rel="noopener">upscaling old anime</a></p>
<p>Different from what Photoshop or any image editor does, if you try to enlarge an image, it just takes a pixel and tries to duplicate, quadruplicate the surrounding pixels. But that usually only makes the image bigger but blurred, because it has no way of knowing the details, and it will not even try to &ldquo;invent&rdquo; details.</p>
<p>To &ldquo;invent&rdquo; details, you need a pre-trained model that learned what to draw. It is also how NVIDIA&rsquo;s DLSS 3 works for games, where the game renders in low resolution, like 720p, and DLSS, in real time, redraws the new screen in 4K. It is not perfect, but playing at 60fps, you will rarely notice the difference. You will only notice that it got more <strong>CRISP</strong>.</p>
<p>That is also why, if you use your own photo, or photos of people you know, you will see that the new photo after upscaling looks <strong>slightly different</strong>, it is no longer the same person, because the model <strong>REDRAWS</strong> details that did not exist. It is a process of interpretation and not of discovering &ldquo;hidden&rdquo; details (that does not exist, only in CSI fiction).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2he2fgmus8vmp3wfp95s3qbh7ztf" alt="Upscale 2"  loading="lazy" /></p>
<p>Mick&rsquo;s workflow goes one step further and has a second upscaling pass, focusing on fixing only the face with a Face Detailer Node, made specifically to fix and improve faces. And here we enter plastic surgery and harmonization territory for real lol the final face will be different from the original. But for NPCs and secondary characters it does not matter, nobody will know the difference anyway, as long as it is not deformed, cross-eyed or something else that jumps out at you.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/3d40a8n9b32mmkub5l8c04oou37h" alt="Emotions"  loading="lazy" /></p>
<p>There are Nodes specialized in expressions, like the Expression Editor (PHM) which starts from a single face photo and manages to generate new photos in the most varied emotions - which we can configure exactly, numerically, however we want. I will not show it, but note that the generated images are in low quality, but the next step in the workflow is precisely to upscale them too.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/cxdua4dfalu6d68tpaleljie5y1e" alt="Lighting"  loading="lazy" /></p>
<p>This step is interesting. First the top part, with the various scenarios. They were all generated via the green prompts below them, prompts that Mick left but that we can edit however we want OR swap the generation node and put a Load Image node to load our own background images, from real photos.</p>
<p>The objective of this step is to generate the faces under different light configurations. Many people think that &ldquo;light&rdquo; in games is just &ldquo;turn on the red light&rdquo; and everyone will automatically turn red. In new games, with <strong>RAY TRACING</strong> yes, that is why it is SUPER HEAVY. But in games made for old machines, indie games, you cannot demand this from everyone.</p>
<p>So &ldquo;tricks&rdquo; are used to fake things like lights and shadows. It is the <strong>BAKING</strong> process where we literally &ldquo;stamp the light onto the texture&rdquo; of objects and characters. With global illumination on or off, they will always be &ldquo;painted&rdquo; with the colored light of the scene. To switch scenes, new textures are loaded with other &ldquo;baked&rdquo; lights. For programmers, think of baking as <strong>Caching</strong>. Why recalculate everything dynamically every time, if the scene&rsquo;s light never changes? Save that step and leave it &ldquo;cached&rdquo;.</p>
<p>Going back, this step uses the <a href="https://github.com/lllyasviel/IC-Light"target="_blank" rel="noopener"><strong>IC-Light</strong></a> technology which is another open source project worth learning, even if you are not interested in A.I. It can manipulate the light of any image or photo. You, as a photographer, can change the light if you have changed your mind, in post-production. Adobe or BlackMagic tools, I think they already include similar technology, but it is good to know that there is open source and you can integrate it into your product.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qup2n6vadpc7qrtgokbqbfkpvmjg" alt="final sheet"  loading="lazy" /></p>
<p>After generating the faces with different lights, obviously there is another upscaling step - which I am skipping showing - and it goes to the final group of nodes that joins everything into a &ldquo;sheet&rdquo;, a &ldquo;character sheet&rdquo;. The game developer can use it directly in a 2D game (think of a visual novel game), or it can serve as a basis for UV Mapping textures of a simple 3D model with Armature to animate and you already have some emotional faces. Good for placing in the background scenery, pedestrians walking on the street, shopping, sitting in the square or something like that.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/cgv0qp3slipgw4nrlgnrserl1n8s" alt="Loras"  loading="lazy" /></p>
<p>In addition, Mick&rsquo;s tutorial describes how it is possible to create <strong>LORAS</strong> (Low-Rank Adaptation). If you want to be able to generate characters with a specific style or even specific faces (without &ldquo;artistic&rdquo; intervention from the A.I.), you choose a model like FLUX or SDXL and &ldquo;on top&rdquo; of it you add a second compatible sub-model with information about that person or character.</p>
<p>For this you take several clean and well-lit photos of the person, from many different angles. You do not even need much, just a dozen is enough. For each image you create prompts that describe it well, and that is it. There are tools to generate these Loras locally or sites that do this (watch the video).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/tk8tbtrr1ealm4c4eu26alk63xlh" alt="Creating Prompts"  loading="lazy" /></p>
<p>Speaking of prompts, in several workflows there are steps to describe detailed prompts of dynamically generated images. Instead of requiring the user (you) to do this manually, you can use a decoding model, which decodes images into text prompts. The most used model for this is Microsoft&rsquo;s <strong>FLORENCE 2</strong>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/dwr87ajtkj7w15ftsy86l5u4hz44" alt="Florence 2"  loading="lazy" /></p>
<p>That text box at the bottom is read-only, you do not need to type it manually. The Florence 2 Run Node will read the image and generate this text. See how it described the first image of the woman in the &ldquo;T&rdquo; pose. Doing tests, for your case, you determine if just this is enough; if it is not, just put a second prompt Node and concatenate the two before sending them to other nodes.</p>
<p>Anyway, there is A LOT to explore just in this workflow. Mick did an excellent job both on the workflow and on the video to explain it, and it is worth paying the 10 dollars to contribute. There are dozens of workflows like this available on the Web, just search. And my setup is getting more and more complex. I plan to keep adjusting it for each new interesting workflow I run into. There are already several pre-tested ones in the &ldquo;workflows&rdquo; folder of my project for you to play with.</p>
]]></content:encoded><category>generative-media</category><category>gaming</category><category>local-models</category></item><item><title>Matches, Rankings, The Social Network, League of Legends, and Ruby?</title><link>https://akitaonrails.github.io/en/2016/11/01/matches-rankings-the-social-network-league-of-legends-and-ruby/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2016/11/01/matches-rankings-the-social-network-league-of-legends-and-ruby/</guid><pubDate>Tue, 01 Nov 2016 10:20:00 -0200</pubDate><description>&lt;p&gt;There is a segment in a series of talks that I have been presenting in Brazil for the last 3 years or so that I never blogged about. Yesterday I just &lt;a href="http://www.akitaonrails.com/en/2016/10/31/implementacao-ruby-on-rails-sistema-ranking-popularidade-correto"target="_blank" rel="noopener"&gt;posted about the proper way to rank content by &amp;ldquo;popularity&amp;rdquo;&lt;/a&gt; so I thought I should revisit the theme.&lt;/p&gt;
&lt;p&gt;To begin, I believe by now most people already watched the movie &amp;ldquo;The Social Network&amp;rdquo;. It&amp;rsquo;s interesting that I heard many people saying how this movie influenced them to begin their own startups. So I was thinking, &amp;ldquo;what does this movie actually teach?&amp;rdquo;&lt;/p&gt;</description><content:encoded><![CDATA[<p>There is a segment in a series of talks that I have been presenting in Brazil for the last 3 years or so that I never blogged about. Yesterday I just <a href="http://www.akitaonrails.com/en/2016/10/31/implementacao-ruby-on-rails-sistema-ranking-popularidade-correto"target="_blank" rel="noopener">posted about the proper way to rank content by &ldquo;popularity&rdquo;</a> so I thought I should revisit the theme.</p>
<p>To begin, I believe by now most people already watched the movie &ldquo;The Social Network&rdquo;. It&rsquo;s interesting that I heard many people saying how this movie influenced them to begin their own startups. So I was thinking, &ldquo;what does this movie actually teach?&rdquo;</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/567/pasted-image-765.jpg" alt="The Social Network Casting"  loading="lazy" /></p>
<p>Well, from the movie we learn that David Fincher is a fantastic director, that Aaron Sorkin writes very sharp dialogue that is very compelling, that Justin Timberlake does a better Sean Parker than the real deal, that Andrew Garfield does an ok Eduardo Saverin, and that Jesse Eisenberg will forever be Mark Zuckerberg.</p>
<p>And that&rsquo;s it, we can&rsquo;t learn anything else from the movie.</p>
<p>Or can we?</p>
<h3>Facemesh<span class="hx:absolute hx:-mt-20" id="facemesh"></span>
    <a href="#facemesh" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>One of my favorite scenes from the movie is when Zuckerberg/Eisenberg is pissed by the Erica Albright split up and he starts scraping photos of women from Harvard websites and organizing them into a troll website called &ldquo;Facemesh&rdquo; where he puts them to compete. People can vote on which photo they prefer and it sorts them out in a ranking of popularity.</p>
<p>When Eduardo Saverin/Andrew Garfield shows up, Zuckerberg asks him:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/BzZRr4KV59I"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<blockquote>
  <p>&ldquo;Wardo, I need you (&hellip;) I need the algorithm used to rank chess players&rdquo;</p>

</blockquote>
<p>And he goes ahead and writes the following on the dorm room window:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/569/pasted-image-759.jpg" alt="Through the Looking Glass"  loading="lazy" /></p>
<p>Here most people would just think:</p>
<blockquote>
  <p>&ldquo;pff, another gibberish formula just to show off that they are little geniuses, but most certainly this formula doesn&rsquo;t even exist&rdquo;</p>

</blockquote>
<p>Except it does. And this is the one scene in the movie that stuck in my head as I had seen it before. To help you out, let&rsquo;s un-mirror the mirrored image:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/568/pasted-image-761.jpg" alt="The Algorithm"  loading="lazy" /></p>
<p>And this is the &ldquo;algorithm&rdquo;.</p>
<p>As I said in my previous article, most developers would create a Facemesh-like website adding integer fields in the table of contestants with the count of upvotes, downvotes and they would do something silly such as:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">score</span> <span class="o">=</span> <span class="n">upvotes</span> <span class="o">-</span> <span class="n">downvotes</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Or even sillier:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">score</span> <span class="o">=</span> <span class="n">upvotes</span> <span class="o">/</span> <span class="p">(</span><span class="n">upvotes</span> <span class="o">+</span> <span class="n">downvotes</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And it doesn&rsquo;t work that way, you will get very wrong rankings.</p>
<h3>The Demonstration<span class="hx:absolute hx:-mt-20" id="the-demonstration"></span>
    <a href="#the-demonstration" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>To show you how wrong, I created a simple demonstration project called <a href="https://github.com/akitaonrails/elo_demo"target="_blank" rel="noopener">elo_demo</a> which you can git clone and run yourself.</p>
<p>It will create 2,000 random matches against 10 players. This will be the sorted results if we use the wrong methods of subtracting losses from wins and order through that result:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>   Name        Games  Wins  Losses Points (wins - losses)
 1 Kong          217   117    100     17
 2 Samus         211   110    101      9
 3 Wario         197   102     95      7
 4 Luigi         186    95     91      4
 5 Zelda         160    81     79      2
 6 Pikachu       209   105    104      1
 7 Yoshi         223   112    111      1
 8 Mario         203   101    102     -1
 9 Fox           208    95    113    -18
10 Bowser        186    82    104    -22</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now, let&rsquo;s make the 2nd place Samus win 10 times in a row against the 3rd place Wario. This is the new ranking:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>   Name        Games  Wins  Losses Points
 1 Samus         221   120    101     19
 2 Kong          217   117    100     17
 3 Luigi         186    95     91      4
 4 Zelda         160    81     79      2
 5 Pikachu       209   105    104      1
 6 Yoshi         223   112    111      1
 7 Mario         203   101    102     -1
 8 Wario         207   102    105     -3
 9 Fox           208    95    113    -18
10 Bowser        186    82    104    -22</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Sounds fair, Samus jumps to 1st place and Wario goes down to 8th place.</p>
<p>Now, what if we make the weaker 10th place Bowser win 10 times against the current 2nd place Kong?</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>   Name        Games  Wins  Losses Points
 1 Samus         221   120    101     19
 2 Kong          227   117    110      7
 3 Luigi         186    95     91      4
 4 Zelda         160    81     79      2
 5 Pikachu       209   105    104      1
 6 Yoshi         223   112    111      1
 7 Mario         203   101    102     -1
 8 Wario         207   102    105     -3
 9 Bowser        196    92    104    -12
10 Fox           208    95    113    -18</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This is where you see how <strong>wrong</strong> this method is. Even though he lost 10 times against the weakest player, Kong still reigns supreme at 2nd place. And poor Bowser, in spite of all his hard work and effort, levels up just 1 meager position from 10th to 9th.</p>
<p>This is very frustrating and if it feels unfair, it&rsquo;s because it is. This kind of calculation is <strong>wrong</strong>.</p>
<h3>From Chess to League of Legends<span class="hx:absolute hx:-mt-20" id="from-chess-to-league-of-legends"></span>
    <a href="#from-chess-to-league-of-legends" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>If you ever played League of Legends you are probably familiar with something called &ldquo;ELO Boosts&rdquo;.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/570/Screen_Shot_2016-11-01_at_10.19.42.png" alt="Elo Boost"  loading="lazy" /></p>
<p>This is a way to level up your account for money. I&rsquo;d strongly recommend against it if you intend to compete professionally as it&rsquo;s against the rules stipulated by Riot.</p>
<p>Anyway, have you ever wondered why it&rsquo;s called &ldquo;ELO&rdquo;?</p>
<p>This is for Austro-Hungarian professor <strong>Arpad Emmerich Elo</strong>. He is best known for his system of rating chess players. Quoting from Wikipedia:</p>
<blockquote>
  <p>The original chess rating system was developed in 1950 by Kenneth Harkness (&hellip;). By 1960, using the data developed through the Harkness Rating System, Elo developed his own formula which had a <strong>sound statistical basis</strong> and constituted an improvement on the Harkness System. The new rating system was approved and passed at a meeting of the United States Chess Federation in St. Louis in 1960.</p>

</blockquote>
<blockquote>
  <p>In 1970, FIDE, the World Chess Federation, agreed to adopt the Elo Rating System. From then on until the mid-1980s, Elo himself made the rating calculations. At the time, the computational task was relatively easy because fewer than 2000 players were rated by FIDE.</p>

</blockquote>
<p>His system has been refined and evolved to make tournament leaderboards actually fair and competitive. One such evolution is in the form of Microsoft&rsquo;s <a href="https://www.microsoft.com/en-us/research/project/trueskill-ranking-system/"target="_blank" rel="noopener">TrueSkill Ranking System</a> used in all Xbox Live games.</p>
<p>That &ldquo;algorithm&rdquo; that Eduardo Saverin writes on the window of Harvard&rsquo;s dorm room? It&rsquo;s the <strong>ELO Rating System</strong>!!</p>
<p>I don&rsquo;t know if Zuckerberg actually implemented the ELO rating system equations. If he did, it was the <strong>correct</strong> choice. But the whole Eduardo writing the equations on the window probably didn&rsquo;t happen that way as it would be way easier to Google for it :-)</p>
<h3>ELO Rating System Demonstration<span class="hx:absolute hx:-mt-20" id="elo-rating-system-demonstration"></span>
    <a href="#elo-rating-system-demonstration" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>My pet demonstration project also calculates that exact ELO score. The calculations are done by the <a href="https://github.com/iain/elo"target="_blank" rel="noopener">elo</a> rubygem.</p>
<p>The idea is to calculate the probability that one player has to win over the other player. So if a strong player plays against a weak player, he is expected to win, and if this is the outcome, he will not score a lot and the losing player will not fall a lot either. But if the unexpected happens and the strong one loses then it&rsquo;s expected for him to fall down a lot and for the &ldquo;weaker&rdquo; player to jump up a lot.</p>
<p>That will make the tournaments more competitive and make the new players more motivated to play against the strongest and also make the strongest play harder to hold their positions.</p>
<p>From the elo gem documentation, this is how you use it:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">kong</span>  <span class="o">=</span> <span class="no">Elo</span><span class="o">::</span><span class="no">Player</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl"><span class="n">bowser</span> <span class="o">=</span> <span class="no">Elo</span><span class="o">::</span><span class="no">Player</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">:rating</span> <span class="o">=&gt;</span> <span class="mi">1500</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">game1</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">wins_from</span><span class="p">(</span><span class="n">bowser</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">game2</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">loses_from</span><span class="p">(</span><span class="n">bowser</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">game3</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">plays_draw</span><span class="p">(</span><span class="n">bowser</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">game4</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">versus</span><span class="p">(</span><span class="n">bowser</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">game4</span><span class="o">.</span><span class="n">winner</span> <span class="o">=</span> <span class="n">bowser</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">game5</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">versus</span><span class="p">(</span><span class="n">bowser</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">game5</span><span class="o">.</span><span class="n">loser</span> <span class="o">=</span> <span class="n">bowser</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">game6</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">versus</span><span class="p">(</span><span class="n">bowser</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">game6</span><span class="o">.</span><span class="n">draw</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">game7</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">versus</span><span class="p">(</span><span class="n">bowser</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">game7</span><span class="o">.</span><span class="n">result</span> <span class="o">=</span> <span class="mi">1</span> <span class="c1"># result is in perspective of kong, so kong wins</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">game8</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">versus</span><span class="p">(</span><span class="n">bowser</span><span class="p">,</span> <span class="ss">:result</span> <span class="o">=&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="c1"># bowser wins</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And this is how you assess the results:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">kong</span><span class="o">.</span><span class="n">rating</span>       <span class="c1"># =&gt; 1080</span>
</span></span><span class="line"><span class="cl"><span class="n">kong</span><span class="o">.</span><span class="n">pro?</span>         <span class="c1"># =&gt; false</span>
</span></span><span class="line"><span class="cl"><span class="n">kong</span><span class="o">.</span><span class="n">starter?</span>     <span class="c1"># =&gt; true</span>
</span></span><span class="line"><span class="cl"><span class="n">kong</span><span class="o">.</span><span class="n">games_played</span> <span class="c1"># =&gt; 8</span>
</span></span><span class="line"><span class="cl"><span class="n">kong</span><span class="o">.</span><span class="n">games</span>        <span class="c1"># =&gt; [ game1, game2, ... game8 ]</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The gem has more tuning besides that original algorithm, such as the K-factor to reward new players. Those kinds of tunings are what makes matches more competitive today and how you evolve it to TrueSkill levels, but it&rsquo;s beside the point of this article.</p>
<p>Let&rsquo;s see the wrong ranking again:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>   Name        Games  Wins  Losses Points (wins - losses)
 1 Kong          217   117    100     17
 2 Samus         211   110    101      9
 3 Wario         197   102     95      7
 4 Luigi         186    95     91      4
 5 Zelda         160    81     79      2
 6 Pikachu       209   105    104      1
 7 Yoshi         223   112    111      1
 8 Mario         203   101    102     -1
 9 Fox           208    95    113    -18
10 Bowser        186    82    104    -22</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now let&rsquo;s see the <strong>correct</strong> ranking by calculating the Elo score using the exact same 2,000 matches:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>   Name        Games  Wins  Losses Points  Elo Rating
 1 Pikachu       209   105    104      1         851
 2 Zelda         160    81     79      2         847
 3 Samus         211   110    101      9         842
 4 Luigi         186    95     91      4         841
 5 Wario         197   102     95      7         824
 6 Mario         203   101    102     -1         820
 7 Yoshi         223   112    111      1         803
 8 Kong          217   117    100     17         802
 9 Bowser        186    82    104    -22         785
10 Fox           208    95    113    -18         754</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>See how different it is? In the wrong ranking, Kong is considered the strongest, but in the Elo ranking he is just 8th place. And the reason is that even though he is the one that won most matches (217) he also lost a heck of a lot (117). Someone with fewer wins such as Zelda in 2nd place (160 wins) lost a heck of a lot less (81), which is why she is higher in the ranking.</p>
<p>Now, if we make her win 10 matches in a row against 3rd place Samus, this is the new ranking:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>   Name        Games  Wins  Loses  Points  Elo Rating
 1 Zelda         170    91     79     12         904
 2 Pikachu       209   105    104      1         851
 3 Luigi         186    95     91      4         841
 4 Wario         197   102     95      7         824
 5 Mario         203   101    102     -1         820
 6 Yoshi         223   112    111      1         803
 7 Kong          217   117    100     17         802
 8 Bowser        186    82    104    -22         785
 9 Samus         221   110    111     -1         775
10 Fox           208    95    113    -18         754</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Again, Zelda jumps up from 2nd to 1st place and Samus falls down from 3rd to 9th. So far so good. But what about the scenario where we make strong 2nd place Pikachu play against a much weaker 10th place Fox McCloud?</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>   Name        Games  Wins  Loses  Points  Elo Rating
 1 Zelda         170    91     79     12         904
 2 Luigi         186    95     91      4         841
 3 Fox           218   105    113     -8         829
 4 Wario         197   102     95      7         824
 5 Mario         203   101    102     -1         820
 6 Yoshi         223   112    111      1         803
 7 Kong          217   117    100     17         802
 8 Bowser        186    82    104    -22         785
 9 Samus         221   110    111     -1         775
10 Pikachu       219   105    114     -9         766</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now, this is fairness: Pikachu should have won, but losing 10 times in a row against someone considered much weaker makes him fall down from 2nd place all the way to the last place. And noobie Fox, having won 10 times against a much stronger opponent, deserves jumping up all the way to 3rd place.</p>
<p>This is the kind of dynamic that can make matches and games competitive, which is exactly why every online leaderboard and professional tournament uses those kinds of algorithms.</p>
<p>And it all began in chess, using math that has been known since the late 40&rsquo;s!!</p>
<blockquote>
  <p>This is the point of this post and my previous one: <strong>the math is not new</strong>.</p>

</blockquote>
<p>Developers waste a great amount of time in stupid pissing contests over which language or tool is &ldquo;shinier&rdquo;, but they ignore the proper math and deliver wrong results. But the math has been around for decades, in some cases more than a full century already!</p>
<p>We should strive to earn the title of Computer <em>Scientists</em>. There is a lot of <strong>science</strong> lacking from computing nowadays. Pissing contests do not make a programmer any good.</p>
]]></content:encoded><category>algorithms-and-data-structures</category><category>gaming</category></item><item><title>Off Topic: Why Programmers Should Play Go</title><link>https://akitaonrails.github.io/en/2008/07/15/off-topic-por-que-programadores-devem-jogar-go/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2008/07/15/off-topic-por-que-programadores-devem-jogar-go/</guid><pubDate>Tue, 15 Jul 2008 01:07:00 -0300</pubDate><description>&lt;p&gt;It&amp;rsquo;s been a while since I did a translation, but this article felt quite nostalgic to me, so I made a point of sharing it. It&amp;rsquo;s by &lt;a href="http://web.archive.org/web/20080720152831/http://railspikes.com/2008/7/14/why-programmers-should-play-go"target="_blank" rel="noopener"&gt;Jon Dahl&lt;/a&gt; from the &lt;strong&gt;RailSpikes&lt;/strong&gt; blog. Read my &lt;a href="#akitas-notes"&gt;notes&lt;/a&gt; at the end. Here it is:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://flickr.com/photos/andres-colmen/2539473895/"target="_blank" rel="noopener"&gt;&lt;img src="http://s3.amazonaws.com/akitaonrails/assets/2008/7/15/2539473895_47418f2049.jpg" alt="" loading="lazy" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://en.wikipedia.org/wiki/Go_%28board_game%29"target="_blank" rel="noopener"&gt;Go&lt;/a&gt; is an ancient strategy game with simple rules and a profound degree of complexity.&lt;/p&gt;
&lt;p&gt;Software development is the art of managing complexity using a limited number of rules, structures, and patterns.&lt;/p&gt;</description><content:encoded><![CDATA[<p>It&rsquo;s been a while since I did a translation, but this article felt quite nostalgic to me, so I made a point of sharing it. It&rsquo;s by <a href="http://web.archive.org/web/20080720152831/http://railspikes.com/2008/7/14/why-programmers-should-play-go"target="_blank" rel="noopener">Jon Dahl</a> from the <strong>RailSpikes</strong> blog. Read my <a href="#akitas-notes">notes</a> at the end. Here it is:</p>
<p><a href="http://flickr.com/photos/andres-colmen/2539473895/"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/7/15/2539473895_47418f2049.jpg" alt=""  loading="lazy" /></a></p>
<p><a href="http://en.wikipedia.org/wiki/Go_%28board_game%29"target="_blank" rel="noopener">Go</a> is an ancient strategy game with simple rules and a profound degree of complexity.</p>
<p>Software development is the art of managing complexity using a limited number of rules, structures, and patterns.</p>
<p>Programmers should play Go.</p>
<h3>Go in 2<sup>8</sup> words or less<span class="hx:absolute hx:-mt-20" id="go-in-28-words-or-less"></span>
    <a href="#go-in-28-words-or-less" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The beauty of Go is its combination of simplicity and complexity. On the one hand, go has only a handful of rules. Place stones, don&rsquo;t get completely surrounded, control territory. Like chess, the mechanics can be picked up in a few minutes, though Go only has a single type of &ldquo;move&rdquo;, and only one edge case (the <a href="http://en.wikipedia.org/wiki/Rules_of_Go#Ko_.28no_repetition_of_the_same_shape.29"target="_blank" rel="noopener">ko</a> rule). And like chess, one can spend a lifetime discovering the strategic and tactical layers of the game.</p>
<p>While chess is quite complex and rich, such that it took a 30-node supercomputer to defeat the reining chess champion, no computer comes close to defeating even a skilled amateur Go player. There are 361 positions on a Go board, and with two players, there are 2.08168199382×10<sup>170</sup> valid positions. That&rsquo;s quite a bit bigger than a <a href="http://en.wikipedia.org/wiki/Googol"target="_blank" rel="noopener">googol</a> (yes, that is the correct spelling). Realistically, there are something on the order of 10<sup>400</sup> possible ways that a typical game could play out. And the number of possible moves roughly follows 361!, which means that only 20 moves in, there are many googols of possible ways that the game could shake down. (As a fun exercise, try plugging 361! into an online <a href="http://web.archive.org/web/20240416232143/https://www.cs.uml.edu/~ytran/factorial.html"target="_blank" rel="noopener">factorial calculator</a>.)</p>
<h3>Managing complexity<span class="hx:absolute hx:-mt-20" id="managing-complexity"></span>
    <a href="#managing-complexity" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>So how does one play Go, given this near-infinite complexity? On a tactical level, a player approaches Go like chess, thinking several moves ahead. But this only works in small spaces, like a tight battle in a small sector of the board. Beyond there, there are just too many possibilities. So on a strategic level, a player must think in <a href="http://en.wikipedia.org/wiki/Shape_%28Go%29"target="_blank" rel="noopener">shapes</a> or patterns. These shapes provide shorthand ways of managing the complexity of Go. As a non-master, I may have no idea how things will proceed in one sector of the board, but I may be able to recognize strong and weak patterns of stones, vulnerable shapes and effective formations.</p>
<p>But there&rsquo;s more: Go has several sorts of patterns. Beyond shapes, there are <a href="http://senseis.xmp.net/?GoProverbs"target="_blank" rel="noopener">Go proverbs</a>. These can be general: <em>&ldquo;Your opponent&rsquo;s good move is your good move&rdquo;</em>; specific: <em>&ldquo;Don&rsquo;t try to cut the one-point jump&rdquo;</em>; funny: <em>&ldquo;Even a moron connects against a peep&rdquo;</em>; and meta: <em>&ldquo;Don&rsquo;t follow proverbs blindly.&rdquo;</em> These proverbs are principles which help a player make good decisions. They are less specific than shapes, and so they provide guidelines for whatever situations may arise on the Go board. Proverbs often conflict, and a player must determine when and how to apply them.</p>
<p>Finally, there are <a href="http://en.wikipedia.org/wiki/Joseki"target="_blank" rel="noopener">joseki</a>. Joseki are patterns of play that are considered even for both sides. They typically happen in the corners of the board, and typically at the beginning of the game. Interestingly, there is a Go proverb that says <em>&ldquo;Learning joeski costs two stones,&rdquo;</em> meaning that memorizing these patterns isn&rsquo;t helpful. Instead, a player should learn <em>from</em> joseki by understanding what is going on in each move.</p>
<h3>Patterns in Go, patterns in software design<span class="hx:absolute hx:-mt-20" id="patterns-in-go-patterns-in-software-design"></span>
    <a href="#patterns-in-go-patterns-in-software-design" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Each of these Go patterns has a rough programming analogue.</p>
<p>Shapes in Go aren&rsquo;t unlike software <a href="http://c2.com/cgi/wiki?DesignPatterns"target="_blank" rel="noopener">design patterns</a>. While there is nothing preventing you from placing logic in your views, this shape is <a href="http://www.vimeo.com/1050804"target="_blank" rel="noopener">recognized</a> to be a weak one. Think of Gang-of-Four design patterns: the MVC, Adapter, and Factory patterns are recognized to be helpful in some circumstances (and not appropriate in others). On a lower level, iteration and recursion have commonly recognized shapes, as do database normalization vs. denormalization. Even if you can&rsquo;t hold an entire program or algorithm in your head at once, recognizing common shapes helps you to understand what is going on.</p>
<p>Go proverbs are like another type of pattern in software: CapitalizedPrinciples (for lack of a better term) made popular by Extreme Programming. Think DontRepeatYourself, YouArentGonnaNeedIt, CollectiveCodeOwnership, DailyBuild, TestFirst. These aren&rsquo;t specific code &ldquo;shapes&rdquo;, like a singleton class – they are general principles that guide the practice of programming.</p>
<p>Because joseki is about exchange between competing parties, its programming parallel is a little less clear. The closest comparison, in my mind, is programming exercises. <a href="http://binstock.blogspot.com/2008/04/perfecting-oos-small-classes-and-short.html"target="_blank" rel="noopener">This article</a>, for instance, suggests 9 exercises to help you become a better OO programmer, like:</p>
<ul>
<li>Use only one dot per line</li>
<li>Use only one level of indentation per method</li>
<li>Don&rsquo;t use setters, getters, or properties</li>
</ul>
<p>In a real-world program, you&rsquo;re unlikely to stick to these principles 100% of the time. But forcing yourself to write code in this way can be an eye-opening experience and can make you a better developer.</p>
<h3>So what can Go really do for you?<span class="hx:absolute hx:-mt-20" id="so-what-can-go-really-do-for-you"></span>
    <a href="#so-what-can-go-really-do-for-you" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Obviously, these parallels are structural. Specific Go proverbs (&ldquo;Your opponent&rsquo;s good move is your good move&rdquo;) may not have direct relevance to software development. So can Go really make you a better developer?</p>
<p>I think it can, and I&rsquo;ll go one further. I think Go can make you smarter. There is a lot of anecdotal evidence to this effect <a href="http://web.archive.org/web/20170616121831/http://www.godiscussions.com/forum/archive/index.php/t-6061.html"target="_blank" rel="noopener"><sup class="footnote" id="fnr1"><a href="#fn1">1</a></sup></a>, <a href="http://news.ycombinator.com/item?id=133178"target="_blank" rel="noopener"><sup class="footnote" id="fnr2"><a href="#fn2">2</a></sup></a>, <a href="http://news.ycombinator.com/item?id=228356"target="_blank" rel="noopener"><sup class="footnote" id="fnr3"><a href="#fn3">3</a></sup></a>, for example, <a href="http://www.china.org.cn/english/features/Archaeology/131298.htm"target="_blank" rel="noopener"><sup class="footnote" id="fnr4"><a href="#fn4">4</a></sup></a>:</p>
<blockquote>
  <p>In fact, all of our minds can benefit from playing Go, which officially has the capacity to make you smarter. Research has shown that that children who play Go have the potential for greater intelligence, since it motivates both the right and left sides of the brain.</p>

</blockquote>
<p>The research mentioned isn&rsquo;t footnoted, unfortunately, so take statements like this with a grain of salt.</p>
<p>But it makes sense: like chess, Go requires pattern recognition, a mix of strategic and tactical thinking, and comprehension of complex structures, though in Go the patterns are larger and the complexity is greater. A mind trained to think in these ways is going to have an easier time attacking similar problems in other spheres.</p>
<p>Like software development.</p>
<h3>Akita&rsquo;s Notes<span class="hx:absolute hx:-mt-20" id="akitas-notes"></span>
    <a href="#akitas-notes" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I&rsquo;ve liked Go for a long time. As someone of Japanese descent, of course I&rsquo;d been exposed to Go, Shogi, and other oriental games. But, like any good westernized Japanese, I paid very little attention to them. Because of this, today I know no more than the basic rules and philosophies behind Go.</p>
<p>My interest was particularly renewed when I read the <a href="http://en.wikipedia.org/wiki/Hikaru_no_Go"target="_blank" rel="noopener">Hikaru no Go</a> series, which is an entire story built around Go, and one of my favorite series, by the way. I even bought a Go board and some joseki books, but didn&rsquo;t get very far. These days I just peek at online games on the <a href="https://pandanet-igs.com/"target="_blank" rel="noopener">IGS</a> servers.</p>
<p><a href="http://web.archive.org/web/20080725154909/http://www.onemanga.com/Hikaru_no_Go/"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/7/15/1hikaru800.jpg" alt=""  loading="lazy" /></a></p>
<p>I don&rsquo;t remember where I read this, but someone once mentioned something interesting: chess is primarily a game of destruction. Go is essentially a game of conquest and expansion. Not literally true, but there are nuances in Go that bring this to mind.</p>
<p>And in the parallel to software development, design patterns aren&rsquo;t just structures you copy and paste wherever you &ldquo;think&rdquo; they&rsquo;re needed. A handful of design patterns doesn&rsquo;t make good software.</p>
<p>What Jon was getting at makes a lot of sense. Just like a Go player, a software developer needs to be an artist. Playing is a creative activity. Strategy is a creative activity. Given a set of constraints, what&rsquo;s the best path forward?</p>
<p>More than that: the only way to learn Go is by playing, hundreds of thousands of times over many, many years. Go professionals are formed around age 10 and climb from there into old age. The only way to learn is by failing, failing, and failing again. Which circles back to what Ryan said about <a href="/2008/06/14/machucando-c-digo-por-divers-o-e-lucro/">Hurting Code</a>.</p>
<p>Refactoring is something similar to this: conquest and expansion. Only a good developer understands the real reasons for refactoring. Nobody refactors because someone told them to, just as an artist doesn&rsquo;t make a certain stroke because someone told them to.</p>
<p>A good developer values the form and philosophy of what is being built. Acronyms, brands, and fashionable names say nothing to them.</p>
<p>There&rsquo;s a lack of eastern philosophy in today&rsquo;s programmers. Being a brick-stacking coder is very easy: any grunt can do it. But reaching the <a href="http://en.wikipedia.org/wiki/Go_ranks_and_ratings"target="_blank" rel="noopener">10th dan</a> of programming is only for those who have put in the work in the art.</p>
]]></content:encoded><category>learning</category><category>programming</category><category>gaming</category><category>off-topic</category></item></channel></rss>