@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/ac.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare class ACBuilder extends RollBuilder {
|
|
|
16
16
|
onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;
|
|
17
17
|
onHit(count: number, sides: number, modifier: number): AttackBuilder;
|
|
18
18
|
get critThreshold(): number;
|
|
19
|
+
cacheKey(): string | null;
|
|
19
20
|
critOn(threshold: number): ACBuilder;
|
|
20
21
|
alwaysCrits(): AlwaysCritBuilder;
|
|
21
22
|
toExpression(): string;
|
package/dist/builder/ac.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ac.d.ts","sourceRoot":"","sources":["../../src/builder/ac.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;CACvB;AACD,qBAAa,SAAU,SAAQ,WAAW;IACxC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IAEpC,YAAY,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,YAAY,EAQzE;IAMD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAC;IAClC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAC;IAClC,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,aAAa,CAAC;IACvC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,aAAa,CAAC;IACtD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC;IACnD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAAC;IACxE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAAC;IAMrE,IAAI,aAAa,IAAI,MAAM,CAE1B;IAGD,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAMnC;IAED,WAAW,IAAI,iBAAiB,CAS/B;IAGQ,YAAY,IAAI,MAAM,CAM9B;IAEQ,KAAK,CAAC,GAAG,GAAE,MAAU,GAAG,GAAG,CA0BnC;IAEQ,IAAI,IAAI,SAAS,CAOzB;CACF"}
|
|
1
|
+
{"version":3,"file":"ac.d.ts","sourceRoot":"","sources":["../../src/builder/ac.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;CACvB;AACD,qBAAa,SAAU,SAAQ,WAAW;IACxC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IAEpC,YAAY,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,YAAY,EAQzE;IAMD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAC;IAClC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAC;IAClC,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,aAAa,CAAC;IACvC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,aAAa,CAAC;IACtD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC;IACnD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAAC;IACxE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAAC;IAMrE,IAAI,aAAa,IAAI,MAAM,CAE1B;IAEQ,QAAQ,IAAI,MAAM,GAAG,IAAI,CAGjC;IAGD,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAMnC;IAED,WAAW,IAAI,iBAAiB,CAS/B;IAGQ,YAAY,IAAI,MAAM,CAM9B;IAEQ,KAAK,CAAC,GAAG,GAAE,MAAU,GAAG,GAAG,CA0BnC;IAEQ,IAAI,IAAI,SAAS,CAOzB;CACF"}
|
package/dist/builder/attack.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { ACBuilder } from "./ac.js";
|
|
|
4
4
|
import { AlwaysCritBuilder, AlwaysHitBuilder, RollBuilder } from "./roll.js";
|
|
5
5
|
import type { AttackResolution, CheckBuilder } from "./types.js";
|
|
6
6
|
type ActionEffect = RollBuilder;
|
|
7
|
+
/** Clears the resolved-attack PMF cache (test/bench seam; mirrors {@link clearParserCache}). */
|
|
8
|
+
export declare function clearAttackCache(): void;
|
|
7
9
|
export declare class AttackBuilder implements CheckBuilder {
|
|
8
10
|
readonly check: ACBuilder | AlwaysHitBuilder | AlwaysCritBuilder;
|
|
9
11
|
private readonly hitEffect?;
|
|
@@ -33,6 +35,13 @@ export declare class AttackBuilder implements CheckBuilder {
|
|
|
33
35
|
pMiss: number;
|
|
34
36
|
};
|
|
35
37
|
resolve(eps?: number): AttackResolution;
|
|
38
|
+
/**
|
|
39
|
+
* A cheap, complete key for this attack's resolved PMF, or `null` when it can't be cached soundly (an
|
|
40
|
+
* effect whose PMF isn't captured by its {@link RollConfig}s — see {@link RollBuilder.cacheKey}). Composed
|
|
41
|
+
* from the check + hit/crit/miss effect keys + `eps`. `critEffect === null` (noCrit) and `undefined`
|
|
42
|
+
* (auto-double the hit dice) are distinct crit states, encoded separately.
|
|
43
|
+
*/
|
|
44
|
+
private cacheKey;
|
|
36
45
|
toPMF(eps?: number): PMF;
|
|
37
46
|
get pmf(): PMF;
|
|
38
47
|
toQuery(eps?: number): DiceQuery;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attack.d.ts","sourceRoot":"","sources":["../../src/builder/attack.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"attack.d.ts","sourceRoot":"","sources":["../../src/builder/attack.ts"],"names":[],"mappings":"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"}
|
package/dist/builder/dc.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { SaveBuilder } from "./save.js";
|
|
|
4
4
|
interface SaveConfig {
|
|
5
5
|
dc: number;
|
|
6
6
|
}
|
|
7
|
+
/** Clears the DC-check PMF cache (test/bench seam). */
|
|
8
|
+
export declare function clearDCCache(): void;
|
|
7
9
|
export declare class DCBuilder extends RollBuilder {
|
|
8
10
|
private readonly saveConfig;
|
|
9
11
|
constructor(baseRoll: RollBuilder, saveConfig?: SaveConfig);
|
|
@@ -20,6 +22,12 @@ export declare class DCBuilder extends RollBuilder {
|
|
|
20
22
|
onSaveFailure(count: number, sides: number, modifier: number): SaveBuilder;
|
|
21
23
|
withElvenAccuracy(): never;
|
|
22
24
|
toExpression(): string;
|
|
25
|
+
/**
|
|
26
|
+
* The DC check's PMF is fully determined by the save DC plus everything `toPMF` below reads off the roll
|
|
27
|
+
* configs (`rollType`, `baseReroll`, `modifier`, bonus dice) — all of which `super.cacheKey()` already
|
|
28
|
+
* serializes. So extend the base key with the DC, mirroring {@link AlwaysHitBuilder.cacheKey}.
|
|
29
|
+
*/
|
|
30
|
+
cacheKey(): string | null;
|
|
23
31
|
toPMF(eps?: number): PMF;
|
|
24
32
|
}
|
|
25
33
|
export {};
|
package/dist/builder/dc.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dc.d.ts","sourceRoot":"","sources":["../../src/builder/dc.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dc.d.ts","sourceRoot":"","sources":["../../src/builder/dc.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAGjC,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ;AAQD,uDAAuD;AACvD,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAED,qBAAa,SAAU,SAAQ,WAAW;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IAExC,YAAY,QAAQ,EAAE,WAAW,EAAE,UAAU,CAAC,EAAE,UAAU,EAGzD;IAEQ,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAOrC;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAEQ,GAAG,CAAC,WAAW,EAAE,WAAW,GAAG,SAAS,CAGhD;IAEQ,OAAO,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAG1C;IAED,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAC;IACxC,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAC;IACxC,aAAa,CAAC,GAAG,EAAE,WAAW,GAAG,WAAW,CAAC;IAC7C,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,WAAW,CAAC;IAC5D,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC;IACzD,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAAC;IAC9E,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAAC;IAMlE,iBAAiB,IAAI,KAAK,CAIlC;IAGQ,YAAY,IAAI,MAAM,CAK9B;IAED;;;;OAIG;IACM,QAAQ,IAAI,MAAM,GAAG,IAAI,CAGjC;IAEQ,KAAK,CAAC,GAAG,GAAE,MAAU,GAAG,GAAG,CAoCnC;CACF"}
|
package/dist/builder/index.cjs
CHANGED
|
@@ -3454,6 +3454,10 @@ function d20PMF(rerollOne) {
|
|
|
3454
3454
|
}
|
|
3455
3455
|
|
|
3456
3456
|
// src/builder/roll.ts
|
|
3457
|
+
var rollPMFCache = new LRUCache(4e3);
|
|
3458
|
+
function clearRollCache() {
|
|
3459
|
+
rollPMFCache.clear();
|
|
3460
|
+
}
|
|
3457
3461
|
var defaultConfig = {
|
|
3458
3462
|
count: 1,
|
|
3459
3463
|
sides: 0,
|
|
@@ -3503,6 +3507,17 @@ var RollBuilder = class _RollBuilder {
|
|
|
3503
3507
|
getSubRollConfigs() {
|
|
3504
3508
|
return this.subRollConfigs.map((c) => ({ ...c }));
|
|
3505
3509
|
}
|
|
3510
|
+
/**
|
|
3511
|
+
* A cheap, stable string that FULLY identifies this builder's PMF — used by {@link AttackBuilder.toPMF}
|
|
3512
|
+
* to cache resolved attack PMFs across rebuilds without walking the AST via {@link toExpression}. A plain
|
|
3513
|
+
* roll is fully determined by its {@link RollConfig} array (count/sides/modifier/reroll/explode/minimum/
|
|
3514
|
+
* bestOf/keep/rollType/isSubtraction), so serializing that is sound. Subclasses whose PMF depends on
|
|
3515
|
+
* hidden state NOT captured by `subRollConfigs` (half/scale/max/parsed/pooled/composite transforms) return
|
|
3516
|
+
* `null` to opt OUT of caching — a conservative miss is always safe; a wrong key would corrupt DPR.
|
|
3517
|
+
*/
|
|
3518
|
+
cacheKey() {
|
|
3519
|
+
return JSON.stringify(this.subRollConfigs);
|
|
3520
|
+
}
|
|
3506
3521
|
// for testing
|
|
3507
3522
|
static fromConfig(config) {
|
|
3508
3523
|
return new _RollBuilder([{ ...defaultConfig, ...config }]);
|
|
@@ -3855,8 +3870,16 @@ var RollBuilder = class _RollBuilder {
|
|
|
3855
3870
|
}
|
|
3856
3871
|
return result.replace(/\+ -/g, "-");
|
|
3857
3872
|
}
|
|
3873
|
+
// Main AST entry point. Cached by the cheap config key across identical rebuilds; see `rollPMFCache`.
|
|
3858
3874
|
toPMF(eps = 0) {
|
|
3859
|
-
|
|
3875
|
+
const key = this.cacheKey();
|
|
3876
|
+
if (key === null) return pmfFromRollBuilder(this, eps);
|
|
3877
|
+
const fullKey = `${key}*e${eps}`;
|
|
3878
|
+
const cached = rollPMFCache.get(fullKey);
|
|
3879
|
+
if (cached) return cached;
|
|
3880
|
+
const pmf = pmfFromRollBuilder(this, eps);
|
|
3881
|
+
rollPMFCache.set(fullKey, pmf);
|
|
3882
|
+
return pmf;
|
|
3860
3883
|
}
|
|
3861
3884
|
get pmf() {
|
|
3862
3885
|
return this.toPMF();
|
|
@@ -4011,6 +4034,9 @@ var HalfRollBuilder = class _HalfRollBuilder extends RollBuilder {
|
|
|
4011
4034
|
hasHiddenState() {
|
|
4012
4035
|
return this.innerRoll.hasHiddenState();
|
|
4013
4036
|
}
|
|
4037
|
+
cacheKey() {
|
|
4038
|
+
return null;
|
|
4039
|
+
}
|
|
4014
4040
|
// No need to override create if we don't expose RollBuilder methods that use it,
|
|
4015
4041
|
// but HalfRollBuilder extends RollBuilder so it does.
|
|
4016
4042
|
// However, HalfRollBuilder seems to just wrap another roll.
|
|
@@ -4055,6 +4081,9 @@ var ScaleRollBuilder = class _ScaleRollBuilder extends RollBuilder {
|
|
|
4055
4081
|
hasHiddenState() {
|
|
4056
4082
|
return this.innerRoll.hasHiddenState();
|
|
4057
4083
|
}
|
|
4084
|
+
cacheKey() {
|
|
4085
|
+
return null;
|
|
4086
|
+
}
|
|
4058
4087
|
get lastConfig() {
|
|
4059
4088
|
return this.innerRoll.lastConfig;
|
|
4060
4089
|
}
|
|
@@ -4099,6 +4128,9 @@ var MaxOfRollBuilder = class _MaxOfRollBuilder extends RollBuilder {
|
|
|
4099
4128
|
hasHiddenState() {
|
|
4100
4129
|
return this.innerRoll.hasHiddenState();
|
|
4101
4130
|
}
|
|
4131
|
+
cacheKey() {
|
|
4132
|
+
return null;
|
|
4133
|
+
}
|
|
4102
4134
|
get lastConfig() {
|
|
4103
4135
|
return this.innerRoll.lastConfig;
|
|
4104
4136
|
}
|
|
@@ -4176,6 +4208,10 @@ var AlwaysHitBuilder = class _AlwaysHitBuilder extends RollBuilder {
|
|
|
4176
4208
|
get critThreshold() {
|
|
4177
4209
|
return this.attackConfig.critThreshold;
|
|
4178
4210
|
}
|
|
4211
|
+
cacheKey() {
|
|
4212
|
+
const base = super.cacheKey();
|
|
4213
|
+
return base === null ? null : `H|${this.attackConfig.critThreshold}|${base}`;
|
|
4214
|
+
}
|
|
4179
4215
|
// TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error?
|
|
4180
4216
|
critOn(critThreshold) {
|
|
4181
4217
|
const newConfig = { critThreshold };
|
|
@@ -4226,6 +4262,10 @@ var AlwaysCritBuilder = class _AlwaysCritBuilder extends RollBuilder {
|
|
|
4226
4262
|
get critThreshold() {
|
|
4227
4263
|
return this.attackConfig.critThreshold;
|
|
4228
4264
|
}
|
|
4265
|
+
cacheKey() {
|
|
4266
|
+
const base = super.cacheKey();
|
|
4267
|
+
return base === null ? null : `C|${this.fromAlwaysHit ? 1 : 0}|${this.attackConfig.critThreshold}|${this.attackConfig.ac ?? ""}|${base}`;
|
|
4268
|
+
}
|
|
4229
4269
|
critOn(critThreshold) {
|
|
4230
4270
|
const newConfig = { critThreshold, ac: this.attackConfig.ac };
|
|
4231
4271
|
return new _AlwaysCritBuilder(this, newConfig, this.fromAlwaysHit);
|
|
@@ -4256,6 +4296,9 @@ var ParsedRollBuilder = class _ParsedRollBuilder extends RollBuilder {
|
|
|
4256
4296
|
hasHiddenState() {
|
|
4257
4297
|
return true;
|
|
4258
4298
|
}
|
|
4299
|
+
cacheKey() {
|
|
4300
|
+
return null;
|
|
4301
|
+
}
|
|
4259
4302
|
create(configs) {
|
|
4260
4303
|
return new RollBuilder(configs);
|
|
4261
4304
|
}
|
|
@@ -4291,6 +4334,9 @@ var PooledRollBuilder = class _PooledRollBuilder extends RollBuilder {
|
|
|
4291
4334
|
hasHiddenState() {
|
|
4292
4335
|
return true;
|
|
4293
4336
|
}
|
|
4337
|
+
cacheKey() {
|
|
4338
|
+
return null;
|
|
4339
|
+
}
|
|
4294
4340
|
d(_sides) {
|
|
4295
4341
|
throw new Error("Cannot add dice to a pooled roll. The pool is finalized.");
|
|
4296
4342
|
}
|
|
@@ -4394,6 +4440,9 @@ var CompositeSumRollBuilder = class _CompositeSumRollBuilder extends RollBuilder
|
|
|
4394
4440
|
hasHiddenState() {
|
|
4395
4441
|
return true;
|
|
4396
4442
|
}
|
|
4443
|
+
cacheKey() {
|
|
4444
|
+
return null;
|
|
4445
|
+
}
|
|
4397
4446
|
getSubRollConfigs() {
|
|
4398
4447
|
return [];
|
|
4399
4448
|
}
|
|
@@ -4946,6 +4995,10 @@ function getASTSignature(node) {
|
|
|
4946
4995
|
}
|
|
4947
4996
|
|
|
4948
4997
|
// src/builder/attack.ts
|
|
4998
|
+
var attackPMFCache = new LRUCache(4e3);
|
|
4999
|
+
function clearAttackCache() {
|
|
5000
|
+
attackPMFCache.clear();
|
|
5001
|
+
}
|
|
4949
5002
|
var AttackBuilder = class _AttackBuilder {
|
|
4950
5003
|
constructor(check, hitEffect, critEffect, missEffect) {
|
|
4951
5004
|
this.check = check;
|
|
@@ -5124,9 +5177,46 @@ var AttackBuilder = class _AttackBuilder {
|
|
|
5124
5177
|
weights: { hit: phit, crit: pcrit, miss: pmiss }
|
|
5125
5178
|
};
|
|
5126
5179
|
}
|
|
5127
|
-
|
|
5180
|
+
/**
|
|
5181
|
+
* A cheap, complete key for this attack's resolved PMF, or `null` when it can't be cached soundly (an
|
|
5182
|
+
* effect whose PMF isn't captured by its {@link RollConfig}s — see {@link RollBuilder.cacheKey}). Composed
|
|
5183
|
+
* from the check + hit/crit/miss effect keys + `eps`. `critEffect === null` (noCrit) and `undefined`
|
|
5184
|
+
* (auto-double the hit dice) are distinct crit states, encoded separately.
|
|
5185
|
+
*/
|
|
5186
|
+
cacheKey(eps) {
|
|
5187
|
+
const checkKey = this.check.cacheKey();
|
|
5188
|
+
if (checkKey === null) return null;
|
|
5189
|
+
let hitKey = "";
|
|
5190
|
+
if (this.hitEffect) {
|
|
5191
|
+
const k = this.hitEffect.cacheKey();
|
|
5192
|
+
if (k === null) return null;
|
|
5193
|
+
hitKey = k;
|
|
5194
|
+
}
|
|
5195
|
+
let critKey;
|
|
5196
|
+
if (this.critEffect === null) critKey = "n";
|
|
5197
|
+
else if (this.critEffect === void 0) critKey = "a";
|
|
5198
|
+
else {
|
|
5199
|
+
const k = this.critEffect.cacheKey();
|
|
5200
|
+
if (k === null) return null;
|
|
5201
|
+
critKey = k;
|
|
5202
|
+
}
|
|
5203
|
+
let missKey = "";
|
|
5204
|
+
if (this.missEffect) {
|
|
5205
|
+
const k = this.missEffect.cacheKey();
|
|
5206
|
+
if (k === null) return null;
|
|
5207
|
+
missKey = k;
|
|
5208
|
+
}
|
|
5209
|
+
return `${checkKey}*H${hitKey}*C${critKey}*M${missKey}*e${eps}`;
|
|
5210
|
+
}
|
|
5211
|
+
// By default, create PMF with no pruning. Cached by the cheap config key across identical rebuilds.
|
|
5128
5212
|
toPMF(eps = 0) {
|
|
5129
|
-
|
|
5213
|
+
const key = this.cacheKey(eps);
|
|
5214
|
+
if (key === null) return this.resolve(eps).pmf;
|
|
5215
|
+
const cached = attackPMFCache.get(key);
|
|
5216
|
+
if (cached) return cached;
|
|
5217
|
+
const pmf = this.resolve(eps).pmf;
|
|
5218
|
+
attackPMFCache.set(key, pmf);
|
|
5219
|
+
return pmf;
|
|
5130
5220
|
}
|
|
5131
5221
|
get pmf() {
|
|
5132
5222
|
return this.toPMF();
|
|
@@ -5154,6 +5244,10 @@ var ACBuilder = class _ACBuilder extends RollBuilder {
|
|
|
5154
5244
|
get critThreshold() {
|
|
5155
5245
|
return this.attackConfig.critThreshold;
|
|
5156
5246
|
}
|
|
5247
|
+
cacheKey() {
|
|
5248
|
+
const base = super.cacheKey();
|
|
5249
|
+
return base === null ? null : `A|${this.attackConfig.ac}|${this.attackConfig.critThreshold}|${base}`;
|
|
5250
|
+
}
|
|
5157
5251
|
// TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error?
|
|
5158
5252
|
critOn(threshold) {
|
|
5159
5253
|
const newConfig = {
|
|
@@ -5214,6 +5308,10 @@ RollBuilder.prototype.ac = function(targetAC) {
|
|
|
5214
5308
|
};
|
|
5215
5309
|
|
|
5216
5310
|
// src/builder/save.ts
|
|
5311
|
+
var savePMFCache = new LRUCache(4e3);
|
|
5312
|
+
function clearSaveCache() {
|
|
5313
|
+
savePMFCache.clear();
|
|
5314
|
+
}
|
|
5217
5315
|
var SaveBuilder = class _SaveBuilder {
|
|
5218
5316
|
constructor(check, failureEffect, saveOutcome = "normal") {
|
|
5219
5317
|
this.check = check;
|
|
@@ -5250,9 +5348,32 @@ var SaveBuilder = class _SaveBuilder {
|
|
|
5250
5348
|
weights: { success: psuccess, fail: pfail }
|
|
5251
5349
|
};
|
|
5252
5350
|
}
|
|
5253
|
-
|
|
5351
|
+
/**
|
|
5352
|
+
* A cheap, complete key for this save's resolved PMF, or `null` when it can't be cached soundly.
|
|
5353
|
+
* {@link resolve} reads exactly three things: the DC check (via `resolveProbabilities`), the failure
|
|
5354
|
+
* effect's PMF, and the save outcome — so composing their keys pins it. A `ParsedRollBuilder` failure
|
|
5355
|
+
* effect returns `null`, which correctly forces this uncached.
|
|
5356
|
+
*/
|
|
5357
|
+
cacheKey(eps) {
|
|
5358
|
+
const checkKey = this.check.cacheKey();
|
|
5359
|
+
if (checkKey === null) return null;
|
|
5360
|
+
let failKey = "";
|
|
5361
|
+
if (this.failureEffect) {
|
|
5362
|
+
const k = this.failureEffect.cacheKey();
|
|
5363
|
+
if (k === null) return null;
|
|
5364
|
+
failKey = k;
|
|
5365
|
+
}
|
|
5366
|
+
return `${checkKey}*F${failKey}*O${this.saveOutcome}*e${eps}`;
|
|
5367
|
+
}
|
|
5368
|
+
// By default, create PMF with no pruning. Cached by the cheap config key across identical rebuilds.
|
|
5254
5369
|
toPMF(eps = 0) {
|
|
5255
|
-
|
|
5370
|
+
const key = this.cacheKey(eps);
|
|
5371
|
+
if (key === null) return this.resolve(eps).pmf;
|
|
5372
|
+
const cached = savePMFCache.get(key);
|
|
5373
|
+
if (cached) return cached;
|
|
5374
|
+
const pmf = this.resolve(eps).pmf;
|
|
5375
|
+
savePMFCache.set(key, pmf);
|
|
5376
|
+
return pmf;
|
|
5256
5377
|
}
|
|
5257
5378
|
get pmf() {
|
|
5258
5379
|
return this.toPMF();
|
|
@@ -5288,6 +5409,10 @@ function resolveProbabilities(check) {
|
|
|
5288
5409
|
}
|
|
5289
5410
|
|
|
5290
5411
|
// src/builder/dc.ts
|
|
5412
|
+
var dcPMFCache = new LRUCache(4e3);
|
|
5413
|
+
function clearDCCache() {
|
|
5414
|
+
dcPMFCache.clear();
|
|
5415
|
+
}
|
|
5291
5416
|
var DCBuilder = class _DCBuilder extends RollBuilder {
|
|
5292
5417
|
constructor(baseRoll, saveConfig) {
|
|
5293
5418
|
super(baseRoll.getSubRollConfigs());
|
|
@@ -5328,7 +5453,22 @@ var DCBuilder = class _DCBuilder extends RollBuilder {
|
|
|
5328
5453
|
const expression = new RollBuilder(allConfigs).toExpression();
|
|
5329
5454
|
return `(${expression} DC ${this.saveConfig.dc})`;
|
|
5330
5455
|
}
|
|
5456
|
+
/**
|
|
5457
|
+
* The DC check's PMF is fully determined by the save DC plus everything `toPMF` below reads off the roll
|
|
5458
|
+
* configs (`rollType`, `baseReroll`, `modifier`, bonus dice) — all of which `super.cacheKey()` already
|
|
5459
|
+
* serializes. So extend the base key with the DC, mirroring {@link AlwaysHitBuilder.cacheKey}.
|
|
5460
|
+
*/
|
|
5461
|
+
cacheKey() {
|
|
5462
|
+
const base = super.cacheKey();
|
|
5463
|
+
return base === null ? null : `DC|${this.saveConfig.dc}|${base}`;
|
|
5464
|
+
}
|
|
5331
5465
|
toPMF(eps = 0) {
|
|
5466
|
+
const key = this.cacheKey();
|
|
5467
|
+
const fullKey = key === null ? null : `${key}*e${eps}`;
|
|
5468
|
+
if (fullKey !== null) {
|
|
5469
|
+
const cached = dcPMFCache.get(fullKey);
|
|
5470
|
+
if (cached) return cached;
|
|
5471
|
+
}
|
|
5332
5472
|
const saveDC = this.saveDC;
|
|
5333
5473
|
const rollType = this.rollType;
|
|
5334
5474
|
const rerollOne = this.baseReroll > 0;
|
|
@@ -5350,7 +5490,9 @@ var DCBuilder = class _DCBuilder extends RollBuilder {
|
|
|
5350
5490
|
[0, psuccess > 0 ? psuccess : 0],
|
|
5351
5491
|
[1, pfail > 0 ? pfail : 0]
|
|
5352
5492
|
]);
|
|
5353
|
-
|
|
5493
|
+
const pmf = PMF.fromMap(m, eps);
|
|
5494
|
+
if (fullKey !== null) dcPMFCache.set(fullKey, pmf);
|
|
5495
|
+
return pmf;
|
|
5354
5496
|
}
|
|
5355
5497
|
};
|
|
5356
5498
|
RollBuilder.prototype.dc = function(saveDC) {
|
|
@@ -5371,6 +5513,10 @@ exports.RollBuilder = RollBuilder;
|
|
|
5371
5513
|
exports.SaveBuilder = SaveBuilder;
|
|
5372
5514
|
exports.ScaleRollBuilder = ScaleRollBuilder;
|
|
5373
5515
|
exports.builderPMFCache = builderPMFCache;
|
|
5516
|
+
exports.clearAttackCache = clearAttackCache;
|
|
5517
|
+
exports.clearDCCache = clearDCCache;
|
|
5518
|
+
exports.clearRollCache = clearRollCache;
|
|
5519
|
+
exports.clearSaveCache = clearSaveCache;
|
|
5374
5520
|
exports.d = d;
|
|
5375
5521
|
exports.d10 = d10;
|
|
5376
5522
|
exports.d100 = d100;
|