<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en" xml:base="https://viniciuscole.com/">
  <title>Vinicius Cole</title>
  <subtitle>Notes on things I build.</subtitle>
  <link href="https://viniciuscole.com/feed.xml" rel="self" type="application/atom+xml"/>
  <link href="https://viniciuscole.com/blog/" rel="alternate" type="text/html"/>
  <id>https://viniciuscole.com/feed.xml</id>
  <updated>2026-11-01T00:00:00Z</updated>
  <author><name>Vinicius Cole</name></author>
  <entry>
    <title>Linking is not running: a C++ OpenGL game in the browser</title>
    <link href="https://viniciuscole.com/2026/11/01/linking-is-not-running/" rel="alternate" type="text/html"/>
    <id>https://viniciuscole.com/2026/11/01/linking-is-not-running/</id>
    <published>2026-11-01T00:00:00Z</published>
    <updated>2026-11-01T00:00:00Z</updated>
    <category term="webassembly" label="WebAssembly"/>
    <category term="opengl" label="OpenGL"/>
    <category term="testing" label="Testing"/>
    <content type="html">&lt;p&gt;The &lt;a href=&quot;/projects/2d-graphics/&quot;&gt;side-scrolling game&lt;/a&gt; on this site was a
Computer Graphics assignment: C++, immediate-mode OpenGL, GLUT, an arena read
from an SVG file. Porting it was the opposite of the
&lt;a href=&quot;/2026/08/16/porting-dos-assembly-to-the-browser/&quot;&gt;tic-tac-toe port&lt;/a&gt;. There, a
1.4 MB emulator runs a 3 KB DOS program. Here there is no emulator. The
181,051 bytes of &lt;code class=&quot;highlighter-rouge&quot;&gt;jogo.wasm&lt;/code&gt; are my C++, compiled by Emscripten.&lt;/p&gt;

&lt;p&gt;Getting it to compile took one command:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;em++ -std=c++11 -O2 main.cpp arena.cpp tinyxml2.cpp character.cpp \
     hero.cpp enemy.cpp shot.cpp \
     -sLEGACY_GL_EMULATION=1 -lGL -lglut \
     --preload-file arena_teste.svg -o jogo.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It linked clean. I almost wrote down “immediate-mode OpenGL works”. Then I opened
it in a browser, and it died on the first frame:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Aborted(unsupported immediate mode 9)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Mode 9 is &lt;code class=&quot;highlighter-rouge&quot;&gt;GL_POLYGON&lt;/code&gt;, which the game uses to draw every character’s head.
Emscripten’s emulation of old-style OpenGL doesn’t implement it. Swapping in
&lt;code class=&quot;highlighter-rouge&quot;&gt;GL_TRIANGLE_FAN&lt;/code&gt; draws the same thing for a convex shape, so it’s a one-word
patch.&lt;/p&gt;

&lt;p&gt;The patch didn’t bother me. What bothered me was that the compiler, the linker
and a quick look at the output sizes had all said yes to a program that
couldn’t draw a single frame. Nothing short of actually running it would have
caught that.&lt;/p&gt;

&lt;p&gt;So I built a bench: a script that opens the game in a real Chromium (headless,
inside the Playwright Docker image), presses keys and clicks the mouse, and
checks what happens on screen. Most of what I learned from this port came from
that bench being wrong.&lt;/p&gt;

&lt;h2 id=&quot;you-cant-read-a-webgl-canvas&quot;&gt;You can’t read a WebGL canvas&lt;/h2&gt;

&lt;p&gt;The first thing the bench needs is “did anything draw?”. The obvious answer is
to read the canvas pixels. That returns solid black, every time, because WebGL
throws the drawing buffer away once the frame is on screen unless you ask it
not to.&lt;/p&gt;

&lt;p&gt;What works is going the long way round. Take a Playwright screenshot, load it
into an &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt;, draw that onto a 2D canvas and call &lt;code class=&quot;highlighter-rouge&quot;&gt;getImageData&lt;/code&gt;. Every
measurement in the bench goes through that path.&lt;/p&gt;

&lt;h2 id=&quot;a-test-that-passes-with-the-keyboard-unplugged&quot;&gt;A test that passes with the keyboard unplugged&lt;/h2&gt;

&lt;p&gt;The first version of “pressing D moves the player” compared two screenshots of
the whole canvas, one before the key and one after, and passed if they were
different.&lt;/p&gt;

&lt;p&gt;They are always different. The arena has seven enemies that walk and shoot on
their own from the first frame. The picture changes between any two moments,
with or without a keyboard. The test would have passed with input completely
broken, which is worse than not having it, since everything after it trusted it.&lt;/p&gt;

&lt;p&gt;So the bench measures the thing that should move. The hero is the only green
on screen, so the bench finds the horizontal centre of the green pixels and
asserts that D pushes it more than 20 px right and A pulls it back. It has to be
measured near the spawn point, too: the camera follows the hero, so after a few
steps the hero stays centred and the on-screen movement shrinks to almost
nothing.&lt;/p&gt;

&lt;p&gt;Jumping was a timing problem. A quick tap of W lifts the hero 21 px and lands
in under 90 ms. My first measurement ran 250 ms after releasing the key, saw the
hero on the ground and reported a failure that wasn’t one. Holding W for 900 ms
reaches 147 px. The bench now takes three samples right after release and keeps
the highest point.&lt;/p&gt;

&lt;h2 id=&quot;exit0-doesnt-exit&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;exit(0)&lt;/code&gt; doesn’t exit&lt;/h2&gt;

&lt;p&gt;The original game quits on ESC with &lt;code class=&quot;highlighter-rouge&quot;&gt;exit(0)&lt;/code&gt;. On a desktop that’s fine. On a
web page there is nowhere to quit to, so the port turns ESC into a no-op. The
bench checked that by pressing ESC and confirming the hero was still visible.&lt;/p&gt;

&lt;p&gt;That check was useless, and I only found out by breaking the guard on purpose
and rebuilding. With Emscripten’s &lt;code class=&quot;highlighter-rouge&quot;&gt;-sEXIT_RUNTIME=0&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;exit(0)&lt;/code&gt; doesn’t stop
anything. It throws an uncaught &lt;code class=&quot;highlighter-rouge&quot;&gt;ExitStatus&lt;/code&gt; exception inside the key handler,
the page logs an error, and the &lt;code class=&quot;highlighter-rouge&quot;&gt;requestAnimationFrame&lt;/code&gt; loop that was already
scheduled keeps going. The hero never left the screen, so my check was
testing the one thing the regression doesn’t break.&lt;/p&gt;

&lt;p&gt;The assertion that catches the regression counts page errors before and after
ESC. “The hero can still walk after ESC” stays in as a second line of defence,
in case the build ever switches to &lt;code class=&quot;highlighter-rouge&quot;&gt;EXIT_RUNTIME=1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This became the rule for the whole bench. Every assertion had to be shown to
fail: put the bug back, watch the check go red, then restore it. For the
&lt;code class=&quot;highlighter-rouge&quot;&gt;GL_POLYGON&lt;/code&gt; fix that meant editing the patch back to &lt;code class=&quot;highlighter-rouge&quot;&gt;glBegin(GL_POLYGON)&lt;/code&gt;,
rebuilding, and seeing the bench catch the abort. One check didn’t survive
the rule at all: “holding W jumps higher than tapping” compared against
&lt;code class=&quot;highlighter-rouge&quot;&gt;Math.min&lt;/code&gt; of a list that could be empty. &lt;code class=&quot;highlighter-rouge&quot;&gt;Math.min()&lt;/code&gt; of nothing is
&lt;code class=&quot;highlighter-rouge&quot;&gt;Infinity&lt;/code&gt;, so any height passed.&lt;/p&gt;

&lt;h2 id=&quot;what-the-game-did-to-the-page&quot;&gt;What the game did to the page&lt;/h2&gt;

&lt;p&gt;GLUT on Emscripten listens to &lt;code class=&quot;highlighter-rouge&quot;&gt;keydown&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;keyup&lt;/code&gt; on the whole window and
calls &lt;code class=&quot;highlighter-rouge&quot;&gt;preventDefault&lt;/code&gt; on Tab, Enter and Space. The game doesn’t use any of
them. Once someone clicked Play, keyboard users were stuck on the canvas. Tab
couldn’t reach the footer or the theme button, or even the “play again”
button on the game’s own end screen. A capturing listener on the window,
registered before the game module loads, now grabs those three keys before
GLUT sees them.&lt;/p&gt;

&lt;p&gt;That end screen was broken too. GLUT’s bitmap fonts don’t exist in
Emscripten, so “You won” and “You lost” are HTML now, shown over the canvas. The
C++ calls into the page on every frame while the game is over. When R restarts
the game it just stops calling, and nothing told the page to hide the message.
The overlay stayed on top of a running game and swallowed every click. Now
&lt;code class=&quot;highlighter-rouge&quot;&gt;restartGame()&lt;/code&gt; tells the page when play starts again.&lt;/p&gt;

&lt;p&gt;The last one only showed up on the real site. The bench served the three build
files from one flat directory. On the site, the page lives at &lt;code class=&quot;highlighter-rouge&quot;&gt;/projects/...&lt;/code&gt;
and the game at &lt;code class=&quot;highlighter-rouge&quot;&gt;/demos/...&lt;/code&gt;. Emscripten’s loader looks for &lt;code class=&quot;highlighter-rouge&quot;&gt;jogo.data&lt;/code&gt; (the
packed SVG arena) relative to the page, not to &lt;code class=&quot;highlighter-rouge&quot;&gt;jogo.js&lt;/code&gt;, so on the real site it
was a 404 every time. A &lt;code class=&quot;highlighter-rouge&quot;&gt;locateFile&lt;/code&gt; option in the loader fixed it, and the
bench gained a second mode that loads the real built page, because the flat
version can’t see this kind of bug by design.&lt;/p&gt;

&lt;h2 id=&quot;three-patches-and-a-lot-of-bench&quot;&gt;Three patches, and a lot of bench&lt;/h2&gt;

&lt;p&gt;The game itself barely changed: three small patches on a pinned commit of the
original repo, and the assignment’s own &lt;code class=&quot;highlighter-rouge&quot;&gt;make&lt;/code&gt; still builds the desktop version
with the original GLUT text.&lt;/p&gt;

&lt;p&gt;I went in expecting OpenGL to be the hard part. It was the bench.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Teaching a C program to show its work</title>
    <link href="https://viniciuscole.com/2026/11/01/teaching-c-to-show-its-work/" rel="alternate" type="text/html"/>
    <id>https://viniciuscole.com/2026/11/01/teaching-c-to-show-its-work/</id>
    <published>2026-11-01T00:00:00Z</published>
    <updated>2026-11-01T00:00:00Z</updated>
    <category term="c" label="C"/>
    <category term="webassembly" label="WebAssembly"/>
    <category term="algorithms" label="Algorithms"/>
    <content type="html">&lt;p&gt;The &lt;a href=&quot;/projects/car-routes/&quot;&gt;route project&lt;/a&gt; started as a 2023 assignment I did
with two classmates: read a map, find the fastest route, and replan when
traffic changes mid-trip. It’s plain C. You give it a file, and it writes back
three lines: the path, the kilometres and the time.&lt;/p&gt;

&lt;p&gt;That’s a fine answer for a grader and a useless one for a web page. I wanted
readers to watch Dijkstra open and close nodes, see the car leave, see a road
jam and the car change its mind. The C knew all of that while it ran. It just
never said any of it out loud.&lt;/p&gt;

&lt;p&gt;Rewriting the algorithm in JavaScript would have turned the C into a link to
the repo, and pre-recorded animations would have meant nobody could cause
their own traffic jam. So the C had to learn to narrate.&lt;/p&gt;

&lt;h2 id=&quot;hooks-that-cost-nothing-when-nobody-listens&quot;&gt;Hooks that cost nothing when nobody listens&lt;/h2&gt;

&lt;p&gt;The C got a struct of function pointers, one per event, all &lt;code class=&quot;highlighter-rouge&quot;&gt;NULL&lt;/code&gt; by default:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* ...update, replan, unreachable, done */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TraceHooks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;dijkstra&lt;/code&gt; and the replanning loop call each one only if it’s set. The
original command-line program never sets them, so for it the change is a
handful of &lt;code class=&quot;highlighter-rouge&quot;&gt;NULL&lt;/code&gt; checks.&lt;/p&gt;

&lt;p&gt;Before moving a single line, I saved the CLI’s output for a few test maps as
the expected result. Any change after that had to reproduce those files
exactly. On the first run of that baseline, one of them came out like this:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1;2;4;6
3.000000
00:02:60.000000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Two minutes and sixty seconds. The route is right; it’s exactly 180 seconds.
The time adds up to something like 179.99999 in floating point, the code
truncates it with &lt;code class=&quot;highlighter-rouge&quot;&gt;(int)&lt;/code&gt; to get the minutes, gets 2, and then prints the
leftover, which &lt;code class=&quot;highlighter-rouge&quot;&gt;printf&lt;/code&gt; rounds back up to 60. It had been printing that
since 2023. The fix rounds to milliseconds before splitting the time up. That baseline line is the only one the whole project was
allowed to change.&lt;/p&gt;

&lt;h2 id=&quot;one-line-per-event&quot;&gt;One line per event&lt;/h2&gt;

&lt;p&gt;With the hooks in place, a new function, &lt;code class=&quot;highlighter-rouge&quot;&gt;run_trace&lt;/code&gt;, prints one line for each
thing that happens. This is the real output for the map on the project page,
where road 2→4 drops to 5 km/h 30 seconds in:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pop 1 0.000
relax 1 2 60.000
relax 1 3 90.000
pop 2 60.000
relax 2 4 120.000
relax 2 5 210.000
...
plan 1 2 4 6 180.000
move 1 2 0.000 60.000
update 2 4 5.000 60.000
replan 2 60.000
pop 2 0.000
...
plan 2 5 6 180.000
move 2 5 60.000 210.000
move 5 6 210.000 240.000
done 240.000 4.000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Look at the times after &lt;code class=&quot;highlighter-rouge&quot;&gt;replan 2 60.000&lt;/code&gt;. The next &lt;code class=&quot;highlighter-rouge&quot;&gt;pop 2&lt;/code&gt; says &lt;code class=&quot;highlighter-rouge&quot;&gt;0.000&lt;/code&gt;, and
the new plan says the trip takes &lt;code class=&quot;highlighter-rouge&quot;&gt;180.000&lt;/code&gt;, but the car arrives at 240. Both
are right. When the car replans, Dijkstra starts again from node 2 with a
fresh clock, so everything it prints is relative to that moment. &lt;code class=&quot;highlighter-rouge&quot;&gt;move&lt;/code&gt;,
&lt;code class=&quot;highlighter-rouge&quot;&gt;update&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;done&lt;/code&gt; use the real clock. My own spec got this wrong: it had
the second plan at &lt;code class=&quot;highlighter-rouge&quot;&gt;240.000&lt;/code&gt;, and I only fixed it after the real C
printed 180. The JavaScript keeps the clock of the last &lt;code class=&quot;highlighter-rouge&quot;&gt;replan&lt;/code&gt; and adds it to every
Dijkstra event after it.&lt;/p&gt;

&lt;p&gt;The trace also shows things the code never mentions. Node 5 is relaxed twice,
at 210 and then at 156, because our heap has no decrease-key. It just inserts
the node again and leaves the stale copy in the queue. And &lt;code class=&quot;highlighter-rouge&quot;&gt;pop 4 720.000&lt;/code&gt;
shows up after the destination is already closed, because the loop doesn’t
stop early. Both behaviours were in the code all along. I only saw them once
they were printed one per line.&lt;/p&gt;

&lt;h2 id=&quot;reading-a-file-that-isnt-a-file&quot;&gt;Reading a file that isn’t a file&lt;/h2&gt;

&lt;p&gt;The original program reads its map with &lt;code class=&quot;highlighter-rouge&quot;&gt;fscanf&lt;/code&gt; from a &lt;code class=&quot;highlighter-rouge&quot;&gt;FILE *&lt;/code&gt;. A web page
has no file to hand it, and I didn’t want a second parser just for the
browser. &lt;code class=&quot;highlighter-rouge&quot;&gt;fmemopen&lt;/code&gt; solved that: it opens a string as a &lt;code class=&quot;highlighter-rouge&quot;&gt;FILE *&lt;/code&gt;, so
&lt;code class=&quot;highlighter-rouge&quot;&gt;run_trace&lt;/code&gt; takes the map as text and feeds it to the same three reader
functions the CLI uses. The only cost was compiling with &lt;code class=&quot;highlighter-rouge&quot;&gt;-std=gnu99&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For the browser, Emscripten compiles every file except &lt;code class=&quot;highlighter-rouge&quot;&gt;main.c&lt;/code&gt;. Without a
&lt;code class=&quot;highlighter-rouge&quot;&gt;main&lt;/code&gt;, the module loads and waits. The page calls &lt;code class=&quot;highlighter-rouge&quot;&gt;run_trace&lt;/code&gt; with the map as
a string, collects whatever it prints through the module’s &lt;code class=&quot;highlighter-rouge&quot;&gt;print&lt;/code&gt; callback,
and gets back the whole run as text. It’s 49,687 bytes of WebAssembly, loaded
once per page, and a run on these maps takes a few milliseconds. When you add
a jam with the traffic editor, the page builds a new input and calls the C
again.&lt;/p&gt;

&lt;h2 id=&quot;input-from-strangers&quot;&gt;Input from strangers&lt;/h2&gt;

&lt;p&gt;Before the traffic editor, the only input this program had ever read was
files the three of us wrote. Now it reads whatever a reader clicks together. Three things broke right away:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;read_updates&lt;/code&gt; read each line into &lt;code class=&quot;highlighter-rouge&quot;&gt;char aux[50]&lt;/code&gt;, so a long line wrote past
the end of the buffer.&lt;/li&gt;
  &lt;li&gt;An update for a road that doesn’t exist walked the adjacency list off its
end and crashed.&lt;/li&gt;
  &lt;li&gt;An unreachable destination crashed too, instead of saying so.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There was a fourth one, quieter. &lt;code class=&quot;highlighter-rouge&quot;&gt;dijkstra&lt;/code&gt; sizes its arrays from the node
count in the file’s first line, so a header claiming a billion nodes would
have blown the stack. &lt;code class=&quot;highlighter-rouge&quot;&gt;run_trace&lt;/code&gt; now refuses anything above a million.&lt;/p&gt;

&lt;p&gt;None of these were bugs for the assignment: we never fed it bad files. Each
one has a test now, and the CLI’s output for the original maps still
matches the baseline byte for byte, apart from that one line.&lt;/p&gt;

&lt;h2 id=&quot;playing-it-back&quot;&gt;Playing it back&lt;/h2&gt;

&lt;p&gt;On the JavaScript side, the trace goes through a reducer that turns each event
into a full snapshot: which nodes are open or closed, the current plan, where
the car is. All of them are computed up front, so play, pause, step and speed
are just a position in an array. The canvas reads its colours from the site’s
CSS variables on every frame, which is why the graph follows the light/dark
toggle mid-animation without any extra code.&lt;/p&gt;

&lt;p&gt;The C itself still prints three minutes as &lt;code class=&quot;highlighter-rouge&quot;&gt;00:03:0.000000&lt;/code&gt;, with the
seconds not zero-padded. The README documents it now, and I’m leaving it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Porting DOS assembly to the browser</title>
    <link href="https://viniciuscole.com/2026/08/16/porting-dos-assembly-to-the-browser/" rel="alternate" type="text/html"/>
    <id>https://viniciuscole.com/2026/08/16/porting-dos-assembly-to-the-browser/</id>
    <published>2026-08-16T00:00:00Z</published>
    <updated>2026-08-16T00:00:00Z</updated>
    <category term="assembly" label="Assembly"/>
    <category term="emulation" label="Emulation"/>
    <category term="webassembly" label="WebAssembly"/>
    <content type="html">&lt;p&gt;My tic-tac-toe game is written in 16-bit x86 real mode assembly. There is no
compiler that turns that into WebAssembly. The instruction set, the segmented
memory model and the DOS interrupts have no equivalent target.&lt;/p&gt;

&lt;p&gt;So the plan is emulation: assemble the original source with NASM, link it into a
DOS executable, and run that executable inside DOSBox compiled to WebAssembly.
The WebAssembly here is the emulator, not my code, and I think that honesty
matters more than the label.&lt;/p&gt;

&lt;p&gt;The build already works: &lt;code class=&quot;highlighter-rouge&quot;&gt;nasm -f obj&lt;/code&gt; assembles both source files without a
single change, and Open Watcom’s linker produces a 3,244 byte DOS executable.&lt;/p&gt;

&lt;h2 id=&quot;what-the-game-actually-does&quot;&gt;What the game actually does&lt;/h2&gt;

&lt;p&gt;It is 1,740 lines across two files. &lt;code class=&quot;highlighter-rouge&quot;&gt;vca.asm&lt;/code&gt; holds the game: the command
parser, the board, win detection. &lt;code class=&quot;highlighter-rouge&quot;&gt;draw.asm&lt;/code&gt; holds the graphics primitives.&lt;/p&gt;

&lt;p&gt;The entire game state is nine bytes:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;tabuleiro    db    &#39;         &#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nine ASCII spaces. A move overwrites one of them with &lt;code class=&quot;highlighter-rouge&quot;&gt;X&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;C&lt;/code&gt;, and a new game
is nine spaces again. There is no struct, no bitboard, no packing. The board is
literally the string you would draw on paper.&lt;/p&gt;

&lt;p&gt;Win detection is fully unrolled. Eight winning lines, two players, sixteen
blocks of explicit &lt;code class=&quot;highlighter-rouge&quot;&gt;cmp byte [tabuleiro + n], al&lt;/code&gt;, one after another, with no
loop and no table of line indices. That single decision accounts for a good
share of the 1,187 lines in &lt;code class=&quot;highlighter-rouge&quot;&gt;vca.asm&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Everything on screen arrives by one of two routes, and the two have nothing in
common. The position labels are BIOS text: &lt;code class=&quot;highlighter-rouge&quot;&gt;int 10h&lt;/code&gt; with &lt;code class=&quot;highlighter-rouge&quot;&gt;AH=02h&lt;/code&gt; to place the
cursor and &lt;code class=&quot;highlighter-rouge&quot;&gt;AH=09h&lt;/code&gt; to write a character in the ROM font, straight onto a
graphics-mode screen. The board, the crosses and the circles are pixels, plotted
one at a time by Bresenham routines the game implements itself.&lt;/p&gt;

&lt;h2 id=&quot;one-interrupt-per-pixel&quot;&gt;One interrupt per pixel&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;draw.asm&lt;/code&gt; is 553 lines and contains exactly three &lt;code class=&quot;highlighter-rouge&quot;&gt;int 10h&lt;/code&gt; calls. The third one
is the whole graphics layer:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;plot_xy:
        ; ... prologue: saves bp and every register it touches
        mov     ah,0ch
        mov     al,[cor]
        mov     bh,0
        mov     dx,479
        sub     dx,[bp+4]
        mov     cx,[bp+6]
        int     10h
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;AH=0Ch&lt;/code&gt; is the BIOS write-pixel service. Every line, every circle, every X on
the board goes through it, one pixel per interrupt. Nothing is ever written
directly to video memory at &lt;code class=&quot;highlighter-rouge&quot;&gt;A000h&lt;/code&gt;, and none of the VGA planar tricks that made
mode 12h fast are anywhere in the source.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;mov dx,479 / sub dx,[bp+4]&lt;/code&gt; is the other detail I like. VGA counts rows from
the top; the game thinks bottom-left, like graph paper. Rather than convert at
every call site, it flips the axis once, inside the only routine that touches a
pixel.&lt;/p&gt;

&lt;p&gt;The cost is easy to measure. The empty grid is two horizontal lines 313 pixels
long and two vertical lines 297 pixels long: about 1,220 BIOS calls before a
single move is played. A circle of radius 40 is another 250 or so. In the
browser every one of those traps into the emulator instead of into real ROM.&lt;/p&gt;

&lt;p&gt;It does not matter (a tic-tac-toe board is a few thousand pixels, and the thing
draws instantly), but it does clarify what emulation is buying here. Not speed.
Fidelity of a BIOS that has not existed in hardware for decades.&lt;/p&gt;

&lt;h2 id=&quot;the-shape-of-the-bundle&quot;&gt;The shape of the bundle&lt;/h2&gt;

&lt;p&gt;js-dos takes a &lt;code class=&quot;highlighter-rouge&quot;&gt;.jsdos&lt;/code&gt; file, which turns out to be a plain zip. Mine holds two
files: &lt;code class=&quot;highlighter-rouge&quot;&gt;VCA.EXE&lt;/code&gt;, and a 77-byte &lt;code class=&quot;highlighter-rouge&quot;&gt;.jsdos/dosbox.conf&lt;/code&gt; telling DOSBox to boot the
machine as &lt;code class=&quot;highlighter-rouge&quot;&gt;vgaonly&lt;/code&gt;, mount the bundle as &lt;code class=&quot;highlighter-rouge&quot;&gt;C:&lt;/code&gt; and run the executable. That is
the entire packaging step.&lt;/p&gt;

&lt;p&gt;The whole bundle is 2,236 bytes. The emulator that runs it is 1.4 MB of
&lt;code class=&quot;highlighter-rouge&quot;&gt;wdosbox.wasm&lt;/code&gt; plus another 315 KB of loader. The WebAssembly alone is 450 times
the size of the 3,244-byte program it exists to run. That asymmetry is why the
demo loads on a click and not on page load: someone who came to read about the
project should not pay for a DOS box they never asked to boot.&lt;/p&gt;

&lt;h2 id=&quot;two-things-the-browser-half-cost-me&quot;&gt;Two things the browser half cost me&lt;/h2&gt;

&lt;p&gt;js-dos defaults &lt;code class=&quot;highlighter-rouge&quot;&gt;pathPrefix&lt;/code&gt; to its own CDN. Leave it alone and the page quietly
downloads 1.4 MB of WebAssembly from someone else’s server, which is exactly the
thing this site is built not to do. The failure is silent: everything works, and
the page just is not what I said it was. My existing check for third-party
requests read &lt;code class=&quot;highlighter-rouge&quot;&gt;src&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;href&lt;/code&gt; attributes out of the built HTML, so it was blind
by construction: this request is born at runtime, inside JavaScript. The new
check scans the published JavaScript for off-origin URLs instead.&lt;/p&gt;

&lt;p&gt;The second one was louder. js-dos ships a stylesheet, and that stylesheet opens
with a full Tailwind reset: &lt;code class=&quot;highlighter-rouge&quot;&gt;h1..h6{font-size:inherit}&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;a{color:inherit;
text-decoration:inherit}&lt;/code&gt;. It loads after mine, at the same specificity. Clicking
“Play” flattened every heading and drained the colour out of every link on the
page, all at once. The fix was to stop serving those 118 KB entirely and
hand-write the handful of rules the emulator’s own DOM actually needs, scoped
under &lt;code class=&quot;highlighter-rouge&quot;&gt;.demo-screen&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-readme-was-wrong&quot;&gt;The readme was wrong&lt;/h2&gt;

&lt;p&gt;The game’s readme says to type &lt;code class=&quot;highlighter-rouge&quot;&gt;OLC&lt;/code&gt; to play a circle. The code compares against
&lt;code class=&quot;highlighter-rouge&quot;&gt;&#39;C&#39;&lt;/code&gt;, at &lt;code class=&quot;highlighter-rouge&quot;&gt;vca.asm:80&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;C22&lt;/code&gt; is what worked when I first ran it under
DOSBox. The messages never moved: the game still prints &lt;code class=&quot;highlighter-rouge&quot;&gt;O Venceu&lt;/code&gt; and
&lt;code class=&quot;highlighter-rouge&quot;&gt;Vez do O&lt;/code&gt;. At some point the display kept &lt;code class=&quot;highlighter-rouge&quot;&gt;O&lt;/code&gt; and the parser took &lt;code class=&quot;highlighter-rouge&quot;&gt;C&lt;/code&gt;, and the
readme followed the half that was easier to see.&lt;/p&gt;

&lt;p&gt;So the demo documents &lt;code class=&quot;highlighter-rouge&quot;&gt;C&lt;/code&gt;, and there is a test that fails if anyone helpfully
“corrects” it back to &lt;code class=&quot;highlighter-rouge&quot;&gt;O&lt;/code&gt; by reading the readme. It is a small thing, but it is
the kind of thing that only surfaces when you actually run the program instead of
describing it. Seven years later, that is most of what this port was for.&lt;/p&gt;
</content>
  </entry>
</feed>
