A high-velocity, local-first narrative and tactical command console designed for tabletop roleplaying Game Masters.
Running modern narrative tabletop RPGs (such as Powered by the Apocalypse, Forged in the Dark, or rules-light investigative systems) creates severe cognitive friction during live sessions:
- Information Fragmentation: Game Masters must constantly juggle multiple browser tabs, PDF rulebooks, scratch notes, threat clocks, and character trackers.
- Combat Mathematics Friction: Traditional combat trackers force DMs into tracking high-number Hit Points and granular arithmetic, stalling dynamic narrative momentum.
- Session State Desynchronization: Passing secrets, tracking active threats, and recording live player dice rolls across physical tables or voice channels often leads to communication bottlenecks.
NodeBoard solves this by unifying preparation and live session management into an ultra-fast, local-first console. It marries segmented threat defeat clocks with an interactive relational web, a mobile-ready player companion portal, and a sub-millisecond deterministic rule engine that reacts to campaign state shifts instantly.
- 🗂️ 3-Tier Command Navigation: Strict structural division separating live play (
live), world prep (world), and system configuration (engine). - ⚔️ HP-Less Tactical Combat: Track threats using segmented defeat clocks (3, 4, 6, 8, or 12 segments) with interactive heat-map styling, direct
+1 Impact/+2 Criticalroll integration, and hero effort counters. - 🌐 Unified Relational Canvas: Interactive SVG graph with force-directed auto-layout and card-based directory mapping connections between scenes, clues, factions, NPCs, clocks, and heroes.
- 📱 Mobile-First Player Portal: Dedicated player interface protected by individual PIN passwords, featuring real-time 2d6 PbtA rolling, tactical arena views, and shared secret logs.
- ⚡ Sub-Millisecond Deterministic Rule Engine: Evaluates active campaign state against customizable trigger rules in under
<0.05mswithout external network dependencies or LLM latency. - 🔒 Local-First Architecture: Zero-config operation with instant tab synchronization via
BroadcastChanneland reliable JSON campaign backup/import.
- Node.js:
>= 18.0.0(LTSv20.xrecommended) - npm:
>= 9.0.0 - A modern web browser supporting ES2022 and modern CSS (Chrome, Firefox, Safari, Edge).
- Linux / macOS: Ejecuta
./start.shen tu terminal o con doble clic. - Windows: Haz doble clic en
start.bat.
El script comprobará Node.js, instalará dependencias si es la primera vez, compilará la versión optimizada, arrancará el servidor y abrirá tu navegador automáticamente en http://localhost:3000.
# 1. Instalar dependencias
npm install
# 2. Servidor de Producción con Persistencia en Disco
npm run serve
# (o para desarrollo con recarga en caliente: npm run dev)docker compose up -dPara partidas presenciales en la misma mesa:
- Conecta los teléfonos o portátiles de los jugadores a la misma red Wi-Fi.
- En la consola del DM, haz clic en el botón superior "Compartir con Jugadores".
- Se abrirá un modal con un Código QR de alta resolución y la URL de red local (
http://192.168.x.x:3000/?view=player). - Los jugadores simplemente apuntan la cámara de su móvil al QR, eligen su personaje e introducen su PIN (por defecto
1234). - Todas las tiradas de dados y cambios se sincronizan en tiempo real y se persisten automáticamente en
data/campaign.json.
NodeBoard provides a headless, deterministic rule engine that can evaluate campaign states in node or browser environments:
import { RuleEngine } from './src/engine/RuleEngine';
import { CampaignData } from './src/types';
import sampleCampaign from './sample_campaign.json';
// Initialize the rule engine
const engine = new RuleEngine({ enableLogging: false });
// Evaluate active rules against the campaign state
const report = engine.evaluate(sampleCampaign as CampaignData, 'threat_clock');
console.log(`Evaluated ${report.ruleCount} rules in ${report.executionTimeMs}ms`);
// If conditions were met (e.g. clock filled to 100%), inspect generated actions
for (const action of report.actions) {
console.log(`Triggered [${action.type}]: ${action.payload.title}`);
}
// Immutably apply actions to the campaign state
const updatedCampaign = engine.applyActions(sampleCampaign as CampaignData, report.actions);NodeBoard is organized around a unidirectional data flow with local-first persistence:
nodeboard/
├── .github/ # GitHub Actions CI workflows & issue templates
├── docs/ # Specifications & API documentation
│ ├── API_REFERENCE.md # Engine & algorithm API documentation
│ ├── CAMPAIGN_SPEC.md # Domain schema definition & field descriptions
│ └── campaign_schema.json # JSON Schema for CampaignData
├── public/ # Static assets
├── scripts/ # Automated test suites, benchmarks, and generator tools
│ ├── test-engine.mjs # RuleEngine core unit tests
│ ├── test-combat-integration.mjs # Combat flow & defeat clock integration tests
│ ├── test-force-layout.mjs # Graph positioning layout tests
│ └── test-campaign-migration.mjs # Milestone state transition tests
├── src/
│ ├── components/ # UI components
│ │ ├── tabs/ # Main tab views (Session, Combat, Nodes, Clocks, etc.)
│ │ ├── ui/ # Reusable modals, drawers, and form controls
│ │ ├── Header.tsx # Top navigation, status indicators, and export tools
│ │ ├── Sidebar.tsx # 3-tier navigation menu
│ │ └── PlayerPortal.tsx # Player-facing mobile interface
│ ├── engine/ # Core domain logic and deterministic algorithms
│ │ ├── RuleEngine.ts # Rule evaluation coordinator and benchmark harness
│ │ ├── ruleEvaluator.ts # Pure condition matching and action generator
│ │ ├── relationshipGraph.ts # Graph topological metrics and tension calculations
│ │ ├── campaignMigration.ts # Session progression and schema validation
│ │ └── graph/ # Visual canvas synchronization
│ ├── types/ # Master TypeScript domain definitions
│ │ ├── core.ts # Campaign, Scene, Clock, and NPC definitions
│ │ ├── rules.ts # Rule engine conditions and triggers
│ │ └── graph.ts # Visual canvas node & edge interfaces
│ ├── utils/ # Agnostic mathematical & UI helpers
│ │ ├── diceStats.ts # 2d6 PbtA probability distributions
│ │ ├── forceLayout.ts # N-body repulsive graph positioning algorithm
│ │ └── sampleData.ts # World-agnostic starter campaign dataset
│ ├── App.tsx # Global state management and synchronization
│ └── main.tsx # React 19 application entry point
├── package.json
├── tsconfig.json
└── vite.config.ts
[User Action / Player Portal Roll]
│
▼
[React Global State (App.tsx)]
│
├──────► [BroadcastChannel (Multi-tab Sync)]
├──────► [localStorage (Local-First Persistence)]
│
▼
[RuleEngine (In-Memory <0.05ms)]
│
▼
[Active Triggers & Toasts]
NodeBoard is completely local-first and zero-configuration. It does not require any external backend services or credentials.
| Variable | Type | Default | Description |
|---|---|---|---|
PORT |
number |
3000 |
Local port used by the Vite development server. |
HOST |
string |
0.0.0.0 |
Host binding interface (enables local Wi-Fi sharing). |
The repository includes a suite of automated unit, integration, and benchmark tests executed directly with tsx:
# Run all test suites
npm test
# Type-check the codebase with strict TypeScript compiler options
npm run lint
# Verify code formatting against Prettier rules
npm run format:checktest-engine.mjs: Benchmarks rule evaluation throughput and condition operator accuracy.test-agnostic-engine.mjs: Confirms that rules and triggers operate without setting-specific assumptions.test-combat-integration.mjs: Verifies combat participant additions, defeat clock mutations, and turn tracking.test-campaign-migration.mjs: Ensures historical archiving preserves character progression across scenes.test-force-layout.mjs: Validates repulsive and attractive force physics for graph layouts.test-image-utils.mjs: Tests image fallback and sanitation routines.
Contributions are welcome! Please read CONTRIBUTING.md for details on our code of conduct, branching strategy, commit conventions, and pull request checklist.
Distributed under the MIT License. See LICENSE for more information.