@yipe/dice 0.2.22 → 0.3.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.
@@ -57,9 +57,20 @@ declare const onPotentCantripOnly: OutcomeType[];
57
57
  */
58
58
  declare class DiceQuery {
59
59
  readonly singles: PMF[];
60
- readonly combined: PMF;
60
+ private readonly _eps;
61
+ private readonly _combinedProvided;
62
+ private _combined?;
61
63
  private _combinedWithAttr?;
62
64
  constructor(singles: PMF | PMF[], combined?: PMF, eps?: number);
65
+ /**
66
+ * The combined damage distribution of all single PMFs (their convolution),
67
+ * normalized to total probability 1.
68
+ *
69
+ * Computed lazily on first access and cached. Queries that only need
70
+ * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance},
71
+ * {@link DiceQuery.stddev} — never trigger this convolution.
72
+ */
73
+ get combined(): PMF;
63
74
  private static readonly DEFAULT_OUTCOMES;
64
75
  /**
65
76
  * Returns a new PMF with damage attribution metadata populated.
@@ -104,6 +115,8 @@ declare class DiceQuery {
104
115
  * Used to determine how consistent the damage is.
105
116
  */
106
117
  stddev(): number;
118
+ /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */
119
+ stdev(): number;
107
120
  /**
108
121
  * Returns the Cumulative Distribution Function.
109
122
  */
@@ -148,6 +161,18 @@ declare class DiceQuery {
148
161
  */
149
162
  max(): number;
150
163
  private singleProb;
164
+ /**
165
+ * Full count distribution [P(0), P(1), …, P(n)] for "an attack succeeds if it
166
+ * carries ANY of `labels`", over the n independent singles.
167
+ *
168
+ * Each single's per-event success probability is the Poisson-binomial
169
+ * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne),
170
+ * computed exactly once. The binomial DP then runs once to produce the whole
171
+ * distribution, so the array-label paths of probExactlyK / probAtLeastK /
172
+ * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and
173
+ * re-running the DP per requested k.
174
+ */
175
+ private countDistribution;
151
176
  probAtLeastK(labels: OutcomeType | OutcomeType[], k: number): number;
152
177
  /**
153
178
  * Returns the probability that at least one attack has the specified outcome(s).
@@ -192,7 +217,7 @@ declare class DiceQuery {
192
217
  * Array examples:
193
218
  * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed
194
219
  * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds
195
- * - probExactlyK(['miss', 'missNone'], 0) = probability no attacks miss
220
+ * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss
196
221
  *
197
222
  * Use cases:
198
223
  * - "What's the chance exactly one of my attacks hits?"
@@ -209,7 +234,7 @@ declare class DiceQuery {
209
234
  * Single label examples:
210
235
  * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1)
211
236
  * - probAtMostK('crit', 0) = probability no attacks crit
212
- * - probAtMostK('miss', 2) = probability at most 2 attacks miss
237
+ * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss
213
238
  *
214
239
  * Array examples:
215
240
  * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds
@@ -253,7 +278,7 @@ declare class DiceQuery {
253
278
  *
254
279
  * Array examples:
255
280
  * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds
256
- * - damageStatsFrom(['miss', 'missNone']) = damage range when at least one attack misses
281
+ * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses
257
282
  *
258
283
  * Tactical Use Cases:
259
284
  * - "Given that I don't completely whiff (99% of turns), what damage should I expect?"
@@ -271,6 +296,12 @@ declare class DiceQuery {
271
296
  * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which
272
297
  * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats.
273
298
  *
299
+ * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an
300
+ * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and
301
+ * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than
302
+ * E[dmg | the label occurs]. For a single attack both are the plain
303
+ * conditional figures. Use {@link probAtLeastOne} for the scenario probability.
304
+ *
274
305
  * @example
275
306
  * // High-level tactical planning
276
307
  * const successStats = query.damageStatsFrom('hit')
@@ -335,7 +366,8 @@ declare class DiceQuery {
335
366
  count: number;
336
367
  };
337
368
  /**
338
- * Returns the probability that a result includes ANY of the specified labels.
369
+ * Returns the probability that at least one attack carries ANY of the
370
+ * specified labels (the marginal P(≥1) across the independent attacks).
339
371
  *
340
372
  * Examples:
341
373
  * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs)
@@ -344,6 +376,12 @@ declare class DiceQuery {
344
376
  * Use cases:
345
377
  * - "What's the chance my resolution includes a success label?"
346
378
  * - "How likely am I to get any hits or crits across all attacks?"
379
+ *
380
+ * Note: this must NOT be computed by summing `combined` bin probabilities. A
381
+ * single combined damage total is reachable by many outcome combinations and
382
+ * a bin can hold several labels at once, so summing `bin.p` over bins that
383
+ * contain a label over-counts. The correct marginal is the Poisson-binomial
384
+ * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}.
347
385
  */
348
386
  probabilityOf(labels: OutcomeType | OutcomeType[]): number;
349
387
  /**
@@ -525,6 +563,16 @@ declare class DiceQuery {
525
563
  * Snapshot of the distribution in the exact shape the UI consumes.
526
564
  * - outcome probabilities are "at least one" (and equal to "all" for a single PMF)
527
565
  * - damageRange is conditional on the outcome occurring
566
+ *
567
+ * The outcome probabilities use the correct Poisson-binomial marginals
568
+ * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)),
569
+ * so they are always valid probabilities in [0,1].
570
+ *
571
+ * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from
572
+ * the combined PMF's `count`, which the convolution accumulates as an EXPECTED
573
+ * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label]
574
+ * rather than a clean conditional expectation. It is correct for a single
575
+ * attack.
528
576
  */
529
577
  snapshot(order?: readonly OutcomeType[]): Snapshot;
530
578
  /**
@@ -629,7 +677,7 @@ declare class DiceQuery {
629
677
  *
630
678
  * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone]
631
679
  */
632
- firstSuccessSplit(successOutcome: string | string[], subsetOutcome: string | string[], eps?: number): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number];
680
+ firstSuccessSplit(successOutcome: OutcomeType | OutcomeType[], subsetOutcome: OutcomeType | OutcomeType[], eps?: number): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number];
633
681
  }
634
682
  type OutcomeSnapshot = {
635
683
  atLeastOneProbability: number;
@@ -669,6 +717,7 @@ declare class PMF {
669
717
  private _mean?;
670
718
  private _variance?;
671
719
  private _stdev?;
720
+ private _fingerprint?;
672
721
  constructor(map?: Map<number, Bin>, epsilon?: number, normalized?: boolean, identifier?: string, _preservedProvenance?: boolean);
673
722
  static empty(epsilon?: number, identifier?: string): PMF;
674
723
  static zero(epsilon?: number): PMF;
@@ -726,7 +775,7 @@ declare class PMF {
726
775
  * @param fallback PMF to apply when this PMF is *not* selected.
727
776
  * @returns A new PMF representing the weighted mixture of this PMF and the fallback.
728
777
  */
729
- gate(p: number, zero: PMF): PMF;
778
+ gate(p: number, fallback: PMF): PMF;
730
779
  /**
731
780
  * PMF.exclusive()
732
781
  *
@@ -773,6 +822,13 @@ declare class PMF {
773
822
  *
774
823
  * @returns New PMF with attr field populated in each bin
775
824
  */
825
+ /**
826
+ * Returns true if this PMF already carries damage attribution metadata.
827
+ *
828
+ * Only the first positive-damage bin is inspected (parser-generated PMFs
829
+ * populate `attr` uniformly), so this is O(1) in practice.
830
+ */
831
+ hasAttribution(): boolean;
776
832
  withAttribution(): PMF;
777
833
  /**
778
834
  * General-purpose N-way mixture.
@@ -827,6 +883,10 @@ declare class PMF {
827
883
  * Returns the standard deviation of the damage distribution.
828
884
  */
829
885
  stdev(): number;
886
+ /** Deep-copies a Bin, cloning its count and (optional) attr maps. */
887
+ private static cloneBin;
888
+ /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */
889
+ private static scaleBin;
830
890
  private static mergeInto;
831
891
  add(other: PMF): PMF;
832
892
  /**
@@ -841,6 +901,13 @@ declare class PMF {
841
901
  mapDamage(damageTransformFunction: (damageValue: number) => number): PMF;
842
902
  scaleDamage(factor: number, rounding?: "floor" | "round" | "ceil"): PMF;
843
903
  private getPMFCombineCacheKey;
904
+ /**
905
+ * A small content fingerprint (mass + bin count + face sum) so convolution
906
+ * cache keys change if the underlying numbers do. Memoized because a PMF is
907
+ * immutable once constructed — this avoids re-summing every key on each
908
+ * convolve() call (including cache hits).
909
+ */
910
+ fingerprint(): string;
844
911
  convolve(other: PMF, eps?: number, raw?: boolean): PMF;
845
912
  combineRaw(other: PMF, eps?: number): PMF;
846
913
  private static reduceConvolveLeft;
@@ -855,7 +922,20 @@ declare class PMF {
855
922
  * - Order-independent cache keys work better with consistent build patterns
856
923
  */
857
924
  static convolveMany(pmfList: PMF[], eps?: number): PMF;
858
- toJSON(): string;
925
+ /**
926
+ * Returns a plain, JSON-serializable representation of this PMF.
927
+ *
928
+ * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces
929
+ * the expected output (no double-encoding). Use {@link PMF.fromJSON} to
930
+ * reconstruct, or {@link PMF.toJSONString} if you need the string directly.
931
+ */
932
+ toJSON(): {
933
+ bins: Array<[number, Bin]>;
934
+ normalized: boolean;
935
+ identifier: string;
936
+ };
937
+ /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */
938
+ toJSONString(): string;
859
939
  static fromJSON(jsonData: {
860
940
  bins: Array<[number, Bin]>;
861
941
  normalized?: boolean;
@@ -868,7 +948,6 @@ declare class PMF {
868
948
  * Returns a new, non-normalized PMF.
869
949
  */
870
950
  prune(epsRel: number, minBins?: number): PMF;
871
- /** NEW - REVIEW IF THESE ARE USEFUL OR DUPLCIATIVE? */
872
951
  /** Probability mass at exactly x. */
873
952
  pAt(x: number): number;
874
953
  /** Dense integer support from min..max (inclusive).
@@ -932,4 +1011,4 @@ declare class PMF {
932
1011
  query(): DiceQuery;
933
1012
  }
934
1013
 
935
- export { type Bin as B, type CritConfig as C, type DamageDistribution as D, EPS as E, LRUCache as L, type OutcomeLabelMap as O, PMF as P, type Rounding as R, type Snapshot as S, type OutcomeType as a, onCritOnly as b, onHitOnly as c, onMissOnly as d, onMissDamageOnly as e, onSaveHalfOnly as f, onSaveFailOnly as g, onPotentCantripOnly as h, DiceQuery as i, type OutcomeSnapshot as j, onAnyHit as o, pmfCache as p };
1014
+ export { type Bin as B, type CritConfig as C, DiceQuery as D, EPS as E, LRUCache as L, type OutcomeLabelMap as O, PMF as P, type Rounding as R, type Snapshot as S, type DamageDistribution as a, type OutcomeSnapshot as b, type OutcomeType as c, onCritOnly as d, onHitOnly as e, onMissDamageOnly as f, onMissOnly as g, onPotentCantripOnly as h, onSaveFailOnly as i, onSaveHalfOnly as j, onAnyHit as o, pmfCache as p };
@@ -57,9 +57,20 @@ declare const onPotentCantripOnly: OutcomeType[];
57
57
  */
58
58
  declare class DiceQuery {
59
59
  readonly singles: PMF[];
60
- readonly combined: PMF;
60
+ private readonly _eps;
61
+ private readonly _combinedProvided;
62
+ private _combined?;
61
63
  private _combinedWithAttr?;
62
64
  constructor(singles: PMF | PMF[], combined?: PMF, eps?: number);
65
+ /**
66
+ * The combined damage distribution of all single PMFs (their convolution),
67
+ * normalized to total probability 1.
68
+ *
69
+ * Computed lazily on first access and cached. Queries that only need
70
+ * additive statistics — {@link DiceQuery.mean}, {@link DiceQuery.variance},
71
+ * {@link DiceQuery.stddev} — never trigger this convolution.
72
+ */
73
+ get combined(): PMF;
63
74
  private static readonly DEFAULT_OUTCOMES;
64
75
  /**
65
76
  * Returns a new PMF with damage attribution metadata populated.
@@ -104,6 +115,8 @@ declare class DiceQuery {
104
115
  * Used to determine how consistent the damage is.
105
116
  */
106
117
  stddev(): number;
118
+ /** Alias of {@link DiceQuery.stddev}, matching {@link PMF.stdev}. */
119
+ stdev(): number;
107
120
  /**
108
121
  * Returns the Cumulative Distribution Function.
109
122
  */
@@ -148,6 +161,18 @@ declare class DiceQuery {
148
161
  */
149
162
  max(): number;
150
163
  private singleProb;
164
+ /**
165
+ * Full count distribution [P(0), P(1), …, P(n)] for "an attack succeeds if it
166
+ * carries ANY of `labels`", over the n independent singles.
167
+ *
168
+ * Each single's per-event success probability is the Poisson-binomial
169
+ * marginal P(≥1 of labels) from {@link probabilityOf} (i.e. probAtLeastOne),
170
+ * computed exactly once. The binomial DP then runs once to produce the whole
171
+ * distribution, so the array-label paths of probExactlyK / probAtLeastK /
172
+ * probAtMostK can slice or sum from it instead of rebuilding a DiceQuery and
173
+ * re-running the DP per requested k.
174
+ */
175
+ private countDistribution;
151
176
  probAtLeastK(labels: OutcomeType | OutcomeType[], k: number): number;
152
177
  /**
153
178
  * Returns the probability that at least one attack has the specified outcome(s).
@@ -192,7 +217,7 @@ declare class DiceQuery {
192
217
  * Array examples:
193
218
  * - probExactlyK(['hit', 'crit'], 2) = probability exactly 2 attacks succeed
194
219
  * - probExactlyK(['hit', 'crit'], 1) = probability exactly 1 attack succeeds
195
- * - probExactlyK(['miss', 'missNone'], 0) = probability no attacks miss
220
+ * - probExactlyK(['missDamage', 'missNone'], 0) = probability no attacks miss
196
221
  *
197
222
  * Use cases:
198
223
  * - "What's the chance exactly one of my attacks hits?"
@@ -209,7 +234,7 @@ declare class DiceQuery {
209
234
  * Single label examples:
210
235
  * - probAtMostK('hit', 1) = probability 0 or 1 attacks hit (at most 1)
211
236
  * - probAtMostK('crit', 0) = probability no attacks crit
212
- * - probAtMostK('miss', 2) = probability at most 2 attacks miss
237
+ * - probAtMostK('missDamage', 2) = probability at most 2 attacks miss
213
238
  *
214
239
  * Array examples:
215
240
  * - probAtMostK(['hit', 'crit'], 1) = probability at most 1 attack succeeds
@@ -253,7 +278,7 @@ declare class DiceQuery {
253
278
  *
254
279
  * Array examples:
255
280
  * - damageStatsFrom(['hit', 'crit']) = damage range when at least one attack succeeds
256
- * - damageStatsFrom(['miss', 'missNone']) = damage range when at least one attack misses
281
+ * - damageStatsFrom(['missDamage', 'missNone']) = damage range when at least one attack misses
257
282
  *
258
283
  * Tactical Use Cases:
259
284
  * - "Given that I don't completely whiff (99% of turns), what damage should I expect?"
@@ -271,6 +296,12 @@ declare class DiceQuery {
271
296
  * This includes mixed scenarios (2 hits + 1 crit, 3 hits + 1 miss, etc.) which
272
297
  * occur far more frequently than pure scenarios. For pure scenarios, use combinedDamageStats.
273
298
  *
299
+ * KNOWN LIMITATION (multi-attack, single label): the returned `count` is an
300
+ * EXPECTED COUNT (E[#label], so > 1 for N≥2 attacks, not a probability), and
301
+ * `avg` is the size-biased conditional mean E[dmg·#label]/E[#label] rather than
302
+ * E[dmg | the label occurs]. For a single attack both are the plain
303
+ * conditional figures. Use {@link probAtLeastOne} for the scenario probability.
304
+ *
274
305
  * @example
275
306
  * // High-level tactical planning
276
307
  * const successStats = query.damageStatsFrom('hit')
@@ -335,7 +366,8 @@ declare class DiceQuery {
335
366
  count: number;
336
367
  };
337
368
  /**
338
- * Returns the probability that a result includes ANY of the specified labels.
369
+ * Returns the probability that at least one attack carries ANY of the
370
+ * specified labels (the marginal P(≥1) across the independent attacks).
339
371
  *
340
372
  * Examples:
341
373
  * - `query.probabilityOf('hit')` → 0.88 (probability at least one hit occurs)
@@ -344,6 +376,12 @@ declare class DiceQuery {
344
376
  * Use cases:
345
377
  * - "What's the chance my resolution includes a success label?"
346
378
  * - "How likely am I to get any hits or crits across all attacks?"
379
+ *
380
+ * Note: this must NOT be computed by summing `combined` bin probabilities. A
381
+ * single combined damage total is reachable by many outcome combinations and
382
+ * a bin can hold several labels at once, so summing `bin.p` over bins that
383
+ * contain a label over-counts. The correct marginal is the Poisson-binomial
384
+ * complement over the per-attack probabilities, i.e. {@link probAtLeastOne}.
347
385
  */
348
386
  probabilityOf(labels: OutcomeType | OutcomeType[]): number;
349
387
  /**
@@ -525,6 +563,16 @@ declare class DiceQuery {
525
563
  * Snapshot of the distribution in the exact shape the UI consumes.
526
564
  * - outcome probabilities are "at least one" (and equal to "all" for a single PMF)
527
565
  * - damageRange is conditional on the outcome occurring
566
+ *
567
+ * The outcome probabilities use the correct Poisson-binomial marginals
568
+ * (`atLeastOneProbability` = P(≥1 attack has it), `allProbability` = P(all do)),
569
+ * so they are always valid probabilities in [0,1].
570
+ *
571
+ * KNOWN LIMITATION (multi-attack): `damageRange.avg` is still aggregated from
572
+ * the combined PMF's `count`, which the convolution accumulates as an EXPECTED
573
+ * COUNT, so for N≥2 attacks it is the size-biased mean E[dmg·#label]/E[#label]
574
+ * rather than a clean conditional expectation. It is correct for a single
575
+ * attack.
528
576
  */
529
577
  snapshot(order?: readonly OutcomeType[]): Snapshot;
530
578
  /**
@@ -629,7 +677,7 @@ declare class DiceQuery {
629
677
  *
630
678
  * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone]
631
679
  */
632
- firstSuccessSplit(successOutcome: string | string[], subsetOutcome: string | string[], eps?: number): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number];
680
+ firstSuccessSplit(successOutcome: OutcomeType | OutcomeType[], subsetOutcome: OutcomeType | OutcomeType[], eps?: number): readonly [pSuccess: number, pSubset: number, pAny: number, pNone: number];
633
681
  }
634
682
  type OutcomeSnapshot = {
635
683
  atLeastOneProbability: number;
@@ -669,6 +717,7 @@ declare class PMF {
669
717
  private _mean?;
670
718
  private _variance?;
671
719
  private _stdev?;
720
+ private _fingerprint?;
672
721
  constructor(map?: Map<number, Bin>, epsilon?: number, normalized?: boolean, identifier?: string, _preservedProvenance?: boolean);
673
722
  static empty(epsilon?: number, identifier?: string): PMF;
674
723
  static zero(epsilon?: number): PMF;
@@ -726,7 +775,7 @@ declare class PMF {
726
775
  * @param fallback PMF to apply when this PMF is *not* selected.
727
776
  * @returns A new PMF representing the weighted mixture of this PMF and the fallback.
728
777
  */
729
- gate(p: number, zero: PMF): PMF;
778
+ gate(p: number, fallback: PMF): PMF;
730
779
  /**
731
780
  * PMF.exclusive()
732
781
  *
@@ -773,6 +822,13 @@ declare class PMF {
773
822
  *
774
823
  * @returns New PMF with attr field populated in each bin
775
824
  */
825
+ /**
826
+ * Returns true if this PMF already carries damage attribution metadata.
827
+ *
828
+ * Only the first positive-damage bin is inspected (parser-generated PMFs
829
+ * populate `attr` uniformly), so this is O(1) in practice.
830
+ */
831
+ hasAttribution(): boolean;
776
832
  withAttribution(): PMF;
777
833
  /**
778
834
  * General-purpose N-way mixture.
@@ -827,6 +883,10 @@ declare class PMF {
827
883
  * Returns the standard deviation of the damage distribution.
828
884
  */
829
885
  stdev(): number;
886
+ /** Deep-copies a Bin, cloning its count and (optional) attr maps. */
887
+ private static cloneBin;
888
+ /** Returns a new Bin with p, count, and attr all multiplied by `factor`. */
889
+ private static scaleBin;
830
890
  private static mergeInto;
831
891
  add(other: PMF): PMF;
832
892
  /**
@@ -841,6 +901,13 @@ declare class PMF {
841
901
  mapDamage(damageTransformFunction: (damageValue: number) => number): PMF;
842
902
  scaleDamage(factor: number, rounding?: "floor" | "round" | "ceil"): PMF;
843
903
  private getPMFCombineCacheKey;
904
+ /**
905
+ * A small content fingerprint (mass + bin count + face sum) so convolution
906
+ * cache keys change if the underlying numbers do. Memoized because a PMF is
907
+ * immutable once constructed — this avoids re-summing every key on each
908
+ * convolve() call (including cache hits).
909
+ */
910
+ fingerprint(): string;
844
911
  convolve(other: PMF, eps?: number, raw?: boolean): PMF;
845
912
  combineRaw(other: PMF, eps?: number): PMF;
846
913
  private static reduceConvolveLeft;
@@ -855,7 +922,20 @@ declare class PMF {
855
922
  * - Order-independent cache keys work better with consistent build patterns
856
923
  */
857
924
  static convolveMany(pmfList: PMF[], eps?: number): PMF;
858
- toJSON(): string;
925
+ /**
926
+ * Returns a plain, JSON-serializable representation of this PMF.
927
+ *
928
+ * Follows the standard `toJSON` contract, so `JSON.stringify(pmf)` produces
929
+ * the expected output (no double-encoding). Use {@link PMF.fromJSON} to
930
+ * reconstruct, or {@link PMF.toJSONString} if you need the string directly.
931
+ */
932
+ toJSON(): {
933
+ bins: Array<[number, Bin]>;
934
+ normalized: boolean;
935
+ identifier: string;
936
+ };
937
+ /** Serializes this PMF to a JSON string (equivalent to `JSON.stringify(pmf)`). */
938
+ toJSONString(): string;
859
939
  static fromJSON(jsonData: {
860
940
  bins: Array<[number, Bin]>;
861
941
  normalized?: boolean;
@@ -868,7 +948,6 @@ declare class PMF {
868
948
  * Returns a new, non-normalized PMF.
869
949
  */
870
950
  prune(epsRel: number, minBins?: number): PMF;
871
- /** NEW - REVIEW IF THESE ARE USEFUL OR DUPLCIATIVE? */
872
951
  /** Probability mass at exactly x. */
873
952
  pAt(x: number): number;
874
953
  /** Dense integer support from min..max (inclusive).
@@ -932,4 +1011,4 @@ declare class PMF {
932
1011
  query(): DiceQuery;
933
1012
  }
934
1013
 
935
- export { type Bin as B, type CritConfig as C, type DamageDistribution as D, EPS as E, LRUCache as L, type OutcomeLabelMap as O, PMF as P, type Rounding as R, type Snapshot as S, type OutcomeType as a, onCritOnly as b, onHitOnly as c, onMissOnly as d, onMissDamageOnly as e, onSaveHalfOnly as f, onSaveFailOnly as g, onPotentCantripOnly as h, DiceQuery as i, type OutcomeSnapshot as j, onAnyHit as o, pmfCache as p };
1014
+ export { type Bin as B, type CritConfig as C, DiceQuery as D, EPS as E, LRUCache as L, type OutcomeLabelMap as O, PMF as P, type Rounding as R, type Snapshot as S, type DamageDistribution as a, type OutcomeSnapshot as b, type OutcomeType as c, onCritOnly as d, onHitOnly as e, onMissDamageOnly as f, onMissOnly as g, onPotentCantripOnly as h, onSaveFailOnly as i, onSaveHalfOnly as j, onAnyHit as o, pmfCache as p };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yipe/dice",
3
- "version": "0.2.22",
3
+ "version": "0.3.0",
4
4
  "description": "A high-performance dice probability engine for D&D 5e DPR calculations. Powers dprcalc.com.",
5
5
  "keywords": [
6
6
  "dnd",
@@ -62,6 +62,7 @@
62
62
  "./dist/builder/dc.js"
63
63
  ],
64
64
  "scripts": {
65
+ "format": "eslint . --fix",
65
66
  "lint": "eslint .",
66
67
  "typecheck": "tsc -p config/tsconfig.build.json --noEmit",
67
68
  "build": "tsup --config config/tsup.config.ts",
@@ -79,17 +80,14 @@
79
80
  "node": ">=18.17"
80
81
  },
81
82
  "devDependencies": {
82
- "@types/node": "^24.3.1",
83
- "@typescript-eslint/eslint-plugin": "^8.43.0",
84
- "@typescript-eslint/parser": "^8.43.0",
85
- "@vitest/coverage-v8": "^3.2.4",
86
- "eslint": "^9.35.0",
87
- "eslint-plugin-import": "^2.32.0",
88
- "ts-node": "^10.9.2",
89
- "tsconfig-paths": "^4.2.0",
90
- "tsup": "^8.5.0",
91
- "tsx": "^4.20.5",
92
- "typescript": "^5.9.2",
93
- "vitest": "^3.2.4"
83
+ "@types/node": "^26.0.1",
84
+ "@typescript-eslint/eslint-plugin": "^8.62.0",
85
+ "@typescript-eslint/parser": "^8.62.0",
86
+ "@vitest/coverage-v8": "^4.1.9",
87
+ "eslint": "^10.6.0",
88
+ "tsup": "^8.5.1",
89
+ "tsx": "^4.22.4",
90
+ "typescript": "^6.0.3",
91
+ "vitest": "^4.1.9"
94
92
  }
95
93
  }