A browser-based tool for modelling resource economies and game-system feedback loops, then simulating them step by step. Draw nodes and connections on a canvas, press Run, and watch resources flow, accumulate, convert, and drain — with charts, batch (Monte Carlo) analysis, and shareable diagrams.
Pure vanilla JavaScript. No build step, no framework — just static files and an SVG canvas. The only runtime code dependency is a vendored copy of math.js powering the formula language; Font Awesome icons are vendored too.
Status — docs in progress. The editor's visual redesign (the Graphite · Lime design system) has landed. This README documents the parts that are stable regardless of how the interface looks: the simulation model, the feature set, the architecture, and how to run and test the project. A visual walkthrough, screenshots, and a step-by-step editor tutorial are still on the way. See Deferred docs.
There's nothing to install. Serve the folder and open it:
# any static server works; here's one that ships with Python
python3 -m http.server 8080
# then open http://localhost:8080/Opening index.html directly via file:// also works for the core editor and
simulation. A few conveniences (clipboard copy of share links, embed routing)
behave best over http://, so a local server is recommended.
The app boots with a small example diagram. Use the Library to load the bundled samples, or start from scratch.
You build a directed graph of nodes (pools, sources, drains, gates, converters, registers, delays, queues, traders) joined by connections (resource flows or state links). A discrete-time engine then advances the model one step at a time:
- Resources move along resource connections at configurable rates (fixed, dice, formula, or sampled from a statistical distribution).
- Contended pools split their output max‑min fairly and work‑conservingly, so allocation is order‑independent and never creates or destroys resources.
- State connections publish named variables, drive triggers, gate nodes with activators, and apply in‑place growth/decay — enabling feedback loops.
- The whole step commits atomically, so results don't depend on node ordering.
You can then analyse the model with live charts, export the run history, run thousands of Monte‑Carlo trials, and share the whole diagram in a URL.
- Pool — accumulates resources (optional capacity).
- Source — emits resources; infinite by default or a limited finite stock that can run dry.
- Drain — consumes resources and tallies throughput.
- Gate — routes incoming resources across outputs by weight: deterministic (proportional), probabilistic (weighted random per unit), or all (each output gets its full weight).
- Converter — consumes N held resources per conversion and emits an output (in its own colour/type).
- Register — a computed value from a formula over shared variables (chains across registers, resolved to a fixpoint each tick).
- Delay — holds a batch of resources for N steps, then releases them together.
- Queue — a FIFO line feeding one or more parallel
servers(servers ÷ processTimethroughput); a single server is the classic bottleneck with per‑item latency, distinct from Delay. OptionalmaxLine(balk) andpatience(renege) model lost demand. Reports live metrics: throughput, average/longest wait, peak line length, balked/reneged losses. - Trader — an atomic exchange between two partners:
A → T → Bmeans A pays the in‑rate to B and B pays the out‑rate back to A, all or nothing. Extra in/out pairs trade in wiring order; the trader itself never holds resources.
automatic (fires every step), passive (only when triggered), interactive
(fired by a click or the artificial player), and starting (fires once on step 1).
- Resource connections — rate modes: fixed, dice (
XdY), formula (over shared variables), and distribution (normal / uniform / exponential / Poisson). Plus per‑connection interval (every N steps), chance %, colour/type filter, and a condition that compares the source value — or any named variable — against a threshold. - State connections — publish a named variable; act as a trigger (
✷, fire the target when the source fires), a fail/reverse trigger (fire the target when the source fails to act), an activator (⊢, the target may only fire while a condition holds), or a modifier (Δ, addfactor × sourceto the target each step — negative decays; self‑loops enable interest/decay).
- Discrete time‑step engine with atomic, order‑independent commits.
- Max‑min fair, work‑conserving allocation under contention.
- Capacity reservation — no overfill, no resource creation/loss; capacity‑blocked shares are retried next step, never lost.
- Pull mode — pools/drains can draw along incoming connections from pool/source providers (
any= partial,all= atomic). Each connection is driven by exactly one endpoint, so there's no double flow. - Coloured resources tracked end‑to‑end, with optional named resource types layered over the colours (with per‑type readouts).
- Shared variable store + register formulas (chaining, fixpoint evaluation).
- math.js formula language — every formula (rates, registers, math variables) is evaluated with math.js:
^power, ternaries, comparisons,round/floor/ceil/abs/min/max/sqrt/log/exp/mod, trig, constants (pi,e),random(),randomInt(a,b),pickRandom([…]), and more. Legacy JavaScript-style expressions (e.g.Math.round(x)) still work via a fallback evaluator. - Custom variables — named values usable in any formula. Random kinds: interval (any number between min and max), array (one of a validated number list), or dice (
XdY), each with a uniform or gaussian distribution. Computed kind: math, a formula over the other variables. All re-evaluate every step or once per Run press. - Goals / end conditions on any node halt the run when met.
- Time modes —
sync(turn‑based: every automatic node fires each step) orasync(real‑time: each automatic node fires on its own fire every / phase rhythm). - Artificial player — a scripted actor that fires interactive nodes during a run, on a fixed interval or while a named‑variable condition holds.
Place / select / move / delete; click‑ or drag‑to‑connect; pan & zoom (wheel + fit); undo/redo; marquee multi‑select, shift‑click, copy/paste/duplicate, group delete; grid snap; optional auto‑revert to the Select tool after placing; touch / mobile support (single‑touch gestures, two‑finger pinch‑zoom & pan); an accessibility pass (keyboard tool shortcuts, focus outlines, ARIA roles/labels, higher‑contrast text); plus container groups, sticky notes, and on‑canvas charts as annotations (groups, notes, and charts are drag‑resizable by their corner handles).
A far‑right icon rail holds the diagram‑wide settings, each opening in the properties panel so they're never crammed together: time mode, parameters, custom variables, resource types, the artificial player, scenario branches (checkpoint / fork / compare), and a live variable watch that ticks with the simulation. Clicking a rail icon shows that editor in the panel; selecting a node or connection hands the panel back to the selection. The left palette stays for tools/nodes and the top bar for run/zoom/file controls.
- Feedback-loop detection — the Loops rail panel finds every feedback cycle in the causal graph (flows, triggers, activators, modifiers, and formula reads via published variables) and classifies it reinforcing (R), balancing (B), a pure resource circulation (F), or unclear (?) from link polarities; formula links are probed numerically. Click a loop to spotlight it on the canvas;
cli.js --loopsprints the same table. - Spike attribution — click any point on the timeline to see why that value changed there: per‑connection inflows and outflows, applied modifiers, and internal changes (conversions, queue losses), summing exactly to the delta even on stride‑sampled long runs. The contributing connections spotlight on the canvas while the breakdown is open;
cli.js --why "Gold@120"prints the same from the terminal. - Scenario branching — checkpoint the simulation mid-run (full state: resources, in-flight queues, variables, history), fork back to it with any tweaks, and run forward again. The previous run is kept automatically as a ghost branch — dashed, faded traces overlaid on the timeline chart (same colour = same node across timelines), toggleable from the legend or the Branch rail panel. Reset still returns to the true run start. Session-only — branches aren't saved with the diagram.
- Global timeline chart — every tracked node's value over time, with a legend.
- On‑canvas charts — live charts placed in the diagram itself, tracking chosen nodes (line / area / bars / step), plus a phase portrait style that plots one node against another in state space — limit cycles draw themselves as orbits, equilibria as inward spirals.
- Monte Carlo / batch runs — run N isolated simulations for up to M steps and report per‑node distributions (mean / min / p10 / p50 / p90 / max) plus goal reach‑rate and end‑step stats — non‑destructive to the live diagram. Batches run chunked off the hot path with live progress, so the UI never freezes.
- Seeded, reproducible randomness — set a run seed in the Simulation panel to make a live run reproducible, or give a batch its own seed in the Monte Carlo modal; the same seed reproduces the exact same results, bit for bit. Every stochastic decision (dice, distributions, chance %, probabilistic gates, custom variables) draws from the seedable RNG, and the headless CLI's
--seedflag does the same from the terminal. - Parameter sweeps — vary one diagram parameter across a range (e.g. 5 values from 0.5× to 1.5×) and compare per‑node means and goal reach‑rates side by side, one column per value. Runs on clones; the live diagram is untouched.
- Sensitivity analysis — perturb every parameter ±10% (configurable) and get a diverging heatmap of each node's elasticity (the % change in its mean outcome per 1% change in the parameter), so the knobs that matter most stand out at a glance. The most‑influential parameter is called out, and results export to CSV. Runs on clones with common random numbers, so cells reflect the parameter, not RNG noise.
- Raw results export — download the Monte Carlo batch as CSV (one row per run, one column per node) for R / pandas / spreadsheets, alongside the in‑app stats.
- CSV export of the recorded run history.
- Full‑run history at bounded memory — long runs are recorded with an adaptive stride (the history always spans step 0 → now at 300–600 samples) instead of silently dropping the oldest steps.
- Per‑type readouts — holdings by type on each node, and live totals across the whole diagram.
- localStorage autosave with a recovery banner on next launch.
- Diagram library — save, rename, load, and delete named diagrams.
- JSON save/load, and SVG / PNG export of the diagram.
- Shareable URL — the whole diagram is base64‑encoded in the URL hash (
#d=…); opening it restores the diagram.?embed(or#embed) hides the editing chrome for a clean, embeddable view.
.econtext format — save/open the diagram as readable, diff‑friendly text that lives happily in git (one line per node/connection; full round‑trip with the canvas). See docs/ECONOMY_AS_CODE.md.- Design tests (assertions) — checks like
always gold < 500oreventually score >= 100, run per step (and across Monte Carlo trials). Edit and run them in the Checks rail panel; they save with the diagram (assertlines in.econ), andcli.js --checkruns the same suite with a non‑zero exit on failure — balance regressions become CI failures. - Export as JS module — compile the diagram + engine into one
dependency‑free
.jsfile with acreateEconomy()API (step/run/get/set/ fire/onStep), so the economy you designed is the economy your game ships.
For the why and how behind the model — the tick order, the fair‑allocation algorithm, the one‑step variable lag, and more — see docs/CONCEPTS.md.
The model and engine are DOM‑free, so diagrams can be simulated from the command line — for scripting, CI, or piping into other analysis tools:
# single run → per-step CSV trace on stdout
node cli.js my-diagram.json --steps 500 > trace.csv
# Monte Carlo summary stats (reproducible with --seed)
node cli.js my-diagram.json --runs 1000 --steps 200 --seed 42
# raw per-run final values as CSV, with a parameter override
node cli.js my-diagram.json --runs 500 --csv --param mine_rate=3 > samples.csv--param name=value (repeatable) overrides diagram parameters without editing
the file; --seed makes any run or batch bit‑for‑bit reproducible. Save a
diagram with File → Save as JSON (or take one from the library) to feed it
to the CLI. Files ending in .econ are parsed as the text format automatically.
# design tests: fail CI when a balance change breaks the economy (exit code 2)
node cli.js economy.econ --steps 300 \
--assert "always gold < 500" \
--assert "eventually tutorial_done >= 1" \
--assert "at step 60: churn <= 3"
# or run the suite saved in the diagram itself (the app's Checks rail panel /
# `assert` lines in .econ files)
node cli.js economy.econ --check
# the same assertions across 1000 Monte Carlo trials; tolerate 5% unlucky runs
node cli.js economy.econ --runs 1000 --seed 42 --pass-rate 95 \
--assert "at end: gold >= 100"
# assertions can live in a file (one per line, // comments)
node cli.js economy.econ --assert-file economy.checks
# convert between formats (text ↔ JSON)
node cli.js economy.json --to-dsl > economy.econ
node cli.js economy.econ --to-json > economy.json
# emit a standalone, dependency-free JS module of the economy
node cli.js economy.econ --emit economy.module.js
node -e "const E=require('./economy.module.js');
console.log(E.createEconomy({seed:42}).run(200).values())"The .econ grammar, assertion language, and generated‑module API are documented
in docs/ECONOMY_AS_CODE.md.
Press
?in the app (or the?button, top right) for the full in‑app overlay, including mouse gestures.
| Key | Action |
|---|---|
S |
Select tool |
D |
Delete tool |
R |
Resource‑connection tool |
T |
State‑connection tool |
? |
Shortcuts & gestures overlay |
Ctrl/⌘ + Z |
Undo |
Ctrl/⌘ + Shift + Z / Ctrl + Y |
Redo |
Ctrl/⌘ + C / V / D |
Copy / paste / duplicate selection |
Ctrl/⌘ + 0 |
Fit / reset view |
Delete / Backspace |
Delete selection |
Esc |
Cancel pending connection / clear selection |
Each is a self-contained systems model with emergent behaviour, shipped with an explanatory note and an on-canvas chart. Open the Library to load one.
- Predator & Prey — coupled populations settle into a stable limit cycle (foxes peak just after rabbits). Logistic growth via a register, predation via a formula rate, starvation via a self-modifier.
- Epidemic (SIR) — the textbook outbreak curve; infections crest exactly as the effective reproduction number Rₑ falls through 1, and the run halts when the outbreak fades.
- Supply Chain — ore smelted 2:1 into ingots and shipped through a 3-step delay; nothing sells until the pipeline fills, then output holds steady. Speed up the mine to create a bottleneck.
- Barter Economy — two towns swap grain for timber through a Trader (an atomic 2-for-2 exchange); each storehouse ends up holding both colours.
- Service Desk — a single-server queue with random (Poisson) arrivals; the line builds and clears — the M/D/1 queue behind every checkout.
Each is a large (~30–37 node), multi-subsystem model that exercises nearly every engine feature at once, designed by a panel of domain experts and tuned to produce rich, non-degenerate dynamics. Open the Timeline and press Run.
- F2P Mobile Economy (32 nodes) — a free-to-play live-ops loop: energy regen → play levels → Gold/XP faucets, a sqrt XP→level curve gating an Elite stage via an activator, a probabilistic gacha gate (70/22/7/1% rarity), a battle pass, a gold→gear crafting sink, and a DAU birth-death process feeding an IAP gem faucet. Faucets and sinks self-balance into limit cycles.
- Civilization Empire (34 nodes) — a 4X economy: logistic population bounded by a food-determined carrying capacity and throttled by happiness; five yields (food/production/gold/science/culture); buildings as converters; and a Science-accumulation tech tree that trips four activators in sequence (irrigation, drama, banking, university), each compounding a yield. Theaters stay locked until Drama unlocks.
- Megafactory Line (35 nodes) — a 4-tier automated factory (extraction → smelting → components → assembly/shipping) with finite ore, belt delays, a serial assembly queue, a probabilistic QC gate and pull-mode dispatch. A deliberately undersized circuit buffer drained by a slow station creates classic capacity back-pressure.
- Business Cycle (31 nodes) — a circular-flow macroeconomy: households↔firms money circulation, a gate splitting income into consume/save/tax, banking with interest and an accelerator, government deficit spending, and countercyclical central-bank stimulus through a policy lag that makes output overshoot — a self-sustaining boom-bust cycle.
- Food Web (29 nodes) — a four-trophic ecosystem: seasonal sunlight and recycled nutrients feed logistic producers; three grazers, two carnivores and an apex predator couple through Lotka-Volterra predation; a decomposer loop closes the nutrient cycle. Ten species lock into coupled, bounded oscillations.
- Auction Economy (37 nodes) — a player-driven MMO virtual economy: three gathering→refining chains (with a Forge delay and a Brew queue), a tier-2 toolsmith, an auction house built on Traders, and scarcity-driven price registers feeding price-elastic demand. Stocks and prices oscillate around a balanced gold faucet/sink loop. Exercises all nine node types.
Plain <script> includes, loaded in dependency order from index.html. No modules,
no bundler.
| File | Responsibility |
|---|---|
js/model.js |
Data classes — MNode, MConnection, MGroup, MNote, MChart, Diagram — plus pure helpers (evalFormula, rollDice, sampleDist, dominantColor). No DOM. |
js/engine.js |
SimEngine — the tick loop, fair allocation, triggers/activators/modifiers, variables & registers, and Monte Carlo. No DOM. |
js/renderer.js |
Renderer (SVG drawing, hit‑testing, pan/zoom) and BallSystem (flow animation). |
js/editor.js |
Editor — pointer/keyboard/touch input, tools, selection, drag‑to‑connect. |
js/charts.js |
Sparkline and TimelineChart (canvas 2D). |
js/kb.js |
Concept‑guide / knowledge‑base article content shown in the in‑app help. |
js/app.js (+ js/app-props.js, -demos, -analysis, -fields, -library, -clipboard, -export) |
App — one logical class split across prototype‑mixin files: toolbar, properties panel, persistence, examples, import/export, Monte Carlo / sweeps / sensitivity, and clipboard. |
cli.js |
Headless Node runner — simulate a diagram JSON from the terminal (traces, Monte Carlo, seeds, param overrides). |
css/style.css |
All styling. |
vendor/math.min.js |
Vendored math.js bundle — the formula evaluator. |
index.html |
Markup + script includes. |
Data flow. App owns a Diagram, a SimEngine, a Renderer, and an Editor.
The engine mutates the diagram and calls back on each step; the app re‑renders, drives
animations, updates charts, and refreshes the panel. The model layer is intentionally
DOM‑free so it can be unit‑tested headlessly and reused (e.g. Monte Carlo clones a
Diagram + SimEngine per trial).
Serialization. Diagram.toJSON() / loadJSON() are the single source of truth
for persistence — used by save/load, the library, autosave, undo/redo snapshots,
shareable URLs, and the test round‑trips. New fields are written only when they
differ from defaults, keeping saved files small and forward‑compatible.
Headless unit tests (no browser; loads model.js + engine.js into a sandbox):
npm install # once — pulls mathjs for the formula tests
node test/run.jsCovers core flows, fair contention, capacity integrity, registers/formulas, gating, triggers/activators, end conditions, finite sources, queues, state modifiers, pull mode, distributions, time modes, the artificial player, and serialization round‑trips for every model type.
Browser smoke test (Playwright + Chromium) — exercises the real app and fails on any console/page error:
# in one shell: serve the app
python3 -m http.server 8080
# in another: run the smoke test
NODE_PATH=$(npm root -g) node test/smoke.js
# override the URL with SMOKE_URL if neededRequires playwright (and a Chromium build) available on NODE_PATH.
Both suites run automatically on every push and pull request via GitHub Actions
(.github/workflows/tests.yml); the badge at the top of this file reflects the
latest run. (test/export.js is a manual‑run export check, not part of CI.)
Modern evergreen browsers (Chrome, Firefox, Safari, Edge). Uses SVG, Canvas 2D,
localStorage, the Clipboard API (with a prompt fallback), and standard ES2020+
JavaScript. No transpilation.
All P1, P2, and P3 items in ROADMAP.md are implemented. The roadmap also records intentional design decisions and known limitations.
Still to come, now that the visual redesign has landed:
- Screenshots / GIFs of the editor and analysis views.
- A visual, step‑by‑step usage guide (placing nodes, wiring connections, reading the panels).
- Any layout‑specific reference (exact panel locations, button labels).
The simulation model, file architecture, and APIs documented here and in
docs/CONCEPTS.md are independent of the visual design and remain accurate.
This project is licensed under the GNU Affero General Public License v3.0 (AGPL‑3.0). See LICENSE for the full text. In short: you are free to use, study, modify, and share it; if you run a modified version as a network service, you must offer its source to your users under the same license.