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 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.






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.
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.




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.