@yipe/dice 0.2.23 → 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.
- package/README.md +17 -0
- package/dist/builder/index.cjs +391 -295
- package/dist/builder/index.cjs.map +1 -1
- package/dist/builder/index.d.cts +8 -8
- package/dist/builder/index.d.ts +8 -8
- package/dist/builder/index.js +391 -295
- package/dist/builder/index.js.map +1 -1
- package/dist/index.cjs +356 -256
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -3
- package/dist/index.d.ts +29 -3
- package/dist/index.js +356 -257
- package/dist/index.js.map +1 -1
- package/dist/{pmf-BC1poIqF.d.cts → pmf-DqUCnYN9.d.cts} +89 -10
- package/dist/{pmf-BC1poIqF.d.ts → pmf-DqUCnYN9.d.ts} +89 -10
- package/package.json +11 -13
|
@@ -57,9 +57,20 @@ declare const onPotentCantripOnly: OutcomeType[];
|
|
|
57
57
|
*/
|
|
58
58
|
declare class DiceQuery {
|
|
59
59
|
readonly singles: PMF[];
|
|
60
|
-
readonly
|
|
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(['
|
|
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('
|
|
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(['
|
|
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
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
|
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(['
|
|
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('
|
|
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(['
|
|
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
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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": "^
|
|
83
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
84
|
-
"@typescript-eslint/parser": "^8.
|
|
85
|
-
"@vitest/coverage-v8": "^
|
|
86
|
-
"eslint": "^
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
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
|
}
|