Spiral Game System Website



A web tool for designing games, characters, and items for the Spiral Game System, Voidspiral’s next Magnum Opus.

Business Purpose

The SGS website is basically analog of D&D Beyond for SGS. It allows you to create content for games, or even create entire games themselves using SGS.

The business goals of this project include:


The site is built on Node.js. It’s divided into three parts:

In terms of how the content works on the site, there are two levels.

Navigation around the site occurs in two layers parallel to docs and entities.

Dev-ops was a major part of getting this project to work properly, and was actually part of the reason I switched from C# on SGS Console. It became too difficult to collaborate between users on SGS-Console, so I moved to a technology where collaboration would be easier to implement, and delivery of the project to users would be more seamless.

Anyway, here’s how it works.

Master builds are notified on our Discord server’s SGS channel1. I usually try to pop in and explain what the changes were, if they were noticable for our test users.


This project started life after I found deficiencies in how I was trying to develop its previous iteration, SGS Console. I never felt like I wasted the work on SGS Console2, because I learned a ton during that project, and I barely had to make any real decisions for the first few months of development on the website, because I’d already made those decisions on Console. It was very easy to use Console as my roadmap and quickly work up a roadmap for the new iteration.

While I was still working on Console, I started to experiment with using other technologies to accomplish my goals of real-time communication and continuous delivery. I started with Socket.io, and quickly got the example chat app working, despite my initial trouble with React. Once that was working, I tried out Heroku, then MongoDB. With those three technologies working, I was ready to actually start thinking about architecture for a new iteration. There were a few more small experiments along the way that helped, including for ReactRouter, but after the first 3 I was off to the races designing out how the new app would work. That’s about when the idea of analoging D&D Beyond became viable.

From there, instead of breaking up the project into numerous milestones and carefully defining every component, I broke things up into the very rough categories of mission critical, on deck, and enhancements. That allowed me to get right to work building out the structure of the site and get working on the core bits of database manipulation and router navigation within days. The app rapidly evolved from there. Once the socket doc system was in place, I was able to start experimenting with it to build games and invited some friends to try it out. Their input and testing proved invaluable, as they helped reveal a lot of design decisions I’d yet to make. By that time I was much further along than the original Console app, so I was starting to make new decisions about how thi iteration would function, and what operations would be allowed. It also forced me to start thinking about auth. Here was another side-experiment where I tried some pre-packated solutions that didn’t work and came around to building my own implementation of JWT authorization (both for react-router top-level nav, and for socket-doc operations).

From there, things really took off. By that time the app was stable enough to use for real game design and that again fueled more new features and bugfixes. Lather, wrinse, and repeat. Now, features are largely driven by what’s wrong with the current way of doing things, or by features that would be even more convenient.


Points of Interest

SGS Module: calcAll

This function is the meat and potatoes of the SGS module.

SGS Module: DicePool

The DicePool class performs the extremely complex job of combining an arbitrary amount of numbers, dice pools (5d6, 2d20, etc4), die sizes (d4, d6, d8, etc), and operations (+, -, ×, ÷).

Here are a couple of interesting cases I had to design for.

Not all of these cases are easy to solve in a generic way that can be easily applied to an arbitrary set. And not all of the complicated cases are this obvious.

The DicePool class approaches this in phases. Its constructor takes a single string containing any series of values and operators. It throws errors on malformed constructors, ie anything that isn’t in the format of Value1 Operator1 Value2 Operator2 Value3 ... OperatorN ValueN, numbers that are NaN, mismatched parentheses, invalid characters, etc.

Once it has clean data to work from, it sets to work tokenizing the string. The tokenizer iterates over the string character by character and keeps track of explicit parenthetical levels. The tokenizer builds a tree of Parenthetical objects from the data. An example tree might look something like this:

The Parenthetical has some methods that help clean up this mess.

After collapse is run a final time for cleanup, the dice pool is finalized. The final dice pool object can be observed for the following:

Server: docSocketApi

The Doc Socket API handles when a user clicks a button or changes a value on a doc by performing operations on the cached doc.

Note: A higher-level socketApi wraps docSocketApi and handles using JWT to auth every request, as well as returning the mutated doc to users observing the page.

This module only performs the requests.

First, the the module checks to make sure that the request object delivered from the client has an operation field. If not, it doesn’t know what operation to perform, so it simply returns. Since this is occuring in the socket system, no push to the connected clients is made.

Next, the operation is passed to a series of seperate operations handlers. Each handler checks to see if it owns the operation, skips if it does not, and performs work if it does. The handlers are divided up into:

Of course, each operation has a different task and works in a slightly different way, but they generally follow this pattern:

On success, the modified doc is sent back up to the socket api, who then sends the document down to the users. At no point is the document actually returned to the server, avoiding the need to sanitize a huge blob of JSON data.


What I learned

Oh man, what didn’t I learn on this project. This has been one of the best learning experiences of my development career.


Check it out here: https://sgs-web.herokuapp.com/


I do contract work! Contact me via social media or shoot me an email at Joe at Voidspiral com!

  1. and I really try not to spam people, hence the infrequent changes to master, only when stable. ↩︎

  2. and let’s face it, ain’t nobody was gonna use a command line app for game design. ↩︎

  3. Okay, strong words, maybe I’m more like a citizen now. ↩︎

  4. Technically this class by itself could support any arbitrary die size, like 3d333 or 1d1 or 50d1203, but we validate out non-real die sizes higher up in the call stack. ↩︎

  5. wake me up when #5280 is finished ↩︎


comments powered by Disqus