Infinite Mac on a (Virtual) LAN #

tl;dr

I’ve added a Cloudflare Durable Object-based LAN mode to Infinite Mac, allowing networked games of Marathon, Bolo and anything else that works over AppleTalk. To try it out, use demo.system7.app (or any other subdomain — it defines the “zone” where packets are broadcast and thus instances are visible to each other).


Remmeber to aim for the ground when using rocket launchers

Remembering The LAN

Though a computer without internet access can feel like a useless brick nowadays, in the 80s and early 90s that was the default state. But even without the internet, local networking was somewhat common, especially in offices (some solutions were more successful than others, and some were elegant kludges). Classic Macs were a part of this, with AppleTalk arriving only one year after the launch of the Mac. Beyond the office use-cases like file sharing and networked printers, this was used for games, with Bolo and Minotaur (Bungie’s first game) being early examples.

After Infinite Mac was announced it took less than an hour for someone to ask for (Marathon) network play — I was not the only one with fond memories of Thunderdome. The Basilisk II architecture is quite modular, and adding networking support for a platform is mostly a matter of implementing a few functions called by the virtual Ethernet driver. There was also an existing option to relay them over UDP, which had pointers for the special broadcast addresses that needed to be handled. oldweb.today had used this approach to get the emulator on the internet.

I began by implementing an ether_js.cpp version of the Ethernet functions that sent/received data from the JS side, and a basic framework for passing the packets to/from the worker to the UI process. Initially that used a BroadcastChannel for local testing, but then I added a Cloudflare Durable Object-based transport (somewhat inspired by this Doom port). That turned into a bit of a yak shave because the tooling and recommended setup for Cloudflare Workers had changed since I last updated them. However, it worked! The appeal of this approach is that it should have reasonable latency since the durable object will be created close to the clients that end up using it.

Infinite Mac network architecture

As I was developing this, I noticed that the emulated Mac would pause for 5 seconds during the boot whenever AppleTalk was enabled. I verified this on actual hardware and confirmed that it was not an emulation glitch. I decided to add some logging to understand why this was happening, and was amused to see that GitHub Copilot was capable of generating suggestions for TypeScript code that parsed AARP packets, which is surely not a common combination:

GitHub Copilot suggesting AARP packet logging code

Unfortunately, after some quality time with Inside AppleTalk it turned out that this delay was by design. AppleTalk nodes will self-assign an address, send out broadcast packets with it, and then wait to see if any nodes report conflicts. While this might be fixable by patching the ROM (a technique that Basilisk II makes heavy use of), it’s not something that I’m doing at this time. Because of the delay in booting, AppleTalk is not enabled in the default Infinite Mac instance, only when using a subdomain (to choose a “zone”). This also has the benefit of ensuring that zones do not get too big. Coincidentally wildcard subdomains recently became free on Cloudflare, enabling this approach.

I added some basic end-to-end latency tracking and was surprised to see that even when using the BroadcastChannel-based transport it was averaging around 8ms, and sometimes was approaching 16ms. These times were suspiciously close to the 60 Hz screen refresh interrupt, and it turned out to be due to my approach of hooking into the input loop — it was only triggered before refreshing the screen. I fixed this by moving input reading to being done on a higher frequency (1,000 Hz) — this both helped with reduced network latency and made mouse input feel smoother as well.

Infinite Mac screenshot showing 1ms ping times
More acceptable ping times

The actual experience when playing Marathon is mixed. The network protocol was designed for LAN play, and does not handle the increased latency of being played over the internet well. If playing for more than 15-20 minutes the game state gets out of sync between players. That being said, it’s satisfying that it works at all.

3 Comments

Very cool, thanks for sharing!
Hey, something people keep asking me when I share these projects is how to use the emulated system's screenshot functionality. I thought the fullscreen API (in Chrome, as Safari doesn't support it) would do it, as it supports many other direct keyboard input but it seems the screenshot shortcut is coded at too much of a low level in MacOS and can't be caught.

Is there a way to trigger it, perhaps with modifiers, that works within the emulation itself when using it from a modern Mac?
@Eduo: see my response to https://github.com/mihaip/infinite-mac/issues/69

Post a Comment