CapsulePress – Gemini / Spartan / Gopher to WordPress bridge

For a while now, this site has been partially mirrored via the Gemini1 and Gopher protocols.2 Earlier this year I presented hacky versions of the tools I’d used to acieve this (and made people feel nostalgic).

Now I’ve added support for Spartan3 too and, seeing as the implementations shared functionality, I’ve combined all three – Gemini, Spartan, and Gopher – into a single package: CapsulePress.

Diagram illustrating the behaviour of CapsulePress: a WordPress installation provides content, and CapsulePress makes that content available via gemini://, spartan://, and gopher:// URLs.

CapsulePress is a Gemini/Spartan/Gopher to WordPress bridge. It lets you use WordPress as a CMS for any or all of those three non-Web protocols in addition to the Web.

For example, that means that this post is available on all of:

Composite screenshot showing this blog post in, from top-left to bottom-right: (1) Firefox, via HTTPS, (2) Lagrange, via Gemini, (3) Lagrange, via Spartan, and (4) Lynx, via Gopher.

It’s also possible to write posts that selectively appear via different media: if I want to put something exclusively on my gemlog, I can, by assigning metadata that tells WordPress to suppress a post but still expose it to CapsulePress. Neat!

90s-style web banners in the style of Netscape ads, saying "Gemini now!", "Spartan now!", and "Gopher now!". Beneath then a wider banner ad promotes CapsulePress v0.1.
Using Gemini and friends in the 2020s make me feel like the dream of the Internet of the nineties and early-naughties is still alive. But with fewer banner ads.

I’ve open-sourced the whole thing under a super-permissive license, so if you want your own WordPress blog to “feed” your Gemlog… now you can. With a few caveats:

  • It’s hard to use. While not as hacky as the disparate piles of code it replaced, it’s still not the cleanest. To modify it you’ll need a basic comprehension of all three protocols, plus Ruby, SQL, and sysadmin skills.
  • It’s super opinionated. It’s very much geared towards my use case. It’s improved by the use of templates. but it’s still probably only suitable for this site for the time being, until you make changes.
  • It’s very-much unfinished. I’ve got a growing to-do list, which should be a good clue that it’s Not Finished. Maybe it never will but. But there’ll be changes yet to come.

Whether or not your WordPress blog makes the jump to Geminispace4, I hope you’ll came take a look at mine at one of the URLs linked above, and then continue to explore.

If you’re nostalgic for the interpersonal Internet – or just the idea of it, if you’re too young to remember it… you’ll find it there. (That Internet never actually went away, but it’s harder to find on today’s big Web than it is on lighter protocols.)

Footnotes

1 Also available via Gemini.

2 Also via Finger (but that’s another story).

3 Also available via Spartan.

4 Need a browser? I suggest Lagrange.

× × ×

Stopping WordPress Emoji ‘Images’ in Feeds

After sharing that Octopuns has started posting again after a 9½-year hiatus earlier today, I noticed something odd: where I’d written “I ❤️ FreshRSS“, the heart emoji was huge when viewed in my favourite feed reader.

Screenshot from a web-based RSS reader application, showing recent repost "Groundhog Day". The final line contains a link with the text "I ❤️ FreshRSS", but the red heart emoji seems to be enormous compared to the next adjacent to it.
Why yes, I do subscribe to my own RSS feed. What of it?

It turns out that by default, WordPress replaces emoji in its feeds (and when sending email) with images of those emoji, using the Tweemoji set, and with the alt-text set to the original emoji. These images are hosted at https://s.w.org/images/core/emoji/…-based URLs.

For example, this heart was served with the following HTML code (the number 2764 refers to the codepoint of the emoji):

<img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2764.png"
     alt="❤"
   class="wp-smiley"
   style="height: 1em; max-height: 1em;"
/>

I can see why this functionality was added: what if the feed reader didn’t support Unicode or didn’t have a font capable of showing the appropriate emoji?

But I can also see reasons why it might not be desirable to everybody. For example:

  1. Downloading an image will always be slower than rendering an emoji.
  2. The code to include an image is always more-verbose than simply including an emoji.
  3. As seen above: a feed reader which imposes a minimum size on embedded images might well render one “wrong”.
  4. It’s marginally more-verbose for screen reader users to say “Image: heart emoji” than just “heart emoji”, I imagine.
  5. Serving an third-party image when a feed item is viewed has potential privacy implications that I try hard to avoid.
  6. Replacing emoji with images is probably unnecessary for modern feed readers anyway.

I opted to remove this functionality. I briefly considered overriding the emoji_url filter (which could be used to selfhost the emoji set) but I discovered that I could just un-hook the filters that were being added in the first place.

Here’s what I added to my theme’s functions.php:

remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );

That’s all there is to it. Now, my feed reader shows my system’s emoji instead of a huge image:

Screenshot from a web-based RSS reader application, showing recent repost "Groundhog Day". The final line contains a link with the text "I ❤️ FreshRSS" shown correctly, with a red heart emoji at the appropriate font size.

I’m always grateful to discover that a piece of WordPress functionality, whether core or in an extension, makes proper use of hooks so that its functionality can be changed, extended, or disabled. One of the single best things about the WordPress open-source ecosystem is that you almost never have to edit somebody else’s code (and remember to re-edit it every time you install an update).

Want to hear about other ways I’ve improved WordPress’s feeds?

× ×

Debian + DKIM for Dummies

Earlier this year, for reasons of privacy/love of selfhosting, I moved the DanQ.me mailing list from Mailchimp to Listmonk (there’s a blog post about how I set it up), relaying outbound messages via an SMTP server provided by my domain registrar, Gandi.

Subscribe for email updates, or discover other ways to subscribe:

Email no more than
I assume that you knew that you can get an email, no more than once per day or once per week (your choice!) of what I get up to online, right? Email not your jam: there are plenty of other options too!

Unfortunately, Gandi recently announced that they’ll no longer be providing email services for free, so rather than fork out €4/month for an email address I won’t even read, I decided to inhouse it.

And because I learned a few things while doing so, I wrote this blog post so that next time I have to configure Postfix + DKIM, I’ll know where to find a guide. If it helps you in the meantime, that’s just a bonus.

Photograph of a French Bulldog on a wooden floor playing tug-of-war using a multicoloured plaited rope (the human holding the other end of the rope is behind the camera).
If the first rule of computing is “never roll your own crypto” (based on Schneier’s Law), the second rule might be “don’t run your own mailserver”. I don’t have a good picture to illustrate that, so here’s a photo of my dog playing tug-of-war.

Postfix

Running your own mailserver is a pain. I used to do it for all of my email, but – like many other nerds – when spam reached its peak and deliverability became an issue, I gave up and oursourced it1.

Screenshot of a Weekly Digest email from DanQ.me, showing in Mozilla Thunderbird.
Fun fact: when I’m at my desktop, I use a classic desktop email application for my personal email, like it’s the 90s or something2.
Luckily, I don’t need it to do much. I just need a mail transfer agent with an (unauthenticated, but local-only) SMTP endpoint: something that Listmonk can dump emails into, which will then reach out to the mailservers representing each of the recipients and relay them on. A default install of Postfix does all that out-of-the-box, so I ran sudo apt install postfix, accepted all the default options, and put the details into Listmonk.
Screenshot showing Listmonk's SMTP configuration screen. The host "192.168.2.12" and port "25" have been entered, TLS has been set to "STARTTLS", Skip TLS verification is enabled, and Auth Protocol is set to "None".
Listmonk makes adding an SMTP server very easy, and even includes a quick “test connection” link with which you can try out your settings.

Next, I tweaked my DNS configuration to add an SPF record, and tested it. This ought to have been enough to achieve approximate parity with what Gandi had been providing me with. Not bad.

$ dig +short -t TXT danq.link
"v=spf1 a mx a:fox.q-t-a.uk ip4:83.151.206.115 ~all"
You really can’t be doing without an SPF record as a minimum these days.

I sent a test email to a Gmail account, where I noticed two problems:

Screenshot from GMail showing a message with a red slashed padlock icon, which when clicked advises that "mail.danq.link did not encrypt this message".
It turns out that since the last time I ran a mailserver “for real”, the use of TLS for inter-server communication has become… basically mandatory. You don’t strictly have to do it, but if you don’t, some big email providers will put scary security warnings on your messages. This is a good thing.

The first problem was that Postfix on Debian isn’t configured by-default to use opportunistic TLS when talking to other mailservers. That’s a bit weird, but I’m sure there’s a good reason for it. The solution was to add smtp_tls_security_level = may to my /etc/postfix/main.cf.

The second problem was that without a valid DKIM signature on them, about half of my test emails were going straight to the spam folder. Again, it seems that since the last time I seriously ran a mailserver 20 years ago, this has become something that isn’t strictly required… but your emails aren’t going to get through if you don’t.

I’ve put it off this long, but I think it’s finally time for me to learn some practical DKIM.

Understanding DKIM

What’s DKIM, then?

Diagram illustrating the flow of email from sender to recipient. On the way it's signed by the sender's mailserver's private key, which publishes the public key via DNS. Further along, the recipient's mailserver retreives the public key and uses it to verify the signature.
I’ve already got an elementary understanding of how DKIM works, which I’ll summarise below.
  1. A server that wants to send email from a domain generates a cryptographic keypair.
  2. The public part of the key is published using DNS. The private part is kept securely on the server.
  3. When the server relays mail on behalf of a user, it uses the private key to sign the message body and a stated subset of the headers3, and attaches the signature as an email header.
  4. When a receiving server (or, I suppose, a client) receives mail, it can check the signature by acquiring the public key via DNS and validating the signature.

In this way, a recipient can be sure that an email received from a domain was sent with the authorisation of the owner of that domain. Properly-implemented, this is a strong mitigation against email spoofing.

OpenDKIM

To set up my new server to sign outgoing mail, I installed OpenDKIM and its keypair generator using sudo apt install opendkim opendkim-tools. It’s configuration file at /etc/opendkim.conf needed the following lines added to it:

# set up a socket for Postfix to connect to:
Socket inet:12301@localhost

# set up a file to specify which IPs/hosts can send through us without authentication and get their messages signed:
ExternalIgnoreList      refile:/etc/opendkim/TrustedHosts
InternalHosts           refile:/etc/opendkim/TrustedHosts

# set up a file to specify which selector/domain are used to each incoming email address:
SigningTable            refile:/etc/opendkim/SigningTable

# set up a file to specify which signing key to use for each selector/domain:
KeyTable                refile:/etc/opendkim/KeyTable

Into /etc/opendkim/TrustedHosts I put a list of local IPs/domains that would have their emails signed by this server. Mine looks like this (in this example I’m using example.com as my domain name, and default as the selector for it: the selector can be anything you like, it only matters if you’ve got multiple mailservers signing mail for the same domain). Note that 192.168.0.0/16 is the internal subnet on which my sending VM will run.

127.0.0.0.1
::1
192.168.0.0/16
*.example.com
example.com

/etc/opendkim/SigningTable maps email addresses (I’m using a wildcard) to the subdomain whose TXT record will hold the public key for the signature. This also goes on to inform the KeyTable which private key to use:

*@example.com default._domainkey.example.com

And then /etc/opendkim/KeyTable says where to find the private key for that:

default._domainkey.example.com example.com:default:/etc/opendkim/keys/example.com/default.private

Next, we need to create and secure that keypair. In /etc/opendkim/keys/example.com/, we run:

sudo opendkim-genkey -s default -d example.com
sudo chown opendkim:opendkim default.private

At last, we can configure Postfix to filter all mail through OpenDKIM by adding to our /etc/postfix/main.cf file:

milter_protocol = 2
milter_default_action = accept
smtpd_milters = inet:localhost:12301
non_smtpd_milters = inet:localhost:12301

DNS

The public key needs publishing via DNS. Conveniently, when you create a keypair using its tools, OpenDKIM provides a sample (in BIND-style) for you to copy-paste from or adapt: look in /etc/opendkim/keys/example.com/default.txt!

Screenshot from Gandi's Simple DNS management tools, showing danq.link with an SPF record as descibed earlier and a new TXT record on default._domainkey as just described.
Gandi’s DNS “Simple View” is great for one-off and quick operations, but I really appreciate that they have a BIND-style syntax “Advanced View” for when I’m making bigger and more-complex DNS configuration changes.

Once we’ve restarted both services (sudo service postfix restart; sudo service opendkim restart), we can test it!

Screenshot from GMail showing "DKIM: 'PASS' with domain danq.link".
Once the major email providers – who have the worst spam problem to deal with – say that your email signature looks good, you’re good.

So I learned something new today.

If you, too, love to spend your Saturday mornings learning something new, have a look at those subscription options to decide how you’d like to hear about whatever I get up to next.

Footnotes

1 I still outsource my personal email, and I sing the praises of the excellent folks behind ProtonMail.

2 My desktop email client also doubles as my newsreader, because, yes, of course you can still find me on USENET. Which, by the way, is undergoing a mini-revival

3 Why doesn’t DKIM sign all the headers in an email? Because intermediary servers and email clients will probably add their own headers, thereby invalidating the signature! DKIM gets used to sign the From: header, for obvious reasons, and ought to be used for other headers whose tampering could be significant such as the Date: and Subject:, but it’s really up to the signing server to choose a subset.

× × × × × × ×

Watch Together with WhatsApp on the side

A virtual party

This weekend, I threw a Virtual Free Fringe party for some friends. The party was under-attended, but it’s fine because I got to experiment with some tech that I’d been meaning to try.

Phootgraph of a wall-mounted television screen. On the screen, comedian Peter Buckley Hill sits with his guitar on his lap in front of an audience: the "PBH's Free Fringe" logo is on the curtain behind him. On the left of the screen a series of WhatsApp messages appear, including one showing a photo of Dan holding a can of Old Speckled Hen beer.
The Abnibbers and I have experimented with watching things together, but apart, before, but this is the first time we’ve watched stand-up comedy this way.

If you ever want to run something like this yourself1, here’s how I did it.

My goals were:

  • A web page at which any attendee could “watch together” a streaming video2,
  • A “chat” overlay, powered by a WhatsApp group3 (the friend group I was inviting were all using WhatsApp anyway, so this was an obvious choice), and
  • To do all the above cheaply or for free.
Selfie photograph of Dan, in a bar with a rooftop view of daylight out the windows in the background, looks concerned as he stares at the a frothy, bubbling flask of yellow liquid he's holding.
I’m a big fan of experiments. Contrary to this picture, though, they’re usually software experiments.

There were two parts to this project:

  1. Setting up a streaming server that everybody can connect to, and
  2. Decorating the stream with a WhatsApp channel

Setting up a streaming server

Linode offers a free trial of $100 of hosting credit over 60 days and has a ready-to-go recipe for installing Owncast, an open-source streaming server I’ve used before, so I used their recipe, opting for a 4GB dedicated server in their London datacentre: at $36/mo, there’d be no risk of running out of my free trial credit even if I failed to shut down and delete the virtual machine in good time. If you prefer the command-line, here’s the API call for that:

curl -H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-X POST -d '{
    "authorized_users": [
        "[YOUR LINODE USERNAME]"
    ],
    "backups_enabled": false,
    "booted": true,
    "image": "linode/debian10",
    "label": "owncast-eu-west",
    "private_ip": false,
    "region": "eu-west",
    "root_pass": "[YOUR ROOT PASSWORD]",
    "stackscript_data": {
        "server_hostname": "[YOUR DOMAIN NAME]",
        "email_address": "[YOUR EMAIL ADDRESS]"
    },
    "stackscript_id": 804172,
    "tags": [],
    "type": "g6-dedicated-2"
}' https://api.linode.com/v4/linode/instances

The IP address got assigned before the machine finished booting, so I had time to copy that into my DNS configuration so the domain was already pointing to the machine before it was fully running. This enabled it to get its SSL certificate set up rightaway (if not, I’d have had to finish waiting for the DNS change to propogate and then reboot it).

Out of the box, Owncast is insecure-by-default, so I wanted to jump in and change some passwords. For some reason you’re initially only able to correct this over unencrypted HTTP! I opted to take the risk on this server (which would only be alive for a few hours) and just configure it with this limitation, logging in at http://mydomain:8080/admin with the default username and password (admin / abc123), changing the credentials to something more-secure. I also tweaked the configuration in general: setting the service name, URL, disabling chat features, and so on, and generating a new stream key to replace the default one.

Now I was ready to configure OBS Studio to stream video to my new Owncast server, which would distribute it to anybody who tuned-in.

Screenshot showing OBS Studio window with Start Streaming enabled. The layers "VLC", "Abnib Logo", "WhatsApp icon", WhatsApp prompt", and "WhatsApp" are visible. Elsewhere on the screen, a WhatsApp Web view is visible, with its CSS tweaked to give it a red background, among other changes.
Next up, we need to make WhatsApp appear on the stream with a little bit of CSS hackery.

Decorating the stream

I configured OBS Studio with a “Custom…” stream service with server rtmp://mydomain:1935/live and the stream key I chose when configuring Owncast and kicked off a test stream to ensure that I could access it via https://mydomain. I added a VLC source4 to OBS and fed it a playlist of videos, and added some branding.

With that all working, I now needed a way to display the WhatsApp chat superimposed over the video.For this, I added a Window Capture source and pointed it at a Firefox window that was showing a WhatsApp Web view of the relevant channel. I added a Crop/Pad filter to trim off the unnecessary chrome.

OBS Studio screenshot showing a WhatsApp Web (Window) source tied to a Firefox window and with Crop/Pad and Chroma Key filters applied.
The same technique, of course, could be used to superimpose any web page or whatever other content you like onto a stream.

Next, I used the Firefox debugger “Style Editor” to inject some extra CSS into WhatsApp Web. The class names vary frequently, so there’s no point we re-documenting all of them here, but the essence of the changes were:

  1. Changing the chat background to a solid bright color (I used red) that can then be removed/made transparent using OBS’s Chroma Key filter. Because you have a good solid color you can turn the Similarity and Smoothness way down.
  2. Making all messages appear the same (rather than making my messages appear different from everybody else’s). To do this, I added:
    • .message-in, .message-out { align-items: flex-start !important; } to align them all to the left
    • [aria-label="You:"]::after { content: "Dan Q"; height: 15px !important; display: block; color: #00f !important; padding: 8px 0 0 8px; } to force my name to appear even on my own messages
    • [aria-label^="Open chat details for "] { display: none; } to remove people’s avatars
    • [data-testid="msg-meta"] { display: none !important; } to remove message metadata
    • A hacky bit of CSS to make the backgrounds all white and to remove the speech bubble “tails”
  3. Removing all the sending/received/read etc. icons with [data-icon] { display: none; }

I aimed where possible to exploit selectors that probably won’t change frequently, like [aria-label]s; this improves the chance that I can use the same code next time. I also manually removed “old” messages from the channel that didn’t need to be displayed on the big screen. I wasn’t able to consistently remove “X new messages” notifications, but I’ll probably try again another time, perhaps with the help of an injected userscript.

A little bit of a shame that more people didn’t get to see the results of this experiment, but I’m sure I’ll use the techniques I’ve learned on another ocassion.

Footnotes

1 Or, let’s be honest, if you’re Future Dan and you’re trying to remember how you did it in last time.

2 We were to watch a show by one of my favourite comedians Peter Buckley Hill, the man behind the Free Fringe. I’ve written about him previously… here, there, also several times in 2012 when I also helped make an official digital map of Free Fringe venues. I was especially delighted to have my photo taken with him in 2006. I might be a bit of a fanboy.

3 This could probably be adapted for any other chat system that has a web interface, so if you prefer Telegram or Slack or whatever ever, that’s fine.

4 OBS’s VLC source is just amazing: not only can you give it files, but you can give it URLs, meaning that you can set up a playlist of YouTube videos, or RTSP security camera feeds, or pretty much anything else you feel like (and have the codecs for).

× × × ×

Are Geocache Logs Getting Shorter?

Background and hypothesis

When geocachers find a geocache, they typically “log” their find both in the cache’s paper logbook and on one of the online listing sites on which the cache’s coordinates can be found.1

Photograph showing a medium-sized geocache container with its contents laid-out around it: various pieces of swag for trade, plus a notebook.
A typical geocacher can find their cache container, logbook, swag, toothbrush, face flannel, soap, tin of biscuits, flask, compass, and most-importantly towel. Hang on, I’ve got my geekeries crossed again. Photo courtesy cachemania, used under a CC BY-SA license.

I’ve been finding and hiding geocaches for… a long while, so I’ve seen lots of log entries from people who’ve found my caches (and those of others). And it feels to me like the average length of a geocaching log entry is getting shorter.

Screenshot of a digital log entry from Geocaching.com, titled "MagicV77 found Grove Farm" on 22 August 2023. The entirety of the log entry itself is a thumbs-up emoji.
A single emoji is probably the shortest log entry I’ve ever seen. I’m not claiming that its cache deserves a longer log (it’s far from my best work!): just using it as an example of a wider trend towards shorter logs.

“It feels to me like…” isn’t very scientific, though. Let’s see if we can do better.

Getting the data

To test my hypothesis, I needed a decade or so of logs. I didn’t want to compare old caches to new caches (in case people are biased by the logs before them) so I used Geocaching.com’s own search to open the pages for the 500 caches closest to me that are each at least 10 years old.

Browser tab bar showing many hundreds of Geocaching.com tabs.
My browser hates me right now.

I hacked together a quick userscript to save all of the logs in a way that was easier than copy-pasting each of them but still didn’t involve hitting Geocaching.com’s API or automating bulk-scraping (which would violate their terms of service). Clicking each of several hundred tabs once every few minutes in the background while I got on with other things wasn’t as much of an ordeal as you might think… but it did take a while.

Needless to say I only had to go through the cycle a couple of times before I set up a keyboard shortcut.

I mashed that together into a CSV file and for the first time looked at the size of my sample data: ~134,000 log entries, spanning 20 years. I filtered out everything over 10 years old (because some of the caches might have no logs that old) and stripped out everything that wasn’t a “found it” or “didn’t find it” log.

That gave me a far more-reasonable ~80,000 records with which I could make Excel cry.2

Results

It looks like my hunch is right. The wordcount of “found” logs on traditional and multi-stage caches has generally decreased over time:

Graph showing word counts (log10) of geocache logs on different dates from August 2013 through August 2023, There's a slight downward trend.
“Found” logs are great for cache owner morale: a simple “TFTC” is a lot less-inspiring that hearing about your adventure to get to that point.

“Did not find” logs, which can be really helpful for cache owners to diagnose problems with their caches, have an even more-pronounced dip:

Graph showing word counts (log10) of geocache logs on different dates from August 2013 through August 2023, There's a pronounced downward trend.
Geocachers are just typing “Didn’t find it” and moving on. Without an indication of the conditions at the GZ, how long they spent looking, or an indication of whether the hint was followed, that doesn’t give a cache owner much to work with.

When I first saw that deep dip on the average length of “did not find” logs, my first thought was to wonder whether the sample might not be representative because the did-not-find rate itself might have fallen over time. But no: the opposite is true:

Graph showing how the "did not find" rate in my samples has climbed from an average of 4% to an average of 7.5% over the last decade.
A higher proportion than ever of geocachers are logging that they couldn’t find the cache, but they’re simultaneously saying less than ever about it.

Strangely, the only place that the trend is reversed is in “found” logs of virtual caches, which have seen a slight increase in verbosity.

Graph showing word counts (log10) of geocache logs on different dates from August 2013 through August 2023, There's a slight upward trend.
I initially assumed that this resulted from “virtual rewards” from 2017 onwards3 but this doesn’t make any sense because all of the caches in my study are 10+ years old: none of them can be “virtual rewards”.

Conclusion

Within the limitations of my research (80,000 logs from 500 caches each 10+ years old, near me), there are a handful of clear trends over the last decade:

  • Geocachers are leaving increasingly concise logs when they find geocaches.
  • That phenomenon is even more-pronounced when they don’t find them.
  • And they’re failing-to-find caches and giving up with significantly greater frequency.

Are these trends a sign of shortening attention spans? Increased use of mobile phones for logging? Use of emoji and acronyms to pack more detail into shorter messages? I don’t know.

I’d love to see some wider research, perhaps by somebody at Geocaching.com HQ (who has database access and is thus able to easily extract enough data for a wider analysis!). I’m also very interested in whether the identity of the cache finder has an impact on log length: is it impacted by how long ago they started ‘caching? Whether or not they have hidden caches of their own? How many caches they’ve found?

But personally, I’m just pleased to have been able to have a question in the back of my mind and – through a little bit of code and a little bit of data-mashing – have a pretty good go at answering it.

Footnotes

1 I have a dream that someday cache logging could be powered by Webmentions or ActivityPub or some similar decentralised-Web technology, so that cachers can log their finds on any site on which a cache is listed or even on their own site and have all the dots joined-up… but that’s pretty far-fetched I’m afraid. It’s not stopping some of us from experimenting with possible future standards, though…

2 Just for fun, try asking Excel to extrapolate a second-order polynomial trendline across 80,000 pairs of datapoints. Just don’t do it if you’re hoping to use your computer for anything in the next quarter hour.

3 With stricter guidelines on how a “virtual rewards” virtual caches should work than existed for original pre-2005 virtuals, these new virtuals are more-likely than their predecessor to encourage or require longer logs.

× × × × × × ×

Inclusivity

Motivational poster showing a photograph from a Manchester street. A Pride flag banner is hanging from a post with the words "everybody welcome" at the top. Nearby, attached to the same post, a road sign has the words "except buses". The poster is captioned with the word "inclusivity" (in pride colours), and subcaptioned "the 'B' is not for Buses". The joke is that the 'B' in LGBTQ+ stands for 'bisexual' and not 'buses', although of course the real meaning of that street sign is to ban everybody from driving straight ahead except buses, so the joke isn't perfect.

Max credit to garry (@repeattofade) for the original toot. All I did was adapt it into a motivational poster.

The thing I’m wondering is whether that bus lane is one that a bi-cyclist like me can use? 😂

×

RSS Zero isn’t the path to RSS Joy

Feed overload is real

The week before last, Katie shared with me that article from last month, Who killed Google Reader? I’d read it before so I didn’t bother clicking through again, but we did end up chatting about RSS a bit1.

Screenshot: Google Reader Notifier popup advises of "461 unread items".
I ditched Google Reader several years before its untimely demise, but I can confirm “461 unread items” was a believable message.

Katie “abandoned feeds a few years ago” because they were “regularly ending up with 200+ unread items that felt overwhelming”.

Conversely: I think that dropping your feed reader because there’s too much to read is… solving the wrong problem.

A white man with dark hair, wearing jeans and a t-shirt, moves to push over a stack of carboard boxes, each smaller than the one beneath it. From bottom to top, the boxes are labelled: stress, email client, mobile pings, doomscrolling, social media silos... and the very top, very smallest box, which glows with sunbeams emitted from it, reads "rss reader".
About half way through editing this image I completely forgot what message I was trying to convey, but I figured I’d keep it anyway and let you come up with your own interpretation.

Dave Rupert last week wrote about his feed reader’s “unread” count having grown to a mammoth 2,000+ items, and his plan to reduce that.

I think that he, like Katie, might be looking at his reader in a different way than I do mine.

FreshRSS sidebar, showing 567 unread items (of which 1 are comics, 2 are friends, 186 are communities, 1 are distractions, 278 are geeky, 1 is "me", 57 are youtube, 13 are strangers, 1 is software, 7 are rss club, 29 are podcasts, and 3 are polyamory. A further 107 are marked as favourites. The "friends" and "rss club" categories are showing warning triangles.
At time of writing, I’ve got 567 unread items. And that’s fine.

RSS is not email!

I’ve been in the position that Katie and David describe: of feeling overwhelmed by the sheer volume of unread items. And I know others have, too. So let me share something I’ve learned sooner:

There’s nothing special about reaching Inbox Zero in your feed reader.

It’s not noble nor enlightened to get to the bottom of your “unread” list.

Your 👏  feed 👏 reader 👏 is 👏 not 👏 an 👏 email 👏 client. 👏

The idea of Inbox Zero as applied to your email inbox is about productivity. Any message in your email might be something that requires urgent action, and you won’t know until you filter through and categorise .

But your RSS reader doesn’t (shouldn’t?) be there to add to your to-do list. Your RSS reader is a list of things you might like to read. In an ideal world, reaching “RSS Zero” would mean that you’ve seen everything on the Internet that you might enjoy. That’s not enlightened; that’s sad!

Google Reader's "Congratulations, you've reached the End of the Internet." Easter Egg screen, shown when all your feeds are empty.
Google Reader understood this, although the word “congratulations” was misplaced.

Use RSS for joy

My RSS reader is a place of joy, never of stress. I’ve tried to boil down the principles that makes it so, and here they are:

  1. Zero is not the target.
    The numbers are to inspire about how much there is “out there” for you, not to enumerate how much work need have to do.
  2. Group your feeds by importance.
    Your feed reader probably lets you group (folder, tag…) your feeds, so you can easily check-in on what you care about and leave other feeds for a rainy day.2 This is good.
  3. Don’t read every article.
    Your feed reader gives you the convenience of keeping content in one place, but you’re not obligated to read every single one. If something doesn’t interest you, mark it as read and move on. No judgement.
  4. Keep things for later.
    Something you want to read, but not now? Find a way to “save for later” to get it out of your main feed so you. Don’t have to scroll past it every day! Star it or tag it3 or push it to your link-saving or note-taking app. I use a link shortener which then feeds back into my feed reader into a “for later” group!
  5. Let topical content expire.
    Have topical/time-dependent feeds (general news media, some social media etc.)? Have reader “purge” unread articles after a time. I have my subscription to BBC News headlines expire after 5 days: if I’ve taken that long to read a headline, it might as well disappear.4
  6. Use your feed reader deliberately.
    You don’t need popup notifications (a new article’s probably already up to an hour stale by the time it hits your reader). We’re all already slaves to notifications! Visit your reader when it suits you. I start and end every day in mine; most days I hit it again a couple of other times. I don’t need a notification: there’s always new content. The reader keeps track of what I’ve not looked at.
  7. It’s not just about text.
    Don’t limit your feed reader to just text. Podcasts are nothing more than RSS feeds with attached audio files; you can keep track in your reader if you like. Most video platforms let you subscribe to a feed of new videos on a channel or playlist basis, so you can e.g. get notified about YouTube channel updates without having to fight with The Algorithm. Features like XPath Scraping in FreshRSS let you subscribe to services that don’t even have feeds: to watch the listings of dogs on local shelter websites when you’re looking to adopt, for example.
  8. Do your reading in your reader.
    Your reader respects your preferences: colour scheme, font size, article ordering, etc. It doesn’t nag you with newsletter signup popups, cookie notices, or ads. Make the most of that. Some RSS feeds try to disincentivise this by providing only summary content, but a good feed reader can work around this for you, fetching actual content in the background.5
  9. Use offline time to catch up on your reading.
    Some of the best readers support offline mode. I find this fantastic when I’m on an aeroplane, because I can catch up on all of the interesting articles I’d not had time to yet while grounded, and my reading will get synchronised when I touch down and disable flight mode.
  10. Make your reader work for you.
    A feed reader is a tool that works for you. If it’s causing you pain, switch to a different tool6, or reconfigure the one you’ve got. And if the way you find joy from RSS is different from me, that’s fine: this is a personal tool, and we don’t have to have the same answer.

And if you’d like to put those tips in your RSS reader to digest later or at your own pace, you can:  here’s an RSS feed containing (only) these RSS tips!

Footnotes

1 You’d  be forgiven for thinking that RSS was my favourite topic, given that so-far-this-year I’ve written about improving WordPress’s feeds, about mathematical quirks in FreshRSS, on using XPath scraping as an RSS alternative (twice), and the joy of getting notified when a vlog channel is ressurected (thanks to RSS). I swear I have other interests.

2 If your feed reader doesn’t support any kind of grouping, get a better reader.

3 If your feed reader doesn’t support any kind of marking/favouriting/tagging of articles, get a better reader.

4 If your feed reader doesn’t support customisable expiry times… well that’s not too unusual, but you might want to consider getting a better reader.

5 FreshRSS calls the feature that fetches actual post content from the resulting page “Article CSS selector on original website”, which is a bit of a mouthful, but you can see what it’s doing. If your feed reader doesn’t support fetching full content… well, it’s probably not that big a deal, but it’s a good nice-to-have if you’re shopping around for a reader, in my opinion.

6 There’s so much choice in feed readers, and migrating between them is (usually) very easy, so everybody can find the best choice for them. Feedly, Inoreader, and The Old Reader are popular, free, and easy-to-use if you’re looking to get started. I prefer a selfhosted tool so I use the amazing FreshRSS (having migrated from Tiny Tiny RSS). Here’s some more tips on getting started. You might prefer a desktop or mobile tool, or even something exotic: part of the beauty of RSS feeds is they’re open and interoperable, so if for example you love using Slack, you can use Slack to push feed updates to you and get almost all the features you need to do everything in my list, including grouping (using channels) and saving for later (using Slackbot/”remind me about this”). Slack’s a perfectly acceptable feed reader for some people!

× × × ×

Not the Isle of Man

This week, Ruth and I didn’t go the Isle of Man.

A laptop screen shows Automattic's "Work With Us" web page. Beyond it, in an airport departure lounge (with diners of Wagamama and The Breakfast Club in the background), Dan sits at another laptop, wearing a black "Accessibility Woke Platoon" t-shirt and grey Tumblr hoodie.
We’d intended to actually go to the Isle of Man, even turning up at Gatwick Airport six hours before our flight and working at Pret in order to optimally fit around our workdays.

It’s (approximately) our 0x10th anniversary1, and, struggling to find a mutually-convenient window in our complex work schedules, we’d opted to spend a few days exploring the Isle of Man. Everything was fine, until we were aboard the ‘plane.

Ruth, wearing a green top with white stripes, sits alongside Dan, wearing a black t-shirt and grey hoodie, by the wingside emergency exits in an aeroplane.
As the last few passengers were boarding, putting their bags into overhead lockers, and finding their seats, Ruth observed that out on the tarmac, bags were being removed from the aircraft.

Once everybody was seated and ready to take off, the captain stood up at the front of the ‘plane and announced that it had been cancelled2.

The Isle of Man closes, he told us (we assume he just meant the airport) and while they’d be able to get us there before it did, there wouldn’t be sufficient air traffic control crew to allow them to get back (to, presumably, the cabin crews’ homes in London).

Two passengers - a man and a woman - disembark from an EasyJet plane via wheeled stairs.
To add insult to injury: even though the crew clearly knew that the ‘plane would be cancelled before everybody boarded, they waited until we were all aboard to tell us then made us wait for the airport buses to come back to take us back to the terminal.

Back at the terminal we made our way through border control (showing my passport despite having not left the airport, never mind the country) and tried to arrange a rebooking, only to be told that they could only manage to get us onto a flight that’d be leaving 48 hours later, most of the way through our mini-break, so instead we opted for a refund and gave up.3

Ruth and Dan, looking tired and frustrated, sit at a pub table. Ruth is using her tablet computer.
After dinner at the reliably-good Ye Old Six Bells in Horley, down the road from Gatwick Airport, we grumpily made our way back home.

We resolved to try to do the same kinds of things that we’d hoped to do on the Isle of Man, but closer to home: some sightseeing, some walks, some spending-time-together. You know the drill.

Panoramic photo showing a field containing the remains of a Roman villa in West Oxfordshire, under grey skies. The walls are barely visible in this wide shot.
There’s evidence on the Isle of Man of Roman occupation from about the 1st century BCE through the 5th century CE, so we found a local Roman villa and went for a look around.

A particular highlight of our trip to the North Leigh Roman Villa – one of those “on your doorstep so you never go” places – was when the audio tour advised us to beware of the snails when crossing what was once the villa’s central courtyard.

At first we thought this was an attempt at humour, but it turns out that the Romans brought with them to parts of Britain a variety of large edible snail – helix pomatia – which can still be found in concentration in parts of the country where they were widely farmed.4

Large cream-coloured snail in moderately-long grass, alongside a twenty-pence piece (for scale). The snail is around three times as long as the coin is.
Once you know you’re looking for them, these absolute unit gastropods are easy to spot.

There’s a nice little geocache near the ruin, too, which we were able to find on our way back.

Before you think that I didn’t get anything out of my pointless hours at the airport, though, it turns out I’d brought home a souvenier… a stinking cold! How about that for efficiency: I got all the airport-germs, but none of the actual air travel. By mid-afternoon on Tuesday I was feeling pretty rotten, and it only got worse from then on.

A box of tissues and a Nintendo Switch Pro Controller on the arm of a sofa.
I felt so awful on Wednesday that the most I was able to achieve was to lie on the sofa feeling sorry for myself, between sessions of The Legend of Zelda: Tears of the Kingdom.

I’m confident that Ruth didn’t mind too much that I spent Wednesday mostly curled up in a sad little ball, because it let her get on with applying to a couple of jobs she’s interested in. Because it turns out there was a third level of disaster to this week: in addition to our ‘plane being cancelled and me getting sick, this week saw Ruth made redundant as her employer sought to dig itself out of a financial hole. A hat trick of bad luck!

Dan, sitting in bed, holding a tissue and looking unwell.
Sniffle. Ugh.

As Ruth began to show symptoms (less-awful than mine, thankfully) of whatever plague had befallen me, we bundled up in bed and made not one but two abortive attempts at watching a film together:

  • Spin Me Round, which looked likely to be a simple comedy that wouldn’t require much effort by my mucus-filled brain, but turned out to be… I’ve no idea what it was supposed to be. It’s not funny. It’s not dramatic. The characters are, for the most part, profoundly uncompelling. There’s the beginnings of what looks like it was supposed to be a romantic angle but it mostly comes across as a creepy abuse of power. We watched about half and gave up.
  • Ant-Man and the Wasp: Quantumania, because we figured “how bad can a trashy MCU sequel be anyway; we know what to expect!” But we couldn’t connect to it at all. Characters behave in completely unrealistic ways and the whole thing feels like it was produced by somebody who wanted to be making one of the new Star Wars films, but with more CGI. We watched about half and gave up.

As Thursday drew on and the pain in my head and throat was replaced with an unrelenting cough, I decided I needed some fresh air.

Dan, looking slightly less-unwell, stands holding Demmy, a French Bulldog, in front of a hedge.
The dog needed a walk, too, which is always a viable excuse to get out and about.

So while Ruth collected the shopping, I found my way to the 2023-07-27 51 -1 geohashpoint. And came back wheezing and in need of a lie-down.

I find myself wondering if (despite three jabs and a previous infection) I’ve managed to contract covid again, but I haven’t found the inclination to take a test. What would I do differently if I do have it, now, anyway? I feel like we might be past that point in our lives.

All in all, probably the worst anniversary celebration we’ve ever had, and hopefully the worst we’ll ever have. But a fringe benefit of a willingness to change bases is that we can celebrate our 10th5 anniversary next year, too. Here’s to that.

Footnotes

1 Because we’re that kind of nerds, we count our anniversaries in base 16 (0x10 is 16), or – sometimes – in whatever base is mathematically-pleasing and gives us a nice round number. It could be our 20th anniversary, if you prefer octal.

2 I’ve been on some disastrous aeroplane journeys before, including one just earlier this year which was supposed to take me from Athens to Heathrow, got re-arranged to go to Gatwick, got delayed, ran low on fuel, then instead had to fly to Stansted, wait on the tarmac for a couple of hours, then return to Gatwick (from which I travelled – via Heathrow – home). But this attempt to get to the Isle of Man was somehow, perhaps, even worse.

3 Those who’ve noticed that we were flying EasyJet might rightly give a knowing nod at this point.

4 The warning to take care not to tread on them is sound legal advice: this particular variety of snail is protected under the Wildlife and Countryside Act 1981!

5 Next year will be our 10th anniversary… in base 17. Eww, what the hell is base 17 for and why does it both offend and intrigue me so?

× × × × × × × × ×

Short-Term Blogging

There’s a perception that a blog is a long-lived, ongoing thing. That it lives with and alongside its author.1

But that doesn’t have to be true, and I think a lot of people could benefit from “short-term” blogging. Consider:

  • Photoblogging your holiday, rather than posting snaps to social media
    You gain the ability to add context, crosslinking, and have permanent addresses (rather than losing eveything to the depths of a feed). You can crosspost/syndicate to your favourite socials if that’s your poison..
Photo showing a mobile phone, held in a hand, being used to take a photograph of a rugged coastline landscape.
Photoblog your holiday and I might follow it, and I’ll do so at my convenience. Put your snaps on Facebook and I almost certainly won’t bother. Photo courtesy ArtHouse Studio.
  • Blogging your studies, rather than keeping your notes to yourself
    Writing what you learn helps you remember it; writing what you learn in a public space helps others learn too and makes it easy to search for your discoveries later.2
  • Recording your roleplaying, rather than just summarising each session to your fellow players
    My D&D group does this at levellers.blog! That site won’t continue to be updated forever – the party will someday retire or, more-likely, come to a glorious but horrific end – but it’ll always live on as a reminder of what we achieved.

One of my favourite examples of such a blog was 52 Reflect3 (now integrated into its successor The Improbable Blog). For 52 consecutive weeks my partner‘s brother Robin blogged about adventures that took him out of his home in London and it was amazing. The project’s finished, but a blog was absolutely the right medium for it because now it’s got a “forever home” on the Web (imagine if he’d posted instead to Twitter, only for that platform to turn into a flaming turd).

I don’t often shill for my employer, but I genuinely believe that the free tier on WordPress.com is an excellent way to give a forever home to your short-term blog4. Did you know that you can type new.blog (or blog.new; both work!) into your browser to start one?

What are you going to write about?

Footnotes

1 This blog is, of course, an example of a long-term blog. It’s been going in some form or another for over half my life, and I don’t see that changing. But it’s not the only kind of blog.

2 Personally, I really love the serendipity of asking a web search engine for the solution to a problem and finding a result that turns out to be something that I myself wrote, long ago!

3 My previous posts about 52 Reflect: Challenge Robin, Twatt, Brixton to Brighton by Boris Bike, Ending on a High (and associated photo/note)

4 One of my favourite features of WordPress.com is the fact that it’s built atop the world’s most-popular blogging software and you can export all your data at any time, so there’s absolutely no lock-in: if you want to migrate to a competitor or even host your own blog, it’s really easy to do so!

×

Werewolves and Wanderer

This post is also available as a video. If you'd prefer to watch/listen to me talk about this topic, give it a look.

This blog post is also available as a video. Would you prefer to watch/listen to me tell you about the video game that had the biggest impact on my life?

Of all of the videogames I’ve ever played, perhaps the one that’s had the biggest impact on my life1 was: Werewolves and (the) Wanderer.2

This simple text-based adventure was originally written by Tim Hartnell for use in his 1983 book Creating Adventure Games on your Computer. At the time, it was common for computing books and magazines to come with printed copies of program source code which you’d need to re-type on your own computer, printing being significantly many orders of magnitude cheaper than computer media.3

Front cover of The Amazing Amstrad Omnibus, by Martin Fairbanks, with its bright yellow text on a red background.
Werewolves and Wanderer was adapted for the Amstrad CPC4 by Martin Fairbanks and published in The Amazing Amstrad Omnibus (1985), which is where I first discovered it.
When I first came across the source code to Werewolves, I’d already begun my journey into computer programming. This started alongside my mother and later – when her quantity of free time was not able to keep up with my level of enthusiasm – by myself.

I’d been working my way through the operating manual for our microcomputer, trying to understand it all.5

Scan of a ring-bound page from a technical manual. The page describes the use of the "INPUT" command, saying "This command is used to let the computer know that it is expecting something to be typed in, for example, the answer to a question". The page goes on to provide a code example of a program which requests the user's age and then says "you look younger than [age] years old.", substituting in their age. The page then explains how it was the use of a variable that allowed this transaction to occur.
The ring-bound 445-page A4 doorstep of a book quickly became adorned with my pencilled-in notes, the way a microcomputer manual ought to be. It’s strange to recall that there was a time that beginner programmers still needed to be reminded to press [ENTER] at the end of each line.
And even though I’d typed-in dozens of programs before, both larger and smaller, it was Werewolves that finally helped so many key concepts “click” for me.

In particular, I found myself comparing Werewolves to my first attempt at a text-based adventure. Using what little I’d grokked of programming so far, I’d put together a series of passages (blocks of PRINT statements6) with choices (INPUT statements) that sent the player elsewhere in the story (using, of course, the long-considered-harmful GOTO statement), Choose-Your-Own-Adventure style.

Werewolves was… better.

Photograph of Dan in his mid-teens, with shoulder-length bleached-blonde hair and wearing a t-shirt with a picture of a snarling wolf, sits in front of a running PC (with its beige case open) on which an external modem is precariously balanced.
By the time I was the model of a teenage hacker, I’d been writing software for years. Most of it terrible.

Werewolves and Wanderer was my first lesson in how to structure a program.

Let’s take a look at a couple of segments of code that help illustrate what I mean (here’s the full code, if you’re interested):

10 REM WEREWOLVES AND WANDERER

20 GOSUB 2600:REM INTIALISE
30 GOSUB 160
40 IF RO<>11 THEN 30

50 PEN 1:SOUND 5,100:PRINT:PRINT "YOU'VE DONE IT!!!":GOSUB 3520:SOUND 5,80:PRINT "THAT WAS THE EXIT FROM THE CASTLE!":SOUND 5,200
60 GOSUB 3520
70 PRINT:PRINT "YOU HAVE SUCCEEDED, ";N$;"!":SOUND 5,100
80 PRINT:PRINT "YOU MANAGED TO GET OUT OF THE CASTLE"
90 GOSUB 3520
100 PRINT:PRINT "WELL DONE!"
110 GOSUB 3520:SOUND 5,80
120 PRINT:PRINT "YOUR SCORE IS";
130 PRINT 3*TALLY+5*STRENGTH+2*WEALTH+FOOD+30*MK:FOR J=1 TO 10:SOUND 5,RND*100+10:NEXT J
140 PRINT:PRINT:PRINT:END

...

2600 REM INTIALISE
2610 MODE 1:BORDER 1:INK 0,1:INK 1,24:INK 2,26:INK 3,18:PAPER 0:PEN 2 
2620 RANDOMIZE TIME
2630 WEALTH=75:FOOD=0
2640 STRENGTH=100
2650 TALLY=0
2660 MK=0:REM NO. OF MONSTERS KILLED

...

3510 REM DELAY LOOP
3520 FOR T=1 TO 900:NEXT T
3530 RETURN
Locomotive BASIC had mandatory line numbering. The spacing and gaps (...) have been added for readability/your convenience.

What’s interesting about the code above? Well…

  • The code for “what to do when you win the game” is very near the top. “Winning” is the default state. The rest of the adventure exists to obstruct that. In a language with enforced line numbering and no screen editor7, it makes sense to put fixed-length code at the top… saving space for the adventure to grow below.
  • Two subroutines are called (the GOSUB statements):
    • The first sets up the game state: initialising the screen (2610), the RNG (2620), and player characteristics (26302660). This also makes it easy to call it again (e.g. if the player is given the option to “start over”). This subroutine goes on to set up the adventure map (more on that later).
    • The second starts on line 160: this is the “main game” logic. After it runs, each time, line 40 checks IF RO<>11 THEN 30. This tests whether the player’s location (RO) is room 11: if so, they’ve exited the castle and won the adventure. Otherwise, flow returns to line 30 and the “main game” subroutine happens again. This broken-out loop improving the readability and maintainability of the code.8
  • A common subroutine is the “delay loop” (line 3520). It just counts to 900! On a known (slow) processor of fixed speed, this is a simpler way to put a delay in than relying on a real-time clock.

The game setup gets more interesting still when it comes to setting up the adventure map. Here’s how it looks:

2680 REM SET UP CASTLE
2690 DIM A(19,7):CHECKSUM=0
2700 FOR B=1 TO 19
2710   FOR C=1 TO 7
2720     READ A(B,C):CHECKSUM=CHECKSUM+A(B,C)
2730   NEXT C:NEXT B
2740 IF CHECKSUM<>355 THEN PRINT "ERROR IN ROOM DATA":END

...

2840 REM ALLOT TREASURE
2850 FOR J=1 TO 7
2860   M=INT(RND*19)+1
2870   IF M=6 OR M=11 OR A(M,7)<>0 THEN 2860
2880   A(M,7)=INT(RND*100)+100
2890 NEXT J

2910 REM ALLOT MONSTERS
2920 FOR J=1 TO 6
2930   M=INT(RND*18)+1
2940   IF M=6 OR M=11 OR A(M,7)<>0 THEN 2930
2950   A(M,7)=-J
2960 NEXT J
2970 A(4,7)=100+INT(RND*100)
2980 A(16,7)=100+INT(RND*100)

...

3310 DATA   0,  2,  0,  0,  0,  0,  0
3320 DATA   1,  3,  3,  0,  0,  0,  0
3330 DATA   2,  0,  5,  2,  0,  0,  0
3340 DATA   0,  5,  0,  0,  0,  0,  0
3350 DATA   4,  0,  0,  3, 15, 13,  0
3360 DATA   0,  0,  1,  0,  0,  0,  0
3370 DATA   0,  8,  0,  0,  0,  0,  0
3380 DATA   7, 10,  0,  0,  0,  0,  0
3390 DATA   0, 19,  0,  8,  0,  8,  0
3400 DATA   8,  0, 11,  0,  0,  0,  0
3410 DATA   0,  0, 10,  0,  0,  0,  0
3420 DATA   0,  0,  0, 13,  0,  0,  0
3430 DATA   0,  0, 12,  0,  5,  0,  0
3440 DATA   0, 15, 17,  0,  0,  0,  0
3450 DATA  14,  0,  0,  0,  0,  5,  0
3460 DATA  17,  0, 19,  0,  0,  0,  0
3470 DATA  18, 16,  0, 14,  0,  0,  0
3480 DATA   0, 17,  0,  0,  0,  0,  0
3490 DATA   9,  0, 16,  0,  0,  0,  0
Again, I’ve tweaked this code to improve readability, including adding indention on the loops, “modern-style”, and spacing to make the DATA statements form a “table”.

What’s this code doing?

  • Line 2690 defines an array (DIM) with two dimensions9 (19 by 7). This will store room data, an approach that allows code to be shared between all rooms: much cleaner than my first attempt at an adventure with each room having its own INPUT handler.
  • The two-level loop on lines 2700 through 2730 populates the room data from the DATA blocks. Nowadays you’d probably put that data in a separate file (probably JSON!). Each “row” represents a room, 1 to 19. Each “column” represents the room you end up at if you travel in a given direction: North, South, East, West, Up, or Down. The seventh column – always zero – represents whether a monster (negative number) or treasure (positive number) is found in that room. This column perhaps needn’t have been included: I imagine it’s a holdover from some previous version in which the locations of some or all of the treasures or monsters were hard-coded.
  • The loop beginning on line 2850 selects seven rooms and adds a random amount of treasure to each. The loop beginning on line 2920 places each of six monsters (numbered -1 through -6) in randomly-selected rooms. In both cases, the start and finish rooms, and any room with a treasure or monster, is ineligible. When my 8-year-old self finally deciphered what was going on I was awestruck at this simple approach to making the game dynamic.
  • Rooms 4 and 16 always receive treasure (lines 29702980), replacing any treasure or monster already there: the Private Meeting Room (always worth a diversion!) and the Treasury, respectively.
  • Curiously, room 9 (the lift) defines three exits, even though it’s impossible to take an action in this location: the player teleports to room 10 on arrival! Again, I assume this is vestigal code from an earlier implementation.
  • The “checksum” that’s tested on line 2740 is cute, and a younger me appreciated deciphering it. I’m not convinced it’s necessary (it sums all of the values in the DATA statements and expects 355 to limit tampering) though, or even useful: it certainly makes it harder to modify the rooms, which may undermine the code’s value as a teaching aid!
Map showing the layout of the castle in video game "Werewolves and the Wanderer". Entering from outside the castle, to the West, the player must progress through the ground floor, up the stairwell in the Inner Hallway, into the Lift, and then East to the exit, but there are several opportunities to diverge from this path and e.g. explore the dungeons or various dead ends on the ground or first floors.
By the time I was 10, I knew this map so well that I could draw it perfectly from memory. I almost managed the same today, aged 42. That memory’s buried deep!

Something you might notice is missing is the room descriptions. Arrays in this language are strictly typed: this array can only contain integers and not strings. But there are other reasons: line length limitations would have required trimming some of the longer descriptions. Also, many rooms have dynamic content, usually based on random numbers, which would be challenging to implement in this way.

As a child, I did once try to refactor the code so that an eighth column of data specified the line number to which control should pass to display the room description. That’s a bit of a no-no from a “mixing data and logic” perspective, but a cool example of metaprogramming before I even knew it! This didn’t work, though: it turns out you can’t pass a variable to a Locomotive BASIC GOTO or GOSUB. Boo!10

An experimental program being run that attempts to GOSUB a variable, failing with a syntax error on the relevant line.
In hindsight, I could have tested the functionality before I refactored with a very simple program, but I was only around 10 or 11 and still had lots to learn!

Werewolves and Wanderer has many faults11. But I’m clearly not the only developer whose early skills were honed and improved by this game, or who hold a special place in their heart for it. Just while writing this post, I discovered:

A decade or so later, I’d be taking my first steps as a professional software engineer. A couple more decades later, I’m still doing it.

And perhaps that adventure -the one that’s occupied my entire adult life – was facilitated by this text-based one from the 1980s.

Footnotes

1 The game that had the biggest impact on my life, it might surprise you to hear, is not among the “top ten videogames that stole my life” that I wrote about almost exactly 16 years ago nor the follow-up list I published in its incomplete form three years later. Turns out that time and impact are not interchangable. Who knew?

2 The game is variously known as Werewolves and Wanderer, Werewolves and Wanderers, or Werewolves and the Wanderer. Or, on any system I’ve been on, WERE.BAS, WEREWOLF.BAS, or WEREWOLV.BAS, thanks to the CPC’s eight-point-three filename limit.

3 Additionally, it was thought that having to undertake the (painstakingly tiresome) process of manually re-entering the source code for a program might help teach you a little about the code and how it worked, although this depended very much on how readable the code and its comments were. Tragically, the more comprehensible some code is, the more long-winded the re-entry process.

4 The CPC’s got a fascinating history in its own right, but you can read that any time.

5 One of my favourite features of home microcomputers was that seconds after you turned them on, you could start programming. Your prompt was an interface to a programming language. That magic had begun to fade by the time DOS came to dominate (sure, you can program using batch files, but they’re neither as elegant nor sophisticated as any BASIC dialect) and was completely lost by the era of booting directly into graphical operating systems. One of my favourite features about the Web is that it gives you some of that magic back again: thanks to the debugger in a modern browser, you can “tinker” with other people’s code once more, right from the same tool you load up every time. (Unfortunately, mobile devices – which have fast become the dominant way for people to use the Internet – have reversed this trend again. Try to View Source on your mobile – if you don’t already know how, it’s not an easy job!)

6 In particular, one frustration I remember from my first text-based adventure was that I’d been unable to work around Locomotive BASIC’s lack of string escape sequences – not that I yet knew what such a thing would be called – in order to put quote marks inside a quoted string!

7 “Screen editors” is what we initially called what you’d nowadays call a “text editor”: an application that lets you see a page of text at the same time, move your cursor about the place, and insert text wherever you feel like. It may also provide features like copy/paste and optional overtyping. Screen editors require more resources (and aren’t suitable for use on a teleprinter) compared to line editors, which preceeded them. Line editors only let you view and edit a single line at a time, which is how most of my first 6 years of programming was done.

8 In a modern programming language, you might use while true or similar for a main game loop, but this requires pushing the “outside” position to the stack… and early BASIC dialects often had strict (and small, by modern standards) limits on stack height that would have made this a risk compared to simply calling a subroutine from one line and then jumping back to that line on the next.

9 A neat feature of Locomotive BASIC over many contemporary and older BASIC dialects was its support for multidimensional arrays. A common feature in modern programming languages, this language feature used to be pretty rare, and programmers had to do bits of division and modulus arithmetic to work around the limitation… which, I can promise you, becomes painful the first time you have to deal with an array of three or more dimensions!

10 In reality, this was rather unnecessary, because the ON x GOSUB command can – and does, in this program – accept multiple jump points and selects the one referenced by the variable x.

11 Aside from those mentioned already, other clear faults include: impenetrable controls unless you’ve been given instuctions (although that was the way at the time); the shopkeeper will penalise you for trying to spend money you don’t have, except on food, presumably as a result of programmer laziness; you can lose your flaming torch, but you can’t buy spares in advance (you can pay for more, and you lose the money, but you don’t get a spare); some of the line spacing is sometimes a little wonky; combat’s a bit of a drag; lack of feedback to acknowledge the command you enterted and that it was successful; WHAT’S WITH ALL THE CAPITALS; some rooms don’t adequately describe their exits; the map is a bit linear; etc.

× × × × ×

Local Expert

At school, our 9-year-old is currently studying the hsitory of human civilization from the late stone age through to the bronze age. The other week, the class was split into three groups, each of which was tasked with researching a different piece of megalithic architecture:

  • One group researched Stonehenge, because it’s a pretty obvious iconic choice
  • Another group researched the nearby Rollright Stones, which we’ve made a family tradition of visiting on New Year’s Day and have dragged other people along to sometimes
  • The final group took the least-famous monument, our very own local village henge The Devil’s Quoits
Dan, wearing a black t-shirt with the words "Let's make the web a better place" on, sits with his back to a standing stone. Four more standing stones can be seen stretching away into the bakground, atop a flowery meadow and beneath a slightly cloudy but bright sky.
Love me some ancient monuments, even those that are perhaps less authentically-ancient than others.

And so it was that one of our eldest’s classmates was searching on the Web for information about The Devil’s Quoits when they found… my vlog on the subject! One of them recognised me and said, “Hey, isn’t that your Uncle Dan?”1

On the school run later in the day, the teacher grabbed me and asked if I’d be willing to join their school trip to the henge, seeing as I was a “local expert”. Naturally, I said yes, went along, and told a bunch of kids what I knew!

A group of schoolchildren in a mixture of white and blue shirts, and with most wearing sunhats, sit on a pile of rocks alongside a ring ditch and listen intently to Dan.
I’ve presented to much-larger audiences before on a whole variety of subjects, but this one still might have been the most terrifying.

I was slightly intimidated because the class teacher, Miss Hutchins, is really good! Coupled with the fact that I don’t feel like a “local expert”2, this became a kick-off topic for my most-recent coaching session (I’ve mentioned how awesome my coach is before).

A young girl, her hair wild, sits at a kitchen table with a laptop and a homework book, writing.
I originally thought I might talk to the kids about the Bell Beaker culture people who are believed to have constructed the monument. But when I pitched the idea to our girl she turned out to know about as much about them as I did, so I changed tack.

I eventually talked to the class mostly about the human geography aspects of the site’s story. The area around the Devil’s Quoits has changed so much over the millenia, and it’s a fascinating storied history in which it’s been:

  • A prehistoric henge and a circle of 28 to 36 stones (plus at least one wooden building, at some point).
  • Medieval farms, from which most of the stones were taken (or broken up) and repurposed.
  • A brief (and, it turns out, incomplete) archeological survey on the remains of the henge and the handful of stones still-present.
  • A second world war airfield (a history I’ve also commemorated with a geocache).
  • Quarrying operations leaving a series of hollowed-out gravel pits.
  • More-thorough archeological excavation, backed by an understanding of the cropmarks visible from aircraft that indicate that many prehistoric people lived around this area.
  • Landfill use, filling in the former gravel pits (except for one, which is now a large lake).
  • Reconstruction of the site to a henge and stone circle again.3
Ultrawide panoramic picture showing a full circle of standing stones under a clear sky. The dry grass has been cut back, and the remains of a campfire can be seen.
It doesn’t matter to me that this henge is more a modern reconstruction than a preserved piece of prehistory. It’s still a great excuse to stop and learn about how our ancestors might have lived.

It turns out that to be a good enough to pass as a “local expert”, you merely have to know enough. Enough to be able to uplift and inspire others, and the humility to know when to say “I don’t know”.4

That’s a lesson I should take to heart. I (too) often step back from the opportunity to help others learn something new because I don’t feel like I’m that experienced at whatever the subject is myself. But even if you’re still learning something, you can share what you’ve learned so far and help those behind you to follow the same path. I’m forever learning new things, and I should try to be more-open to sharing “as I learn”. And to admit where I’ve still got a long way to go.

Footnotes

1 Of course, I only made the vlog because I was doing a videography course at the time and needed subject matter, and I’d recently been reading a lot about the Quoits because I was planning on “hiding” a virtual geocache at the site, and then I got carried away. Self-nerdsniped again!

2 What is a local expert? I don’t know, but what I feel like is just a guy who read a couple of books because he got distracted while hiding a geocache!

3 I’ve no idea what future archeologists will make of this place when they finda reconstructed stone circle and then, when they dig nearby, an enormous quantity of non-biodegradable waste. What was this strange stone circle for, they’ll ask themselves? Was it a shrine to their potato-based gods, to whom they left crisp packets as a sacrifice?

4 When we’re talking about people from the neolithic, saying “I don’t know” is pretty easy, because what we don’t know is quite a lot, it turns out!

× × × ×

Making a Home of Each Other (The Eggs)

This is a repost promoting content originally published elsewhere. See more things Dan's reposted.

I dislike recipe posts that, before you get anywhere near the list of ingredients, tell you what feels like the entire life story of the author and their family.

“Every morning my mother would warm up the stove, and this was a wood-fired stove back in the day, and make these. We lived in Minnosota…” I don’t care. I can’t begin to tell you how much I don’t care. Just tell me how to make the damn muffins ‘cos the picture’s got me drooling.

This is different. This is the latest and so-far only exception. This, I care about:

When we moved into a house of our own, I bought us a tea kettle that whistled in harmony when it boiled. Rent was cheap, and we were happy. Those were the days of sweet potato hash, wilted kale, and increasingly exotic baked goods. There was the Me-Making-You-Tea-in-the-Morning-Because-You-Hated-Mornings Phase, but also the You-Making-Me-Tea-in-the-Morning-Because-You-Went-to-Work-at-5am Phase.

Lucy tells a story so rich and personal about her and her wife’s experience of life, cohabitation, food, and the beauty of everyday life. I haven’t even read the recipe for The Eggs, even though it sounds pretty delicious.

Over the years I’ve found words for people who have done what we’re doing now, but I’ve also found a deeper truth: our queer community doesn’t demand a definition. They know that chili oil can change a life just as much as a marriage. That love is in the making and unmaking of beds. The candlelit baths. The laughter. The proffered feast that nourishes.

Queerness makes room within it for these relationships, or rather: queerness spirals outward. It blooms and embraces. That is the process by which we broaden our palates, welcoming what might seem new to us, but which is actually older than we know.

It’s a great reminder about focussing on what’s important. About the value of an ally whether the world’s working with you or against you. And, of course, about how every relationship, no matter what shape, size, or form, can enjoy a little more queering once in a while. Go read it.