Turning the Tufty 2350 Into a Multiplayer Platform
I was delighted to attend Supabase Select in SF this year. I got to chat with some great people, and the talks were epic. I got to shake Steve Wozniak's hand! And I discovered that he and I have many shared opinions, such as a mutual disdain for Oracle Corp 😝
One super fun thing was that the conference badges were tiny computers! The kids loved messing around with mine when I got home.
Much to my delight, Supabase recently announced a competition to build something on your badge:
What to build?
Well duh.. OBVIOUSLY I was going to use PowerSync and Supabase for this project, but with the stack out of the way, what to build?
Initially I ported my colleague Manrich's pomogotchi project to the badge that he had built for the PowerSync AI Hackathon. The idea was that you could carry your badge around as your pomogotchi instead of using your phone. Offline support was also a nice touch.
I actually got that running!
But it didn't quite do it for me, so I went hunting for ideas.
I got inspired by Neon's Realtime launch and I thought hey, dots and boxes is a great game to demonstrate multiplayer with. And getting it to work on the badge will be a challenge!
How to build it?
I already knew Supabase and PowerSync were a great fit for this, so my backend stack was taken care of.
For the badge app to work, I'd need to:
- Get SQLite to run on the badge
- Ensure the SQLite build can load extensions, since PowerSync ships core logic as an extension (written in Rust)
- Get the PowerSync native SDK working on the badge (also written in Rust)
- Wire up the game UI to the sync engine
- Optimize performance for the constrained embedded environment
SQLite on the badge, with extension loading
This was fairly trivial for GPT-6.1 Sol Ultra. What can I say, SQLite really does run everywhere.
PowerSync Native SDK
This was quite a crazy turn of events:
- The initial port of the project did use the native SDK, but GPT identified that the easiest path to do this was to create a fully custom firmware for the entire badge, overwriting the cute little BadgeWare OS that the badge came preloaded with.
- When I caught onto the fact that the firmware was being rewritten, I hit the breaks. I wanted to keep the badge's preloaded OS that has a nice launcher and mini OS vibes, and it lets you sideload MicroPython apps via USB.
- Once I gave GPT the instructions and when I got back, it had decided that building a MicropPthon SDK was the simplest way to achieve this.
Wiring up the game UI
Creating the UI was pure joy. No CSS. Just whiteboarding with GPT. I was once again struck by how incredbly versatile SoTA models are at simple UI design.
One challenge I had was how to manage user accounts. Using an on-screen keyboard is a drag, so you don't want anything remotely resembling a username/password. Something that the Tufty does have is the three "ABC" keys. So we use Supabase anonymous auth to create an account in the browser, then display a random, five-letter pairing code made up entirely of "A", "B", and "C". Entering that code on the Tufty exchanges it for an authenticated session, linking the badge to your player account without typing a username or password. For a 5-letter sequence you have 3^5=243 possible combinations. Not banking grade security... but good enough for this.
Sidenote: Supabase Anonymous Sign-Ins is best in class, there's nothing quite like it out there. Kudos to the teams that built it.
Optimization
At this point everything was working, but it was suuuper slow. I'll be perfectly honest, I hadn't even explored the hardware capabilities up to this point! It was time to dig in.
The first thing I noticed was that SQLite was running entirely on slow flash, but that we had 8MB of fast PSRAM nearby. Inspired by projects like TanStack DB, I thought what if we use faster PSRAM and then batch SQLite operations to slower flash?
Enter the Shadow VFS - it keeps a copy of the database in snappy PSRAM. This lets SQLite perform its random I/O in fast memory, while preserving SQLite’s transaction boundaries: the rollback journal stays on flash, and commits still wait for SQLite’s required synchronization barriers.
This basically means that we have a fully featured and reasonably fast database on the Tufty! Very useful for things like offline operations, persisting across restarts, etc.
Getting HTTPS to work
This was surprisingly tricky. Badgeware offers urequests, urllib.urequest, and its own fetch.AsyncFetch helper, but PowerSync needs verified TLS and a long-lived streaming response that we can be processed incrementally (we want to keep the stream open), within a tight memory budget, while keeping the games’s UI responsive. The options that we (GPT and I) investigated didn’t meet that combination out of the box, so we built a small, stepped HTTP/1.1 client with certificate verification, bounded buffers, and nonblocking DNS.
References
All the code for the game is available here: https://github.com/kobiebotha/tufty-dots-and-boxes
If you want to use any of the components described in this post (SQLite, Shadow VFS, PowerSync MicroPython SDK, stepped HTTPS streaming), below is an overview of where to find what.
| Component | Accurate description | Location in project code |
|---|---|---|
| Tufty native SQLite MicroPython module | Dynamically loadable .mpy, available as SQLite-only or SQLite + PowerSync-core variants. The PowerSync extension is bundled into its variant. | native-sqlite/ — C bindings, build tools, dependency pins, and prebuilt modules. |
| MicroPython PowerSync SDK | A bounded Tufty client providing database lifecycle, sync orchestration, framing, and transport. Applications supply schema, credentials, and durable upload handling through a connector. | badge/lib/powersync/ — principally database.py, sync.py, and stream.py. |
| PSRAM-backed SQLite shadow VFS | A bounded whole-database RAM image—128 KiB by default—with a physical flash journal. Random database I/O uses PSRAM; SQLite sync barriers synchronously flush the image to flash. | sqlite-shadow-vfs/sqlite_shadow_vfs.py — standalone storage adapter, with its own examples and tests. |
| HTTPS module | The SDK’s stepped, certificate-verified HTTP transport: DNS, TCP/TLS, POST requests, response framing, limits, and deadlines. | badge/lib/powersync/http.py, with DNS resolution in bounded_dns.py. |
Architecture diagram
Claude generated a fairly nice architecture diagram of how everything pieces together :)
Final thoughts
I really fell in love with the Tufty platform, it's an amazing product. I think PowerSync and a backend database like Supabase serves as a pretty usable platform for any kind of turn-based multiplayer game. Not sure about realtime, but I suspect there is still a fair amount of room for optimization in this implementation, primarily by moving more stuff over into rustland.
Did I say that everything magically works whether the network is poor or totally offline? Since the Tufty's radio and antenna are fairly basic, this is a big plus for a huge range of use cases IMO.
It could also be a nice way for conference organizers to ship updates and surprises to badges during the conference event itself, and you could probably build in the sync at a badgeware level without too much effort (to avoid requiring any sideloading).
If you made it this far, you get to watch an animated video of everything in action :)