TarotFS from Grimm Labs

or Why I Stopped Worrying and Wrote A Divination System Considered Harmful

Trope subtitles are the worst.

In this post I’ll be talking about TarotFS from Grimm Labs, my divination system filesystem for Plan 9. In part, I’m using this as an exercise to figure out what parts are going to make the cut for my EMFcamp talk.

Divination systems

I’ll hopefully talk in depth about this at another time in another place, but the tl;dr is that my first practical foray into occultism was through divination systems. I can’t remember if I got a tarot deck first (thanks tj!) or a set of Elder Futhark runes (thanks Joey and Mae!), but the value was pretty easy to see from the point of view of a person struggling so much with their emotional and mental health during COVID. I was able to pick out images at random and see how they resonated with me, and what the historical symbolism was around these archetypes that could be plumbed further to find more interpretations and develop my own associations.

Looking back it makes a lot of sense - people think in terms of narratives, allegories and symbols, so having these arrayed out in front of you, drawn at random such as they’re “presented by the universe” (a trope deeply embedded in culture and religion) strikes a chord and drives engagement with what is essentially a system for introspection and decision making with an RNG (random number generator) built in.

Where did the idea come from?

Okay, so one of my biggest projects and the one I talk about the most if you know me in person (or on IRC or on gridchat) is TarotFS. It’s really the first use case for Plan 9 from Bell Labs (or the fork 9front which I’m going to lovingly refer to as ⑨ from here on in, despite the fact that it completely fucks with vt not to use a single width character but here we are) that I had which was really an occult system. And ⑨ really suits the use-case. One of the most famous if not totally fundamental ideas behind Plan 9 is putting the Unix concept of “everything is a file” to shame, showing it as the true hyperbole that it is. In ⑨ just about everything actually is a file, and the real case-in-point here is the 9p protocol. When you access the web, you can do that using webfs using the 9p protocal bridging with HTTP. Want to make a TCP connection? Do the same. Want to access a file? You guessed it, use 9p.

So when I started playing around with ⑨, I was really interested in this concept and so I started playing with webFS and really liked how it effectively meant you could leave your HTTP transactions on disk. Around the same time, I was also reading Techniques of High Magic by Francis King and Stephen Skinner (or at least that’s how I remember it? It could be that the book’s right in front of me…) and about various divination systems including geomancy and runecasting. Tarot stood out because it’s well known in Western society as being… well… goth as fuck quite frankly, and I was in a place where archetype systems were incredibly appealing to me. Possibly more on that elsewhere or later or something.

At any rate, the mode of transaction with webfs really struck a chord:

† cd /mnt/web/
† ls
clone
ctl
† cat clone
0
† cd 0
† ls
body
ctl
parsed
postbody
† echo 'url https://grimmwa.re' > ctl
† cat parsed/url
https://grimmwa.re/† 
† 
† cat body
<html>
<head>
  <title>(((GRIMMWA.RE</title></head>
    <meta charset="UTF-8">
    ... and so on ...

The workflow goes like this: 1. “clone” a new session by reading the clone file. This will return an integer which will be the name of the directory which will be created for your session (which you can see we cd into. 2. Enter any parameters for the session into the “control” file for the session, ctl - in this case we’re entering the (somewhat necessary) url parameter. We can then verify that we entered it correctly by reading out the parsed/url file. 3. Read out the request body by reading the body file! When we do this, it looks like we’re reading a file from disk, but actually we’re dispatching the HTTP request that we’ve prepared by creating the session and telling it the URL, and this is essentially the response body!

Like I say, you have the transaction apparently “on disk” and are able to set parameters and headers and the like by writing to files and reading responses by reading them. A query and a response, all effectively encoded statefully in a way that you can sensibly manage and retrospect on.

Something twigged with me here, because it was at the intersection of ideas - I was exploring ⑨ which I was clearly going to do from a participatory viewpoint, and I was also beginning to start to understand some of the less supernatural value in divination systems, and frankly I understand by doing and I needed a distraction.

And somewhere in here is where TarotFS was born as a concept.

How does it work then?

Yes, we’re doing this in skip-to-the-end style, mostly because I didn’t make enough notes on the way so I can’t actually remember what I did and what problems I hit and why. A big warning here is that the code’s a bit of a trash fire, so I’m going to present you with a couple of things: a TarotFS session, which I’ll use to explain a high level idea of the basic usage and rationale, and a manpage which I’ll dump at the end.

Example Session

† tarot -p definitions -dkP
definitions/Smith-Waite

We launch tarotFS with a definitions directory, and descriptions (-d), keywords (-k) and paths (-P) enabled in the card output. This is pretty much the whole selection as we’ll see in a bit! Essentially these three options only do anything if you specify a definitions directory as they simply read data for the relevant card from it. TarotFS helpfully then tells you which subdirectory of definitions it’s using because I haven’t fixed that old shitty debug output yet!

† cd /mnt/tarot
† ls
clone

/mnt/tarot is the default mountpoint - not much to see here yet apart from the clone file!

† cat clone
1
deckstyle Smith-Waite
definitions_path definitions/Smith-Waite
keywords 1
description 1
orientation on

When we read the file it does what webfs does - creates a new session for us! This file actually redirects reads and writes from the ctl file from the new spread, so we could actually have cloned the session by overriding a setting with a write instead if we really knew what we were doing.

Helpfully, you can see that when I hacked in the orientation parameter recently I didn’t check my boolean convention and have instead used “off” and “on”. Some parts of tarotFS are wildly inconsistent, and for my part I will actually fix this. At some point. Maybe.

† cd 1
† ls
ctl
draw
remaining
shuffle

Other than the ctl file, it should be pretty much immediately obvious what each of these files will do if you read them!

† echo spread past present future advice > ctl
† cat ctl
1
deckstyle Smith-Waite
spread past present future advice
definitions_path definitions/Smith-Waite
keywords 1
description 1
orientation on

For the sake of showing off we’re going to specify the names of some of the cards which will be drawn. By default, the card filenames are just incrementing numbers, but these arguments will make so that we get names e.g. 1:past, 2:present and so on. Part of the advantage of this is you can’t decide on one spread and then change your mind half way through when you get a card you don’t like ;)

† cat remaining
78

78 cards in a standard Rider-Smith-Waite deck!

† cat draw
Four of Pentacles
reversed
path  definitions/Smith-Waite/Pentacles/Four/
UPRIGHT: Saving money, security, conservatism, scarcity, control.
REVERSED: Over-spending, greed, self-protection.


The Four of Pentacles shows a man sitting on a stool, beyond the
boundaries of his hometown. His arms are wrapped tightly around a coin
as if he fears he may lose it if he loosens his grip. He balances
... <SNIP> ...

So what we’re seeing here is the contents of one of the cards being drawn, containing the orientation (on by default, cards can be upright or reversed), the path of the card definition that the keywords and definition text are coming from, and then the keywords followed by the definitions.

The path is useful if you want to do things like ship the card images! It makes it simple to script a setup to view the spread graphically!

† cat remaining
77

Just in case you doubted that these are actually being drawn from a deck…

At any rate, that was a bit more verbose than we care about right now so we can toggle the descriptions and the orientations off:

† echo description > ctl
† echo orient > ctl
† cat ctl
1
deckstyle Smith-Waite
spread past present future advice
definitions_path definitions/Smith-Waite
keywords 1
description 0
orientation off

Now when we draw, we’ll just get the path and the keywords:

† cat draw
Seven of Cups
path  definitions/Smith-Waite/Cups/Seven/
UPRIGHT: Opportunities, choices, wishful thinking, illusion.
REVERSED: Alignment, personal values, overwhelmed by choices.

† cat draw
Ⅷ: Justice
path  definitions/Smith-Waite/Major/Justice/
UPRIGHT: Justice, fairness, truth, cause and effect, law
REVERSED: Unfairness, lack of accountability, dishonesty

† cat draw
ⅩⅩ: Judgement
path  definitions/Smith-Waite/Major/Judgement/
UPRIGHT: Judgement, rebirth, inner calling, absolution
REVERSED: Self-doubt, inner critic, ignoring the call

So what about the card names and the state on disk I was talking about? Well here we can see that the output can be read again by reading out the respective numbered file:

† ls
1:past
2:present
3:future
4:advice
ctl
draw
remaining
shuffle
† cat remaining
74

Features and misfeatures

A lot of this has grown organically, so there are a few warts, gotchas and implementation details:

  1. Definitions are user-supplied because I ripped mine off from the internet so they’re not mine to redistribute
  2. A few “deckstyles”, however, are supplied (see /lib/tarot/decks)! The default is Smith-Waite (as in the famous Rider-Waite deck, for whom Rider is a publisher and Pamela Coleman-Smith is the largely unknown artist who actually did most of the work), but we also have the Marseille deck which uses slightly different card and suit names, and also Elder-Futhark which is me showing off that actually technically you can use TarotFS to do runecasting as well:
   † tarot
   † echo deckstyle Elder-Futhark > /mnt/tarot/clone
   † cd /mnt/tarot/1
   † cat draw
   ᚺ
   reversed
  1. Some arguments and commands are a little inconsistent or poorly documented - sorry about that!
  2. I’ve also been working on a fun script for rendering the spread in a rio window, with a tarot cloth as a background and everything, but it requires a patched version of rio and also a patch which I’ve written for page to auto-resize the image which I’ve not gotten round to documenting yet (and this post is getting pretty long anyway!)

Future work

I’ve got a few plans here. For one, I’d like to be able to use arbitrary bitstreams as an entropy source given that none of the randomness here is used for cryptographic purposes, so it would be great to be drawing the randomness from the processes of the universe (like gridchat!). Some more work to improve the consistency of the interface and documentation would certainly be good, and I’d also like to do more work on convenience scripts for graphically rendering in Rio for example. Finally, a good default definitions source which is appropriately licensed for redistribution would also be great.

Appendix: Manpage

This needs updating! I’ve not actually added the instructions on using the orient command and a couple of other things, but fixing instead of writing is how I end up never writing things.



     TAROT(4)                                                 TAROT(4)

     NAME
          tarot - tarot card filesystem

     SYNOPSIS
          tarot [ -DdkPh ] [ -p definitions ] [ -m mtpt ]

     DESCRIPTION
          Tarotfs presents a file system interface to generating and
          interacting with tarot spreads.  Tarotfs mounts itself at
          mtpt (default /mnt/tarot).  The -D flag enables 9P debug
          prints and other debug files and -h displays the short list
          of options.

          Tarotfs also optionally supports displaying keywords and
          definitions when reading files for the drawn cards by pro-
          viding a definitions directory with the flag -p wherein the
          directory structure should follow the structure:
          Name/Suit/Card/ and contain the files keywords and
          description, both of which can be freeform text files. The
          keywords should ideally contain a short list of keywords to
          associate with both the upright and reversed configuration
          of each file. The Name/Suit/Card naming convention has the
          following restrictions:

          Name This can be any directory name, and isn't actually uti-
               lized (yet).  Currently tarotfs will just pick the
               first named pack inside the supplied definitions direc-
               tory.

          Suit and Card
               These must match exactly the form given in `suits`, and
               `major` and `minor` for each deck definition, but with
               anything preceding and including a single colon charac-
               ter stripped, and all spaces removed. For example: "I:
               The Magician" should map to "TheMagician".   Any major
               arcana cards will always map to the suit name `Major`.

          The flags -dkP enable displaying the definitions, keywords
          and definition paths when reading the card files respec-
          tively when -p is supplied.

          The top level contains the file clone, and the directory
          card_library, the latter of which contains a tree of all the
          available cards.

          Reading or writing to the clone file creates a new numbered
          subdirectory for a new tarot spread and redirects the opera-
          tion to the ctl file at the top of the directory.

          Each "spread" directory contains the files: ctl, draw,
          shuffle and remaining.

          The ctl file is used to control the various settings for the
          spread by writing commands and arguments to the file:

          spread
               Sets the names for the drawn cards in addition to their
               numbers.  Arguments are space separated.

          keywords
               Toggles whether keywords are displayed when reading
               card files

          descriptions
               Toggles whether descriptions are displayed when reading
               card files

          paths
               Toggles whether definition paths are displayed when
               reading card files

          Reading the ctl file will return the spread number (matching
          the spread's directory) on the first line. If a deck defini-
          tion was supplied, it will also print on the following lines
          the attributes deckpath,keywords,description each followed
          by a space and then the path of the deck definition and the
          booleans for whether keywords and descriptions are enabled
          for the spread respectively.

          Reading draw draws a card, reading remaining shows how many
          cards are remaining in the deck and shuffle will reshuffle
          the deck.

          If the -D option is passed on startup, there will also be
          the deck file which outputs the integer representations of
          all the cards in the deck along with their number order.
          This is currently only used for debugging purposes.

     EXAMPLE
          The following shows a simple example of cloning a new
          spread, setting the spread name and then drawing all the
          cards from the spread.

          † tarot
          † ls /mnt/tarot
          /mnt/tarot/card_library
          /mnt/tarot/clone
          † cat /mnt/tarot/clone
          1
          † cd /mnt/tarot/1
          † ls
          ctl
          draw
          remaining
          † echo spread past present future advice > ctl
          † for(i in `{seq 1 4}) cat draw
          Four of Pentacles
          upright
          Ⅱ: The High Priestess
          reversed
          King of Wands
          upright
          Seven of Cups
          reversed
          † ls
          1:past
          2:present
          3:future
          4:advice
          ctl
          draw
          remaining
          † cat remaining
          74

     SOURCE
          https://git.sr.ht/~grimmware/tarotfs