@yipe/dice 0.2.10 → 0.2.12

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.
@@ -1,38 +1,47 @@
1
1
  import { P as PMF, i as DiceQuery, L as LRUCache } from '../pmf-D1p2Re-J.cjs';
2
2
 
3
- type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode;
3
+ type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode | HalfNode | MaxOfNode;
4
4
  type DieNode = {
5
- type: 'die';
5
+ type: "die";
6
6
  sides: number;
7
7
  reroll?: number;
8
8
  minimum?: number;
9
9
  explode?: number;
10
10
  };
11
11
  type ConstantNode = {
12
- type: 'constant';
12
+ type: "constant";
13
13
  value: number;
14
14
  };
15
15
  type SumNode = {
16
- type: 'sum';
16
+ type: "sum";
17
17
  count: number;
18
18
  child: ExpressionNode;
19
19
  };
20
20
  type AddNode = {
21
- type: 'add';
21
+ type: "add";
22
22
  children: {
23
23
  node: ExpressionNode;
24
24
  sign: 1 | -1;
25
25
  }[];
26
26
  };
27
27
  type KeepNode = {
28
- type: 'keep';
29
- mode: 'highest' | 'lowest';
28
+ type: "keep";
29
+ mode: "highest" | "lowest";
30
30
  count: number;
31
31
  child: SumNode | KeepNode;
32
32
  };
33
33
  type D20RollNode = {
34
- type: 'd20Roll';
35
- rollType: 'advantage' | 'disadvantage' | 'elven accuracy';
34
+ type: "d20Roll";
35
+ rollType: "advantage" | "disadvantage" | "elven accuracy";
36
+ child: ExpressionNode;
37
+ };
38
+ type HalfNode = {
39
+ type: "half";
40
+ child: ExpressionNode;
41
+ };
42
+ type MaxOfNode = {
43
+ type: "maxOf";
44
+ count: number;
36
45
  child: ExpressionNode;
37
46
  };
38
47
 
@@ -151,6 +160,32 @@ declare class RollBuilder {
151
160
  get modifier(): number;
152
161
  get rollType(): RollType;
153
162
  get baseReroll(): number;
163
+ half(): HalfRollBuilder;
164
+ maxOf(count: number): MaxOfRollBuilder;
165
+ }
166
+ declare class HalfRollBuilder extends RollBuilder {
167
+ private readonly innerRoll;
168
+ constructor(innerRoll: RollBuilder);
169
+ get lastConfig(): any;
170
+ getSubRollConfigs(): readonly RollConfig[];
171
+ toExpression(): string;
172
+ toAST(): ExpressionNode;
173
+ toPMF(eps?: number): PMF;
174
+ copy(): HalfRollBuilder;
175
+ }
176
+ declare class MaxOfRollBuilder extends RollBuilder {
177
+ private readonly innerRoll;
178
+ private readonly count;
179
+ private readonly diceCount?;
180
+ private readonly diceSides?;
181
+ constructor(innerRoll: RollBuilder, count: number, diceCount?: number, diceSides?: number);
182
+ get lastConfig(): any;
183
+ getSubRollConfigs(): readonly RollConfig[];
184
+ toExpression(): string;
185
+ private findDieInAST;
186
+ toAST(): ExpressionNode;
187
+ toPMF(eps?: number): PMF;
188
+ copy(): MaxOfRollBuilder;
154
189
  }
155
190
 
156
191
  type ActionEffect = RollBuilder;
@@ -264,4 +299,4 @@ declare const flat: (n: number) => RollBuilder;
264
299
  declare const roll: RollFactory;
265
300
  declare const builderPMFCache: LRUCache<string, PMF>;
266
301
 
267
- export { ACBuilder, type AttackConfig, type AttackResolution, type CheckBuilder, DCBuilder, type KeepMode, type Resolution, RollBuilder, type RollConfig, type RollFactory, type RollType, SaveBuilder, type SaveOutcome, type SaveResolution, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll };
302
+ export { ACBuilder, type AttackConfig, type AttackResolution, type CheckBuilder, DCBuilder, HalfRollBuilder, type KeepMode, MaxOfRollBuilder, type Resolution, RollBuilder, type RollConfig, type RollFactory, type RollType, SaveBuilder, type SaveOutcome, type SaveResolution, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll };
@@ -1,38 +1,47 @@
1
1
  import { P as PMF, i as DiceQuery, L as LRUCache } from '../pmf-D1p2Re-J.js';
2
2
 
3
- type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode;
3
+ type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode | HalfNode | MaxOfNode;
4
4
  type DieNode = {
5
- type: 'die';
5
+ type: "die";
6
6
  sides: number;
7
7
  reroll?: number;
8
8
  minimum?: number;
9
9
  explode?: number;
10
10
  };
11
11
  type ConstantNode = {
12
- type: 'constant';
12
+ type: "constant";
13
13
  value: number;
14
14
  };
15
15
  type SumNode = {
16
- type: 'sum';
16
+ type: "sum";
17
17
  count: number;
18
18
  child: ExpressionNode;
19
19
  };
20
20
  type AddNode = {
21
- type: 'add';
21
+ type: "add";
22
22
  children: {
23
23
  node: ExpressionNode;
24
24
  sign: 1 | -1;
25
25
  }[];
26
26
  };
27
27
  type KeepNode = {
28
- type: 'keep';
29
- mode: 'highest' | 'lowest';
28
+ type: "keep";
29
+ mode: "highest" | "lowest";
30
30
  count: number;
31
31
  child: SumNode | KeepNode;
32
32
  };
33
33
  type D20RollNode = {
34
- type: 'd20Roll';
35
- rollType: 'advantage' | 'disadvantage' | 'elven accuracy';
34
+ type: "d20Roll";
35
+ rollType: "advantage" | "disadvantage" | "elven accuracy";
36
+ child: ExpressionNode;
37
+ };
38
+ type HalfNode = {
39
+ type: "half";
40
+ child: ExpressionNode;
41
+ };
42
+ type MaxOfNode = {
43
+ type: "maxOf";
44
+ count: number;
36
45
  child: ExpressionNode;
37
46
  };
38
47
 
@@ -151,6 +160,32 @@ declare class RollBuilder {
151
160
  get modifier(): number;
152
161
  get rollType(): RollType;
153
162
  get baseReroll(): number;
163
+ half(): HalfRollBuilder;
164
+ maxOf(count: number): MaxOfRollBuilder;
165
+ }
166
+ declare class HalfRollBuilder extends RollBuilder {
167
+ private readonly innerRoll;
168
+ constructor(innerRoll: RollBuilder);
169
+ get lastConfig(): any;
170
+ getSubRollConfigs(): readonly RollConfig[];
171
+ toExpression(): string;
172
+ toAST(): ExpressionNode;
173
+ toPMF(eps?: number): PMF;
174
+ copy(): HalfRollBuilder;
175
+ }
176
+ declare class MaxOfRollBuilder extends RollBuilder {
177
+ private readonly innerRoll;
178
+ private readonly count;
179
+ private readonly diceCount?;
180
+ private readonly diceSides?;
181
+ constructor(innerRoll: RollBuilder, count: number, diceCount?: number, diceSides?: number);
182
+ get lastConfig(): any;
183
+ getSubRollConfigs(): readonly RollConfig[];
184
+ toExpression(): string;
185
+ private findDieInAST;
186
+ toAST(): ExpressionNode;
187
+ toPMF(eps?: number): PMF;
188
+ copy(): MaxOfRollBuilder;
154
189
  }
155
190
 
156
191
  type ActionEffect = RollBuilder;
@@ -264,4 +299,4 @@ declare const flat: (n: number) => RollBuilder;
264
299
  declare const roll: RollFactory;
265
300
  declare const builderPMFCache: LRUCache<string, PMF>;
266
301
 
267
- export { ACBuilder, type AttackConfig, type AttackResolution, type CheckBuilder, DCBuilder, type KeepMode, type Resolution, RollBuilder, type RollConfig, type RollFactory, type RollType, SaveBuilder, type SaveOutcome, type SaveResolution, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll };
302
+ export { ACBuilder, type AttackConfig, type AttackResolution, type CheckBuilder, DCBuilder, HalfRollBuilder, type KeepMode, MaxOfRollBuilder, type Resolution, RollBuilder, type RollConfig, type RollFactory, type RollType, SaveBuilder, type SaveOutcome, type SaveResolution, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll };
@@ -2657,6 +2657,14 @@ var RollBuilder = class _RollBuilder {
2657
2657
  const scaleInt = Math.floor(scale);
2658
2658
  if (scaleInt !== scale) throw new Error("Scale must be an integer");
2659
2659
  if (scaleInt <= 0) throw new Error("Scale must be > 0");
2660
+ if (scaleInt > 1) {
2661
+ const configs = this.getSubRollConfigs();
2662
+ if (configs.length === 1 && configs[0].keep && configs[0].count === 1) {
2663
+ const config = configs[0];
2664
+ const newConfig = { ...config, count: scaleInt };
2665
+ return new _RollBuilder([newConfig]);
2666
+ }
2667
+ }
2660
2668
  const newConfigs = this.getSubRollConfigs().map((config) => {
2661
2669
  if (!config.sides || config.sides <= 0) return config;
2662
2670
  return { ...config, count: config.count * scaleInt };
@@ -2873,6 +2881,113 @@ var RollBuilder = class _RollBuilder {
2873
2881
  const rootConfig = this.getRootDieConfig();
2874
2882
  return rootConfig?.reroll || 0;
2875
2883
  }
2884
+ half() {
2885
+ return new HalfRollBuilder(this);
2886
+ }
2887
+ // Create a "max of N rolls" version of this roll for crit damage with keep operations
2888
+ maxOf(count) {
2889
+ return new MaxOfRollBuilder(this, count);
2890
+ }
2891
+ };
2892
+ var HalfRollBuilder = class _HalfRollBuilder extends RollBuilder {
2893
+ constructor(innerRoll) {
2894
+ super(0);
2895
+ this.innerRoll = innerRoll;
2896
+ }
2897
+ get lastConfig() {
2898
+ return this.innerRoll.lastConfig;
2899
+ }
2900
+ getSubRollConfigs() {
2901
+ return this.innerRoll.getSubRollConfigs();
2902
+ }
2903
+ toExpression() {
2904
+ const innerExpression = this.innerRoll.toExpression();
2905
+ return `(${innerExpression}) // 2`;
2906
+ }
2907
+ toAST() {
2908
+ return {
2909
+ type: "half",
2910
+ child: this.innerRoll.toAST()
2911
+ };
2912
+ }
2913
+ toPMF(eps = 0) {
2914
+ return pmfFromRollBuilder(this, eps);
2915
+ }
2916
+ copy() {
2917
+ return new _HalfRollBuilder(this.innerRoll.copy());
2918
+ }
2919
+ };
2920
+ var MaxOfRollBuilder = class _MaxOfRollBuilder extends RollBuilder {
2921
+ constructor(innerRoll, count, diceCount, diceSides) {
2922
+ super(0);
2923
+ this.innerRoll = innerRoll;
2924
+ this.count = count;
2925
+ this.diceCount = diceCount;
2926
+ this.diceSides = diceSides;
2927
+ }
2928
+ get lastConfig() {
2929
+ return this.innerRoll.lastConfig;
2930
+ }
2931
+ getSubRollConfigs() {
2932
+ return this.innerRoll.getSubRollConfigs();
2933
+ }
2934
+ toExpression() {
2935
+ if (this.diceCount && this.diceSides) {
2936
+ return `max${this.count}(${this.diceCount}d${this.diceSides})`;
2937
+ }
2938
+ return `max${this.count}(?d?)`;
2939
+ }
2940
+ findDieInAST(node) {
2941
+ if (node.type === "die") return node;
2942
+ if (node.child) return this.findDieInAST(node.child);
2943
+ if (node.children) {
2944
+ for (const child of node.children) {
2945
+ const result = this.findDieInAST(child.node || child);
2946
+ if (result) return result;
2947
+ }
2948
+ }
2949
+ return null;
2950
+ }
2951
+ toAST() {
2952
+ if (this.diceCount && this.diceSides) {
2953
+ const sumChild = {
2954
+ type: "sum",
2955
+ count: this.diceCount,
2956
+ child: { type: "die", sides: this.diceSides }
2957
+ };
2958
+ return {
2959
+ type: "maxOf",
2960
+ count: this.count,
2961
+ child: sumChild
2962
+ };
2963
+ }
2964
+ try {
2965
+ const configs = this.innerRoll.getSubRollConfigs();
2966
+ if (configs.length === 1 && configs[0].sides) {
2967
+ const config = configs[0];
2968
+ const sumChild = {
2969
+ type: "sum",
2970
+ count: config.count,
2971
+ child: { type: "die", sides: config.sides }
2972
+ };
2973
+ return {
2974
+ type: "maxOf",
2975
+ count: this.count,
2976
+ child: sumChild
2977
+ };
2978
+ }
2979
+ } catch (e) {
2980
+ }
2981
+ throw new Error(
2982
+ `MaxOfRollBuilder.toAST(): Unsupported innerRoll configuration`
2983
+ );
2984
+ }
2985
+ toPMF(eps = 0) {
2986
+ return pmfFromRollBuilder(this, eps);
2987
+ }
2988
+ copy() {
2989
+ return new _MaxOfRollBuilder(this.innerRoll.copy(), this.count);
2990
+ }
2876
2991
  };
2877
2992
 
2878
2993
  // src/builder/factory.ts
@@ -2962,13 +3077,41 @@ function astFromRollConfigs(configs) {
2962
3077
  appliedRollType = true;
2963
3078
  }
2964
3079
  if (cfg.rollType === "flat" && cfg.keep && cfg.keep.total > 0) {
2965
- const base = { type: "sum", count: cfg.keep.total, child: node };
2966
- node = {
2967
- type: "keep",
2968
- mode: cfg.keep.mode,
2969
- count: cfg.keep.count,
2970
- child: base
2971
- };
3080
+ const baseCount = Math.max(1, Math.floor(Math.abs(count || 1)));
3081
+ const trials = Math.max(1, Math.floor(cfg.keep.total));
3082
+ const k = Math.max(0, Math.floor(cfg.keep.count));
3083
+ if (trials === baseCount) {
3084
+ const base = { type: "sum", count: trials, child: node };
3085
+ node = {
3086
+ type: "keep",
3087
+ mode: cfg.keep.mode,
3088
+ count: k,
3089
+ child: base
3090
+ };
3091
+ } else {
3092
+ const perTrial = {
3093
+ type: "sum",
3094
+ count: baseCount,
3095
+ child: node
3096
+ };
3097
+ if (trials === 1) {
3098
+ node = perTrial;
3099
+ } else if (k === 1 && cfg.keep.mode === "highest") {
3100
+ node = { type: "maxOf", count: trials, child: perTrial };
3101
+ } else {
3102
+ const trialPool = {
3103
+ type: "sum",
3104
+ count: trials,
3105
+ child: perTrial
3106
+ };
3107
+ node = {
3108
+ type: "keep",
3109
+ mode: cfg.keep.mode,
3110
+ count: k,
3111
+ child: trialPool
3112
+ };
3113
+ }
3114
+ }
2972
3115
  } else {
2973
3116
  const c = appliedRollType ? 1 : Math.max(1, count || 1);
2974
3117
  node = { type: "sum", count: c, child: node };
@@ -3022,14 +3165,14 @@ function resolve(node, eps = defaultEps) {
3022
3165
  return res;
3023
3166
  }
3024
3167
  case "keep": {
3025
- const total = getTotalCount(node);
3026
- const die = findDie(node.child.child);
3027
- const perDie = die ? resolveSingleDie(die, eps) : PMF.delta(0, eps);
3028
- const keepCount = Math.max(0, Math.min(node.count, total));
3029
- if (keepCount === 0 || total === 0) return PMF.delta(0, eps);
3168
+ const totalTrials = getTotalCount(node);
3169
+ const keepCount = Math.max(0, Math.min(node.count, totalTrials));
3170
+ if (keepCount === 0 || totalTrials === 0) return PMF.delta(0, eps);
3171
+ const perTrialNode = node.child.child;
3172
+ const perTrialPMF = resolve(perTrialNode, eps);
3030
3173
  return keepSumPMF(
3031
- perDie,
3032
- total,
3174
+ perTrialPMF,
3175
+ totalTrials,
3033
3176
  keepCount,
3034
3177
  node.mode === "highest",
3035
3178
  eps
@@ -3040,14 +3183,23 @@ function resolve(node, eps = defaultEps) {
3040
3183
  const rerollOne = !!childDie && (childDie.reroll || 0) >= 1;
3041
3184
  return d20RollPMF(node.rollType, rerollOne);
3042
3185
  }
3186
+ case "half": {
3187
+ const childPMF = resolve(node.child, eps);
3188
+ return childPMF.scaleDamage(0.5, "floor");
3189
+ }
3190
+ case "maxOf": {
3191
+ const childPMF = resolve(node.child, eps);
3192
+ const count = Math.max(1, Math.floor(node.count));
3193
+ if (count === 1) return childPMF;
3194
+ return computeMaxOfPMF(childPMF, count, eps);
3195
+ }
3043
3196
  }
3044
3197
  })();
3045
3198
  builderPMFCache.set(cacheKey, result);
3046
3199
  return result;
3047
3200
  }
3048
3201
  function pmfFromRollBuilder(rb, eps = defaultEps) {
3049
- const configs = rb.getSubRollConfigs?.() ?? [];
3050
- const ast = astFromRollConfigs(configs) || { type: "constant", value: 0 };
3202
+ const ast = rb.toAST();
3051
3203
  return resolve(ast, eps);
3052
3204
  }
3053
3205
  function resolveSingleDie(die, eps = defaultEps) {
@@ -3110,6 +3262,8 @@ function findDie(node) {
3110
3262
  return void 0;
3111
3263
  case "sum":
3112
3264
  case "d20Roll":
3265
+ case "half":
3266
+ case "maxOf":
3113
3267
  return findDie(node.child);
3114
3268
  case "keep":
3115
3269
  return findDie(node.child.child);
@@ -3126,6 +3280,38 @@ function getTotalCount(node) {
3126
3280
  while (cur.type === "keep") cur = cur.child;
3127
3281
  return cur.type === "sum" ? Math.max(0, Math.floor(cur.count)) : 0;
3128
3282
  }
3283
+ function computeMaxOfPMF(pmf, count, eps = defaultEps) {
3284
+ if (count <= 1) return pmf;
3285
+ const support = pmf.support();
3286
+ const out = /* @__PURE__ */ new Map();
3287
+ if (count <= 6 && support.length <= 20) {
3288
+ let dfs = function(rollsLeft, currentMax, probability) {
3289
+ if (rollsLeft === 0) {
3290
+ out.set(currentMax, (out.get(currentMax) || 0) + probability);
3291
+ return;
3292
+ }
3293
+ for (const value of support) {
3294
+ const p = pmf.pAt(value);
3295
+ if (p > 0) {
3296
+ const newMax = Math.max(currentMax, value);
3297
+ dfs(rollsLeft - 1, newMax, probability * p);
3298
+ }
3299
+ }
3300
+ };
3301
+ dfs(count, -Infinity, 1);
3302
+ } else {
3303
+ const sortedSupport = [...support].sort((a, b) => a - b);
3304
+ for (const value of sortedSupport) {
3305
+ const cdfAtValue = pmf.cdfAt(value);
3306
+ const cdfAtValueMinus1 = value > sortedSupport[0] ? pmf.cdfAt(value - 1) : 0;
3307
+ const probMax = Math.pow(cdfAtValue, count) - Math.pow(cdfAtValueMinus1, count);
3308
+ if (probMax > eps) {
3309
+ out.set(value, probMax);
3310
+ }
3311
+ }
3312
+ }
3313
+ return PMF.fromMap(out, eps);
3314
+ }
3129
3315
  function keepSumPMF(single, total, keep, highest, eps = defaultEps) {
3130
3316
  if (keep >= total) return single.power(total, eps);
3131
3317
  if (keep <= 0) return PMF.delta(0, eps);
@@ -3134,24 +3320,106 @@ function keepSumPMF(single, total, keep, highest, eps = defaultEps) {
3134
3320
  const cacheKey = `keep|${pmfSig}|t:${total}|k:${keep}|h:${highest ? 1 : 0}|e:${eps}`;
3135
3321
  const cached = builderPMFCache.get(cacheKey);
3136
3322
  if (cached) return cached;
3137
- const support = single.support();
3138
- const probs = support.map((v) => ({ v, p: single.pAt(v) }));
3139
- const out = /* @__PURE__ */ new Map();
3140
- function dfs(i, accValues, weight) {
3141
- if (i === total) {
3142
- const sorted = [...accValues].sort((a, b) => highest ? b - a : a - b);
3143
- let sum = 0;
3144
- for (let j = 0; j < keep; j++) sum += sorted[j];
3145
- out.set(sum, (out.get(sum) || 0) + weight);
3146
- return;
3323
+ if (keep === 1) {
3324
+ if (highest) {
3325
+ return computeMaxOfPMF(single, total, eps);
3326
+ } else {
3327
+ const neg = single.mapDamage((v) => -v);
3328
+ const minPMF = computeMaxOfPMF(neg, total, eps).mapDamage((v) => -v);
3329
+ builderPMFCache.set(cacheKey, minPMF);
3330
+ return minPMF;
3331
+ }
3332
+ }
3333
+ let state = /* @__PURE__ */ new Map();
3334
+ const keyOf = (used, r) => `${used}|${r}`;
3335
+ state.set(keyOf(0, total), /* @__PURE__ */ new Map([[0, 1]]));
3336
+ const valuesDesc = highest ? [...sortedSupport].sort((a, b) => b - a) : [...sortedSupport].sort((a, b) => a - b);
3337
+ const binomPMF = (r, p) => {
3338
+ if (r <= 0) return [1];
3339
+ if (p <= eps) {
3340
+ const arr2 = new Array(r + 1).fill(0);
3341
+ arr2[0] = 1;
3342
+ return arr2;
3343
+ }
3344
+ if (1 - p <= eps) {
3345
+ const arr2 = new Array(r + 1).fill(0);
3346
+ arr2[r] = 1;
3347
+ return arr2;
3147
3348
  }
3148
- for (const { v, p } of probs) {
3149
- if (p <= 0) continue;
3150
- dfs(i + 1, [...accValues, v], weight * p);
3349
+ const q = 1 - p;
3350
+ const arr = new Array(r + 1).fill(0);
3351
+ arr[0] = Math.pow(q, r);
3352
+ const ratio = p / q;
3353
+ for (let x = 1; x <= r; x++)
3354
+ arr[x] = arr[x - 1] * (r - x + 1) / x * ratio;
3355
+ let s = 0;
3356
+ for (let x = 0; x <= r; x++) s += arr[x];
3357
+ if (Math.abs(1 - s) > 1e-12) for (let x = 0; x <= r; x++) arr[x] /= s;
3358
+ return arr;
3359
+ };
3360
+ const pruneMap = (m, threshold) => {
3361
+ if (threshold <= 0) return m;
3362
+ const out = /* @__PURE__ */ new Map();
3363
+ for (const [sum, pr] of m) if (pr >= threshold) out.set(sum, pr);
3364
+ return out.size === m.size ? m : out;
3365
+ };
3366
+ const pruneState = (st, threshold) => {
3367
+ if (threshold <= 0) return st;
3368
+ const out = /* @__PURE__ */ new Map();
3369
+ for (const [k, m] of st) {
3370
+ const mm = pruneMap(m, threshold);
3371
+ if (mm.size > 0) out.set(k, mm);
3151
3372
  }
3373
+ return out;
3374
+ };
3375
+ let processedMass = 0;
3376
+ for (const v of valuesDesc) {
3377
+ const p = single.pAt(v);
3378
+ if (p <= 0) continue;
3379
+ const q = Math.max(eps, 1 - processedMass);
3380
+ const pCond = Math.min(1, p / q);
3381
+ const next = /* @__PURE__ */ new Map();
3382
+ for (const [k, m] of state) {
3383
+ const [usedStr, rStr] = k.split("|");
3384
+ const used = parseInt(usedStr, 10);
3385
+ const r = parseInt(rStr, 10);
3386
+ if (r === 0) {
3387
+ const destKey = keyOf(used, 0);
3388
+ const dest = next.get(destKey) ?? /* @__PURE__ */ new Map();
3389
+ for (const [sum, pr] of m) dest.set(sum, (dest.get(sum) || 0) + pr);
3390
+ next.set(destKey, dest);
3391
+ continue;
3392
+ }
3393
+ const bin = binomPMF(r, pCond);
3394
+ const remainingCapacity = keep - used;
3395
+ for (let x = 0; x <= r; x++) {
3396
+ const px = bin[x];
3397
+ if (px <= eps) continue;
3398
+ const t = Math.min(x, remainingCapacity);
3399
+ const used2 = used + t;
3400
+ const r2 = r - x;
3401
+ const add = t * v;
3402
+ const destKey = keyOf(used2, r2);
3403
+ const dest = next.get(destKey) ?? /* @__PURE__ */ new Map();
3404
+ for (const [sum, pr] of m) {
3405
+ const s2 = sum + add;
3406
+ const prob = pr * px;
3407
+ const cur = dest.get(s2) || 0;
3408
+ const nv = cur + prob;
3409
+ if (nv >= eps) dest.set(s2, nv);
3410
+ }
3411
+ if (dest.size > 0) next.set(destKey, dest);
3412
+ }
3413
+ }
3414
+ state = pruneState(next, eps * 1e-6);
3415
+ processedMass += p;
3416
+ }
3417
+ const finalKey = keyOf(keep, 0);
3418
+ const dist = state.get(finalKey) ?? /* @__PURE__ */ new Map();
3419
+ if (dist.size === 0) {
3420
+ return PMF.emptyMass();
3152
3421
  }
3153
- dfs(0, [], 1);
3154
- const result = PMF.fromMap(out, eps);
3422
+ const result = PMF.fromMap(dist, eps);
3155
3423
  builderPMFCache.set(cacheKey, result);
3156
3424
  return result;
3157
3425
  }
@@ -3175,6 +3443,10 @@ function getASTSignature(node) {
3175
3443
  return `keep{c:${node.count},m:${node.mode},ch:${getASTSignature(
3176
3444
  node.child
3177
3445
  )}}`;
3446
+ case "half":
3447
+ return `half{ch:${getASTSignature(node.child)}}`;
3448
+ case "maxOf":
3449
+ return `maxOf{c:${node.count},ch:${getASTSignature(node.child)}}`;
3178
3450
  case "add": {
3179
3451
  let constantValue = 0;
3180
3452
  const otherChildrenSigs = [];
@@ -3234,7 +3506,12 @@ var AttackBuilder = class _AttackBuilder {
3234
3506
  if (this.hitEffect) {
3235
3507
  effectPart = `(${this.hitEffect.toExpression()})`;
3236
3508
  if (this.critEffect !== null) {
3237
- const crit = this.critEffect ?? this.hitEffect.copy().doubleDice();
3509
+ let crit;
3510
+ if (this.critEffect) {
3511
+ crit = this.critEffect;
3512
+ } else {
3513
+ crit = this.hitEffect?.copy().doubleDice() ?? RollBuilder.fromArgs(0);
3514
+ }
3238
3515
  const critThreshold = this.check.critThreshold;
3239
3516
  if (critThreshold < 15 || critThreshold > 20) {
3240
3517
  throw new Error(
@@ -3544,6 +3821,6 @@ RollBuilder.prototype.dc = function(saveDC) {
3544
3821
  return new DCBuilder(this).dc(saveDC);
3545
3822
  };
3546
3823
 
3547
- export { ACBuilder, DCBuilder, RollBuilder, SaveBuilder, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll };
3824
+ export { ACBuilder, DCBuilder, HalfRollBuilder, MaxOfRollBuilder, RollBuilder, SaveBuilder, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll };
3548
3825
  //# sourceMappingURL=index.js.map
3549
3826
  //# sourceMappingURL=index.js.map