Changes
All notable changes to this project are written here. The format follows Keep a Changelog, and the project uses Semantic Versioning.
Unreleased
[1.14.0] - 2026-10-01
Added
- One die on its own.
mountDie(element, options)draws a single die with nothing round it, of two kinds: one that rolls (a button; a tap, Enter or Space throws it, and it lands on the face the generator chose before anything moved) and one that does not roll and only shows a face (rollable: false,show(face)).<korokoro-die>is the same as a tag, with akorokoro-die-rollevent. Any die the tray draws, insmall,mediumorlargeor any width, one steady square that nothing moves in, not selectable, with no tumble for reduced motion and no sound unless asked. Shown in the demo (#one-die) and live on the documentation's "Use it in your project" page. - A d6's one pip in black as well as red.
onePip: "red" | "black"on the tray and the die,one-pipon the elements,setOnePip()in place, and?onepip=blackon the embed page. Red stays the default.
1.13.0 - 2026-10-01
Added
- The tray's cloth. Lay the felt in green (its own), blue, red, black or wood, the five the whole family and itsutsu.com's boards offer: the
clothoption, theclothattribute on<korokoro-roller>(changed in place, keeping the dice and the rolls),setCloth()on the tray, and?cloth=on the embed page.KOROKORO_CLOTHSholds the colours. - The demo's header has the family's cloth patches; the tray and the page follow them, and the embed code carries the cloth chosen.
1.12.0 - 2026-09-30
Added
- Embed the tray on any site.
embed/on the demo site is the dice and nothing else, for an iframe:embed/?dice=2d6%2B3&size=small. It takesdice,size,lang,sound=off,seedand the coloursfeltandink, tells the page that frames it each roll, tracks nothing and loads nothing from anywhere else. The demo writes the iframe and the one-tag code for the dice last rolled, with a look at each size. size, onmountRoller, the element (size="small"), and the React and Vue components:"small"is the felt and the result alone,"medium"adds the choice of dice and bonus,"large"(the default) is everything. The type isRollerSize.roll, a third name for the command line besidekorokoroandkoro:roll 2d6+3.
1.11.0 - 2026-09-30
Added
- A documentation site, at https://johnmorrisdotca.github.io/korokoro/docs/: a guide, the notation, the games, the command line, an API reference made from the source by TypeDoc, search, light and dark, and the real tray on the pages for live examples. Its pages are made from the README and
docs/byscripts/docs-site.mjs, so nothing is written twice and every example on it is one the tests run. A stub in Japanese says the rest is to come. - Dice as plain text, from an address:
/api/?roll=2d20kh1%2B5&seed=tableshows that roll as text, JSON or CSV and nothing else, by the same function the command line is. It runs in the browser and says so; a page that frames it is sent the answer and can ask for more. - A conformance suite and a specification, for ports to other languages:
conformance/korokoro-conformance.json(seeds and the numbers they give, notation and how it is written back or why it is refused, seeded rolls die for die, exact odds),docs/spec/random.mdanddocs/spec/notation.md. The suite is made from this implementation and a test fails when they differ;conformance/port_check.pyis the generator written in Python from the specification alone, checking itself against the suite. - Use Korokoro from another language: the shape of the command line's JSON, and a working example in Python, Go, Rust and C#, each a file CI runs.
@johnmorrisdotca/korokoro/element/define: the custom element registered by being imported, so that one script tag is the whole of it.defineRoller()stays for a page that wants to choose when. It is the one module with an effect of its own, andsideEffectsinpackage.jsonnames it so that a bundler keeps it.notationon the React component, as on the Vue component and the element:<DiceRoller notation="2d20kh1+5" />, followed as it changes.keyboard: false(keyboard="off"on the element) leaves a page its Space key, for a tray that is one thing among many on a page.- A page can use the package from a CDN with nothing to install; the example is tested as written.
pnpm docs:site,pnpm test:languages, andnode tray/look.mjs, which compares pictures of the demo before and after a change, pixel for pixel.
Changed
- The demo's look is the family's:
demo/family.css, shared unchanged with the sibling packages and held to its hash by a test, and the header, footer and language chooser fromscripts/family-template.mjs. The footer now names the family. Everything above it is pixel for pixel what it was. - The file of recorded sounds is
dist/ui/sounds-data.js(it wassounds.data.js). The old name made VitePress take it for one of its own data files and fail to build a site that used the tray. The./soundsentry is unchanged.
1.10.0 - 2026-09-30
Added
- A web component:
<korokoro-roller notation="2d20kh1+5" wide>, from@johnmorrisdotca/korokoro/element. CalldefineRoller()once. Attributes for the dice, the language and the everyday options; anoptionsproperty for what a string cannot carry;roll(),setSpec()andhistoryon the element; and akorokoro-rollevent for each roll. Importing it on a server does nothing and throws nothing. - A Vue component:
<DiceRoller notation="2d20kh1+5" wide @roll="…" />, from@johnmorrisdotca/korokoro/vue. The tray's options as props, arollevent, the dice and the language followed as they change, androll(),history(),setSpec()andsetLocale()on a template ref. It renders an empty box on a server. Vue is an optional peer dependency, 3.3 or later; the package itself still has none. - Both are tapped in Chromium and WebKit by
pnpm test:tray, imported from the npm tarball by ESM and byrequireinpnpm test:package, and built into a small project each byscripts/check-frameworks.mjs.
1.9.0 - 2026-09-30
Added
- Arithmetic.
(2d6+3)*2,2d6x2,1d20-1d4,floor(4d6/2),ceil(…),round(…),abs(1d6-1d6),max(1d6,1d8),min(…), with brackets as deep as you like. Worked out in whole-number fractions, never in floating point, in the roll and in its odds alike, and the odds are exact. A division has to be rounded, so that a roll always comes to a whole number. - Grouped rolls that keep one:
{4d6,3d8}kh1andkl1, written back asmax(4d6,3d8)andmin(…). - Dice that all differ:
4d6u. Each die is thrown from the faces not yet showing. Exact odds, counted in whole numbers. - More than ten dice, for a caller that asks: the one option
maxDice, onparseNotation(text, { maxDice: 100 }),normalizeSpec(spec, { maxDice }),roll(spec, source, { maxDice })androllMany, takes a roll up to a hundred plain dice, all added or one kept, and--max-dicedoes the same on the command line. Without it each stops at ten, as it always has:roll({ count: 50, sides: 6 })still throws ten dice.roll's third argument is the time of the roll, as before, or{ at, maxDice }. MathNode,evaluateMath,checkMath,mathText,mathRange,spreadMath,groupTotals,groupRange,isPlainDice,formulaText,MAX_DICE_BY_CODE,MAX_UNIQUE_SIDESand theMAX_MATH_…limits.- In the tray, a formula is typed and shown as it was written, each kind of dice as it fell:
([6 4]+3)*2. The dice buttons start a new roll and cannot change a formula, and the tray says so.
Changed
- Four spellings that were refused are now read:
1d20-1d4(dice taken away), and2d6+3+1d4,3+2d6and2d6+1+1, which are the plain rolls2d6+1d4+3,2d6+3and2d6+2.checkNotationno longer gives the"minus"problem. Nothing that was read before is read differently, and every seeded roll replays as it did.
1.8.0 - 2026-09-30
Added
- A command line:
korokoro, andkorofor short, on Linux, macOS and Windows. It rolls any notation (koro 2d20kh1+5), several rolls at once, a roll many times (--times, or6#), from a seed (--seed); shows the exact odds without rolling (--odds); rolls and reads a game's dice (--game,--games); tests a real die for fairness (--test); reads rolls from standard input (--stdin); and prints JSON (--json, versioned byformat) or CSV (--csv) for other programs. English or Japanese, from--lang, the environment or the system.NO_COLORis honoured. Exit codes 0, 1 and 2. runCli(args, surroundings): the whole command line as a pure function.- Export:
toJSON,toCSVandtoTextwrite rolls out, andfromJSONreads the JSON back, trusting nothing in it. The CSV never hands a spreadsheet a formula.rollText,diceText,csvCell,CSV_COLUMNS,EXPORT_FORMAT. - Export and import in the tray, one level down under the history: save as CSV, JSON or text, and bring a JSON export back in.
VERSION.pnpm test:cliruns the built command line as a child process, andpnpm test:packagepacks the package with npm, installs the tarball into an empty project, imports every entry inexportsby ESM and byrequire, and runs both commands as installed. CI runs the two on Linux, macOS and Windows with Node 22 and 24, and the release workflow runs the package check before it publishes.
1.7.0 - 2026-09-30
Added
- Counting successes.
6d10>=8is how many of six d10 show 8 or more, andf=1takes one away for each 1. Any comparison:=,<,>,<=,>=,<>. The headline reads Successes, each die says whether it is one, and the odds are exact. - Explosions on the faces you say, and two more kinds of them:
3d6!>=5, compounding3d6!!, penetrating3d6!p, and3d6!!p. - Rerolls at any comparison:
2d6r=3,2d6r>=5,2d6ro=6;randroalone reroll the lowest face. - A least and a most for each die:
4d6min2,4d6max5. - Critical marks:
1d20cs>=19cf=1. Marks only; the total is the same. - Sorted dice:
4d6sd,4d6sa. The roll keeps the order thrown. - A label for the roll:
2d6+3 # fire damage, or[fire damage] 2d6+3. Text, and only ever shown as text. - Short forms other rollers write:
4d6k3,4d6d1,4d6b3,2d20w1. Compare,meets,isSuccessRoll,countsSuccesses,dieOutcomes,rulesText,playsByNewRules,rollLabel,MAX_ROLL_LABEL; on a die,countsandcritical, andvaluewherever a die is worth something other than its face.- Notation compared: what is read, what is still to come, and where the order of modifiers differs from RPG Dice Roller's. A test holds the page to the package.
- A table of the order modifiers are applied in, in the README.
- The tray's tests are in the repository:
pnpm test:trayopens the built demo in Chromium and WebKit with Playwright (a development dependency only) and taps it, at a phone's width by touch and at a desktop's by mouse. CI runs them. - A game can say that dice are not held in it (
hold: falseon a preset), and the tray then holds none: craps, Monopoly and the rest no longer offer it.
Changed
3d6!!and2d6r2were refused and are now read, as compounding dice and as a reroll of 2s. Nothing that was read before is read differently, and every seeded roll replays as it did.- A refusal of a reroll or an explosion is worded for the wider rules.
1.6.0 - 2026-09-30
Added
- Games. The dice of 44 games, and how each game reads them: Monopoly, Catan, Backgammon and its doubling cube, Snakes and Ladders, Ludo, Parcheesi, Pachisi, Risk; Yahtzee, Farkle, Bunco, Pig, Liar's dice, Poker dice, Ship captain and crew, Shut the box, Mexico, Left Center Right; Craps, Sic bo, Chuck-a-luck, Hazard, Chō-han (丁半) and Chinchirorin (チンチロリン); a d20 check, advantage, disadvantage, ability scores, Fate, Blades in the Dark, Powered by the Apocalypse, d10 and d6 pools, percentile, 3d6 roll-under and d66; and a coin, yes-no-maybe, pick a number, rock-paper-scissors, a compass and a colour die. Each is one entry of data. Korokoro rolls and reads the dice; it does not run the game.
- One Games control in the tray, closed until it is opened: a search box and the games on their shelves. Choosing one sets the dice and reads every roll in the game's terms ("A small straight", "8 is the point", "The defender loses 2"). Yahtzee and Ship, captain and crew count their three rolls and hold dice between them; craps, Pig, Hazard and the doubles games read a roll in the light of the ones before it.
?game=yahtzeeopens one by link, and a shared roll carries its game. - Exact odds of each outcome in the Odds tab and in
presetOdds, counted over every way the dice can fall, withyahtzeeWithin,crapsPassandchinchirorinHandWithinfor the figures that take more than one roll. PRESETS(alsopresets),getPreset,findPresets,presetSpec,rollPreset,readPreset,presetOdds,readDiceAs,patternsOf,waysToShutandREADING_WORDS. A game is found by its id, its name, its Japanese name or any other name it goes by.- Several rolls in one go.
rollMany(spec, times)throws the same dice up to 100 times from the one generator and returns ordinary rolls with their sum, highest and lowest;6#4d6dl1writes it as notation. In the tray it is Times, under More, from 1 to 10: one tap, a list of every roll with the highest and lowest marked, one entry in the history, one sound, and one link for the lot.setOf,MAX_TIMES,shareQueryMany,readSharedMany. chanceAnyAtLeast(spec, target, times)andexpectedHighest(spec, times): the chance that at least one of several rolls reaches a target, and the best of them on average.- A gallery of the games, made from the data and checked by a test, a Suggest a game issue template, and a worked example of adding a game in CONTRIBUTING.
Changed
- The tray's Custom dice, loaded dice and sets is now More: times, custom dice, loaded dice and sets.
- While several rolls are thrown at once, dice are not held: the tray says why.
1.5.0 - 2026-09-30
Added
- Custom dice. A die with any faces you like:
d[Yes,No,Maybe],2d[Hit=1,Miss=0,Miss=0],d[1,1,2,3,5,8]. Up to 20 faces, each up to 16 characters, with an optional value for totals and an optional colour; a face written twice comes up twice as often. Exact odds over what the faces are worth, and how often each face comes up where they are only words. A face's words are text and are never read as HTML. - Sets of dice, saved by name on the device and shared by a link (
?dice=…&name=…).makeSet,loadSets,storeSets,setQuery,readSet. - Loaded dice, for the curious and the suspicious.
d6{6:3}is a d6 that favours its six. Korokoro's own dice remain fair; these are the world's most conscientious cheats, marked on the felt, in the history, beside the total, in the data (loaded: true) and in every link, with no way to share one as fair. Three house dice come with it: the Optimist, the Six-Ace Flat and the Odd Couple, who have never made a seven.isFair(spec)refuses the lot in one call. - A fairness test.
fairnessTest(counts)is a chi-square test of whether a die's results look fair, with the chance worked out exactly and no verdict on a handful of rolls. The Stats tab uses it, and Test a real die takes results typed or pasted from a die you can pick up. - In the tray, all of the above lives one level down, under Custom dice, loaded dice and sets. The buttons still roll fair dice and nothing else.
- The odds of a loaded roll drawn over the same dice if they were fair, with a line such as "6 comes up 3 in 8, not 1 in 6".
- A language choice:
languageChooser: trueadds English and 日本語 to the tray,setLocalechanges it from code, and the demo has a chooser of its own that follows the browser, remembers the choice and takes?lang=ja. docs/loaded-dice.md: how to load a die, how to catch one, and a short history of crooked dice with its sources.- The README gains Roll in 30 seconds, Use it in your project (the API, plain HTML, React, Vue, Svelte and Angular, each proved from the packed tarball by
scripts/check-frameworks.mjs), The name, Used by and The family. isFair,isLoaded,hasTotal,dieName,chancesOf,valueOfFace,groupOf,rollFrom,faceChances,loadingOf,LOADED_PRESETS,readResults, and the limits for custom and loaded dice.
Changed
chiSquareTailis now exact (the incomplete gamma function), where it was an approximation good to a couple of digits. Fairness figures in the Stats tab may differ in their last digit.faceStatscounts only fair dice when given a number of sides, so a loaded d6's results are never mixed into the fair d6's.- Notation may be up to 400 characters, to make room for a custom die.
- Long notation wraps in the tray and no longer widens the page.
1.4.0 - 2026-09-30
Changed
rnow rerolls until clear, androrerolls once, as Roll20 and the dice libraries that follow it write them.2d8r<3used to throw a 1 or a 2 again once; it now throws it again until it shows 3 or more. Write2d8ro<3for the old behaviour. A reroll until clear may match at most half a die's faces and stops after 10 rerolls (MAX_REROLLS), so it always ends, and its odds are exact, that limit included. Specs, stored histories and links made by 1.2.0 and 1.3.0 still mean what they meant: seedocs/migrating.md.- In the tray, a tap on a die that has been rolled holds it, where it used to roll again. A tap on the felt beside the dice, or Space, rolls.
hold: falsekeeps every tap a roll. - In the tray, tapping a die button adds a die of that kind to the roll, where it used to change the kind of every die. The number row sets how many of the kind last touched. The dice the tray opens with are a suggestion, which the first die tapped replaces, so a fresh tray and one tap on d20 is still
1d20;placeholder: falsemakes them the user's own. - A dropped or rerolled die is struck through in the sum under the total, where it was in brackets; brackets now gather a kind of dice, as in
14 + (3 + 2) + 1.
Added
- Several kinds of dice in one roll:
1d20+1d4,2d6+1d8+3,2d20kh1+1d4+5. Up to four kinds and ten dice, each kind with its own modifiers, with exact odds for the lot.RollSpec.moreholds the other kinds; a roll of one kind is the same object it always was. - Building a roll by tapping: each tap on a die adds one, a chip for each kind takes one away, Clear empties the roll, and the notation box writes it out as you go. At ten dice or four kinds the buttons that would go past dim and the row says why.
- Holding dice: after a roll, tap dice on the felt to hold them and roll the rest, as Yacht and Farkle do. Release all lets them go. The Odds tab follows the dice still to roll.
rollHeld,canHoldanddistributionHoldingdo the same from code; a roll records which dice were held, and a seed replays it. - Every odds function takes a distribution as well as a spec.
groupsOf,specOf,diceCount,sidesOf,MAX_GROUPS,MAX_REROLLS,SHARE_VERSION, and alegacyRerolloption for reading old notation.- Refusals that name their part for the new rules: too many kinds, too many dice over the whole roll, and dice taken away.
- For phones: every control in the tray is at least 44px, the notation box turns off autocorrect and capitals, and nothing below the felt moves when the dice land or the roll grows.
docs/strings-ja.md, every Japanese string beside its English, made from the source; and an issue template for fixing a translation. The Japanese has not yet been reviewed by a native reader.docs/migrating.md.- The README's notation tables and examples are now run by the tests.
1.3.0 - 2026-09-30
Added
- Up to ten dice in one roll, where it was five:
8d6,10d10kh3. The tray's count goes from 1 to 10, in two rows of five on a phone, and ten dice sit on the felt in two even rows. - The sound of real dice: a shake while they tumble and a knock as each lands, with a mute button on the felt that the device remembers. The recordings are fetched only when a roll first needs them. It starts muted where the device asks for reduced motion.
sound: falsefor a silent tray with no button, andplaySoundfor a sound of your own. The React component passes both through.exactCounts: the odds of plain dice and of one die kept, as whole numbers.- The dice now start their tumble one after another and land one after another, all within
animationMs. @johnmorrisdotca/korokoro/sounds, the recordings on their own, andcreateRollSoundfor using them outside the tray.
Changed
- Plain dice and advantage are counted in
BigInt, so ten d1000 (10^30 outcomes) is as exact as two d6. Every figure from 1.0.0 to 1.2.0 is unchanged, and so is every seeded roll. 9d6and the like, refused until now, are rolled;normalizeSpecbrings a count down to ten where it brought it down to five.- The average and the luck of a roll are worked out once for each spec, so a long history of large rolls no longer slows the tray.
- The README is rewritten for everything from 1.0.0 to here, the demo page says what the tray can do, and every exported function and type has a doc comment.
Credits
- Dice sounds from Kenney's Casino Audio, CC0, kenney.nl. See
SOUNDS.md.
1.2.0 - 2026-09-30
Added
- Dice of any size, from 2 sides to 1000:
d3,d14,2d1000. The tray's buttons stay the eight dice a table owns; any other die is typed as notation and drawn as a plain token that says what it is. - Fate dice:
4dF, each −1, 0 or +1. - Keep or drop any number of dice:
4d6kh3,5d10kl2,4d6dl1,5d8dh2. - Rerolls:
2d6r<3or2d6r<=2throws a 1 or a 2 again, once, and the new face stands. - Exploding dice:
3d6!. A die showing its highest face throws another, up toMAX_EXPLOSIONS(10) more for each die, on dice of 100 sides or fewer. - Exact odds for all of the above. Exploding dice stop at the same limit in the roll and in the odds, so the odds are those of the dice as thrown.
Roll.dice: what happened to every die thrown (kept, dropped or rerolled, and whether it exploded), withdiceOfandreadDiceto work it out from a roll's faces.Roll.facesandRoll.keptnow include rerolled dice and the dice an explosion added, and the total is still the kept faces plus the bonus.checkNotation, which says which part of a notation was refused and why.parseNotationstill returnsnull.- In the tray: dropped and rerolled dice are struck through, exploded dice are marked, and a refused notation says which part. Charts of many totals draw neighbouring totals as one bar, and no longer run off a phone.
isSides,faceRange,keptCount, and the limitsMIN_SIDES,MAX_SIDES,MAX_EXPLOSIONSandMAX_EXPLODING_SIDES.
Changed
RollSpec.sidesis nownumber | "F"(the typeSides), where it was one of the listed dice.DieSidesandDIE_SIDESare still that list: the dice with a button.RollSpecgains three optional fields,keepCount,explodeandreroll, which are left out of a roll that does not use them, so a 1.0.0 spec, its notation, its seeded rolls and its odds are unchanged.- Notation that 1.1.0 refused because the die was not on the list, such as
2d7, is now rolled, andnormalizeSpeckeeps such a die where it used to fall back to a d6. - The tray's strings gain the words for refusals and for a die's status;
notationBadno longer lists the dice.
1.1.0 - 2026-09-30
Added
- The d30, a thirty-sided die: in the tray's row of dice, in notation (
d30,2d30+3,2d30kh1), in the exact odds, and in history, stats and shared links. It is drawn as a rhombic triacontahedron seen face on.
Fixed
- The package's
exportsnow carry adefaultcondition besideimport, so tools that resolve it from CommonJS-compiled code (Playwright specs, for one) find it instead of failing withNo "exports" main defined.
1.0.0 - 2026-09-30
Added
- Roll one to five dice at once: d4, d6, d8, d10, d12, d20 or d100, with a bonus, and advantage or disadvantage (keep the highest or lowest).
- Dice notation:
3d6+2,1d20,2d20kh1,2d20kl1,d%. - Fair rolls from
crypto.getRandomValueswith rejection sampling, and a seeded generator (sfc32) that throws the same dice for the same seed. - Exact odds for every roll: each total's chance, the chance to meet a target, the average, the spread, the most likely totals and how lucky a roll was.
- A history of up to 500 rolls kept in
localStorage, read back defensively. - Stats: luck, hot and cold streaks, matching dice, natural 20s and 1s, each face's count with a chi-square fairness test, and totals against the odds.
- Any roll as a shareable link.
- The tray: tap the felt or press Space to roll, animated dice drawn in each die's own shape, history, stats and odds panels, English and Japanese, light and dark, themeable through CSS variables, and reduced motion respected.
DiceRoller, a React component, from@johnmorrisdotca/korokoro/react.- A static demo, published to GitHub Pages.