Using Emacs While Running Online Games

These Are The Tools I Use for Table Top Gaming

Any sufficiently advanced hobby is indistinguishable from work.

The epigraph of unknown origin invokes one of the three famous laws of the future. Below is the original:

Any sufficiently advanced technology is indistinguishable from magic.

, Hazards of Prophecy: The Failure of Imagination


On Sunday mornings, I’ve been facilitating a Stars without Number campaign. You can read about it in my Campaign: New Vistas in the Thel Sector series.

While I prefer in-person gaming, I’m coming around to having access to my work tools while running a game.

Tools of my Hobby

Earlier, I wrote gm-notepad and a gm-notepad data-set for Stars without Number. Truth be told, I used the tool once while running a face to face game. It was alright. The tool worked at the command-line level which hindered its utility. At the time, Alex Schroeder said he would just use Emacs for such a thing. I wasn’t using emacs in , so that path felt prohibitive; And I certainly wasn’t going to make a full-blow application out of gm-notepad. I don’t regret the time I spent building the tool. I learned more about low-level tools, tokenization, refactoring, and ePubs.

, I use the following tools for the Stars without Number game:

Online Video
Either Zoom or Google Meet; I really just need audio.
A highly configurable text-editor.
Org Mode
An organizational framework built into Emacs.
Org Roam
An organizational module that brings aspects of Zettelkasten.
Command line tools for generating random Stars Without Number results.
Custom Emacs Functions
The tools that I wrote.

Let’s dive into each of those components.

I’ve been writing code for at least 30 years, however, this is my first year of Elisp: dialect of Lisp used in GNU Emacs (Elisp 🔍) and Emacs. Several years ago, I poked around Lisp and Clojure, but I didn’t have a compelling reason to learn them.

Online Video

When running a game on-line, I don’t use a virtual table-top nor a dice roller. Instead we go for a Theater of the Mind approach. I describe the situation and relative positioning (if applicable). The players ask for clarification, and we talk it out. Players also roll physical dice and tell me the results.

I find the virtual table tops to interfere with the kind of play I’m looking for. I don’t want to focus on moving pieces on a board; I reserve that for some of my face to face play. Instead I want to focus on the fiction we’re producing. And yes, a map can help facilitate that, however spending t


This is my text editor of choice. I use it for writing software, blogging, taking meeting notes, and just about anything text based.

Over on Reddit, someone asked What’s your job? What’s your daily emacs workflow?

The responses surprised me. Software developers and scientists use Emacs. But so do lawyers, nurses, and authors. It makes sense, Emacs is a great tool for consolidating your text-based work.

Org Mode

When talking about why I chose Emacs, I wrote a short section about Org Mode.

For the purposes of my campaign notes, I don’t use much of the Calendar and Agenda feature. Instead I use the org-mode syntax and publishing features.

org-mode syntax is similar to Markdown, but it’s the tools built on top of org-mode that make it shine.

First, I learned about Comment Lines. I write Referee notes as comments. These comments won’t make their way to my blog and public session reports.

Second, I use org-export-dispatch to export the org file to Markdown. My blogging engine, Hugo (Hugo 🔍), supports org as a valid extension, but for historical reasons everything I write for the blog is in Markdown. I may make the switch, but for now am keeping that separation. The export skips over comments, and leaves me a mostly ready to publish markdown file.

Third, I can use the +INCLUDE directive. Per the Include Files documentation, I can use +INCLUDE to aggregate multiple files into a single file export.

For example, I could create an org file for a single encounter that I plan. In that encounter, I might have Referee notes, Read Aloud text, Design Notes, as well as play throughs of that encounter. If I wanted to generate a “publishable” adventure, I could use +INCLUDE to target just the Notes and Read Aloud text. It’s the versatility of composition that I love.

Org Roam

I also wrote a section about Org Roam. For my campaign, I make extensive use of org-roam.

In essence, org-roam provides tooling to build a campaign wiki.

While I’m taking notes, either before, during, or after a session, I can invoke org-roam-insert (via CMD+i) to quickly find or create a new note. With that note org-roam creates a link at the point of my cursor.

org-roam also queries for backlinks. Let’s say I’m looking at the note for “Vern Schultz”, a player character in the game. I can see all notes that have a link to Vern Schultz. That’s super handy.

It also ships org-roam-server, which provides a graphical view of the network graph of notes.


SWNT is a command line tool written in go-lang. It provides a means of generating results from the random tables in the free version of Stars without Number.

I used swnt to generate the initial Thel Sector. It’s output is text-based and by extension highly portable. I’ve moved it around, repurposed it, and ran scripts against the initial sector output.

While running a Stars without Number game, I keep a terminal open for swnt. Most often, I use it to generate the name of an Non-Player Character (NPC 🔍).

Custom Emacs Functions

I wrote swn-npc, an interactive Emacs function that pipes the swnt new npc output into a file and adds a link to that file at the point of my cursor. This behaves similar to org-roam-insert, except instead of attempting to find an NPC, it just makes one.

In other words, when I need a random character, I run this function to get a random person’s name that links to their details. I may or may not use those details, but they are there for reference.

See the Emacs Code

You can also see this code over on Github

(defun thel-sector-filename (title)
  "Convert the given TITLE to a filename.

The filename is conformant to my org-roam capture templates."
  (f-join org-directory
           (format-time-string "%Y%m%d---")
           (s-snake-case title) ".org")))

;; TODO - Can this be shifted to an org-roam capture template?
(defun swn-npc (culture &optional)
  "Capture a random `swnt' npc of the prompted CULTURE.

This function writes to the as-of-now hard-coded Thel Sector
project in my org-directory."

  ;; Prompt for a culture that will be used as the basis for the
  ;; random name.
  (interactive (list
                 "Culture: " '(
                               ("Arabic" 1)
                               ("Chinese" 2)
                               ("English" 3)
                               ("Greek" 4)
                               ("Indian" 5)
                               ("Japanese" 6)
                               ("Latin" 7)
                               ("Nigerian" 8)
                               ("Russian" 9)
                               ("Spanish" 10)))))
  (let* (
         ;; Get a list of the lines output from
         ;; swnt's command.
         (npc-string-list (split-string-and-unquote
                             "swnt new npc "
                             "--is-patron "
                             "--culture "
         ;; Extract the NPCs name
         (npc-name (string-trim
                     (car npc-string-list))))
         ;; Build the document's body, conforming
         ;; to org-mode format.
         (body (concat
                "#+title: " npc-name
                "#+roam_tags: npc"
                "\n\n* " npc-name
                "\n\n" (string-join
                        npc-string-list "\n")))
         ;; Get the path to the file
         (fpath (thel-sector-filename npc-name)))

    ;; Write the body to the file at the FPATH.
    (write-region body nil fpath nil nil nil t)

    ;; Insert at point an org-mode link to
    ;; the randomly generated NPC.
    (insert (concat
             "[[file:" fpath "]["
             npc-name "]]"))

    ;; Rebuild the org-roam cache, as I've just added an NPC.  This is
    ;; a kludge, as I'm treating org-roam as a black box.  My
    ;; preference would be to avoid rebuilding the cache, but instead
    ;; rely on the inner workings of org-roam to do this work.  If I
    ;; go the path of an org-roam capture template, that would work.

;; Given that I'm in an org-mode context, then the following kbd
;; combination will prompt to generate a random SWN npc.
(define-key org-mode-map (kbd "C-c s n") 'swn-npc)
A Random NPC from swnt

Below is an org-mode file, built from the swnt output and the swn-npc function.

#+title: Dmitri Sikorski
#+roam_tags: npc

* Dmitri Sikorski

Dmitri Sikorski              :
Culture                      : Russian
Gender                       : Other
Age                          : Middle-aged or elderly
Background                   : The local underclass or poorest natives
Role in Society              : Military, soldier, enforcer, law officer
Biggest Problem              : Drug or behavioral addiction
Greatest Desire              : They want fame and glory
Most Obvious Character Trait : Devotion to a cause
Hooks                        :
Initial Manner               : Somewhat intoxicated by recent indulgence
Default Deal Outcome         : They firmly intend to actively betray the PCs
Motivation                   : Greed for wealth and indulgent riches
Want                         : Retrieve a lost or stolen object
Power                        : They control the use of large amounts of violence
Hook                         : Visible signs of drug use
Reaction Roll Results        : Positive, potentially cooperative with PCs
Patron                     :
Patron Eagerness to Hire   : Desperate, might offer what they can’t pay
Patron Trustworthiness     : They’ll pay without quibbling
Basic Challenge of the Job : Get proof of some misdeed
Main Countervailing Force  : The job is spectacularly illegal
Potential Non-Cash Rewards : Information the PCs need
Complication to the Job    : Critical gear will fail partway through

An Example Walkthrough of My Tool Chain

Before a session, I go to my “New Vistas in the Thel Sector Sessions” document; It lists all of the session recaps.

To create a new session document, I use org-roam-insert (via CMD+i) to prompt me to either find or create a document.

I type “New Vistas in the Thel Sector Session NN" where NN is the next session number and hit enter. org-roam didn’t find this file so it creates one. Because I’m creating a new document, I need to pick a template. I choose the “Project > Thel Sector”. I have one template per project. The template defines where I’ll file the document.

I now have a blank document. I start filling out the boilerplate information for a sesson. As a todo, I should probably create a snippet that is a template for my sessions. For now, I usually copy from a previous session.

I save the document, and Emacs writes a link to the new document to the existing “New Vistas in the Thel Sector Sessions” document.

As part of my preparation, I review past sessions, maybe creating new documents or connecting to existing documents. For the upcoming session, I start writing notes. Some notes are only intended for me (e.g. Notes about the off-camera actions of any opposition). Those Referee only notes I mark as comments. Other notes might be “Read Aloud” text or bookkeeping notes.

I might also launch org-roam-server; This creates a web page at http://localhost:8080/ that I can use to explore the links between documents. It’s a quick way to sift through the documents.

During the session, I keep “New Vistas in the Thel Sector Session NN” open. I write down my notes. I’ll use org-roam-insert to connect to documents that come up.

For example, I might write the note “The character’s head over to the Grand Kall Theatre to talk with Suliat Adunola. The bold words are links to corresponding documents.

If I need an NPC, I invoke swn-npc (via Ctrl + c s n), and poof, I get a random PC name along with results from all of the NPC random tables. I might ignore those results or dive into those for further inspiration. As I sift through my NPC notes, I remove any unused or unreferenced aspects of these random NPCs.

Once the sessions over, I go back to my session notes and start converting those notes to a blog post. If I find something interesting that I don’t want in my blog, I’ll mark it as a comment. I’ll also read through and look for any “notable” things that might merit their own note. I’ll create those.

Finally, I export the “New Vistas in the Thel Sector Session NN” and do a quick conversion to get it ready for my blog.


Much of my day is spent writing. It might be software, blog posts, meeting minutes, documentation, or session notes. I’m striving to continue to use the same toolset for all of those. That way, when I learn something new during one task, I might be able to bring it to another task.

In writing this blog post, I started thinking about different approaches I could try. I’ll just keep folding my processes back into my tool chain and see what comes out next.

Post Script

I’ve been writing this blog post as my computer cycles through Operating System (OS 🔍) upgrades. During that time, on another device, I looked through the schedule for the upcoming Emacs Conference 2020. The talks cover a wide-spectrum of how people use Emacs. I’m looking forward to the free 2-day conference on and . I’m sure I’ll pick up more ideas for extending Emacs to best suit my needs.


update posted Why (non-techie) Emacs?, which provides some non-technical insights into Emacs.