Skip to content

feat: add percentile dice (d%) #35

@edloidas

Description

@edloidas

Add d% as an alias for d100. The notation d% and 2d% should produce DiceNode with sides = Literal(100).

Currently d% throws a parse error because % after d is scanned as DICE + MODULO. With the Stage 2 foundation (#34), the lexer already emits DICE_PERCENT for d%. This issue covers the parser and test work.

Parser Changes

  • NUD (prefix d%): create DiceNode(Literal(1), Literal(100))
  • LED (infix 2d%): create DiceNode(left, Literal(100))
  • getLeftBp: DICE_PERCENTBP.DICE_LEFT (40)
  • No getRightBp needed — sides are always 100, no right-hand expression to parse

No new AST node type. Reuses DiceNode with synthetic sides = Literal(100).

Evaluator Changes

None. DiceNode with sides = 100 is already handled correctly by evalDice. Critical detection (result === 100 && 100 > 1) and fumble detection (result === 1) work naturally.

Expression/Rendered Output

  • expression: shows 1d100 (canonical form)
  • notation: preserves d% (original input)
  • rendered: 1d100[42] = 42

Edge Cases

Expression Expected
d% 1d100
2d% 2d100
D% 1d100 (case-insensitive)
d%+5 1d100 + 5
2d%kh1 Roll 2d100, keep highest
(2)d% 2d100 (computed count)
d % 3 Error — whitespace breaks the d% token, producing DICE + MODULO
d%% Error — d% consumed as DICE_PERCENT, then bare % is MODULO with no NUD
10%3 Modulo (unchanged) — % after digit is still MODULO in the operator switch

Test Plan

  • Parser tests for AST structure: d%, 2d%, d%+5, 2d%kh1, (2)d%
  • Evaluator tests with MockRNG: verify sides = 100, total in [1, 100]
  • Integration tests via roll(): d%, 2d%, d%+5
  • Property test: Nd% total is in [N, N*100]
  • Error tests: d%%, standalone d% followed by invalid token

Drafted with AI assistance

Metadata

Metadata

Assignees

Labels

featureNew functionality

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions