Solo tabletop RPG
A single-player RPG that looks like an actual table. Grid map, a tray of physics dice, painted minis, and a DM behind a screen whose hands you can see and whose dice you can't. One hero, no party.
- Godot references in core
- 0
- Rolls per balance run
- 50,000
Why it exists
I spent a long time looking for a single-player RPG that felt like a tabletop campaign without making me run a party of six or lean on companion AI I didn't enjoy. While I was rebuilding the localization engine for Portalborn I liked the game enough to start writing campaign content for it, and that clarified that the thing I actually wanted was something else.
Then the obvious caught up with me. I write software for a living.
The rule the architecture is built on
core/ never references Godot. The dice, the resolution
system, the traits, the combat engine and the statistics are plain C# with
no engine types anywhere in them.
That's what lets the rules run headless, which is what lets the test suite
exist and the balance simulator throw fifty thousand pools overnight. The
Godot project consumes core and adds bodies, audio, and a
felt tray. Nothing goes the other way.
| Project | What lives there |
|---|---|
| core | Dice, resolution, characters, combat, statistics. No Godot. |
| core.tests | xUnit, one file per concern |
| sim | Headless balance harness |
| game | The Godot project. Dice bodies, tray physics, audio, presentation |
Localization from the first commit
The engine emits localization keys and never player-facing strings.
English is a locale file exactly like every other language, so adding one
is a content task. Key grammar is
namespace.subject.aspect, enforced by a predicate in code and
checked end to end by a script that fails if any key has no text or any
string has no key.
None of that is normal for a hobby project at this stage. I did it because I'd just spent weeks retrofitting internationalization into a shipped codebase that grew without it, and I wasn't going to do that twice.
Are the dice actually fair
The dice are physics objects, thrown into a tray, read off their resting orientation. That's more fun than a random number with an animation over it. It's also a way to introduce bias you never notice.
So the faces get tallied and run through a chi-squared test, swept per die shape and per tray skin. Per skin because each one has its own physics material, and three dice colliding with each other is a different physical system from one die thrown alone. I didn't want to assume that a d6 fair on its own stays fair in a handful.
The odds the rules depend on are pinned the same way. A pool of three dice throws exactly one 1 about a third of the time, two or more about six percent, and those two figures add to the thirty-nine percent chance of seeing any 1 at all. Three numbers that are easy to confuse, so there's a function that computes them exactly and a test holding the resolver to it.
Seams that stay open
Anything that's a policy decision sits behind an interface with a default implementation. The random source, the localizer, target selection, combat observation, archetype data. A test file substitutes each one from outside the library, so if I ever weld a seam shut the test stops compiling and tells me.
Then I stopped. The actor, the pool, the result and the dice system are concrete on purpose. An interface per type would have been the easy habit and a worse design.
Rules in code, world in data
The dice system is hard-coded. Monsters, items, maps, encounters and dialogue are content and live in data files. Here's the test for which is which. If adding a campaign would mean touching it, it's content.
That's also the next real milestone. The dice tray works, and after the grid, the minis and the combat loop, what I actually want to know is whether a short campaign can be written entirely in data with no new code. If that fails then the rest of the plan fails with it, so I'd rather find out now than in year three.
Still undecided
The README keeps an open list rather than pretending these are settled. Whether damage runs on a hit-point stat plus conditions or conditions alone. How gear dice scale. Whether skills advance on XP or on use. Whether the DM ever speaks aloud.
Also whether a d20 exists at all. If it does, it's a collectible and never joins a pool, because it would flatten the difficulty ladder the rest of the system is built on.