Skip to content

feat: complete the Roll20 math and matching syntax - #11

Merged
darkliquid merged 17 commits into
masterfrom
feat/roll20-math-and-matching
Oct 6, 2026
Merged

darkliquid merged 17 commits into
masterfrom
feat/roll20-math-and-matching

Conversation

@darkliquid

Copy link
Copy Markdown
Owner

Summary

  • Adds the remaining Roll20 Dice Reference syntax tracked by Complete more of the dice rolling specification #6: math operators (*, /, %, **), math functions (floor, round, ceil, abs), dice matching (mt/m), and computed die sizes.
  • Die sizes may be any expression resolving to a single integer total, including nested rolls, e.g. 3d(4d(d8)kh3+2) resolves its faces from a roll of a roll.
  • Keeps existing normalisation intact by folding constant modifiers into terms, so 4d6kh3+2 still renders as 4d6+2kh3.

Adds exhaustive unit and spec tests, documents the new syntax on the website, and compiles the repl binary in CI. The wasm bundle is rebuilt and the binary wire format is bumped to 0x04.

Fixes #6

💘 Generated with Crush

Capture the design and task breakdown for the remaining Roll20 Dice
Reference gaps (issue #6) before implementing them, so the parser,
value model and serialization changes are agreed up front.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
Roll20 orders multiplication before addition, so a math expression layer
now sits above the existing dice-term parser. Constants added to a roll
are still folded into its modifier to preserve current normalisation,
while products such as 2d6+4*3 evaluate as 2d6 + (4*3).

This lays the groundwork (new opcodes, value channel, canonical and
binary handling) for the remaining operators and functions.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
Division uses the same precedence as multiplication, so 2d6/2*3
evaluates left to right. Dividing by zero returns an explicit error
rather than an unusable result.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
Modulus shares multiplication precedence and keeps the percentile die
syntax working: a percent sign is only a die symbol immediately after
"d", and is the modulus operator everywhere else. Modulus by zero
returns an explicit error.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
Use ** (Roll20 does not accept ^). Exponentiation binds tighter than
multiplication and associates to the right, so 2**3**2 is 512.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
Functions receive the exact (possibly fractional) value of their
argument, so floor(11/2) sees 5.5 and returns 5. This introduces the
function opcode and node used by the remaining math functions.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
Round follows the Roll20 rule: values below a half round toward zero and
values at or above a half round toward positive infinity. The scanner
now tells "round" apart from the "r"/"ro" reroll modifiers by look-ahead.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
Ceil rounds toward positive infinity, so ceil(-3/2) is -1 rather than
-2, matching the Roll20 definition.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
Abs returns the absolute value of its argument, completing the four
math functions listed in the Roll20 Dice Reference.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
Counts how many distinct face values match often enough, so 20d6mt
yields 0-6 matches. An optional count sets how many dice must match
(default two) and an optional comparison filters which face value
qualifies, e.g. 5d6mt3>4. The visual-only m modifier is accepted and
leaves the total unchanged.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
Adds the math operators, math functions and dice matching to the REPL
syntax catalog so autocomplete and help text stay in sync, covers the
new tokens in the scanner tests, and rebuilds the browser bundle.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
A bare d followed by a parenthesised expression now derives the die size
at compile time, so 3d(floor(6/2)) rolls 3d3. Non-constant, fractional
and unsafe sizes are rejected with clear errors, and the new tests cover
these alongside other unusual constructions such as 4d%%2.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
The build task only compiled packages and the wasm target. Building the
repl binary explicitly means a link or entrypoint regression there fails
CI instead of going unnoticed.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
The help panel now lists the math operators, math functions, dice
matching and computed die sizes, with clickable examples, and the lucky
pool includes them so they are discoverable from the landing page.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
A computed die size may now depend on rolled dice, so
3d(4d(d8)kh3+2) resolves its faces from a nested roll before rolling
the outer dice. The side expression shares the roll context, so limits
and the depth budget still apply, and it is folded at compile time when
constant. Nested programs are serialized in the die term (binary
version 0x04).

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
The help panel and design spec now explain that a computed die size may
come from a nested roll, with a worked 3d(4d(d8)kh3+2) example.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
Resolve the ast.go conflict by keeping the extracted reroll/explosion
helpers from master and passing the runtime-resolved die into them, and
rebuild the wasm bundle.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
@darkliquid
darkliquid merged commit 2bd3e40 into master Oct 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Complete more of the dice rolling specification

1 participant