Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
Porthole is a home project: firmware with ten small games for the Waveshare ESP32-S3 round touchscreen, how to put it on a board, and a 3D-printed case.
GamesC++ · PythonGitHub @dreamiurgOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Little games for a round ESP32-S3 touchscreen (Waveshare ESP32-S3-Touch-LCD-2.1), with a 3D-printed case and a browser emulator. Install from porthole.dreamiurg.net
Taken from the repository README
Loading the full README.
Read the full README ↗Small games for one round touchscreen, and the tools to build, test, release and flash them.
The screen is the Waveshare ESP32-S3-Touch-LCD-2.1. It is a 2.1-inch round 480x480 touchscreen with an
ESP32-S3 behind it. In a 3D-printed case, it is a pocket toy with no buttons. Each directory under
apps/ is one complete project for this screen. The shared parts live here once: board
config, build and flash commands, CI tests, release images and screenshots.
Fork it, play the games, and make your own.
All ten games run in one firmware, Porthole. Each kid gets a profile, up to four per board.
| Pets Club | Biscuit | Marble Kick | Tilt FC | Pumpkin Hide & Seek |
| Radar | Starfighter | Kingdom | Toy Town | Rally |
No game punishes a kid who is away. Nothing dies, and no progress is lost.
Put the games on a board from Chrome, with no tools to install.
To try them on a computer, run make -C apps/porthole webemu. More about Porthole.
It started with a MakerWorld project, ESP32 Plane Radar by MatixYo. A small ESP32 board with a 1.28-inch round screen reads live flight data and shows the planes overhead on a radar. I built one with my kids. Then I showed them how to change it with Claude Code. They asked for changes and watched them appear on the screen. I loved that.
Next we bought a bigger round screen, the Waveshare board this repo uses. In one sitting with Claude and Codex, we had a few games running on it. My kids loved them, so I shared them with friends, and their kids loved them too. This repo is the clean home for those games. Anyone can add the next one.
If you like to tinker and have $35-45 for a board, have fun with it.
Every app here runs on the Waveshare ESP32-S3-Touch-LCD-2.1 ($35-45):
Get the exact 2.1-inch flat model, ESP32-S3-Touch-LCD-2.1. Waveshare sells similar round boards in
1.28, 1.85 and 2.8 inch. Those do not run these apps. A curved-glass version (ESP32-S3-Touch-LCD-2.1B)
also exists. We did not try it.
Why this board works well for toys:
docs/hardware.md has the full specs.
The apps support one board only. I do not plan to port them. To use another board, fork the repo and ask
Claude Code or Codex to adapt the code, starting from docs/hardware.md. Most
board-specific code is in apps/porthole/firmware/board.cpp and platform/waveshare-round.ini. A screen
of a different size or shape also needs new layouts, because the games are drawn for a 480x480 circle.
If you make another board work, send a pull request. If you are stuck, open an issue.
You need Python 3.11+, Node 22, a C++17 compiler, and PlatformIO for firmware. Host builds also need zlib
(zlib1g-dev on Debian/Ubuntu, included in the macOS SDK).
git clone https://github.com/dreamiurg/porthole.git
cd porthole
pip install -r platform/requirements.txt # PlatformIO Core and pinned builder deps
pip install pillow fonttools==4.61.1 'gcovr>=8.3' # art and font checks, coverage
make hooks # the CI checks, run on commit and push
make check # lint and tests for every app
Flash a board over USB-C. PlatformIO finds the port when you connect one board.
make flash APP=porthole
make flash APP=porthole PORT=/dev/ttyUSB0
make monitor
This way needs no tools:
<app>-<version>-factory.bin from the releases.0x0.This erases the board, saved progress included. To keep saves, write the separate parts from
<app>-<version>-web.zip at the offsets in its parts.json. The web installer does this. You can also
build from source and run make flash. With esptool, from the unzipped parts:
esptool --chip esp32s3 write-flash 0x0 bootloader.bin 0x8000 partitions.bin 0xe000 boot_app0.bin 0x10000 firmware.bin
esptool 4 spells the command write_flash. Do not give --flash-mode or --flash-freq. They change the
bootloader header, and a mode the board cannot run (qio) puts it in a watchdog reset loop. To recover,
write the parts again without those options. A step-by-step guide with pictures is at
porthole.dreamiurg.net.
You need the USB cable only once. After that, the board updates itself over Wi-Fi.
Once a day, when nobody plays and the screen is dark, the board looks for a new version and downloads it. The screen dims by itself after a few minutes. If someone wakes the screen, the download stops. It continues the next time the screen is dark. The new version starts the next time someone switches the board on, and the kids see a "Porthole has something new!" card. Saves and profiles stay. The board sends nothing about the kids.
To give the board your Wi-Fi, use one of these:
/porthole/wifi.txt. Put the network name on line 1 and the password on
line 2. Put the card in the board and switch it on.W, the network name, a
tab, and the password, for example WHomeWiFi, a tab, then your password.Grown-ups have a hidden menu. On "Who's playing?", hold the title for 5 seconds.
The running version also shows in small print at the bottom of "Who's playing?".
Every merge that changes an app ships a beta: a GitHub pre-release with the next patch version
(0.38.1, 0.38.2, ...). Promotion to stable rebuilds a beta's commit as the next minor version (beta
0.38.7 becomes 0.39.0), makes it GitHub's "Latest" and moves the stable branch to it. The next beta
is 0.39.1. Beta boards skip 0.39.0: it is code they already run.
main; yank them until the fix lands.Each release also has <app>-<version>-ota.bin (the app image alone) and <app>-version.txt (the bare
version string) for boards that update themselves. A rolling <app>-beta pre-release holds a copy of the
newest <app>-version.txt, and beta boards read it there. tools/check_channel.sh porthole stable|beta
finds a channel's version and image the same way a board does. The release and promote workflows run it
after they publish.
Copy the shape of an existing app into apps/<your-app>/. An app needs:
Makefile with the shared targets (lint, test, check, coverage, ci, firmware, flash,
factory),tools/gallery.py.The full checklist is in docs/new-app.md.
What you get:
platform/waveshare-round.ini sets the
PlatformIO platform, PSRAM and flash. platform/requirements.txt pins
PlatformIO.make check, make ci, make firmware APP=... and
make flash APP=....main.main, each new feat: or fix: in an app makes a new
patch version of that app. The release workflow publishes it as a beta with notes and a flashable factory
image; Promote turns a settled beta into the next minor, stable. See
Releases.apps/<app>/ one complete project per directory
platform/ shared PlatformIO base, pinned requirements, factory-image script
tools/gallery.py README screenshot strips from panel captures
docs/ hardware.md (board, pins, round-screen rules), new-app.md (the app contract)
.github/ CI, releases, Pages, Dependabot
site/ the install guide at porthole.dreamiurg.net
.claude/ agents and skills for AI-assisted work (see AGENTS.md)
Fixes, new stories and new apps are welcome. Make a branch, run make hooks once, and open a PR with a
conventional-commit title. CONTRIBUTING.md has the details.
MIT. Bundled fonts keep their own licenses: SIL OFL for most, public domain for font8x8.