@yipe/dice 0.1.1 → 0.1.3
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 +1 -3
- package/dist/index.cjs +248 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -97
- package/dist/index.d.ts +72 -97
- package/dist/index.js +246 -58
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -45,20 +45,19 @@ var LRUCache = class {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
// src/types.ts
|
|
48
|
-
var
|
|
49
|
-
var TEST_EPS = 1e-10;
|
|
48
|
+
var EPS = 1e-12;
|
|
50
49
|
|
|
51
50
|
// src/query.ts
|
|
52
51
|
var _DiceQuery = class _DiceQuery {
|
|
53
|
-
constructor(singles, combined) {
|
|
52
|
+
constructor(singles, combined, eps = EPS) {
|
|
54
53
|
__publicField(this, "singles");
|
|
55
54
|
__publicField(this, "combined");
|
|
56
55
|
this.singles = Array.isArray(singles) ? singles : [singles];
|
|
57
|
-
const c = combined ?? PMF.convolveMany(this.singles);
|
|
58
|
-
this.combined = Math.abs(c.mass() - 1) <= 1e-12 ? c : c.normalize();
|
|
59
56
|
if (this.singles.some((s) => s === void 0)) {
|
|
60
57
|
throw new Error("DiceQuery contains undefined singles");
|
|
61
58
|
}
|
|
59
|
+
const c = combined ?? PMF.convolveMany(this.singles);
|
|
60
|
+
this.combined = Math.abs(c.mass() - 1) <= eps ? c : c.normalize();
|
|
62
61
|
}
|
|
63
62
|
/**
|
|
64
63
|
* Returns the expected damage across all possible outcomes.
|
|
@@ -356,23 +355,16 @@ var _DiceQuery = class _DiceQuery {
|
|
|
356
355
|
* - "How much damage comes from miss effects (like save-for-half spells)?"
|
|
357
356
|
*/
|
|
358
357
|
expectedDamageFrom(labels) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
358
|
+
const wanted = Array.isArray(labels) ? labels : [labels];
|
|
359
|
+
let total = 0;
|
|
360
|
+
for (const single of this.singles) {
|
|
361
|
+
for (const [dmg, bin] of single) {
|
|
362
|
+
let p = 0;
|
|
363
|
+
for (const label of wanted) p += bin.count[label] ?? 0;
|
|
364
|
+
total += dmg * p;
|
|
366
365
|
}
|
|
367
366
|
}
|
|
368
|
-
return
|
|
369
|
-
}
|
|
370
|
-
_expectedDamageFromSingle(label) {
|
|
371
|
-
let totalExpectedDamage = 0;
|
|
372
|
-
for (const [, probabilityBin] of this.combined) {
|
|
373
|
-
totalExpectedDamage += probabilityBin.attr?.[label] || 0;
|
|
374
|
-
}
|
|
375
|
-
return totalExpectedDamage;
|
|
367
|
+
return total;
|
|
376
368
|
}
|
|
377
369
|
/**
|
|
378
370
|
* Returns damage statistics for scenarios where AT LEAST ONE attack results in
|
|
@@ -595,7 +587,7 @@ var _DiceQuery = class _DiceQuery {
|
|
|
595
587
|
* query.toStackedChartData(['hit', 'crit'])
|
|
596
588
|
* // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]}
|
|
597
589
|
*/
|
|
598
|
-
toStackedChartData(labels = [], epsilon =
|
|
590
|
+
toStackedChartData(labels = [], epsilon = EPS) {
|
|
599
591
|
const damageValues = this.combined.support();
|
|
600
592
|
damageValues.map((dmg) => {
|
|
601
593
|
const bin = this.combined.map.get(dmg);
|
|
@@ -1227,7 +1219,7 @@ var _DiceQuery = class _DiceQuery {
|
|
|
1227
1219
|
*
|
|
1228
1220
|
* Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone]
|
|
1229
1221
|
*/
|
|
1230
|
-
firstSuccessSplit(successOutcome, subsetOutcome, eps =
|
|
1222
|
+
firstSuccessSplit(successOutcome, subsetOutcome, eps = EPS) {
|
|
1231
1223
|
const pmfs = this.singles;
|
|
1232
1224
|
if (!pmfs.length) {
|
|
1233
1225
|
throw new Error("firstSuccessSplitFromPMFs: pmfs must be non-empty");
|
|
@@ -1276,15 +1268,14 @@ __publicField(_DiceQuery, "DEFAULT_OUTCOMES", [
|
|
|
1276
1268
|
"missNone"
|
|
1277
1269
|
]);
|
|
1278
1270
|
var DiceQuery = _DiceQuery;
|
|
1279
|
-
var EPSILON = COMPUTATIONAL_EPS;
|
|
1280
1271
|
var pmfCache = new LRUCache(1e3);
|
|
1281
1272
|
var _PMF = class _PMF {
|
|
1282
|
-
constructor(map = /* @__PURE__ */ new Map(), epsilon =
|
|
1273
|
+
constructor(map = /* @__PURE__ */ new Map(), epsilon = EPS, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`, _preservedProvenance = true) {
|
|
1283
1274
|
this.map = map;
|
|
1284
1275
|
this.epsilon = epsilon;
|
|
1285
1276
|
this.normalized = normalized;
|
|
1286
1277
|
this.identifier = identifier;
|
|
1287
|
-
this.
|
|
1278
|
+
this._preservedProvenance = _preservedProvenance;
|
|
1288
1279
|
// Cached computed values
|
|
1289
1280
|
__publicField(this, "_support");
|
|
1290
1281
|
__publicField(this, "_min");
|
|
@@ -1294,15 +1285,18 @@ var _PMF = class _PMF {
|
|
|
1294
1285
|
__publicField(this, "_variance");
|
|
1295
1286
|
__publicField(this, "_stdev");
|
|
1296
1287
|
}
|
|
1297
|
-
static empty(epsilon =
|
|
1288
|
+
static empty(epsilon = EPS, identifier = "empty") {
|
|
1298
1289
|
return new _PMF(/* @__PURE__ */ new Map(), epsilon, false, identifier);
|
|
1299
1290
|
}
|
|
1300
1291
|
// This has a single bin at value 0, mass of 1
|
|
1301
|
-
static zero(epsilon =
|
|
1292
|
+
static zero(epsilon = EPS) {
|
|
1302
1293
|
const m = /* @__PURE__ */ new Map();
|
|
1303
1294
|
m.set(0, { p: 1, count: { miss: 1 }, attr: {} });
|
|
1304
1295
|
return new _PMF(m, epsilon, false, "zero");
|
|
1305
1296
|
}
|
|
1297
|
+
static delta(value, epsilon = EPS) {
|
|
1298
|
+
return _PMF.fromMap(/* @__PURE__ */ new Map([[value, 1]]), epsilon);
|
|
1299
|
+
}
|
|
1306
1300
|
// This creates a single bin at value 0, but with weight 0.
|
|
1307
1301
|
static emptyMass() {
|
|
1308
1302
|
return _PMF.zero().scaleMass(0);
|
|
@@ -1405,7 +1399,7 @@ var _PMF = class _PMF {
|
|
|
1405
1399
|
* @param eps Optional tolerance for floating point rounding
|
|
1406
1400
|
* @returns A single PMF representing the exclusive mixture of all options.
|
|
1407
1401
|
*/
|
|
1408
|
-
static exclusive(options, eps =
|
|
1402
|
+
static exclusive(options, eps = EPS) {
|
|
1409
1403
|
const items = options.map(
|
|
1410
1404
|
(o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o
|
|
1411
1405
|
);
|
|
@@ -1435,8 +1429,7 @@ var _PMF = class _PMF {
|
|
|
1435
1429
|
* [pCrit, critPMF],
|
|
1436
1430
|
* ]);
|
|
1437
1431
|
*/
|
|
1438
|
-
static mixN(weights) {
|
|
1439
|
-
const eps = 1e-12;
|
|
1432
|
+
static mixN(weights, eps = EPS) {
|
|
1440
1433
|
const filtered = weights.filter(([w]) => w > eps);
|
|
1441
1434
|
if (filtered.length === 0) {
|
|
1442
1435
|
return _PMF.emptyMass();
|
|
@@ -1458,17 +1451,23 @@ var _PMF = class _PMF {
|
|
|
1458
1451
|
// This is a convenience method for when we use power
|
|
1459
1452
|
// TODO: It can be smarter in the future, and we can also add it to query
|
|
1460
1453
|
// That way statistics operations on invalid PMFs can throw an error
|
|
1461
|
-
// TODO… how can we detect if manually merging two queries' combined PMFs, as that loses
|
|
1462
|
-
|
|
1463
|
-
if (!this.
|
|
1454
|
+
// TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance?
|
|
1455
|
+
setPreservedProvenance(preserved) {
|
|
1456
|
+
if (!this._preservedProvenance && preserved) {
|
|
1464
1457
|
throw new Error(
|
|
1465
|
-
"Preserved
|
|
1458
|
+
"Preserved provenance is already set to false, cannot fix that"
|
|
1466
1459
|
);
|
|
1467
1460
|
}
|
|
1468
|
-
this.
|
|
1461
|
+
this._preservedProvenance = preserved;
|
|
1469
1462
|
}
|
|
1470
|
-
|
|
1471
|
-
return this.
|
|
1463
|
+
preservedProvenance() {
|
|
1464
|
+
return this._preservedProvenance;
|
|
1465
|
+
}
|
|
1466
|
+
getPowerCacheKey(n, eps) {
|
|
1467
|
+
const id = this.identifier;
|
|
1468
|
+
let key = `${id}`;
|
|
1469
|
+
for (let i = 1; i < n; i++) key += `+${id}`;
|
|
1470
|
+
return `${key}@${eps}`;
|
|
1472
1471
|
}
|
|
1473
1472
|
/**
|
|
1474
1473
|
* Efficiently computes this PMF convolved with itself `n` times.
|
|
@@ -1486,7 +1485,7 @@ var _PMF = class _PMF {
|
|
|
1486
1485
|
}
|
|
1487
1486
|
if (n === 1) return this;
|
|
1488
1487
|
const epsilon = eps ?? this.epsilon;
|
|
1489
|
-
const key = getPowerCacheKey(
|
|
1488
|
+
const key = this.getPowerCacheKey(n, epsilon);
|
|
1490
1489
|
{
|
|
1491
1490
|
const cached = pmfCache?.get(key);
|
|
1492
1491
|
if (cached) return cached;
|
|
@@ -1503,7 +1502,7 @@ var _PMF = class _PMF {
|
|
|
1503
1502
|
base = base.convolve(base, epsilon);
|
|
1504
1503
|
}
|
|
1505
1504
|
}
|
|
1506
|
-
result.
|
|
1505
|
+
result.setPreservedProvenance(false);
|
|
1507
1506
|
{
|
|
1508
1507
|
pmfCache?.set(key, result);
|
|
1509
1508
|
}
|
|
@@ -1532,7 +1531,7 @@ var _PMF = class _PMF {
|
|
|
1532
1531
|
outcomeMass(outcome) {
|
|
1533
1532
|
let totalProbabilityMass = 0;
|
|
1534
1533
|
for (const { p, count } of this.map.values()) {
|
|
1535
|
-
totalProbabilityMass += p * count[outcome];
|
|
1534
|
+
totalProbabilityMass += p * (count[outcome] ?? 0);
|
|
1536
1535
|
}
|
|
1537
1536
|
return totalProbabilityMass;
|
|
1538
1537
|
}
|
|
@@ -1795,7 +1794,7 @@ var _PMF = class _PMF {
|
|
|
1795
1794
|
}
|
|
1796
1795
|
convolve(other, eps, raw = false) {
|
|
1797
1796
|
const epsilon = eps ?? this.epsilon;
|
|
1798
|
-
const norm = (x) => raw ? x : Math.abs(x.mass() - 1) <=
|
|
1797
|
+
const norm = (x) => raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize();
|
|
1799
1798
|
const A0 = norm(this);
|
|
1800
1799
|
const B0 = norm(other);
|
|
1801
1800
|
const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0];
|
|
@@ -1830,14 +1829,13 @@ var _PMF = class _PMF {
|
|
|
1830
1829
|
epsilon,
|
|
1831
1830
|
!raw,
|
|
1832
1831
|
`${A.identifier}${raw ? "*" : "+"}${B.identifier}`
|
|
1833
|
-
// ← canonical order
|
|
1834
1832
|
);
|
|
1835
1833
|
const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1);
|
|
1836
1834
|
const mGot = result.mass();
|
|
1837
|
-
if (mExp !== 0 && Math.abs(mGot - mExp) >
|
|
1835
|
+
if (mExp !== 0 && Math.abs(mGot - mExp) > epsilon) {
|
|
1838
1836
|
result = result.scaleMass(mExp / mGot);
|
|
1839
1837
|
}
|
|
1840
|
-
if (!raw && Math.abs(result.mass() - 1) >
|
|
1838
|
+
if (!raw && Math.abs(result.mass() - 1) > epsilon)
|
|
1841
1839
|
result = result.normalize();
|
|
1842
1840
|
pmfCache?.set(cacheKey, result);
|
|
1843
1841
|
return result;
|
|
@@ -1885,7 +1883,7 @@ var _PMF = class _PMF {
|
|
|
1885
1883
|
* - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.)
|
|
1886
1884
|
* - Order-independent cache keys work better with consistent build patterns
|
|
1887
1885
|
*/
|
|
1888
|
-
static convolveMany(pmfList, eps =
|
|
1886
|
+
static convolveMany(pmfList, eps = EPS) {
|
|
1889
1887
|
if (pmfList.length === 0) return _PMF.empty(eps);
|
|
1890
1888
|
if (pmfList.length === 1) return pmfList[0];
|
|
1891
1889
|
return _PMF.reduceConvolveLeft(pmfList, eps);
|
|
@@ -1900,7 +1898,7 @@ var _PMF = class _PMF {
|
|
|
1900
1898
|
static fromJSON(jsonData) {
|
|
1901
1899
|
return new _PMF(
|
|
1902
1900
|
new Map(jsonData.bins),
|
|
1903
|
-
|
|
1901
|
+
EPS,
|
|
1904
1902
|
!!jsonData.normalized,
|
|
1905
1903
|
jsonData.identifier || "fromJSON"
|
|
1906
1904
|
);
|
|
@@ -2046,6 +2044,21 @@ var _PMF = class _PMF {
|
|
|
2046
2044
|
}
|
|
2047
2045
|
return false;
|
|
2048
2046
|
}
|
|
2047
|
+
tailProbGE(t) {
|
|
2048
|
+
let s = 0;
|
|
2049
|
+
for (const [x, rec] of this) {
|
|
2050
|
+
const p = typeof rec === "number" ? rec : rec.p;
|
|
2051
|
+
if (p > 0 && x >= t) s += p;
|
|
2052
|
+
}
|
|
2053
|
+
return s;
|
|
2054
|
+
}
|
|
2055
|
+
tailProbGT(t) {
|
|
2056
|
+
let s = 0;
|
|
2057
|
+
for (const [x, rec] of this) {
|
|
2058
|
+
if (x > t) s += rec.p;
|
|
2059
|
+
}
|
|
2060
|
+
return s;
|
|
2061
|
+
}
|
|
2049
2062
|
/**
|
|
2050
2063
|
* Returns a new PMF containing only bins where the specified outcome has non-zero probability.
|
|
2051
2064
|
* This creates a marginal distribution for the given outcome type, with probabilities
|
|
@@ -2105,6 +2118,66 @@ var _PMF = class _PMF {
|
|
|
2105
2118
|
const pNone = 1 - pSpecificSuccess - pGeneralSuccess;
|
|
2106
2119
|
return { pSpecificSuccess, pGeneralSuccess, pNone, pAny };
|
|
2107
2120
|
}
|
|
2121
|
+
mapValues(f, eps = EPS, opts) {
|
|
2122
|
+
const rounding = opts?.rounding ?? "none";
|
|
2123
|
+
const preserveCounts = opts?.preserveCounts ?? true;
|
|
2124
|
+
const round = (x) => rounding === "floor" ? Math.floor(x) : rounding === "ceil" ? Math.ceil(x) : rounding === "round" ? Math.round(x) : x;
|
|
2125
|
+
const probs = /* @__PURE__ */ new Map();
|
|
2126
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2127
|
+
for (const [v, rec] of this) {
|
|
2128
|
+
if (Math.abs(rec.p) < eps) continue;
|
|
2129
|
+
const u = round(f(v));
|
|
2130
|
+
probs.set(u, (probs.get(u) ?? 0) + rec.p);
|
|
2131
|
+
if (preserveCounts) {
|
|
2132
|
+
const rec2 = this.map.get(v);
|
|
2133
|
+
const src = typeof rec2 === "number" ? void 0 : rec2?.count;
|
|
2134
|
+
if (src) {
|
|
2135
|
+
const dest = counts.get(u) ?? {};
|
|
2136
|
+
for (const k in src) {
|
|
2137
|
+
dest[k] = (dest[k] ?? 0) + src[k];
|
|
2138
|
+
}
|
|
2139
|
+
counts.set(u, dest);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
const internal = /* @__PURE__ */ new Map();
|
|
2144
|
+
for (const [u, p] of probs) {
|
|
2145
|
+
internal.set(u, { p, count: counts.get(u) ?? {} });
|
|
2146
|
+
}
|
|
2147
|
+
return _PMF.fromMap(
|
|
2148
|
+
new Map(Array.from(internal, ([u, b]) => [u, b.p])),
|
|
2149
|
+
eps
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
static fromMap(m, eps = EPS, { requireIntegerValues = true } = {}) {
|
|
2153
|
+
const filtered = [];
|
|
2154
|
+
for (const [v, p] of m) {
|
|
2155
|
+
if (!Number.isFinite(v) || !Number.isFinite(p)) continue;
|
|
2156
|
+
if (p <= 0 || Math.abs(p) < eps) continue;
|
|
2157
|
+
if (requireIntegerValues && !Number.isInteger(v)) {
|
|
2158
|
+
throw new Error(`fromMap: non-integer outcome ${v}`);
|
|
2159
|
+
}
|
|
2160
|
+
filtered.push([v, p]);
|
|
2161
|
+
}
|
|
2162
|
+
if (filtered.length === 0) {
|
|
2163
|
+
throw new Error("fromMap: empty or invalid input map");
|
|
2164
|
+
}
|
|
2165
|
+
let sum = 0;
|
|
2166
|
+
let c = 0;
|
|
2167
|
+
for (const [, p] of filtered) {
|
|
2168
|
+
const y = p - c;
|
|
2169
|
+
const t = sum + y;
|
|
2170
|
+
c = t - sum - y;
|
|
2171
|
+
sum = t;
|
|
2172
|
+
}
|
|
2173
|
+
if (sum <= 0) throw new Error("pmfFromMap: probabilities sum to 0");
|
|
2174
|
+
filtered.sort((a, b) => a[0] - b[0]);
|
|
2175
|
+
const internal = /* @__PURE__ */ new Map();
|
|
2176
|
+
for (const [v, p] of filtered) {
|
|
2177
|
+
internal.set(v, { p: p / sum, count: {} });
|
|
2178
|
+
}
|
|
2179
|
+
return new _PMF(internal, eps);
|
|
2180
|
+
}
|
|
2108
2181
|
query() {
|
|
2109
2182
|
return new DiceQuery(this);
|
|
2110
2183
|
}
|
|
@@ -2112,12 +2185,127 @@ var _PMF = class _PMF {
|
|
|
2112
2185
|
// Unique ID generator for anonymous PMFs to avoid cache key collisions
|
|
2113
2186
|
__publicField(_PMF, "__anonIdCounter", 1);
|
|
2114
2187
|
var PMF = _PMF;
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2188
|
+
|
|
2189
|
+
// src/mixture.ts
|
|
2190
|
+
var Mixture = class _Mixture {
|
|
2191
|
+
constructor(eps = EPS) {
|
|
2192
|
+
__publicField(this, "totals", /* @__PURE__ */ new Map());
|
|
2193
|
+
// raw mass per outcome (pre-normalization)
|
|
2194
|
+
__publicField(this, "labelMass", /* @__PURE__ */ new Map());
|
|
2195
|
+
// raw mass per outcome per label
|
|
2196
|
+
__publicField(this, "eps");
|
|
2197
|
+
this.eps = Number.isFinite(eps) ? eps : EPS;
|
|
2198
|
+
}
|
|
2199
|
+
/** Remove all accumulated state. */
|
|
2200
|
+
clear() {
|
|
2201
|
+
this.totals.clear();
|
|
2202
|
+
this.labelMass.clear();
|
|
2203
|
+
return this;
|
|
2204
|
+
}
|
|
2205
|
+
/** Number of distinct outcome values currently accumulated. */
|
|
2206
|
+
size() {
|
|
2207
|
+
return this.totals.size;
|
|
2208
|
+
}
|
|
2209
|
+
/** Whether a label was ever added. */
|
|
2210
|
+
hasLabel(label) {
|
|
2211
|
+
for (const bag of this.labelMass.values()) if (bag[label]) return true;
|
|
2212
|
+
return false;
|
|
2213
|
+
}
|
|
2214
|
+
/**
|
|
2215
|
+
* Add a labeled component with a mixture weight.
|
|
2216
|
+
* Weight can be any positive finite number. Very small contributions are pruned by eps.
|
|
2217
|
+
*/
|
|
2218
|
+
add(label, pmf, weight = 1) {
|
|
2219
|
+
if (!Number.isFinite(weight) || weight <= 0) return this;
|
|
2220
|
+
for (const [v, binOrNumber] of pmf) {
|
|
2221
|
+
const isNumber = typeof binOrNumber === "number";
|
|
2222
|
+
const p = isNumber ? binOrNumber : binOrNumber?.p ?? 0;
|
|
2223
|
+
if (p <= 0) continue;
|
|
2224
|
+
const add = weight * p;
|
|
2225
|
+
if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue;
|
|
2226
|
+
this.totals.set(v, (this.totals.get(v) ?? 0) + add);
|
|
2227
|
+
const bag = this.labelMass.get(v) ?? {};
|
|
2228
|
+
bag[label] = (bag[label] ?? 0) + add;
|
|
2229
|
+
this.labelMass.set(v, bag);
|
|
2230
|
+
}
|
|
2231
|
+
return this;
|
|
2232
|
+
}
|
|
2233
|
+
buildPMF(eps = EPS) {
|
|
2234
|
+
let grand = 0;
|
|
2235
|
+
let c = 0;
|
|
2236
|
+
for (const m of this.totals.values()) {
|
|
2237
|
+
const y = m - c;
|
|
2238
|
+
const t = grand + y;
|
|
2239
|
+
c = t - grand - y;
|
|
2240
|
+
grand = t;
|
|
2241
|
+
}
|
|
2242
|
+
if (!(grand > 0)) throw new Error("LabeledMixture: zero total mass");
|
|
2243
|
+
const internal = /* @__PURE__ */ new Map();
|
|
2244
|
+
for (const [v, m] of this.totals) {
|
|
2245
|
+
if (m <= 0 || Math.abs(m) < this.eps) continue;
|
|
2246
|
+
const count = this.labelMass.get(v) ?? {};
|
|
2247
|
+
internal.set(v, { p: m / grand, count });
|
|
2248
|
+
}
|
|
2249
|
+
return new PMF(internal, eps);
|
|
2250
|
+
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Produce normalized *per-label* PMFs (labels independent).
|
|
2253
|
+
* These are unlabeled PMFs built from the raw mass of that label alone.
|
|
2254
|
+
*/
|
|
2255
|
+
byOutcome() {
|
|
2256
|
+
const labels = /* @__PURE__ */ new Set();
|
|
2257
|
+
for (const bag of this.labelMass.values()) {
|
|
2258
|
+
for (const k of Object.keys(bag)) labels.add(k);
|
|
2259
|
+
}
|
|
2260
|
+
const out = {};
|
|
2261
|
+
for (const label of labels) {
|
|
2262
|
+
const m = /* @__PURE__ */ new Map();
|
|
2263
|
+
for (const [v, bag] of this.labelMass) {
|
|
2264
|
+
const w = bag[label];
|
|
2265
|
+
if (w && Math.abs(w) >= this.eps) m.set(v, w);
|
|
2266
|
+
}
|
|
2267
|
+
if (m.size > 0) out[label] = PMF.fromMap(m, this.eps);
|
|
2268
|
+
}
|
|
2269
|
+
return out;
|
|
2270
|
+
}
|
|
2271
|
+
/**
|
|
2272
|
+
* Mixture weights per label, normalized to sum to 1 over labels that appeared.
|
|
2273
|
+
* Uses raw mass before per-outcome normalization.
|
|
2274
|
+
*/
|
|
2275
|
+
weights() {
|
|
2276
|
+
const res = {};
|
|
2277
|
+
for (const [, bag] of this.labelMass) {
|
|
2278
|
+
for (const [lab, w] of Object.entries(bag)) {
|
|
2279
|
+
if (!Number.isFinite(w) || w <= 0) continue;
|
|
2280
|
+
res[lab] = (res[lab] ?? 0) + w;
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
let total = 0;
|
|
2284
|
+
let c = 0;
|
|
2285
|
+
for (const v of Object.values(res)) {
|
|
2286
|
+
const y = v - c;
|
|
2287
|
+
const t = total + y;
|
|
2288
|
+
c = t - total - y;
|
|
2289
|
+
total = t;
|
|
2290
|
+
}
|
|
2291
|
+
if (total > 0) {
|
|
2292
|
+
for (const k in res) res[k] = res[k] / total;
|
|
2293
|
+
}
|
|
2294
|
+
return res;
|
|
2295
|
+
}
|
|
2296
|
+
toJSON() {
|
|
2297
|
+
return {
|
|
2298
|
+
totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]),
|
|
2299
|
+
labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]),
|
|
2300
|
+
eps: this.eps
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
static mix(items, eps = EPS) {
|
|
2304
|
+
const mix = new _Mixture(eps);
|
|
2305
|
+
for (const [lab, pmf, w] of items) mix.add(lab, pmf, w);
|
|
2306
|
+
return mix.buildPMF();
|
|
2307
|
+
}
|
|
2308
|
+
};
|
|
2121
2309
|
|
|
2122
2310
|
// src/dice.ts
|
|
2123
2311
|
var Dice = class _Dice {
|
|
@@ -2433,7 +2621,7 @@ var Dice = class _Dice {
|
|
|
2433
2621
|
* Convert dice to PMF using OutcomeType labels directly from damage distribution.
|
|
2434
2622
|
* This is much cleaner than the original complex distribution conversion.
|
|
2435
2623
|
*/
|
|
2436
|
-
toPMF(numEpsilon =
|
|
2624
|
+
toPMF(numEpsilon = EPS) {
|
|
2437
2625
|
const total = this.total();
|
|
2438
2626
|
if (total === 0) return PMF.empty(numEpsilon);
|
|
2439
2627
|
this.ensureHitDistribution();
|
|
@@ -2612,7 +2800,7 @@ function parseExpression(arr, n) {
|
|
|
2612
2800
|
}
|
|
2613
2801
|
critNorm = crit.total();
|
|
2614
2802
|
crit = op.call(crit, parseBinaryArgument(arg, arr, n));
|
|
2615
|
-
critNorm = critNorm ? crit.total() / critNorm : 1;
|
|
2803
|
+
critNorm = crit && critNorm ? crit.total() / critNorm : 1;
|
|
2616
2804
|
}
|
|
2617
2805
|
let save;
|
|
2618
2806
|
let saveNorm = 1;
|
|
@@ -2627,7 +2815,7 @@ function parseExpression(arr, n) {
|
|
|
2627
2815
|
saveNorm = save.total();
|
|
2628
2816
|
finalResult = finalResult.deleteFace(min);
|
|
2629
2817
|
save = op.call(save, parseBinaryArgument(arg, arr, n));
|
|
2630
|
-
saveNorm = saveNorm ? save.total() / saveNorm : 1;
|
|
2818
|
+
saveNorm = save && saveNorm ? save.total() / saveNorm : 1;
|
|
2631
2819
|
}
|
|
2632
2820
|
let pc;
|
|
2633
2821
|
let pcNorm = 1;
|
|
@@ -2640,7 +2828,7 @@ function parseExpression(arr, n) {
|
|
|
2640
2828
|
const missBefore = pc.total();
|
|
2641
2829
|
finalResult = finalResult.deleteFace(min);
|
|
2642
2830
|
pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2);
|
|
2643
|
-
const missAfter = pc.total();
|
|
2831
|
+
const missAfter = pc ? pc.total() : 0;
|
|
2644
2832
|
pcNorm = missBefore ? missAfter / missBefore : 1;
|
|
2645
2833
|
}
|
|
2646
2834
|
let miss;
|
|
@@ -2656,7 +2844,7 @@ function parseExpression(arr, n) {
|
|
|
2656
2844
|
missNorm = miss.total();
|
|
2657
2845
|
finalResult = finalResult.deleteFace(min);
|
|
2658
2846
|
miss = op.call(miss, parseBinaryArgument(arg, arr, n));
|
|
2659
|
-
missNorm = missNorm ? miss.total() / missNorm : 1;
|
|
2847
|
+
missNorm = miss && missNorm ? miss.total() / missNorm : 1;
|
|
2660
2848
|
}
|
|
2661
2849
|
let norm = finalResult.total();
|
|
2662
2850
|
finalResult = op.call(finalResult, arg);
|
|
@@ -2946,6 +3134,6 @@ function parseOperation(s) {
|
|
|
2946
3134
|
return;
|
|
2947
3135
|
}
|
|
2948
3136
|
|
|
2949
|
-
export {
|
|
3137
|
+
export { DiceQuery, EPS, LRUCache, Mixture, PMF, clearParserCache, getCachingEnabled, parse, pmfCache, setCachingEnabled };
|
|
2950
3138
|
//# sourceMappingURL=index.js.map
|
|
2951
3139
|
//# sourceMappingURL=index.js.map
|