Skip to content

feat(builder): composable scale node for per-type damage transforms#4

Merged
yipe merged 4 commits into
mainfrom
claude/composable-scale-node
Jul 13, 2026
Merged

feat(builder): composable scale node for per-type damage transforms#4
yipe merged 4 commits into
mainfrom
claude/composable-scale-node

Conversation

@yipe

@yipe yipe commented Jul 12, 2026

Copy link
Copy Markdown
Owner

What & why

Adds a general, composable scaling primitive to the builder so a sub-roll can be halved / doubled / dropped and still combine with plain rolls into one payload — without the flat-config .plus() merge silently dropping it (the limitation HalfRollBuilder hits today).

This is the building block for per-damage-type enemy resistance / immunity / vulnerability in the DPR calculator (yipe/dpr): build one sub-roll per damage type, scale (or omit) each, and sumRolls them into a single hit/crit/save payload. Because scaling stays per-type, floor(½) is applied per type (never to the merged total), and it flows into both the PMF and the rendered expression.

API

  • ScaleNode AST node: { type: 'scale', numerator, denominator, rounding, child }, wired into resolve() (PMF), findDie(), getASTSignature(), and the expression renderer.
  • RollBuilder.scaleResult(numerator, denominator = 1, rounding = 'floor')ScaleRollBuilder. Renders N * (expr), (expr) // D, or (expr) * N // D.
  • sumRolls(parts) — additive composite whose PMF convolves the parts and whose expression joins them with +, preserving parts that carry hidden state (scaleResult / half).

Examples:

  • resistance → d6.scaleResult(1, 2)(1d6) // 2, floored PMF {0,1,1,2,2,3}
  • vulnerability → d6.scaleResult(2)2 * (1d6), mean 7
  • sumRolls([base, fire.scaleResult(1, 2)])1d8 + 3 + (1d6) // 2

Testing

New scale.test.ts covers floor/round/ceil scaling, the ///* rendering, the mixed-type per-type floor, and a full attack carrying resistance through both hit and crit payloads with attribution intact. Full suite green (yarn build && yarn typecheck && yarn test, 1230 tests).

Notes

  • Existing flat .plus() hot path and the half node are untouched.
  • The final commit vendors the built dist/ on this branch so consumers can install it as a git dependency before 0.4 is published to npm; revert once @yipe/dice@0.4 is published.

yipe and others added 3 commits July 12, 2026 19:51
Add a general `scale` AST node plus `RollBuilder.scaleResult(numerator,
denominator, rounding)` and a `sumRolls(parts)` composite factory. Unlike
the fixed `half()` node, a scaled sub-roll can be combined with plain rolls
via `sumRolls` without the flat-config `.plus()` merge silently dropping it,
so the scaling survives into both the PMF and the rendered expression.

Renders as `N * (expr)` (e.g. vulnerability, `2 * (1d6)`), `(expr) // D`
(e.g. resistance, `(1d6) // 2`), or `(expr) * N // D`. Enables per-damage-type
resistance / immunity / vulnerability in downstream consumers: build one
sub-roll per damage type, scale (or omit) each, and `sumRolls` them into a
single hit/crit/save payload that convolves per-type PMFs (so `floor` stays
per-type) while attribution and outcome mixing flow through the existing path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UnisdByShvDQBbAgudZggX
`dist/` is normally gitignored and produced by `prepare` on npm install.
Committing the built output on this branch lets a consumer install it as a
git dependency (before 0.4 is published to npm) without running the build.
Revert once @yipe/dice@0.4 is published and consumers pin `^0.4.0`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UnisdByShvDQBbAgudZggX
The dist/ was committed only as a bridge so a consumer could install this
branch as a git dependency before 0.4.0 was published. It's build output
(regenerated by prepublishOnly/prepare), gitignored, and bloated the diff by
~21.5k lines. Removing it from tracking leaves the branch as the ~258-line
source change. Publish 0.4.0 to npm; consumers then depend on ^0.4.0 rather
than a git ref.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UnisdByShvDQBbAgudZggX
@yipe yipe marked this pull request as ready for review July 13, 2026 07:15
Bump version to 0.5.0 and roll the unreleased 0.4.0 changelog into it,
documenting the composable scaleResult / sumRolls API alongside the
existing PMF provenance work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UnisdByShvDQBbAgudZggX
@yipe yipe merged commit e665b38 into main Jul 13, 2026
8 checks 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.

1 participant