README file from
GithubRandomness
An Obsidian plugin for IPP3-compatible random generators.
Roll on tables inline with `rdm:[@Names]`, embed full generators in
randomness codeblocks, and re-use existing .ipt files from twenty
years of the Inspiration Pad
ecosystem.
Features
randomnesscodeblocks — embed a generator directly in a note. Rolls every render; supports the full IPP3 grammar including weighted tables, lookup tables, deck picks, conditionals, dice, expressions, and 21 filters.- Inline
rdm:calls — one-shot rolls scattered through your prose. Preview first, then click 🔒 to commit the result as`rdm:[@Names]⟹Alice`— the lock survives reloads, syncs, and reopening the vault. Click 🎲 to re-roll. Use:other files — share table libraries across notes. Reference.iptfiles or.mdnotes containingrandomnesscodeblocks; resolution follows the calling note's folder first, then a configurable generator root.- Prompts — generators that declare
Prompt:controls render dropdowns or text inputs above the output; changing a value re-rolls with the new prompt set. - Deterministic when you want it — every codeblock can be configured to use a stable seed (off by default), so the same source at the same location produces the same roll on every render. Locks remain the strongest guarantee.
- Generator browser pane — a right-sidebar view that displays
every
.iptfile in your configured Generator root (or whole vault) as a collapsible folder tree mirroring your vault's structure. Click a folder or file chevron to expand; click any table's Roll button to generate a result, or 📋 to copy an inlinerdm:reference for that table to your clipboard (paste into prose; the Notice shows theUse:line you'll need to add to your note). Click the result body to copy the rendered text. The tree starts fully collapsed and remembers what you expand across sessions. A "Collapse all" button resets the tree without clearing your search filter, so collapse-then-filter is a fast way to find a specific generator in a deep folder hierarchy. Open via the dice ribbon icon or the "Open generator browser" command. - Existing
.iptfiles work as-is. The engine survives the full AddCommas/Random-Treasure-CR1-CR30 stress test from the NBOS corpus. - JavaScript API — roll generators from other plugins or Templater
scripts via
app.plugins.plugins["randomness"].api. Scoped and unscoped rolls, prompt overrides, deterministic seeds, and a roll event stream. Ideal for generating notes from a shared generator library. See API.md.
Install
Via BRAT (recommended while in beta)
- Install the BRAT plugin if you don't have it.
- Open BRAT settings → Add Beta Plugin → enter this repo's URL.
- Enable "Randomness" under Community Plugins.
Manually
- Download
main.js,manifest.json(andstyles.cssif present) from the latest release. - Copy them into
.obsidian/plugins/randomness/inside your vault. - Enable "Randomness" under Settings → Community Plugins.
Usage
Codeblocks
```randomness
Table: Settlement
Riverbend
Stonewatch
Greenhollow
```
Renders to one of "Riverbend", "Stonewatch", or "Greenhollow", chosen at random each time the codeblock renders.
Use the full IPP3 grammar — multiple tables, weighted entries, lookup
tables, Set:/Define:, prompts, conditionals, dice expressions, filters,
the lot:
```randomness
Prompt: Tier {Easy|Normal|Hard}Normal
Table: Encounter
1: A single goblin scout.
2: [@Group] goblins.
6: A goblin chieftain with [1d4+2] {$prompt1} guards.
Table: Group
1-3: small group of {2d4}
4-6: warband of {3d6}
```
The dropdown for Tier appears above the result; changing it re-rolls.
Inline rdm:
Anywhere in your prose, wrap an expression in backticks with the rdm:
prefix:
The shopkeeper, a `rdm:[@Names]` from `rdm:[@Origin]`, eyed me suspiciously.
Each `rdm:...` renders inline with a preview, plus 🔒 (lock) and 🎲
(re-roll) buttons. Clicking 🔒 rewrites the underlying text to include the
chosen result:
The shopkeeper, a `rdm:[@Names]⟹Tessith Vone` from `rdm:[@Origin]⟹Coppertown`, eyed me suspiciously.
The lock survives reloads, sync, and reopening the vault. To re-roll a locked call, click 🎲 — it strips the lock and shows a fresh preview.
The expression's scope sees same-note randomness codeblocks plus any
Use: declarations from those blocks, so you can keep table definitions
alongside the prose that uses them.
Sharing tables across notes
In a shared .ipt file (e.g. Generators/common-names.ipt):
Table: Names
Tessith Vone
Korad the Blue
Mira Thornhaven
In any note's codeblock:
```randomness
Use:common-names.ipt
Table: NPC
{1d2=1, A man named, A woman named} [@Names].
```
Use: paths resolve relative to the current note's folder first, then
relative to the Generator root configured in Settings → Randomness.
Commands
- Lock all unfilled
rdm:in current note — evaluates every unfilled inline call (using cached previews where available, fresh evaluations otherwise) and writes all locks in one atomic save. - Reroll all
rdm:in current note — strips every lock and clears cached previews. The next render shows fresh previews everywhere. - Rebuild generator index — rescans the vault for
.iptgenerators. Run this after adding or renaming generator files if the JS API'srollUnscoped/ bare-filenameUse:resolution looks stale.
Scripting: the JS API
Randomness exposes a JavaScript API for other plugins and for Templater scripts, so you can roll generators from code — for example, to populate a freshly created note from a shared generator library.
const api = app.plugins.plugins["randomness"].api;
// Roll a generator found anywhere in the vault (ignores note scope):
const r = await api.rollUnscoped("VillainName");
console.log(r.result); // -> "Mordred the Pale"
The two rolls you'll use most:
roll(name, opts?)— rolls a table in note scope (sees the note's same-note codeblocks andUse:imports). Use it when rolling from the context of a specific note.rollUnscoped(name, opts?)— rolls a table found anywhere in the vault, ignoring scope. Use it for automation and note generation, where there's no scope wired up yet — e.g. a Templater template that builds a note from your shared generators.
Both accept promptValues (override a generator's prompts by label) and
seed (deterministic rolls). rollUnscoped also accepts filePath to
disambiguate when two files define the same table name.
// Pass values into a generator's prompts, by label:
const inn = await api.rollUnscoped("TF-Inn", {
promptValues: { town: "Frostkey", shopName: "The Salty Anchor" },
});
Full reference, including every method, option, the RollResult
shape, collision handling, and recipes: see API.md.
Settings
- Generator root — vault-relative folder used as the fallback for
Use:paths that don't resolve next to the calling note. - Default formatting —
HTML (rich)to enable bold/italic/list filters as visual formatting;Plain textto keep them as plain characters. Individual generators can override via theFormatting:directive. - Stable codeblock seeds — when on, codeblocks render the same result across reloads (until you reroll). Useful for keeping a generator "settled" without committing to a specific lock. Off by default.
Where inline rdm: calls work
Inline rdm: calls render in Reading view only. Obsidian's
Live Preview uses a different rendering pipeline (CodeMirror 6
extensions, not markdown post-processors), and the plugin doesn't
have a CM6 extension yet. In Live Preview the inline calls show as
plain code spans — locks in the source survive, but the 🔒/🎲
buttons don't appear.
Workflow recommendation: author your prose in Live Preview, switch to Reading view (Ctrl/Cmd-E or the read-eye icon) to roll/lock inline calls. Locks written from Reading view show up in Live Preview's underlying source immediately.
Codeblock generators (```randomness) work in both views —
those use the codeblock processor, which Live Preview does handle.
Security
The plugin's HTML output passes through a tag whitelist before being
attached to the DOM. Allowed tags: structural (p, div, ul, ol, li, hr,
blockquote, pre, h1–h6), inline formatting (b, i, u, em, strong, s, code,
br, span, and a few others), and tables. All attributes are stripped.
Anything outside the whitelist — <script>, <iframe>, <a>, event
handlers like onclick — is dropped along with its content.
You should still only use generators you trust. The whitelist is
defence-in-depth, not a free pass to run arbitrary .ipt files from
strangers.
Attribution
This plugin is MIT-licensed (see LICENSE).
The IPP3 (Inspiration Pad Pro 3) grammar and file format are the work of NBOS Software. Their Inspiration Pad ecosystem is the reason a generator written in 2008 still rolls today; this plugin aims to be a faithful, modern execution environment for that work, not a replacement for it. Use the original tools where they fit your workflow better — and consider supporting NBOS.
Generator content (.ipt files) from the wider community remains the
copyright of its original authors and is governed by whatever licenses
those authors chose. The plugin does not include or distribute generator
content; the corpus shipped in the dev repo is for testing only.
Development
Pure-TypeScript engine and resolver, no Obsidian imports outside
src/views/. The test suite runs against in-memory file sources and
jsdom for DOM testing — no Obsidian instance required to develop.
npm install # one-time setup
npm test # run the full suite (~2s, 459 tests)
npx tsc --noEmit # strict typecheck
npm run build # bundle for distribution
npm run dev # watch mode
Architecture in three layers:
- Engine (
src/engine/) — pure IPP3 evaluator. AST, parsers, expression evaluator with seedable PRNG, 21 filters, recursion guard. - Resolver (
src/resolver/) —Use:graph traversal, markdown-codeblock extraction, inline scope assembly. Synchronous; async backend viaasyncPrefetcher. - Views (
src/views/) — the only layer that imports Obsidian. Codeblock processor, inline processor, settings, lock/reroll state machine, prompt UI, HTML sanitiser.
See STATUS.md for the full design log including bugs found and fixed,
trade-offs accepted, and outstanding items.