dreamports is my home for enhanced fan ports of classic games: the port fans always dreamed of, the game you remember with the extras you wished it had. what holds it together is a set of tools that prove each port. they play the same recorded button presses in the original game and in the port, then compare every frame, picture and sound. those tools run after almost every change, so whatever they waste, they waste hundreds of times. i wanted them to ask less of a computer, to work for consoles beyond the game boy advance, and to give exactly the same answers. so i measured everything first, changed the tools, and measured again.

measured on one computer, mine, on 4 november 2026 · 22 jobs · 109,358 frames · 265 results compared, 0 different

waiting for a screen nobody sees

the biggest waste was time spent doing nothing. the tools played every game at 60 frames a second, in a browser with no screen.

the original runs in mgba, an emulator, and the port runs in chrome with no window. a browser hands a page its frames at the pace of the display, and a browser with no display still keeps to 60 a second. so checking a game took exactly as long as playing it: 109,358 frames across 28 recordings, 1,826 seconds, 60 frames a second. nearly all of that time the computer was waiting.

now the tool asks for one frame, compares it, and asks for the next. the same 28 recordings take 105 seconds, about 1,000 frames a second. a port's clock counts frames, not seconds, so the game can't tell the difference, and a --paced switch keeps the old pace for anyone who wants to check that.

how long each job takes, in minutes and seconds
jobbeforeafter
sonic advance, all 21 recordings32:043:16
its longest recording, 11,500 frames3:350:17.2
sonic battle, all 7 recordings2:210:14.0
one title screen, 780 frames0:230:02.6
the median of three runs, two for the longest recording, and one for all of sonic advance, on a scale of powers of ten. one level of sonic advance goes from 1:08 to 0:07.3. every job is about nine to twelve times faster.

a browser per run, not per recording

memory falls by a third to a half. the tool stopped starting a new browser for each recording, and told the one it keeps to care about size.

each recording used to get a new browser with a new profile. now one browser stays open for the whole run, and each recording gets a clean context inside it, with nothing of the last one left. one setting did more than i expected: --optimize-for-size for chrome's javascript engine takes more than a third off the page's memory, and a frame takes no longer. the sound being compared is now kept as a rolling window, not as the whole recording.

memory at its peak, in megabytes
jobbeforeafter
building the emulator1,487363
sonic advance, all1,193796
sonic battle, all770416
one level750466
the longest recording697481
one title screen657368
all the processes of a job together, with what they share split between them.1 for most of a whole sonic advance it holds 410 to 570 MB, and its peak is one moment, with 546 MB in the page and 236 MB in the tool.

building the emulator changed the most, and for a plain reason. it ran a compiler for every thread, 175 processes at the peak on my computer, and each takes a few hundred megabytes. eight at a time, fewer if memory is short, finishes in the same five seconds. that matters most on a small computer, where the old way could ask for gigabytes it would never get.

the disk tells the same story. every new browser wrote a new profile, about 100 MB each, and a run with 21 recordings wrote 21 of them.

written to disk, in megabytes
jobbeforeafter
sonic advance, all4,241927
sonic battle, all65839
the longest recording29397
one level15030
one title screen1094
what's left is mostly the results themselves: the pictures and clips of frames that differ.

the graphics chip was never the problem

i set out to lower its use, and found the proof tools spent zero milliseconds on it. the windows do use it, and there a lower setting turned out worse.

the operating system counts each program's time on the chip, and for every proof it read 0 milliseconds, before and after. the tools draw pictures on the processor and hash them, and a browser that nobody sees is now told plainly not to use the chip.

the windows a person plays in are different, so i tried drawing those on the processor too. it saved almost nothing and made them worse, so they keep the chip.

the same window, drawn with the graphics chip and without it
measurechipprocessor
frames a second59.6758.4
late, in 15 seconds524
slowest 1 in 10016.8 ms33.3 ms
card's memory118 MB2 to 4 MB
card's power19.2 W19.0 W
the card draws 18 to 20 watts just sitting idle, so this work barely shows on it.

what did help is rest. the window that picks a game's capture kept drawing at full speed when nobody was touching it, for as long as it stayed open. now it stops after 30 seconds without a press.

the windows, before and after
measurebeforeafter
at rest, processor29.0%1.5%
at rest, chip20.3 ms3.7 ms
playing, processor5.48 s4.11 s
playing, chip473 ms524 ms
playing, memory293 MB284 MB
playing, disk47.9 MB1.0 MB
at rest is the capture window left alone: its share of one core, and its time on the chip each second. playing is 35 seconds of a game, which stays at 60 frames a second with none late. its time on the chip didn't fall, as that's the work of drawing the game itself.

electricity, as an estimate

this computer won't tell me what its processor draws, so this part is arithmetic. the processor's share of a run halves. the rest depends on what the computer would be doing otherwise.

what i can measure is processor time, and it fell by half or more on every proof. to turn that into energy i took the chip's base power, 125 W, and split it across its 28 threads, about 4.46 W a busy thread.2 trust the ratios more than the watt-hours.

processor time, in seconds
jobbeforeafter
sonic advance, all490239
the longest recording54.320.2
sonic battle, all45.517.4
one level18.88.7
one title screen9.83.0
at the estimate above, a whole sonic advance goes from 0.61 Wh of processor to 0.30 Wh.

those are small numbers. the larger part is the rest of the computer staying on. my graphics card alone draws about 19 watts doing nothing, which over 32 minutes is about 10 Wh and over 3 minutes 16 about 1 Wh. that only counts if the computer would otherwise be off or asleep, so i don't add it to the total. the capture window at rest goes from about 1.29 W of processor to 0.07 W, for as long as someone leaves it open.

tokens are steps times context

agents make ports here too. cutting what they read first by half didn't lower what they read in all, because they then took more small steps to find it.

a coding agent reads its whole conversation again at every step, so a whole task's cost is its steps multiplied by everything it holds. the project's instructions and its one command were long, and every agent read all of it at every step. i cut AGENTS.md from 33.9 KB to 5.8 KB and the command from 18.3 KB to 14.8 KB, moved the rest to where it can be looked up, and added a help tool that prints one section of a long document. the agent's first request fell from 13,742 tokens to 6,797.

then i ran whole tasks to see what that bought: opencode with a free model, sealed in a sandbox, doing the same small job on a made-up game, one with the old tools and one with the new, side by side.

a whole task, old tools against new
pair and toolsstepsper stepin all
first pair, old4848,0032,304,140
first pair, new5634,1331,911,472
second pair, old4253,5552,249,305
second pair, new5742,4122,417,505
tokens read, as the model's provider counted them, almost all of them cached rereading. each pair is one task, and one task varies a lot. all four finished with a pull request.

each step costs about a quarter less, and there are 17 to 36% more of them. one total fell by 17% and the other rose by 7%, which is to say about even. reading the tool calls one by one showed why: a long read had been answering questions the agent now went looking for, such as what a recording file looks like, which buttons exist and when a foundation counts as done. so those answers moved into the command, at the point where it needs them.

i changed that wording after these runs, and when i tried to run it the free model refused every request with a usage limit. so what that last change saves is not measured yet.

the tools also say less, and whatever a tool prints stays in the conversation for every later step. a check that passes now prints almost nothing.

what each job prints, in bytes
jobbeforeafter
the site's checks25,205658
sonic advance, all22,77115,011
sonic battle, all6,4632,585
one title screen1,166294
sonic advance's port isn't finished, so its output is mostly the list of what differs, which is the part worth keeping.

a faster round of work

most rounds only need to know the first thing that's wrong, and a finished step needs one command.

prove --quick stops at the first frame that differs and writes almost nothing: one title screen in 2.1 seconds, 4 MB on disk and 60 bytes printed, where a check used to take 23 seconds, 109 MB and 1,166 bytes. it's the cheapest way to see whether a change did what it should.

ready runs everything a step has to pass before it goes out, the port map, every recording, the site's checks and its build, and prints only what's wrong. on sonic battle it takes 23 seconds. run by hand with the old tools, those same jobs added up to about two and a half minutes.

any console, proved so far on one that never existed

the tools no longer assume a game boy advance. no real console beyond it has run yet, and i'd rather say so plainly.

one file now lists each console: its screen, how long a frame lasts, its sound rate, the kinds of file a copy comes in and its buttons. a game names its console, and everything follows from that. every other console's original runs in a libretro core, a small emulator with one way of being driven, through a little program of the tools' own that gives them the same pictures, sound and memory that mgba does.

to test that with nothing borrowed, i wrote an emulator for a console that doesn't exist, with its own screen, frame time and sound rate. on every pull request, a test makes a game for it and takes it through all the tools: recognising a copy, mapping its code, proving it frame by frame, catching a wrong colour at the frame where it first shows, catching a wrong sound under a right picture, and keeping a capture.

how far each console has got
consolewhere it stands
game boy advanceused for real games, in mgba
a made-up consolea whole game on every pull request, in an emulator written for the test
nes, super nintendo, game boy, game boy color, mega drive, playstation, nintendo 64an emulator chosen for each and fixed at a commit, not run yet
playstation 2, playstation portablenone yet, as their emulators need a graphics context the host doesn't offer
the third row's emulators are fceumm, snes9x, gambatte, genesis plus gx, pcsx rearmed and parallel n64. their numbers are still to be checked against them, and the tools say which is wrong when one is. a game on a disc too large for a browser to hold would also need its own way of being read.

some things stay with the game boy advance for now. mgba can say which of the original's functions ran in each frame, and a libretro core can't, so on other consoles code ported counts only what the port names. the tool that reads a function's instructions knows only that console's.

what nearly fooled me

three times the numbers looked fine and something was wrong. each was found by checking another way.

the frames that weren't the same frames

for runs that needed no function tracking i swapped my step-by-step loop for mgba's own, which is faster. the reports matched. but mgba's loop ends a frame one instruction sooner, and that small shift changes what the game does: from frame 46, 428 of the 780 frames in a title screen came out differently, and the pictures and console state the tool shows when asked came from those. only comparing every result, not just the reports, found it. both runs now use the same loop.

the flag that saved 18 megabytes

running a browser's graphics in its main process, not a process of their own, saves one process and about 18 MB. it also means that when the graphics can't start, the whole browser dies, in under a second. my computer has a working display and a working chip, so every test here passed. the first sealed agent task, in a sandbox with a display variable and nothing behind it, got a browser that didn't start. i worked around that by hiding the display from the browser. then the pull request's checks failed on github's machines in the same way, for a different reason, and i could reproduce it here by taking away a few things a desktop provides. that settled it. 18 MB isn't worth a tool that dies on someone else's computer, so the flag is gone, and both failures with it.

the browser that kept running

a title screen's check was taking ten seconds when the work was done in one and a half. every close was waiting out a limit, because a browser from a measuring run i had stopped was still alive, with the same profile. it had been running for 46 minutes at 160% of a core. the old tools left one behind in the same way: stop a proof partway and its browser just stays. by the same estimate as above, that one browser spent about 5.6 Wh, as much processor energy as 18 whole checks of sonic advance. it would have gone on until someone noticed.

a stopped tool now takes its browser with it, and if it was killed too hard to do that, the next run ends what it left. those ten seconds were also in my first set of results, so i threw them away and measured everything again. the numbers in this article are from that second set.

what stayed the same

all of it, which is the only reason the rest is worth anything.

after a whole run of both games, the old tools and the new ones produced 265 results: the reports with their timing left out, every picture and every clip, and each game's progress. 0 of them differ, byte for byte, and the same holds for each smaller job on its own. sonic advance's run still ends with differences in both versions, since the port isn't finished, and they're the same differences at the same frames. the tools ask less and say the same.

how i measured

one computer, each job alone in its own control group, while my other work went on around it.

each job ran in a systemd scope, so its processor time, its memory and what it wrote could be counted for it alone, with the kernel's own counters. time on the graphics chip came from the kernel's drm counters, and the card's power from its sensor. each window was measured in headless chrome on the real graphics chip, since a window that's covered on my screen stops drawing.

other work was running on the computer throughout, a load of about 4 to 7 of its 28 threads, so times are noisier than processor time. memory moves by about 30 MB between runs of the same job, partly because the count splits what's shared with whatever else is running. the site's own build, which i didn't change, went from 5.6 to 8.2 seconds, which gives an idea how far to trust a small difference. the short tools, the ones that finish in under a second and hold under 80 MB, didn't change at all, and i left them alone. the electricity is the one part that isn't measured directly, which is why its section says so.

what's left

real consoles, and a measure of the last wording.

the next step is the first real console: building its emulator, playing a real game's recording, and finding what the made-up one couldn't show. smaller things remain too. chrome still starts a helper for on-device models about three minutes into a long run, which holds 30 to 40 MB, and the flags i tried don't stop it.

the code is at github.com/NahuelGenchi/dreamports, this work is its pull request 530, and docs/CONSOLES.md there says how to add a console.

notes

  1. proportional set size, from the kernel's smaps_rollup, added up across every process of the job at each sample. what several processes share is split between them, so it isn't counted twice.
  2. the chip's energy counter can only be read by root on this system, and nothing here ran as root. 125 W is intel's base power for this chip. a real thread doesn't draw an even share of it, and efficient cores draw less than fast ones, so this is only good for comparing a job with itself.