I have apparently reached the point where I look at a discontinued Nintendo console and think, “What if Bluesky ran on that?” so I built it.
Cobalt is a native AT Protocol and Bluesky client for the Nintendo Wii U, built as an Aroma homebrew application. It is also my first Wii U homebrew project, which has meant learning an entirely different corner of software development while simultaneously trying to make a modern social network behave itself on hardware from 2012.
The actual reason I started it is considerably less complicated than the engineering involved: I wanted to shove Bluesky onto ridiculous hardware.
Why the Wii U?
Cobalt started in July, although the story really starts with Wolfram. I've been building Wolfram as my own AT Protocol SDK, and I'd already started pushing it beyond the platforms I normally write software for. Wii U support was one of those experiments. At some point, I had the slightly stupid idea of actually putting a client on the console, which turned that experiment into a reason to keep going.
The first Cobalt commits were the usual boring stuff: build system, assets, source tree, documentation. A few days later it was signing into a real PDS and reading an actual timeline. That was the point where this stopped being entirely theoretical. There is something slightly surreal about seeing software you wrote on a desktop make an HTTPS request to Bluesky from a Wii U, especially when you remember how much work has to happen before that request can succeed.
A Wii U build compiling successfully doesn't mean very much by itself. Wolfram's Wii U work has involved networking, TLS, random-number generation, XRPC and platform-specific library work. At one point I ended up writing a compatibility layer around libmicrohttpd because the Wii U environment doesn't give me quite the same thing I'd get on a normal desktop. I also had to deal with TLS entropy properly rather than doing the classic embedded-systems equivalent of hoping for the best.
This is what happens when “what if?” becomes a software requirement.
This is not a web app
Cobalt is native software. There isn't a browser hiding underneath it, there isn't Electron and there isn't some modern runtime quietly taking care of the unpleasant bits for me. It's C, SDL2, libcurl, Wolfram, devkitPro and WUT, running in a Wii U homebrew environment, so suddenly I'm dealing with authentication, HTTPS, JSON, image decoding, fonts, input, caching, rendering, networking and error handling myself.
And then there are two screens.
The GamePad and television aren't just two copies of the same display. They have different sizes, different input methods and different jobs. I've ended up with a two-card GamePad timeline, D-pad navigation, a two-row TV home screen, an emoji keyboard and a proper back control on the GamePad. I've also deliberately made it look like Wii U software rather than trying to squeeze a modern Bluesky client into an entirely different visual language. There are blue headers, rounded typography, avatars sitting outside the cards, post counts presented as pills, focus shadows, liked-post glow and a little round indicator for unread notifications.
It is easy to look at those as purely aesthetic details, but they matter when you're trying to make the application actually feel like it belongs there.
Wolfram got dragged into this too
One of the more interesting things about Cobalt is how much it has fed back into Wolfram. Wolfram came first, and Cobalt didn't magically invent the Wii U support in the SDK, but Cobalt gave all of that work somewhere to go.
It's one thing to say that an SDK supports a platform. It's another to have an actual application depending on it to authenticate, make requests, download images and render an entire social network. Cobalt has exposed places where my abstractions were good enough on a desktop but needed more thought on a console, which has pushed the SDK further.
It has also meant that work I've done for the Wii U has had consequences outside Cobalt. Wolfram now has platform work covering the Wii, Wii U and 3DS. The 3DS part wasn't really relevant to Cobalt when I started it, but it is becoming rather relevant now.
Cobalt got a bit out of hand
The original idea was basically to sign in, read Bluesky and maybe post something. That is not what happened.
Cobalt now has timelines, reposts, paging, threads, replies, likes, reposts, composing, notifications, profiles, following and unfollowing, actor search, custom feeds, curated lists, reply gates, quote posts, deleting your own posts, followers and following lists, profile tabs, pinned posts, saved feeds and post search. Posts can have images and link cards, while images are decoded and scaled away from the render loop because apparently downloading somebody's profile picture shouldn't make the entire interface have a small existential crisis.
Alt text is shown directly in the UI too. The Wii U homebrew environment isn't giving me a screen reader, so if I want image descriptions to actually be useful, Cobalt needs to surface them itself.
There are limits, of course. I'm not particularly interested in trying to turn this into every feature Bluesky has ever had. Video and GIFs aren't a priority, and DMs and push notifications aren't planned. Authentication is also deliberately boring: Cobalt uses app passwords rather than OAuth because I don't have a particularly sensible redirect target on a Wii U. That comes with the obvious limitation that accounts using two-factor authentication can't use app passwords.
Sessions are persisted locally and protected rather than just being dumped into a configuration file. Somewhere along the way, this stopped looking like a technical demo and started looking suspiciously like an application.
The fun part is the hardware
The Wii U being old isn't really the interesting part. The interesting part is taking something designed around a modern distributed social protocol and forcing it through PowerPC hardware, a GamePad, a television and a homebrew environment.
Things I normally wouldn't have to think about suddenly matter. How do I handle TLS entropy? How do I keep network and image work from blocking rendering? How do I make fonts behave properly? What happens when somebody's avatar is enormous? How should navigation work when the D-pad is the primary input? How do I make two screens feel like one application? And how do I make it look like Wii U software instead of an application that happens to run on a Wii U?
Those questions are much more interesting than simply asking whether I can call the API. This is also why I keep finding myself interested in old hardware. The limitations are part of the engineering problem rather than something hidden underneath a modern operating system and application framework.
I'm still building it
I'm writing this while I'm still actively developing Cobalt, so the project is changing as I write about it. The latest work has included build fixes, host-side end-to-end testing against Wolfram's mock PDS, rendering and performance work, moving network and image workers away from the render core, shared curl connections, image download deduplication, CA bundle and TLS work, font profiling and loading, and more UI refinement.
Cemu is useful for getting through the “change something, build it, run it, realise I broke something” loop considerably faster, but it isn't the acceptance test. The acceptance test is putting this thing on an actual Wii U, and that is still the part I'm working towards.
There's another console sitting behind this one
The more I've worked on Cobalt, the more obvious another question has become. If I can make an AT Protocol client work on a Wii U, what happens when I take the same idea somewhere else?
This isn't entirely hypothetical. Wolfram already has 3DS transport work, so the groundwork for another ridiculous platform is already there. The 3DS is interesting for exactly the same reason the Wii U is: it gives me a completely different set of constraints to work around. It isn't just a smaller Wii U. It has different hardware, a different software environment and another very strange dual-screen interface to design around.
So I've decided to give it a go. I've created Indigo, which is intended to become my native AT Protocol and Bluesky client for Nintendo 3DS.
There is, however, a fairly significant problem with calling this a project at the moment: there is literally nothing in the repository yet. It's a bare repository with a name. I haven't written the client, I haven't even found my 3DS yet, and I also need to find the charger, which is probably a fairly important prerequisite for 3DS development.
I'm not pretending Indigo is some fully formed successor to Cobalt. Right now, it's an empty repository and a plan. I do like the idea of taking what I've learned here and applying it to another completely different piece of hardware, though, and it gives Wolfram another reason to exist as more than an SDK that happens to support a collection of increasingly strange platforms.
The pattern is starting to become rather obvious: build an AT Protocol SDK, put it somewhere it probably shouldn't be, build a client around it, find another piece of hardware, and repeat.
Sometimes you build something because you can
Cobalt didn't start with a product brief or a market analysis. I wasn't trying to solve an important problem. I looked at a Wii U and thought it would be funny if Bluesky ran on it, then I started building it.
What I didn't expect was for the joke to turn into an actual engineering project. Take a modern social protocol, put it on PowerPC hardware, give it two screens, remove the browser and the usual modern application runtimes, and suddenly the networking, rendering, input, authentication and storage all become your problem. Then you keep going until it starts feeling like real software.
Along the way, Cobalt has become a practical consumer of Wolfram, pushed the SDK's Wii U support further, given me host-side testing infrastructure and forced me to solve a whole collection of problems that simply don't exist when I'm writing software for a Mac.
I still have no particular expectation that Cobalt is going to become my primary way of using Bluesky. That's not really the point. I wanted to know whether I could make it work, and now I have another piece of hardware sitting in the back of my mind waiting for its turn.
The immediate problem is finding the 3DS. Then I need to find the bloody charger.
After that, I suppose we'll see what happens with Indigo.