@yipe/dice 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,15 +5,30 @@ All notable changes to this project are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [0.4.0]
8
+ ## [0.5.0]
9
9
 
10
10
  Pushes damage-attribution / provenance and D&D-probability logic that the
11
11
  consuming app (dprcalc) had hand-rolled over PMF internals down into the
12
- library, so the provenance model and dice math stay owned here. All additive
13
- except the Elemental-Adept bounce fix noted below.
12
+ library, so the provenance model and dice math stay owned here, and adds a
13
+ **composable scale node** so a scaled/rounded sub-roll can nest inside a larger
14
+ damage payload (per-damage-type resistance / immunity / vulnerability). All
15
+ additive except the Elemental-Adept bounce fix noted below.
14
16
 
15
17
  ### Added
16
18
 
19
+ - **`RollBuilder.scaleResult(numerator, denominator = 1, rounding = 'floor')`** —
20
+ wraps a builder in a composable `scale` AST node that scales its resolved PMF
21
+ by `numerator / denominator` with the given rounding. Unlike the old
22
+ `.half()` wrapper, a scaled builder composes: it survives `sumRolls(...)`
23
+ instead of being dropped on a flat-config merge, so a per-type resisted or
24
+ doubled sub-roll keeps its own scaling inside a larger hit/crit payload. The
25
+ rendered expression reflects it — `denominator === 1 → "N * (child)"`,
26
+ `numerator === 1 → "(child) // D"`, general → `"(child) * N // D"`. `.half()`
27
+ is now `scaleResult(1, 2, 'floor')`.
28
+ - **`sumRolls(parts: RollBuilder[])`** — additive factory whose `toAST()` is an
29
+ `add` node over each part's AST, letting scaled and plain children sit side by
30
+ side without the flat `.plus()` merge collapsing them. `toExpression()` joins
31
+ the parts with ` + ` and `toPMF()` convolves them.
17
32
  - **`PMF.applyHitFrequency(frequency)`** — provenance-preserving mass
18
33
  redistribution for effects that only occur with some probability (conditional
19
34
  attacks, on-hit riders, sub-one AoE fractions): scales every hit bin (damage
@@ -4094,6 +4094,15 @@ var RollBuilder = class _RollBuilder {
4094
4094
  half() {
4095
4095
  return new HalfRollBuilder(this);
4096
4096
  }
4097
+ /**
4098
+ * Scale this roll's result by `numerator / denominator`, rounding each outcome.
4099
+ * A general, composable form of {@link half} — used to model damage-type resistance
4100
+ * (`scaleResult(1, 2)` → `(expr) // 2`) and vulnerability (`scaleResult(2)` → `2 * (expr)`).
4101
+ * Compose several of these (and plain rolls) into one payload with {@link sumRolls}.
4102
+ */
4103
+ scaleResult(numerator, denominator = 1, rounding = "floor") {
4104
+ return new ScaleRollBuilder(this, numerator, denominator, rounding);
4105
+ }
4097
4106
  // Create a "max of N rolls" version of this roll for crit damage with keep operations
4098
4107
  maxOf(count) {
4099
4108
  return new MaxOfRollBuilder(this, count);
@@ -4148,6 +4157,50 @@ var HalfRollBuilder = class _HalfRollBuilder extends RollBuilder {
4148
4157
  return new _HalfRollBuilder(this.innerRoll.copy());
4149
4158
  }
4150
4159
  };
4160
+ var ScaleRollBuilder = class _ScaleRollBuilder extends RollBuilder {
4161
+ constructor(innerRoll, numerator, denominator = 1, rounding = "floor") {
4162
+ super(0);
4163
+ this.innerRoll = innerRoll;
4164
+ this.numerator = numerator;
4165
+ this.denominator = denominator;
4166
+ this.rounding = rounding;
4167
+ }
4168
+ hasHiddenState() {
4169
+ return this.innerRoll.hasHiddenState();
4170
+ }
4171
+ get lastConfig() {
4172
+ return this.innerRoll.lastConfig;
4173
+ }
4174
+ getSubRollConfigs() {
4175
+ return this.innerRoll.getSubRollConfigs();
4176
+ }
4177
+ toExpression() {
4178
+ const inner = this.innerRoll.toExpression();
4179
+ if (this.denominator === 1) return `${this.numerator} * (${inner})`;
4180
+ if (this.numerator === 1) return `(${inner}) // ${this.denominator}`;
4181
+ return `(${inner}) * ${this.numerator} // ${this.denominator}`;
4182
+ }
4183
+ toAST() {
4184
+ return {
4185
+ type: "scale",
4186
+ numerator: this.numerator,
4187
+ denominator: this.denominator,
4188
+ rounding: this.rounding,
4189
+ child: this.innerRoll.toAST()
4190
+ };
4191
+ }
4192
+ toPMF(eps = 0) {
4193
+ return pmfFromRollBuilder(this, eps);
4194
+ }
4195
+ copy() {
4196
+ return new _ScaleRollBuilder(
4197
+ this.innerRoll.copy(),
4198
+ this.numerator,
4199
+ this.denominator,
4200
+ this.rounding
4201
+ );
4202
+ }
4203
+ };
4151
4204
  var MaxOfRollBuilder = class _MaxOfRollBuilder extends RollBuilder {
4152
4205
  constructor(innerRoll, count, diceCount, diceSides) {
4153
4206
  super(0);
@@ -4446,6 +4499,49 @@ var PooledRollBuilder = class _PooledRollBuilder extends RollBuilder {
4446
4499
  return new _PooledRollBuilder(sumNode, newExpr);
4447
4500
  }
4448
4501
  };
4502
+ var CompositeSumRollBuilder = class _CompositeSumRollBuilder extends RollBuilder {
4503
+ constructor(parts) {
4504
+ super(0);
4505
+ this.parts = parts;
4506
+ }
4507
+ hasHiddenState() {
4508
+ return true;
4509
+ }
4510
+ getSubRollConfigs() {
4511
+ return [];
4512
+ }
4513
+ toAST() {
4514
+ return {
4515
+ type: "add",
4516
+ children: this.parts.map((p) => ({
4517
+ node: p.toAST(),
4518
+ sign: 1
4519
+ }))
4520
+ };
4521
+ }
4522
+ toExpression() {
4523
+ const exprs = this.parts.map((p) => p.toExpression()).filter((e) => e && e !== "0");
4524
+ if (exprs.length === 0) return "0";
4525
+ let result = exprs[0];
4526
+ for (let i = 1; i < exprs.length; i++) {
4527
+ const e = exprs[i];
4528
+ result += e.startsWith("-") ? ` - ${e.substring(1)}` : ` + ${e}`;
4529
+ }
4530
+ return result.replace(/\+ -/g, "-");
4531
+ }
4532
+ toPMF(eps = 0) {
4533
+ return pmfFromRollBuilder(this, eps);
4534
+ }
4535
+ copy() {
4536
+ return new _CompositeSumRollBuilder(this.parts.map((p) => p.copy()));
4537
+ }
4538
+ };
4539
+ function sumRolls(parts) {
4540
+ const meaningful = parts.filter((p) => p !== void 0);
4541
+ if (meaningful.length === 0) return new RollBuilder(0);
4542
+ if (meaningful.length === 1) return meaningful[0];
4543
+ return new CompositeSumRollBuilder(meaningful);
4544
+ }
4449
4545
 
4450
4546
  // src/builder/factory.ts
4451
4547
  var rollFn = (count, sidesOrDie, modifier) => {
@@ -4676,6 +4772,11 @@ function resolve(node, eps = defaultEps) {
4676
4772
  if (count === 1) return childPMF;
4677
4773
  return computeMaxOfPMF(childPMF, count, eps);
4678
4774
  }
4775
+ case "scale": {
4776
+ const childPMF = resolve(node.child, eps);
4777
+ const denom = node.denominator === 0 ? 1 : node.denominator;
4778
+ return childPMF.scaleDamage(node.numerator / denom, node.rounding);
4779
+ }
4679
4780
  }
4680
4781
  })();
4681
4782
  builderPMFCache.set(cacheKey, result);
@@ -4747,6 +4848,7 @@ function findDie(node) {
4747
4848
  case "d20Roll":
4748
4849
  case "half":
4749
4850
  case "maxOf":
4851
+ case "scale":
4750
4852
  return findDie(node.child);
4751
4853
  case "keep":
4752
4854
  return findDie(node.child.child);
@@ -4931,6 +5033,8 @@ function getASTSignature(node) {
4931
5033
  return `half{ch:${getASTSignature(node.child)}}`;
4932
5034
  case "maxOf":
4933
5035
  return `maxOf{c:${node.count},ch:${getASTSignature(node.child)}}`;
5036
+ case "scale":
5037
+ return `scale{n:${node.numerator},d:${node.denominator},r:${node.rounding},ch:${getASTSignature(node.child)}}`;
4934
5038
  case "add": {
4935
5039
  let constantValue = 0;
4936
5040
  const otherChildrenSigs = [];
@@ -5378,6 +5482,7 @@ exports.ParsedRollBuilder = ParsedRollBuilder;
5378
5482
  exports.PooledRollBuilder = PooledRollBuilder;
5379
5483
  exports.RollBuilder = RollBuilder;
5380
5484
  exports.SaveBuilder = SaveBuilder;
5485
+ exports.ScaleRollBuilder = ScaleRollBuilder;
5381
5486
  exports.builderPMFCache = builderPMFCache;
5382
5487
  exports.d = d;
5383
5488
  exports.d10 = d10;
@@ -5391,5 +5496,6 @@ exports.defaultConfig = defaultConfig;
5391
5496
  exports.flat = flat;
5392
5497
  exports.hd20 = hd20;
5393
5498
  exports.roll = roll;
5499
+ exports.sumRolls = sumRolls;
5394
5500
  //# sourceMappingURL=index.cjs.map
5395
5501
  //# sourceMappingURL=index.cjs.map