The 5W Dice Log

Every roll is fair, timestamped, and auditable.

ShadowRealms uses cryptographic-grade randomness and HMAC-SHA256 integrity sealing to ensure no one — not even us — can manipulate dice results.

Random Number Algorithm

We use the Web Cryptography API's <code>crypto.getRandomValues()</code> — the same source used for TLS key generation and AES encryption. This draws directly from your server's hardware entropy pool (Intel RDRAND or Linux's <code>getrandom()</code> syscall), which cannot be predicted or reproduced. Unlike most VTT platforms that rely on JavaScript's <code>Math.random()</code> — a non-cryptographic generator that can technically be predicted — ShadowRealms rolls are seeded by true hardware entropy.

dice engine · Cloudflare Workers V8 isolate
// Single batch call for all dice in a roll expression
const buf = new Uint32Array(count + buffer);
crypto.getRandomValues(buf);

// Rejection sampling — eliminates modulo bias
const remainder = 4294967296 % sides;
const rejectFrom = 4294967296 - remainder;
// accept x only if x < rejectFrom → x % sides is uniform

Bias Elimination

We use rejection sampling (the Knuth uniform reduction method) to eliminate modulo bias — the subtle unfairness introduced when mapping a random number to a die face using the % operator. Our implementation rejects any raw value that would over-represent certain outcomes, ensuring a d6 truly has a 1-in-6 probability for each side.

d6

Rejection rate

< 0.000001%

d20

Rejection rate

< 0.000005%

d100

Rejection rate

< 0.000023%

d1000

Rejection rate

< 0.000023%

Performance

Dice expressions are batched: one <code>getRandomValues()</code> call generates all dice in a single expression. A full 1000d100 roll completes in under 200 microseconds — 250× faster than the individual-call approach and well within our 50ms latency target.

1d20

generation time

< 1µs

4d6kh3 × 6

generation time

< 5µs

1000d100

generation time

< 200µs

The 5W Dice Log

Every roll creates a permanent, immutable ledger entry with five-dimensional provenance:

👤

Who rolled: player account ID + character name

🎲

What was rolled: exact expression, every individual die value, kept values, and total

When it happened: server timestamp + cryptographic integrity seal

📍

Where it occurred: session, campaign, and realm identifiers

💬

Why it was rolled: the roll label (e.g., 'Attack Roll', 'Initiative')

Integrity Seal

Each roll is immediately sealed with an HMAC-SHA256 integrity proof using a server-side secret. The seal binds the exact dice values, expression, and timestamp into a single cryptographic commitment. Any retroactive modification would produce a different hash that wouldn't match the stored seal.

Future roadmap: on-chain attestation via web3 (EAS on Base/Optimism, Merkle tree anchoring) for fully trustless verification.

RFC 3161 TSA verification

Why We Can't Cheat

Dice log records are sealed with HMAC-SHA256 immediately on creation. The entropy proof (SHA-256 of the raw random bytes) ties the result to a specific draw from the OS entropy pool. Since we cannot control or predict hardware entropy, and the result is committed to an integrity-sealed ledger, manipulation is cryptographically infeasible.

Roll Syntax & Post Integrity

When you use the ::roll[expr] syntax in a post or message, the server resolves the roll on submission — the client never sees unresolved expressions. Each resolved roll is linked by ID; if someone tries to insert a roll ID from a different session or a fabricated ID, it renders as an 'Unverified Roll' badge, not as legitimate dice data.

Inline roll (pending)

::roll[2d6+3]

Resolved on submit → creates a dice log entry

Linked log (verified)

::diceLog[abc123]

Server-side verified reference to a dice log record

Unverified (fake)

::diceLog[unknown]

ID not in post's attachedDiceLogIds → renders as FAKE

Frequently Asked Questions

Can I see my dice log?

Yes — every roll you make is visible in the Session Dice Log panel. Secret (GM-only) rolls are hidden from players but included in exports.

Can the GM manipulate rolls?

No. GMs roll their own dice the same way players do. The server generates and records all rolls before anyone sees the result.

What happens if integrity sealing fails?

The roll result is displayed immediately regardless. HMAC sealing is near-instant (no external network call). If it fails, the record is marked and the entropy proof still demonstrates the roll's origin.

Can I export my dice logs?

Session dice logs can be exported as JSON or CSV from the session settings. The export includes the full entropy proof and integrity seal for verification.