Jayson LaFrance

Dungeons & Data

Six people around a table, each with a different screen, and a rulebook nobody agrees on. This is the system I built so the table stops arguing about arithmetic: one server owns every number, and everything else — browsers, a shared TV, a little screen at each seat — just shows what it says.

Role
Sole author & architect
Status
Deployed, in use
Scale
179 commits
Surfaces
Web, TV, hardware

What it does

A Go server holds the party roster and the combat tracker, and every connected screen reflects the same state over a WebSocket:

  • Players build their own characters in a browser wizard and keep their sheet open on their phone.
  • The game master runs the table from a console: initiative, the active creature, one-tap monster actions, damage and area-effect resolution, private whispers.
  • A TV at the table shows party status and throws every public roll as 3D physics dice.
  • A palm-sized screen at each seat shows that player their own hit points, conditions, and turn order, with two buttons for damage, healing, and death saves.

The three screens

The shared table display: a round counter, four initiative cards with the active character highlighted, and a status card per character showing current and maximum hit points, a coloured health bar, armour class, and condition tags including Poisoned and Concentrating on Haste.
The shared display. Everything on it — armour class, hit points, whose turn it is — was computed by the server, not typed by a player.
The game master console: a control bar with turn and rest actions, an initiative rail down the left with per-character damage and heal buttons, and a spotlight card for the active cleric showing hit points, armour class, spell slots by level, spell save difficulty, conditions and a private whisper field.
The game master's run screen. The spell save difficulty and slot counts are derived from class and level.
The character creation wizard on a phone-width screen: a six-step progress bar on step one, fields for name, class and species, a portrait style choice, and a Next button that is greyed out until the step is valid.
Step one of six. Next stays disabled until the step is valid — and the subtitle promises the rest is computed for you.

The screenshots are a throwaway party I made for this page, not a real session — but the numbers in them are real output. The cleric is wearing chain mail and a shield, so the server says armour class 18. The barbarian is unarmoured, so it applies Unarmored Defense and says 14. Nobody typed either number.

What a player sees at their seat

The token is a 320×170 colour screen a player holds. It shows their character and the shared table information, and nothing else — no other player's private notes, nothing the game master has not revealed.

There are fourteen screens in a fixed cycle, and seven of them are situational: they only appear when they have something to say, so tapping through stays short. The whole device is operated with three actions — cycle, act up, act down — mapped onto two physical buttons plus a both-press, each distinguishing a tap from a hold.

Token HUD screen: the character name Oda Ironhymn in a title bar with initiative 12, armour class 18, a YOUR TURN cue, current and maximum hit points in large pixel digits, and a green health bar.
HUD. Name, initiative, armour class, the turn cue, hit points, and the bar. The buttons heal and damage — tap for one, hold for five.
Token PARTY screen listing all four characters with health bars and hit point counts, the holder's own name highlighted in yellow at the bottom.
PARTY. Everyone's health at a glance, with you highlighted. Read-only.
Token STATS screen: armour class, passive perception and speed across the top, then all six ability modifiers with save shields on the proficient ones.
STATS. Every number here is derived by the server, including which saves you are proficient in.
Token SPELLS screen showing a per-level spell slot ledger: level one four of four, level two three of three, level three two of two, with a hint reading top pick, bottom use.
SPELLS. The slot ledger. Situational — it only appears in the cycle if you actually cast.
Token COND screen for Bramblewick Tosh listing two conditions, each with a one-line rules reminder: Poisoned, disadvantage on attack rolls and ability checks; Prone, crawl, melee against you has advantage and ranged has disadvantage.
COND. Each active condition with the one-line rule that matters at the table, so nobody reaches for the book mid-fight.
Token CONC screen for Verity Ashgrove: Concentrating on Haste, with the rule that taking damage triggers an automatic Constitution save at a difficulty of the greater of ten or half the damage.
CONC. The concentration check is automatic: take damage and the server rolls the save at the correct difficulty. The screen just tells you what happened.

These are captures of the dashboard's built-in token simulator rather than photographs of the hardware — the simulator is a deliberate 1:1 mirror of the firmware, which is what makes it useful for developing screens without flashing a board every time.

The decision worth explaining

The obvious way to build this is to let each client track its own character and tell the server what happened. That is also how you get six devices quietly disagreeing about whether someone is unconscious.

The rule the whole system is built on

No client ever computes its own hit points, invents a condition, or decides whether it is still alive.

Clients send intent. The server derives every number from raw inputs — class, level, ability scores, gear — and broadcasts the result. A screen is a view, never a source.

Everything follows from that. Armor class, saving throws, skills, spell slots and spell save difficulty are computed in one place from the rules. The 3D dice on the television are an animation played after the server has already rolled — the cubes are told which faces to land on. It looks like chance and is actually just a replay, which is exactly what you want when the number decides whether a character dies.

The same boundary solves a social problem, too. Planned encounters are stored as game-master-only data and never broadcast, so a curious player cannot inspect a payload and find out what is waiting in the next room.

Rules live in data, not in code

Classes, species, skills, armor, gear, spells, monsters and conditions are a JSON content pack loaded at startup, not values buried in the program. Correcting a rule or adding a homebrew variant means editing a file and restarting — and a written contract document spells out exactly what a pack is allowed to change versus what is permanently fixed structure.

It is the same separation that keeps a public-facing application maintainable: the people who know the rules should be able to change the rules without touching the engine.

Character Builder: the accessibility exhibit

Alongside the table system I built a mobile-first character builder as a standalone web app, and it is the piece I would point a hiring manager at for form design.

Its core rule is that the app never lets you make an invalid choice.

  • Each step's Continue unlocks only when that step is actually valid.
  • Every derived number is computed from the rules rather than typed in.
  • Point-buy budgets, ability caps, and prerequisite chains are enforced, not suggested.
  • When an option is unavailable, it says exactly which requirement you fail rather than hiding or greying out silently.

That last one is the important habit. Progressive disclosure, validation at the point of choice, and a specific reason for every rejection are the same disciplines that make a public benefits form usable by someone with a screen reader and one attempt to get it right.

The seat tokens got cases

The hardware at each seat is a bare development board with a screen, which is fine on a bench and wrong on a dining table. So the tokens got enclosures — designed parametrically with the CAD tooling on the next page over, which is the point at which two of these projects started feeding each other.

CAD view of a treasure-chest shaped enclosure with a barrel-vaulted lid, riveted corner straps, a front window opening for the screen, and a side handle.
The treasure-chest case: barrel lid, riveted straps, a window for the screen.
CAD view of a plain rounded rectangular standing case with a recessed screen window, two round buttons and a side port opening.
The plain standing case — screen window, two buttons, one port.
Cutaway section through the chest case showing internal wall thickness and the cavity for the board.
A section cut: wall thickness and the board cavity.
Cutaway section at the side port showing the opening aligned with the board's connector.
Section at the port, checking the opening lines up with the connector.

Every dimension comes from parameters, so a different board or a thicker wall is a number change rather than a remodel — and the section views exist because a case that looks right and does not close is a lesson you only need once.

Running it for real

This is not a demo that lives on my laptop. It deploys as a container behind a reverse proxy with automatic certificates, runs under a service manager with a watchdog timer, and writes session state atomically so an unlucky restart mid-combat resumes the game instead of losing it. Clients reconnect on their own.

Honest limits

  • It implements the free public subset of the rules, not the complete published game.
  • The web surfaces are built for the room they are used in — phones and a television at a table. They are not hardened for hostile public internet traffic.
  • The hardware tokens are a real build, not a product: assembled by hand, provisioned from the console, and fussy in the way hobby electronics are.
  • The character builder has no version-control history worth citing — it was built in one stretch outside a repository, which I would not do again.

Seeing the code

The repository is private. If you are evaluating me and want to read the code or walk through the architecture, ask me and I will arrange read access or a screen share.