@yipe/dice 0.2.0 → 0.2.2

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/dist/index.cjs CHANGED
@@ -48,6 +48,14 @@ var LRUCache = class {
48
48
 
49
49
  // src/common/types.ts
50
50
  var EPS = 1e-12;
51
+ var onAnyHit = ["hit", "crit"];
52
+ var onCritOnly = ["crit"];
53
+ var onHitOnly = ["hit"];
54
+ var onMissOnly = ["missNone", "missDamage"];
55
+ var onMissDamageOnly = ["missDamage"];
56
+ var onSaveHalfOnly = ["saveHalf"];
57
+ var onSaveFailOnly = ["saveFail"];
58
+ var onPotentCantripOnly = ["pc"];
51
59
 
52
60
  // src/pmf/query.ts
53
61
  var _DiceQuery = class _DiceQuery {
@@ -1387,40 +1395,68 @@ var _PMF = class _PMF {
1387
1395
  * - A PMF representing its outcome (e.g., damage dice).
1388
1396
  * - A weight representing its probability of being selected.
1389
1397
  *
1390
- * This is useful for modeling situations like:
1391
- * - Sneak Attack: 6d6 if first hit is a crit, 3d6 if first hit is non-crit, 0 otherwise.
1392
- * - Multiple exclusive spells: Fireball vs Cone of Cold vs nothing.
1393
- *
1394
1398
  * Notes:
1395
- * - If the total weight is less than 1, a zero PMF will automatically be added to
1396
- * make up the remaining probability.
1397
- * - Throws an error if the total weight is greater than 1 (invalid probability sum).
1398
- *
1399
- * @param options Array of `{ pmf, weight }` objects, each representing an outcome and its probability.
1400
- * @param label Optional name for debugging or charting.
1401
- * @param eps Optional tolerance for floating point rounding
1402
- * @returns A single PMF representing the exclusive mixture of all options.
1399
+ * - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero()
1400
+ *
1401
+ * @param options Array of `{ pmf, weight }` or `[PMF, number]`.
1402
+ * @param eps Optional tolerance for floating point rounding.
1403
1403
  */
1404
1404
  static exclusive(options, eps = EPS) {
1405
1405
  const items = options.map(
1406
1406
  (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o
1407
1407
  );
1408
- const totalWeight = items.reduce((s, { weight }) => s + weight, 0);
1409
- if (totalWeight - 1 > eps) {
1408
+ for (const { weight } of items) {
1409
+ if (!Number.isFinite(weight) || weight < -eps) {
1410
+ throw new Error(`PMF.exclusive: invalid weight ${weight}.`);
1411
+ }
1412
+ }
1413
+ let totalWeight = items.reduce((s, { weight }) => s + weight, 0);
1414
+ if (Math.abs(totalWeight) <= eps) totalWeight = 0;
1415
+ if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1;
1416
+ if (totalWeight > 1 + eps) {
1410
1417
  throw new Error(
1411
1418
  `PMF.exclusive: total weight ${totalWeight.toFixed(6)} exceeds 1.`
1412
1419
  );
1413
1420
  }
1414
- let out = items.reduce(
1415
- (acc, { pmf, weight }) => weight > 0 ? acc.addScaled(pmf, weight) : acc,
1416
- _PMF.empty(eps)
1417
- );
1418
- const leftover = 1 - totalWeight;
1421
+ let out = _PMF.empty(eps);
1422
+ for (const { pmf, weight } of items) {
1423
+ if (weight > eps) out = out.addScaled(pmf, weight);
1424
+ }
1425
+ const leftover = Math.max(0, 1 - totalWeight);
1419
1426
  if (leftover > eps) {
1420
1427
  out = out.addScaled(_PMF.zero(), leftover);
1421
1428
  }
1422
1429
  return out;
1423
1430
  }
1431
+ /**
1432
+ * PMF.mix()
1433
+ *
1434
+ * Builds a PMF as a linear combination of input PMFs with the given weights.
1435
+ * Unlike `exclusive`, this does NOT:
1436
+ * - enforce that weights sum to 1
1437
+ * - add leftover probability to δ0 (PMF.zero())
1438
+ *
1439
+ * Use when outcomes are not mutually exclusive, or for interpolation/blending.
1440
+ *
1441
+ * @param options Array of `{ pmf, weight }` or `[PMF, number]`.
1442
+ * @param eps Optional tolerance for skipping tiny weights.
1443
+ */
1444
+ static mix(options, eps = EPS) {
1445
+ const items = options.map(
1446
+ (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o
1447
+ );
1448
+ for (const { weight } of items) {
1449
+ if (!Number.isFinite(weight)) {
1450
+ throw new Error(`PMF.mix: invalid weight ${weight}.`);
1451
+ }
1452
+ }
1453
+ let out = _PMF.empty(eps);
1454
+ for (const { pmf, weight } of items) {
1455
+ if (Math.abs(weight) <= eps) continue;
1456
+ out = out.addScaled(pmf, weight);
1457
+ }
1458
+ return out;
1459
+ }
1424
1460
  /**
1425
1461
  * General-purpose N-way mixture.
1426
1462
  * weights: Array of [weight, PMF].
@@ -3143,6 +3179,14 @@ exports.Mixture = Mixture;
3143
3179
  exports.PMF = PMF;
3144
3180
  exports.clearParserCache = clearParserCache;
3145
3181
  exports.getCachingEnabled = getCachingEnabled;
3182
+ exports.onAnyHit = onAnyHit;
3183
+ exports.onCritOnly = onCritOnly;
3184
+ exports.onHitOnly = onHitOnly;
3185
+ exports.onMissDamageOnly = onMissDamageOnly;
3186
+ exports.onMissOnly = onMissOnly;
3187
+ exports.onPotentCantripOnly = onPotentCantripOnly;
3188
+ exports.onSaveFailOnly = onSaveFailOnly;
3189
+ exports.onSaveHalfOnly = onSaveHalfOnly;
3146
3190
  exports.parse = parse;
3147
3191
  exports.pmfCache = pmfCache;
3148
3192
  exports.setCachingEnabled = setCachingEnabled;