Add the 5e dice grammar: parse and roll master
storied needs a dice engine before the DM can call for a check or roll damage. This lands src/dice.rs: parse() turns notation like 4d6kh3 or 3d6-1 + 4d100+5 into a Notation, and roll() turns that into a RollResult carrying every die that hit the table, what counted, and the total, so the next task can narrate a natural 20 or the 1 that Halfling Luck saved instead of just printing a number. The grammar covers keep/drop (khN/klN/dhN/dlN), rerolls (roN/ro<N/ro>N once, rN/r<N/r>N until the condition stops matching), and per-die minimums (miN), case-insensitive and whitespace-tolerant per the SRD's own notation. Die sizes are restricted to the eight a 5e table actually rolls (2, 4, 6, 8, 10, 12, 20, 100); d3, the SRD's shorthand for a halved d6, is a parse error like any other unsupported size. Rerolls and minimums settle before keep/drop decide which dice count, and a keep/drop count larger than the group takes every die. 84 tests cover every notation form and error path against seeded exact outcomes, at 100% line coverage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUXjWo1zGFhdJUig1hcQGf