Structured breakdown of an evaluated expression, mirroring the AST 1:1 —
every ASTNode produces exactly one RollPart. Discriminants are lowercase
camelCase to distinguish evaluation-tree types from ASTNode.type
(PascalCase) at a glance.
Invariants:
RollResult.parts.total === RollResult.total.
successCount.total === successes - failures.
literal.total === value and variable.total === value.
Each part's rolls[] shares DieResult references with
RollResult.rolls[]; both reflect post-evaluation state (explode
accumulation, reroll flags, keep/drop flags). No deep clone.
Meta-expression sub-trees (4d6kh(1d2), (1+1)d6 counts/sides, computed
thresholds) are not surfaced as nested parts — their resolved numbers
appear in the owning part, and their dice are inspectable in
RollResult.rolls via the 'meta' modifier tag.
Structured breakdown of an evaluated expression, mirroring the AST 1:1 — every ASTNode produces exactly one RollPart. Discriminants are lowercase camelCase to distinguish evaluation-tree types from
ASTNode.type(PascalCase) at a glance.Invariants:
RollResult.parts.total === RollResult.total.successCount.total === successes - failures.literal.total === valueandvariable.total === value.rolls[]sharesDieResultreferences withRollResult.rolls[]; both reflect post-evaluation state (explode accumulation, reroll flags, keep/drop flags). No deep clone.Meta-expression sub-trees (
4d6kh(1d2),(1+1)d6counts/sides, computed thresholds) are not surfaced as nested parts — their resolved numbers appear in the owning part, and their dice are inspectable inRollResult.rollsvia the'meta'modifier tag.