Ok, I know I promised more updates on the Cold War Clock, but what if I posted about some other Cold War-themed nonsense instead?
In any case, here’s one of two (hopefully) hackathon projects that I did in August of this year: Sloviet Blaseball. No, neither of those is a typo.
What the hell are you talking about? Who are you talking to?
For those who are unfamiliar, Blaseball
is was1 an online, multiplayer, browser-based baseball simulation horror game where splorts fans like you and I can watch virtual players play Internet League Blaseball games, bet on game outcomes, and use the money we earn to vote on rule changes that might affect individual players, teams, or even the entire league. The benefit to having virtual players is that seasons of 99 games take only a real-world week to unfold, and the entire postseason takes mere hours. During each season, games begin at the top of each hour and (usually) take only 20 minutes or so to complete. Since all fans can do is watch and place bets, it’s not quite an idle game, but it’s a very low-effort investment.
Blaseball was launched during the height of lockdown season 2020, and promptly took the internet by storm. It’s the perfect work-from-home game, requiring only a few minutes of your time once an hour at most to play, and can easily be a source of much-needed distraction on long Zoom meetings. But perhaps the best part is the community that grew up around the game and flourished on Discord. Tens of thousands of fans have gathered and rallied around their favorite teams (mine is, of course, the New York Millennials LGMBLDM!). People collaborate to create lore for players and teams, host watch parties for games, and just generally be cool folks to hang with.
I hopped on the Blaseball train just almost a year ago and I’ve loved it. The ending to the most recent season literally had me in tears. It’s a weird and wonderful way to escape from the realities of the past year and a half. I definitely recommend everyone check it out; no knowledge of real-world baseball is required.
We Live in a Society
One of the great fan projects that sprang up around Blaseball is the Society for Internet Blaseball Research (no relation to the Society for American Baseball Research), a group of particularly nerdy fans who began collecting game update data, doing math on it, and building tools to do more math. It’s thanks to these Data Witches that we have the Blaseball Reference for viewing player and team stats, the Blases Loaded mobile app, and all sorts of neat and useful projects.
This year, after one whole year of crunching nlumbers, the SIBR folks announced a hackathon seeking cursed ways to experience a Blaseball game. So, naturally, I had to jump in.
Immediately, I had an idea for a project, but it would involve a lot of hardware work, which was dicey. There will be another post about that project, whether it’s done in time or not. But I wanted to hedge my bets; there’s a charitable donation on the line here, after all.
I was chatting about this with Ilona, and in particular we were discussing the difficulty of the sheer speed of game updates: a live feed emits a new update every 4 seconds! An ideal solution, despite being cursed, wouldn’t add too much time to a regular Blaseball game, but the challenge was fitting as much information into that time as possible. It was Ilona who suggested the idea of a numbers station. Just one of many reasons I love her.
It was a perfect concept: numbers stations are creepy and mysterious, but also hypnotic and captivating. It was just the right balance of cursed and cool.
I did a fair amount of research going into this project. It was clear in my mind that I shouldn’t just turn letters into numbers 1-26; I had to do this properly. That meant wading into some pretty fascinating waters.
Numbers stations are generally believed to be mostly a means for government intelligence agencies to broadcast information to their agents and allies in the field. Part of the reason that this is conjecture is that most numbers stations use some form of encryption, and typically one that is difficult or impossible to crack by conventional means (usually a form of shared private key).
I quickly came across an extremely helpful page on linuxcoffee.com detailing a very similar project. Terrifyingly, they were using PHP for most of their scripts, but what was really helpful was their collected links to extremely useful resources about the kinds of cryptography used by actual Cold War-era numbers stations.
Using this example and the linked resources, I was able to come up with a plan for implementing a realistic form of message encoding and encryption. Like several real numbers stations, Sloviet Blaseball would use a straddling checkerboard to encode messages from text to numbers, and one-time pads to perform the encryption. This is all real-world stuff; for more information check out those links, as well as Dirk Rijmenants’ cryptography site, which contains more real life examples of these concepts, as well as detailed explanations of the theories behind them.
But another takeaway from all these examples was something I had been aware of but hadn’t strongly considered: the possibility of turning this into an actual radio transmitter.
Hey man, look at me rockin’ out!
In previous project research, I’d learned a neat thing: people had figured out how to turn an ordinary Raspberry Pi into a short-range FM transmitter without any special equipment. Then, for this project, I saw the Linux Coffee page, and that’s exactly what they were doing.
Now, I was Program Manger for my college radio station (shoutout to WHRW!), and I’ve also studied several times for (but never actually taken) the ham radio licensing exam, so I’m well aware that operating an FM transmitter without a license is, shall we say, frowned upon by the FCC.
But! Extremely low-power FM transmitters are permitted without a license in the US, provided they don’t interfere with any licensed transmissions. This is how some karaoke machines work, or how they used to before Bluetooth speakers were a thing. And, as luck would have it, here in the Bay Area there’s a small dead zone on the FM band nestled between a classical station and generic pop hits. This was looking more and more possible — theoretically.
The major snag, as I discovered, was that the Pi FM transmitters all seem to take audio data either as pre-baked .WAV files, or as raw bytes piped through stdin. This was going to prove to be the source of some of the worst coding sins in the project, since the goal was to have the transmitter operate on live (or pseudo-live) data, which precluded writing output to a .wav file. That meant I had to dump raw audio to the standard output pipe. Yuck.
Turns out, this wasn’t the worst thing in the world, but an annoying issue that came up was the handling of .WAV header data. In testing, I was using the
sox command (or, rather, the
play shortcut), and I quickly noticed that there was a pop before each number. After dumping some output to a file and poking around with a hex editor, it became clear that sox didn’t like that I was sending the header bytes from each .WAV file — it expected that I would send one header at the beginning and that the rest would be all audio. So when it was getting to the header bytes in the stream, it was trying to render them as audio, and since it’s not it came out as a few microseconds of garbage, resulting in the popping sound.
But, when I updated the program to remove the headers, I found that when using the FM transmitter, the opposite was now true - it was expecting that each new stream of file bytes include its own .WAV header.
So I arrived at the current solution, where a command-line flag is used to tell the program whether or not to send .WAV file headers after the first one. A bit of a hack, but it could be worse.
…Am I the Blaseball Mike?
Luckily for me, and everyone doing this hackathon, there is a very convenient Python library called blaseball-mike which allows us to easily get game update data. This includes getting live, or pseudo-live, game updates for any game, past or present.
Now, one thing that is extremely useful in the SIBR data set is the feed event type. I had initially planned to use this to enhance the data compression in messages by simply referring to a code for particular types of play. This immediately ran into a snag: this event type is derived, and is not actually present on the live game updates, including pseudo-live updates from past games.
This is what led to what you see in the
blaseball/types.py file today. Yes, I know the old one about the person who had a problem and said “I’ll use regex” and two problems and blah blah blah. I’ve actually had a pretty happy working relationship with regex throughout my life, so save it. This is far from the most cursed regex I’ve ever written, although in fairness it’s equally far from the most elegant (or is “based” the opposite of “cursed”?).
The point is, this was a little extra work that ultimately gave me the ability to write custom handling for a lot of event types. This is still an area where a lot of improvement can happen yet, though.
Putting it All Together
Honestly, once various parameters had been nailed down (encryption process, output format), the project was pretty straightforward. Technically, I was what I would call “done” on the 17th of August. For perspective, the first commit was on the 9th. It all came together pretty quickly in Python.
Much of the rest of the time from then until now has been taken up in making a tongue-in-cheek little video that accompanies the project and gives a nice bit of in-character flavor to the thing. You can watch that here or below.
Now that I’ve made my submission, I’ve opened up the source code repo for all to see over on my Github. I filled out the Readme with a lot of more detailed information about the operation and how to get it running, so I won’t repeat myself here.
Will I update it? Who knows! It’s definitely an easy thing to update in the future, and like I said, numbers stations are strangely hypnotic, so it would be neat to keep tinkering with it.
I especially look forward to giving it a spin on a live game once the current siesta is over.1
For now, though, I’m going to take a break. But hey, maybe I’ll see you on the Blaseball Discord!