A universal tool for tabletop parlour games: a GM console on a phone or laptop and an atmospheric TV screen with resources, threat clocks, a fog-of-war map and d20 rolls, synced over Wi-Fi with no internet. The whole scenario lives in one data file — any setting is just another data.js. First game built on it: the post-apocalyptic bunker drama Object HOUSE.
Every parlour RPG with timers, hidden roles and resources runs into the same wall: the game master is overloaded. Running the story, counting minutes, keeping part of the information secret and remembering who has seen what — all at once. And players don't see the threats, they only hear about them.
The GM panel is a universal tool that takes that routine away: a GM console on a laptop or phone and an atmospheric screen on a TV, synced over Wi-Fi with no internet. The scenario, setting and mechanics aren't baked into the code — they live in one data file. "Object HOUSE" is the first game built on this engine.
"One engine. A bunker, a space station, a murder in a mansion or a mountain camp is just a different
data.js."
GM console. Tabs are built from the scenario data: intro and setting, acts and scenes with ready-made "if the players do X" branches, role and NPC profiles, the hidden part of the map, random-event and loot tables, rules. On the right — the working panel: resources, threat clocks, timer and dice. Any tap shows on the players' screen instantly.
TV screen. Shows exactly what the GM chooses: resource bars draining before your eyes; a labelled countdown ("STORM", "AIR"); threat clocks toggled with an eye icon; a fog-of-war map where sectors open on the GM's click; a full-screen d20 roll for the checks that matter; a title card between episodes.
Handouts. A print page: player rules and role cards — public ones for everyone, secret ones for each player.
All game content is one js/data.js with a clear schema. The engine knows nothing about bunkers: it renders what's described.
// js/data.js — the scenario schema; the engine builds the UI from these fields
const DATA = {
meta: { title, subtitle, tagline }, // name and setting
mechanics: { stats, statArray, difficulties, rules }, // attributes and checks
roles: [{ id, name, icon, profile, publicDesc, ability, secret: { title, text, goal } }],
npcs: [{ name, motivation, leverage }],
acts: [{ title, scenes: [{ id, title, text, branches, timer }] }],
events: [/* d20 table */], tables: [/* loot d20, complications d6 */],
resourcesDefault: [{ id, name, icon, value, max }], // bars on the TV
clocksDefault: [{ id, name, value, max, visible }], // threats
map: { rooms, defaultRevealed, links }, // fog of war
markerTypes, roomDetails, externalLocations, sceneHelps,
};
Changing the setting means filling these fields anew: other roles and secrets, other resources (clues instead of supplies, a reactor instead of power), another map, other timers. The console, screen, sync and print logic stay the same.
The server is one Python file from the standard library, zero dependencies: it starts with a double-click on start.command and prints the address for a phone on the same Wi-Fi. The console sends state via POST /api/state; the server validates the JSON, saves it to game-state.json and pushes it to every subscriber over Server-Sent Events. The screen receives the event and re-renders — no reloads. State survives a server restart and a closed browser; export and import to a file are built in.
# server.py — SSE: a queue per subscriber, a ping every 25 s
if self.path == '/api/events':
self.send_header('Content-Type', 'text/event-stream; charset=utf-8')
q = queue.Queue()
with listeners_lock: listeners.add(q)
with state_lock:
if game_state_raw: self._send_event(game_state_raw) # a new screen gets the current state at once
while True:
try: self._send_event(q.get(timeout=25))
except queue.Empty: self.wfile.write(b': ping\n\n'); self.wfile.flush()
A chamber survival drama on a simplified d20 system for 4–7 players: forty days underground, and it's still burning up there. Everyone has a public role (commandant, engineer, doctor, scout, radio operator, quartermaster, head of security) and a secret goal that conflicts with the others'. Three acts, 12 branching scenes, 10 NPCs, event and loot tables, three endings — stay, march north, the "8 seats" evacuation — and personal epilogues. About five hours; the timers are the actors: storm 75 minutes, ultimatum 35, air 60.
HTML5 · CSS3 · vanilla JavaScript · Python 3 (http.server, no dependencies) · Server-Sent Events · JSON + localStorage · one data file per game
A universal panel for parlour games: a GM console, a players' screen, sync without internet, printable handouts — and a scenario that changes without touching code. "Object HOUSE" proved the scheme over a five-hour session; the next game is just the next data.js.