@yipe/dice 0.7.0 → 0.8.1
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/builder/ac.d.ts +1 -0
- package/dist/builder/ac.d.ts.map +1 -1
- package/dist/builder/attack.d.ts +9 -0
- package/dist/builder/attack.d.ts.map +1 -1
- package/dist/builder/dc.d.ts +8 -0
- package/dist/builder/dc.d.ts.map +1 -1
- package/dist/builder/index.cjs +152 -6
- package/dist/builder/index.cjs.map +1 -1
- package/dist/builder/index.js +149 -7
- package/dist/builder/index.js.map +1 -1
- package/dist/builder/roll.d.ts +18 -0
- package/dist/builder/roll.d.ts.map +1 -1
- package/dist/builder/save.d.ts +9 -0
- package/dist/builder/save.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/builder/index.js
CHANGED
|
@@ -3452,6 +3452,10 @@ function d20PMF(rerollOne) {
|
|
|
3452
3452
|
}
|
|
3453
3453
|
|
|
3454
3454
|
// src/builder/roll.ts
|
|
3455
|
+
var rollPMFCache = new LRUCache(4e3);
|
|
3456
|
+
function clearRollCache() {
|
|
3457
|
+
rollPMFCache.clear();
|
|
3458
|
+
}
|
|
3455
3459
|
var defaultConfig = {
|
|
3456
3460
|
count: 1,
|
|
3457
3461
|
sides: 0,
|
|
@@ -3501,6 +3505,17 @@ var RollBuilder = class _RollBuilder {
|
|
|
3501
3505
|
getSubRollConfigs() {
|
|
3502
3506
|
return this.subRollConfigs.map((c) => ({ ...c }));
|
|
3503
3507
|
}
|
|
3508
|
+
/**
|
|
3509
|
+
* A cheap, stable string that FULLY identifies this builder's PMF — used by {@link AttackBuilder.toPMF}
|
|
3510
|
+
* to cache resolved attack PMFs across rebuilds without walking the AST via {@link toExpression}. A plain
|
|
3511
|
+
* roll is fully determined by its {@link RollConfig} array (count/sides/modifier/reroll/explode/minimum/
|
|
3512
|
+
* bestOf/keep/rollType/isSubtraction), so serializing that is sound. Subclasses whose PMF depends on
|
|
3513
|
+
* hidden state NOT captured by `subRollConfigs` (half/scale/max/parsed/pooled/composite transforms) return
|
|
3514
|
+
* `null` to opt OUT of caching — a conservative miss is always safe; a wrong key would corrupt DPR.
|
|
3515
|
+
*/
|
|
3516
|
+
cacheKey() {
|
|
3517
|
+
return JSON.stringify(this.subRollConfigs);
|
|
3518
|
+
}
|
|
3504
3519
|
// for testing
|
|
3505
3520
|
static fromConfig(config) {
|
|
3506
3521
|
return new _RollBuilder([{ ...defaultConfig, ...config }]);
|
|
@@ -3853,8 +3868,16 @@ var RollBuilder = class _RollBuilder {
|
|
|
3853
3868
|
}
|
|
3854
3869
|
return result.replace(/\+ -/g, "-");
|
|
3855
3870
|
}
|
|
3871
|
+
// Main AST entry point. Cached by the cheap config key across identical rebuilds; see `rollPMFCache`.
|
|
3856
3872
|
toPMF(eps = 0) {
|
|
3857
|
-
|
|
3873
|
+
const key = this.cacheKey();
|
|
3874
|
+
if (key === null) return pmfFromRollBuilder(this, eps);
|
|
3875
|
+
const fullKey = `${key}*e${eps}`;
|
|
3876
|
+
const cached = rollPMFCache.get(fullKey);
|
|
3877
|
+
if (cached) return cached;
|
|
3878
|
+
const pmf = pmfFromRollBuilder(this, eps);
|
|
3879
|
+
rollPMFCache.set(fullKey, pmf);
|
|
3880
|
+
return pmf;
|
|
3858
3881
|
}
|
|
3859
3882
|
get pmf() {
|
|
3860
3883
|
return this.toPMF();
|
|
@@ -4009,6 +4032,9 @@ var HalfRollBuilder = class _HalfRollBuilder extends RollBuilder {
|
|
|
4009
4032
|
hasHiddenState() {
|
|
4010
4033
|
return this.innerRoll.hasHiddenState();
|
|
4011
4034
|
}
|
|
4035
|
+
cacheKey() {
|
|
4036
|
+
return null;
|
|
4037
|
+
}
|
|
4012
4038
|
// No need to override create if we don't expose RollBuilder methods that use it,
|
|
4013
4039
|
// but HalfRollBuilder extends RollBuilder so it does.
|
|
4014
4040
|
// However, HalfRollBuilder seems to just wrap another roll.
|
|
@@ -4053,6 +4079,9 @@ var ScaleRollBuilder = class _ScaleRollBuilder extends RollBuilder {
|
|
|
4053
4079
|
hasHiddenState() {
|
|
4054
4080
|
return this.innerRoll.hasHiddenState();
|
|
4055
4081
|
}
|
|
4082
|
+
cacheKey() {
|
|
4083
|
+
return null;
|
|
4084
|
+
}
|
|
4056
4085
|
get lastConfig() {
|
|
4057
4086
|
return this.innerRoll.lastConfig;
|
|
4058
4087
|
}
|
|
@@ -4097,6 +4126,9 @@ var MaxOfRollBuilder = class _MaxOfRollBuilder extends RollBuilder {
|
|
|
4097
4126
|
hasHiddenState() {
|
|
4098
4127
|
return this.innerRoll.hasHiddenState();
|
|
4099
4128
|
}
|
|
4129
|
+
cacheKey() {
|
|
4130
|
+
return null;
|
|
4131
|
+
}
|
|
4100
4132
|
get lastConfig() {
|
|
4101
4133
|
return this.innerRoll.lastConfig;
|
|
4102
4134
|
}
|
|
@@ -4174,6 +4206,10 @@ var AlwaysHitBuilder = class _AlwaysHitBuilder extends RollBuilder {
|
|
|
4174
4206
|
get critThreshold() {
|
|
4175
4207
|
return this.attackConfig.critThreshold;
|
|
4176
4208
|
}
|
|
4209
|
+
cacheKey() {
|
|
4210
|
+
const base = super.cacheKey();
|
|
4211
|
+
return base === null ? null : `H|${this.attackConfig.critThreshold}|${base}`;
|
|
4212
|
+
}
|
|
4177
4213
|
// TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error?
|
|
4178
4214
|
critOn(critThreshold) {
|
|
4179
4215
|
const newConfig = { critThreshold };
|
|
@@ -4224,6 +4260,10 @@ var AlwaysCritBuilder = class _AlwaysCritBuilder extends RollBuilder {
|
|
|
4224
4260
|
get critThreshold() {
|
|
4225
4261
|
return this.attackConfig.critThreshold;
|
|
4226
4262
|
}
|
|
4263
|
+
cacheKey() {
|
|
4264
|
+
const base = super.cacheKey();
|
|
4265
|
+
return base === null ? null : `C|${this.fromAlwaysHit ? 1 : 0}|${this.attackConfig.critThreshold}|${this.attackConfig.ac ?? ""}|${base}`;
|
|
4266
|
+
}
|
|
4227
4267
|
critOn(critThreshold) {
|
|
4228
4268
|
const newConfig = { critThreshold, ac: this.attackConfig.ac };
|
|
4229
4269
|
return new _AlwaysCritBuilder(this, newConfig, this.fromAlwaysHit);
|
|
@@ -4254,6 +4294,9 @@ var ParsedRollBuilder = class _ParsedRollBuilder extends RollBuilder {
|
|
|
4254
4294
|
hasHiddenState() {
|
|
4255
4295
|
return true;
|
|
4256
4296
|
}
|
|
4297
|
+
cacheKey() {
|
|
4298
|
+
return null;
|
|
4299
|
+
}
|
|
4257
4300
|
create(configs) {
|
|
4258
4301
|
return new RollBuilder(configs);
|
|
4259
4302
|
}
|
|
@@ -4289,6 +4332,9 @@ var PooledRollBuilder = class _PooledRollBuilder extends RollBuilder {
|
|
|
4289
4332
|
hasHiddenState() {
|
|
4290
4333
|
return true;
|
|
4291
4334
|
}
|
|
4335
|
+
cacheKey() {
|
|
4336
|
+
return null;
|
|
4337
|
+
}
|
|
4292
4338
|
d(_sides) {
|
|
4293
4339
|
throw new Error("Cannot add dice to a pooled roll. The pool is finalized.");
|
|
4294
4340
|
}
|
|
@@ -4392,6 +4438,9 @@ var CompositeSumRollBuilder = class _CompositeSumRollBuilder extends RollBuilder
|
|
|
4392
4438
|
hasHiddenState() {
|
|
4393
4439
|
return true;
|
|
4394
4440
|
}
|
|
4441
|
+
cacheKey() {
|
|
4442
|
+
return null;
|
|
4443
|
+
}
|
|
4395
4444
|
getSubRollConfigs() {
|
|
4396
4445
|
return [];
|
|
4397
4446
|
}
|
|
@@ -4944,6 +4993,10 @@ function getASTSignature(node) {
|
|
|
4944
4993
|
}
|
|
4945
4994
|
|
|
4946
4995
|
// src/builder/attack.ts
|
|
4996
|
+
var attackPMFCache = new LRUCache(4e3);
|
|
4997
|
+
function clearAttackCache() {
|
|
4998
|
+
attackPMFCache.clear();
|
|
4999
|
+
}
|
|
4947
5000
|
var AttackBuilder = class _AttackBuilder {
|
|
4948
5001
|
constructor(check, hitEffect, critEffect, missEffect) {
|
|
4949
5002
|
this.check = check;
|
|
@@ -5122,9 +5175,46 @@ var AttackBuilder = class _AttackBuilder {
|
|
|
5122
5175
|
weights: { hit: phit, crit: pcrit, miss: pmiss }
|
|
5123
5176
|
};
|
|
5124
5177
|
}
|
|
5125
|
-
|
|
5178
|
+
/**
|
|
5179
|
+
* A cheap, complete key for this attack's resolved PMF, or `null` when it can't be cached soundly (an
|
|
5180
|
+
* effect whose PMF isn't captured by its {@link RollConfig}s — see {@link RollBuilder.cacheKey}). Composed
|
|
5181
|
+
* from the check + hit/crit/miss effect keys + `eps`. `critEffect === null` (noCrit) and `undefined`
|
|
5182
|
+
* (auto-double the hit dice) are distinct crit states, encoded separately.
|
|
5183
|
+
*/
|
|
5184
|
+
cacheKey(eps) {
|
|
5185
|
+
const checkKey = this.check.cacheKey();
|
|
5186
|
+
if (checkKey === null) return null;
|
|
5187
|
+
let hitKey = "";
|
|
5188
|
+
if (this.hitEffect) {
|
|
5189
|
+
const k = this.hitEffect.cacheKey();
|
|
5190
|
+
if (k === null) return null;
|
|
5191
|
+
hitKey = k;
|
|
5192
|
+
}
|
|
5193
|
+
let critKey;
|
|
5194
|
+
if (this.critEffect === null) critKey = "n";
|
|
5195
|
+
else if (this.critEffect === void 0) critKey = "a";
|
|
5196
|
+
else {
|
|
5197
|
+
const k = this.critEffect.cacheKey();
|
|
5198
|
+
if (k === null) return null;
|
|
5199
|
+
critKey = k;
|
|
5200
|
+
}
|
|
5201
|
+
let missKey = "";
|
|
5202
|
+
if (this.missEffect) {
|
|
5203
|
+
const k = this.missEffect.cacheKey();
|
|
5204
|
+
if (k === null) return null;
|
|
5205
|
+
missKey = k;
|
|
5206
|
+
}
|
|
5207
|
+
return `${checkKey}*H${hitKey}*C${critKey}*M${missKey}*e${eps}`;
|
|
5208
|
+
}
|
|
5209
|
+
// By default, create PMF with no pruning. Cached by the cheap config key across identical rebuilds.
|
|
5126
5210
|
toPMF(eps = 0) {
|
|
5127
|
-
|
|
5211
|
+
const key = this.cacheKey(eps);
|
|
5212
|
+
if (key === null) return this.resolve(eps).pmf;
|
|
5213
|
+
const cached = attackPMFCache.get(key);
|
|
5214
|
+
if (cached) return cached;
|
|
5215
|
+
const pmf = this.resolve(eps).pmf;
|
|
5216
|
+
attackPMFCache.set(key, pmf);
|
|
5217
|
+
return pmf;
|
|
5128
5218
|
}
|
|
5129
5219
|
get pmf() {
|
|
5130
5220
|
return this.toPMF();
|
|
@@ -5152,6 +5242,10 @@ var ACBuilder = class _ACBuilder extends RollBuilder {
|
|
|
5152
5242
|
get critThreshold() {
|
|
5153
5243
|
return this.attackConfig.critThreshold;
|
|
5154
5244
|
}
|
|
5245
|
+
cacheKey() {
|
|
5246
|
+
const base = super.cacheKey();
|
|
5247
|
+
return base === null ? null : `A|${this.attackConfig.ac}|${this.attackConfig.critThreshold}|${base}`;
|
|
5248
|
+
}
|
|
5155
5249
|
// TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error?
|
|
5156
5250
|
critOn(threshold) {
|
|
5157
5251
|
const newConfig = {
|
|
@@ -5212,6 +5306,10 @@ RollBuilder.prototype.ac = function(targetAC) {
|
|
|
5212
5306
|
};
|
|
5213
5307
|
|
|
5214
5308
|
// src/builder/save.ts
|
|
5309
|
+
var savePMFCache = new LRUCache(4e3);
|
|
5310
|
+
function clearSaveCache() {
|
|
5311
|
+
savePMFCache.clear();
|
|
5312
|
+
}
|
|
5215
5313
|
var SaveBuilder = class _SaveBuilder {
|
|
5216
5314
|
constructor(check, failureEffect, saveOutcome = "normal") {
|
|
5217
5315
|
this.check = check;
|
|
@@ -5248,9 +5346,32 @@ var SaveBuilder = class _SaveBuilder {
|
|
|
5248
5346
|
weights: { success: psuccess, fail: pfail }
|
|
5249
5347
|
};
|
|
5250
5348
|
}
|
|
5251
|
-
|
|
5349
|
+
/**
|
|
5350
|
+
* A cheap, complete key for this save's resolved PMF, or `null` when it can't be cached soundly.
|
|
5351
|
+
* {@link resolve} reads exactly three things: the DC check (via `resolveProbabilities`), the failure
|
|
5352
|
+
* effect's PMF, and the save outcome — so composing their keys pins it. A `ParsedRollBuilder` failure
|
|
5353
|
+
* effect returns `null`, which correctly forces this uncached.
|
|
5354
|
+
*/
|
|
5355
|
+
cacheKey(eps) {
|
|
5356
|
+
const checkKey = this.check.cacheKey();
|
|
5357
|
+
if (checkKey === null) return null;
|
|
5358
|
+
let failKey = "";
|
|
5359
|
+
if (this.failureEffect) {
|
|
5360
|
+
const k = this.failureEffect.cacheKey();
|
|
5361
|
+
if (k === null) return null;
|
|
5362
|
+
failKey = k;
|
|
5363
|
+
}
|
|
5364
|
+
return `${checkKey}*F${failKey}*O${this.saveOutcome}*e${eps}`;
|
|
5365
|
+
}
|
|
5366
|
+
// By default, create PMF with no pruning. Cached by the cheap config key across identical rebuilds.
|
|
5252
5367
|
toPMF(eps = 0) {
|
|
5253
|
-
|
|
5368
|
+
const key = this.cacheKey(eps);
|
|
5369
|
+
if (key === null) return this.resolve(eps).pmf;
|
|
5370
|
+
const cached = savePMFCache.get(key);
|
|
5371
|
+
if (cached) return cached;
|
|
5372
|
+
const pmf = this.resolve(eps).pmf;
|
|
5373
|
+
savePMFCache.set(key, pmf);
|
|
5374
|
+
return pmf;
|
|
5254
5375
|
}
|
|
5255
5376
|
get pmf() {
|
|
5256
5377
|
return this.toPMF();
|
|
@@ -5286,6 +5407,10 @@ function resolveProbabilities(check) {
|
|
|
5286
5407
|
}
|
|
5287
5408
|
|
|
5288
5409
|
// src/builder/dc.ts
|
|
5410
|
+
var dcPMFCache = new LRUCache(4e3);
|
|
5411
|
+
function clearDCCache() {
|
|
5412
|
+
dcPMFCache.clear();
|
|
5413
|
+
}
|
|
5289
5414
|
var DCBuilder = class _DCBuilder extends RollBuilder {
|
|
5290
5415
|
constructor(baseRoll, saveConfig) {
|
|
5291
5416
|
super(baseRoll.getSubRollConfigs());
|
|
@@ -5326,7 +5451,22 @@ var DCBuilder = class _DCBuilder extends RollBuilder {
|
|
|
5326
5451
|
const expression = new RollBuilder(allConfigs).toExpression();
|
|
5327
5452
|
return `(${expression} DC ${this.saveConfig.dc})`;
|
|
5328
5453
|
}
|
|
5454
|
+
/**
|
|
5455
|
+
* The DC check's PMF is fully determined by the save DC plus everything `toPMF` below reads off the roll
|
|
5456
|
+
* configs (`rollType`, `baseReroll`, `modifier`, bonus dice) — all of which `super.cacheKey()` already
|
|
5457
|
+
* serializes. So extend the base key with the DC, mirroring {@link AlwaysHitBuilder.cacheKey}.
|
|
5458
|
+
*/
|
|
5459
|
+
cacheKey() {
|
|
5460
|
+
const base = super.cacheKey();
|
|
5461
|
+
return base === null ? null : `DC|${this.saveConfig.dc}|${base}`;
|
|
5462
|
+
}
|
|
5329
5463
|
toPMF(eps = 0) {
|
|
5464
|
+
const key = this.cacheKey();
|
|
5465
|
+
const fullKey = key === null ? null : `${key}*e${eps}`;
|
|
5466
|
+
if (fullKey !== null) {
|
|
5467
|
+
const cached = dcPMFCache.get(fullKey);
|
|
5468
|
+
if (cached) return cached;
|
|
5469
|
+
}
|
|
5330
5470
|
const saveDC = this.saveDC;
|
|
5331
5471
|
const rollType = this.rollType;
|
|
5332
5472
|
const rerollOne = this.baseReroll > 0;
|
|
@@ -5348,7 +5488,9 @@ var DCBuilder = class _DCBuilder extends RollBuilder {
|
|
|
5348
5488
|
[0, psuccess > 0 ? psuccess : 0],
|
|
5349
5489
|
[1, pfail > 0 ? pfail : 0]
|
|
5350
5490
|
]);
|
|
5351
|
-
|
|
5491
|
+
const pmf = PMF.fromMap(m, eps);
|
|
5492
|
+
if (fullKey !== null) dcPMFCache.set(fullKey, pmf);
|
|
5493
|
+
return pmf;
|
|
5352
5494
|
}
|
|
5353
5495
|
};
|
|
5354
5496
|
RollBuilder.prototype.dc = function(saveDC) {
|
|
@@ -5356,6 +5498,6 @@ RollBuilder.prototype.dc = function(saveDC) {
|
|
|
5356
5498
|
return new DCBuilder(this).dc(saveDC);
|
|
5357
5499
|
};
|
|
5358
5500
|
|
|
5359
|
-
export { ACBuilder, AlwaysCritBuilder, AlwaysHitBuilder, AttackBuilder, DCBuilder, HalfRollBuilder, MaxOfRollBuilder, ParsedRollBuilder, PooledRollBuilder, RollBuilder, SaveBuilder, ScaleRollBuilder, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll, sumRolls };
|
|
5501
|
+
export { ACBuilder, AlwaysCritBuilder, AlwaysHitBuilder, AttackBuilder, DCBuilder, HalfRollBuilder, MaxOfRollBuilder, ParsedRollBuilder, PooledRollBuilder, RollBuilder, SaveBuilder, ScaleRollBuilder, builderPMFCache, clearAttackCache, clearDCCache, clearRollCache, clearSaveCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll, sumRolls };
|
|
5360
5502
|
//# sourceMappingURL=index.js.map
|
|
5361
5503
|
//# sourceMappingURL=index.js.map
|