@yipe/dice 0.10.0 → 0.12.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 +278 -8
- package/dist/builder/ac.d.ts +44 -1
- package/dist/builder/ac.d.ts.map +1 -1
- package/dist/builder/arguments.d.ts +6 -0
- package/dist/builder/arguments.d.ts.map +1 -0
- package/dist/builder/ast.d.ts +36 -7
- package/dist/builder/ast.d.ts.map +1 -1
- package/dist/builder/attack.d.ts +104 -2
- package/dist/builder/attack.d.ts.map +1 -1
- package/dist/builder/dc.d.ts +13 -0
- package/dist/builder/dc.d.ts.map +1 -1
- package/dist/builder/example.d.ts +11 -15
- package/dist/builder/example.d.ts.map +1 -1
- package/dist/builder/expression.d.ts +72 -0
- package/dist/builder/expression.d.ts.map +1 -0
- package/dist/builder/factory.d.ts +2 -3
- package/dist/builder/factory.d.ts.map +1 -1
- package/dist/builder/index.cjs +3844 -1273
- package/dist/builder/index.cjs.map +1 -1
- package/dist/builder/index.js +3836 -1274
- package/dist/builder/index.js.map +1 -1
- package/dist/builder/nodes.d.ts +8 -1
- package/dist/builder/nodes.d.ts.map +1 -1
- package/dist/builder/prob.d.ts +9 -0
- package/dist/builder/prob.d.ts.map +1 -1
- package/dist/builder/roll.d.ts +157 -19
- package/dist/builder/roll.d.ts.map +1 -1
- package/dist/builder/save.d.ts +6 -1
- package/dist/builder/save.d.ts.map +1 -1
- package/dist/builder/types.d.ts +19 -0
- package/dist/builder/types.d.ts.map +1 -1
- package/dist/common/bounce.d.ts +63 -5
- package/dist/common/bounce.d.ts.map +1 -1
- package/dist/common/lru-cache.d.ts +29 -1
- package/dist/common/lru-cache.d.ts.map +1 -1
- package/dist/common/types.d.ts +33 -0
- package/dist/common/types.d.ts.map +1 -1
- package/dist/index.cjs +1288 -388
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1285 -389
- package/dist/index.js.map +1 -1
- package/dist/parser/dice.d.ts +52 -16
- package/dist/parser/dice.d.ts.map +1 -1
- package/dist/parser/parser.d.ts +1 -5
- package/dist/parser/parser.d.ts.map +1 -1
- package/dist/parser/rollType.d.ts +4 -4
- package/dist/parser/scaleDice.d.ts +14 -0
- package/dist/parser/scaleDice.d.ts.map +1 -0
- package/dist/pmf/mixture.d.ts +17 -3
- package/dist/pmf/mixture.d.ts.map +1 -1
- package/dist/pmf/pmf.d.ts +130 -33
- package/dist/pmf/pmf.d.ts.map +1 -1
- package/dist/pmf/query.d.ts +25 -12
- package/dist/pmf/query.d.ts.map +1 -1
- package/dist/turn/effects.d.ts +114 -0
- package/dist/turn/effects.d.ts.map +1 -0
- package/dist/turn/index.d.ts +3 -1
- package/dist/turn/index.d.ts.map +1 -1
- package/dist/turn/plan.d.ts +115 -12
- package/dist/turn/plan.d.ts.map +1 -1
- package/dist/turn/state.d.ts +22 -8
- package/dist/turn/state.d.ts.map +1 -1
- package/dist/turn/turn.d.ts +162 -26
- package/dist/turn/turn.d.ts.map +1 -1
- package/dist/turn/types.d.ts +180 -21
- package/dist/turn/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { DCBuilder } from "../builder/dc.js";
|
|
2
|
+
import type { GrantSpec, RiderOptions, SubstituteSpec } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* A payload transform a trigger verb accepts, as opposed to damage (a rider).
|
|
5
|
+
* Built only by a named factory such as {@link keepBestDamage}, so the wire kind
|
|
6
|
+
* never appears at a call site. Immutable: every method returns a new value.
|
|
7
|
+
*/
|
|
8
|
+
export interface Transform {
|
|
9
|
+
readonly kind: "transform";
|
|
10
|
+
/**
|
|
11
|
+
* Spend only when the base payload total (dice plus the payload's own flat
|
|
12
|
+
* bonus, excluding separate-damage channels) is below `threshold` in the mode
|
|
13
|
+
* that landed; otherwise hold for the next watched attack. The threshold
|
|
14
|
+
* applies only while a later watched step can still land in the current turn
|
|
15
|
+
* state; when none can, any landing spends, since holding is worth nothing.
|
|
16
|
+
* A single number applies to both modes, which for a crit's doubled dice
|
|
17
|
+
* usually means "hold" — pass `{ hit, crit }` to set each.
|
|
18
|
+
*
|
|
19
|
+
* Compares the payload's own values, so it works on any payload: a builder, a
|
|
20
|
+
* parsed string or a bare `PMF`.
|
|
21
|
+
*/
|
|
22
|
+
ifBelow(threshold: number | {
|
|
23
|
+
hit: number;
|
|
24
|
+
crit: number;
|
|
25
|
+
}): Transform;
|
|
26
|
+
/**
|
|
27
|
+
* Spend by backward induction over the remaining steps. Not supported in this
|
|
28
|
+
* release: throws `TurnSpecError("unsupported-policy")`.
|
|
29
|
+
*/
|
|
30
|
+
optimally(): Transform;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Roll the watched attack's whole base payload again and keep the better total —
|
|
34
|
+
* once per turn. Pass it to `Turn.onFirstHit`:
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* turn([sword, sword]).onFirstHit(keepBestDamage());
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* The transform reads the attack's own damage, so no dice are restated. It never
|
|
41
|
+
* touches `plusSeparateDamage` channels, `every-hit` rider damage, or the miss
|
|
42
|
+
* branch, and a crit transforms the crit payload (the already-doubled dice).
|
|
43
|
+
*
|
|
44
|
+
* **Policy.** The default spends on the first watched attack that lands, whatever
|
|
45
|
+
* it rolled. That is a named policy, not optimal play: a player who sees a high
|
|
46
|
+
* roll holds the reroll for a later attack. The figure is therefore a lower bound.
|
|
47
|
+
* Two `d20+5` attacks against AC 12 dealing `2d6+3` score 15.9849 under this
|
|
48
|
+
* policy against 16.1889 for the optimal hold/use policy (14.7000 without the
|
|
49
|
+
* transform); {@link Transform.ifBelow} with `{ hit: 10, crit: 18 }` reaches the
|
|
50
|
+
* optimum on that turn.
|
|
51
|
+
*
|
|
52
|
+
* **Ties.** When the fresh total ties the original, the original roll is kept. The
|
|
53
|
+
* total is the same either way, but a `dice-match` trigger reading the attack sees
|
|
54
|
+
* the kept dice's match status: a player who would take a tied roll because its
|
|
55
|
+
* dice matched is not modelled.
|
|
56
|
+
*/
|
|
57
|
+
export declare function keepBestDamage(): Transform;
|
|
58
|
+
/** True for a {@link Transform} built by a factory in this module. */
|
|
59
|
+
export declare function isTransform(value: unknown): value is Transform;
|
|
60
|
+
/** The JSON-safe fields a {@link Transform} contributes to a {@link SubstituteSpec}. */
|
|
61
|
+
export declare function substituteFields(transform: Transform): Pick<SubstituteSpec, "substitute" | "policy">;
|
|
62
|
+
/**
|
|
63
|
+
* What a grant does to the attack rolls that read it, before it has a lifetime.
|
|
64
|
+
* Not yet a {@link Grant}: a trigger verb refuses it at compile time, so a
|
|
65
|
+
* forgotten lifetime is a type error rather than a silent default. Immutable.
|
|
66
|
+
*/
|
|
67
|
+
export interface Modifiers {
|
|
68
|
+
/** Adds advantage; repeating a modifier is a no-op. */
|
|
69
|
+
advantage(): Modifiers;
|
|
70
|
+
disadvantage(): Modifiers;
|
|
71
|
+
/** Every landing attack is a crit; a natural 1 still misses. */
|
|
72
|
+
critOnHit(): Modifiers;
|
|
73
|
+
/** Consumed by the next attack roll that reads it, hit or miss. */
|
|
74
|
+
untilNextAttack(): Grant;
|
|
75
|
+
/** Lasts for the rest of the turn. */
|
|
76
|
+
untilEndOfTurn(): Grant;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A modifier with a lifetime: the only thing a trigger verb accepts besides damage
|
|
80
|
+
* and transforms. Pass several in an array to have one outcome (and one save)
|
|
81
|
+
* apply them together. Immutable.
|
|
82
|
+
*/
|
|
83
|
+
export interface Grant {
|
|
84
|
+
readonly kind: "grant";
|
|
85
|
+
/**
|
|
86
|
+
* The attack ids, attack-shaped rider ids or tags that read it. Omitted, every
|
|
87
|
+
* later attack roll does. Naming nothing that comes after the condition's
|
|
88
|
+
* sources is `unknown-id`.
|
|
89
|
+
*/
|
|
90
|
+
to(...targets: readonly string[]): Grant;
|
|
91
|
+
}
|
|
92
|
+
/** Advantage on the attack rolls that read the grant: `advantage().untilNextAttack()`. */
|
|
93
|
+
export declare function advantage(): Modifiers;
|
|
94
|
+
/** Disadvantage on the attack rolls that read the grant; cancels an advantage. */
|
|
95
|
+
export declare function disadvantage(): Modifiers;
|
|
96
|
+
/** Every landing attack that reads the grant is a crit: `critOnHit().untilEndOfTurn()`. */
|
|
97
|
+
export declare function critOnHit(): Modifiers;
|
|
98
|
+
/** True for a {@link Grant} built by a factory in this module. */
|
|
99
|
+
export declare function isGrant(value: unknown): value is Grant;
|
|
100
|
+
/** The JSON-safe form of a {@link Grant}. */
|
|
101
|
+
export declare function grantSpec(grant: Grant): GrantSpec;
|
|
102
|
+
/**
|
|
103
|
+
* Rider options plus what gates a call's grants. The gate applies to grants only:
|
|
104
|
+
* damage passed in the same call lands whatever the save does.
|
|
105
|
+
*/
|
|
106
|
+
export interface ConditionOptions extends RiderOptions {
|
|
107
|
+
/** The target's save; its P(fail) is the chance the grants take. */
|
|
108
|
+
save?: DCBuilder;
|
|
109
|
+
/** In `[0, 1]`; mutually exclusive with `save`. */
|
|
110
|
+
chance?: number;
|
|
111
|
+
/** Grants applied on the other branch: the save succeeded (or `1 - chance`). */
|
|
112
|
+
onSave?: Grant | readonly Grant[];
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=effects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effects.d.ts","sourceRoot":"","sources":["../../src/turn/effects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAoB,cAAc,EAAE,MAAM,SAAS,CAAC;AAGzF;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACtE;;;OAGG;IACH,SAAS,IAAI,SAAS,CAAC;CACxB;AA4BD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,IAAI,SAAS,CAE1C;AAED,sEAAsE;AACtE,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAE9D;AAED,wFAAwF;AACxF,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,SAAS,GACnB,IAAI,CAAC,cAAc,EAAE,YAAY,GAAG,QAAQ,CAAC,CAG/C;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,uDAAuD;IACvD,SAAS,IAAI,SAAS,CAAC;IACvB,YAAY,IAAI,SAAS,CAAC;IAC1B,gEAAgE;IAChE,SAAS,IAAI,SAAS,CAAC;IACvB,mEAAmE;IACnE,eAAe,IAAI,KAAK,CAAC;IACzB,sCAAsC;IACtC,cAAc,IAAI,KAAK,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,EAAE,CAAC,GAAG,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAAC;CAC1C;AAsCD,0FAA0F;AAC1F,wBAAgB,SAAS,IAAI,SAAS,CAErC;AAED,kFAAkF;AAClF,wBAAgB,YAAY,IAAI,SAAS,CAExC;AAED,2FAA2F;AAC3F,wBAAgB,SAAS,IAAI,SAAS,CAErC;AAED,kEAAkE;AAClE,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,CAEtD;AAED,6CAA6C;AAC7C,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAEjD;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,oEAAoE;IACpE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,MAAM,CAAC,EAAE,KAAK,GAAG,SAAS,KAAK,EAAE,CAAC;CACnC"}
|
package/dist/turn/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { bounce, turn, Turn } from "./turn.js";
|
|
2
|
+
export { advantage, critOnHit, disadvantage, keepBestDamage } from "./effects.js";
|
|
3
|
+
export type { ConditionOptions, Grant, Modifiers, Transform } from "./effects.js";
|
|
2
4
|
export * from "./types.js";
|
|
3
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/turn/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/turn/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/turn/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC/E,YAAY,EAAE,gBAAgB,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC/E,cAAc,SAAS,CAAC"}
|
package/dist/turn/plan.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { PMF } from "../pmf/pmf.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { StepOutcome } from "./state.js";
|
|
3
|
+
import type { AttackTriggerOn, Trigger, TurnSpec } from "./types.js";
|
|
4
|
+
/** Which payload a rider used when it fired, or `null` while it has not fired. */
|
|
5
|
+
export type FireMode = "hit" | "crit" | null;
|
|
3
6
|
/**
|
|
4
7
|
* A resolved, validated turn: the ordered steps to walk, plus the trigger
|
|
5
8
|
* groups it tracks. Building this is where every {@link TurnSpecError} is raised,
|
|
@@ -15,30 +18,112 @@ export interface TurnPlan {
|
|
|
15
18
|
attackIds: readonly string[];
|
|
16
19
|
/** Every rider id, in declaration order, including `every-hit` riders. */
|
|
17
20
|
riderIds: readonly string[];
|
|
18
|
-
/**
|
|
19
|
-
|
|
21
|
+
/** Every substitute id, in declaration order. */
|
|
22
|
+
substituteIds: readonly string[];
|
|
23
|
+
/**
|
|
24
|
+
* Ids of the attack-shaped `any-miss` / `first-miss` riders, in declaration
|
|
25
|
+
* order: a reroll continues the attacks it watches, so `Turn`'s chaining
|
|
26
|
+
* methods add these to a later rider's defaulted `of`. A list of several
|
|
27
|
+
* attacks is not attack-shaped, so it is never among them.
|
|
28
|
+
*/
|
|
29
|
+
rerollIds: readonly string[];
|
|
30
|
+
/**
|
|
31
|
+
* Rider or substitute id → its fire slot: the index into the walk's per-state
|
|
32
|
+
* `fired` array. A rider's slot records the mode it fired in; a substitute's
|
|
33
|
+
* records the mode it was spent in. `every-hit` riders have none (see
|
|
34
|
+
* `perHitGroups`); a `first-miss` rider's several steps share one.
|
|
35
|
+
*/
|
|
36
|
+
fireSlots: ReadonlyMap<string, number>;
|
|
37
|
+
slotCount: number;
|
|
20
38
|
/**
|
|
21
39
|
* `every-hit` rider id → the group index whose "something landed" bit answers
|
|
22
40
|
* P(it fired at least once). Such riders are folded into their sources' slices
|
|
23
41
|
* rather than becoming steps, so they have no step index.
|
|
24
42
|
*/
|
|
25
43
|
perHitGroups: ReadonlyMap<string, number>;
|
|
44
|
+
/**
|
|
45
|
+
* The LAST step index (in final walk order) that reads each group, by group
|
|
46
|
+
* index — `steps.length` for a group `perHitGroups` still needs at the final
|
|
47
|
+
* collapse. Once the walk passes a group's last reader, its specific code
|
|
48
|
+
* stops discriminating any future decision, so `Turn.resolve`'s `merge` stops
|
|
49
|
+
* keying on it past that point — the dominant cost fix for a long `dice-match`
|
|
50
|
+
* chain (a group read by exactly one downstream step, the common shape, would
|
|
51
|
+
* otherwise keep splitting states for every step after that single read).
|
|
52
|
+
*/
|
|
53
|
+
groupLastReadStep: readonly number[];
|
|
54
|
+
/** Every condition id, in declaration order. */
|
|
55
|
+
conditionIds: readonly string[];
|
|
56
|
+
/**
|
|
57
|
+
* The LAST step index that reads each flag bit, the twin of `groupLastReadStep`:
|
|
58
|
+
* past it the bit no longer changes any decision, so the walk drops it from the
|
|
59
|
+
* merge key. A turn with no conditions has no bits.
|
|
60
|
+
*/
|
|
61
|
+
flagLastReadStep: readonly number[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* One outcome a step's walk convolves in: which group-state outcome it advances,
|
|
65
|
+
* whether it counts as a dice-match, whether drawing it spends the step's
|
|
66
|
+
* substitute, and the sub-mass PMF itself.
|
|
67
|
+
*/
|
|
68
|
+
export interface Draw {
|
|
69
|
+
outcome: StepOutcome;
|
|
70
|
+
matched: boolean;
|
|
71
|
+
spends: boolean;
|
|
72
|
+
slice: PMF;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The draws of one attack step under one state-dependent context. Masses sum to 1.
|
|
76
|
+
*
|
|
77
|
+
* Hit and crit expand into two draws each when the source is match-sliced (a
|
|
78
|
+
* `dice-match` trigger names it) and into more when a substitute's policy splits
|
|
79
|
+
* them into spend and hold parts; every other step walks the same three draws —
|
|
80
|
+
* hit, crit, miss — so turns without those features pay nothing extra.
|
|
81
|
+
*/
|
|
82
|
+
export type StepVariant = readonly Draw[];
|
|
83
|
+
/**
|
|
84
|
+
* One condition, applied at one of its source steps. When the drawn outcome
|
|
85
|
+
* qualifies, the draw splits by `chance`: `grants` are set on one part, `onSave`
|
|
86
|
+
* on the other.
|
|
87
|
+
*/
|
|
88
|
+
export interface GrantApplication {
|
|
89
|
+
/** Index into {@link TurnPlan.conditionIds}. */
|
|
90
|
+
condition: number;
|
|
91
|
+
on: AttackTriggerOn;
|
|
92
|
+
/** Group whose pre-draw code `first-hit` / `first-miss` reads, else -1. */
|
|
93
|
+
reads: number;
|
|
94
|
+
chance: number;
|
|
95
|
+
/** Flag bits set on the `chance` branch. */
|
|
96
|
+
grants: number;
|
|
97
|
+
/** Flag bits set on the other branch. */
|
|
98
|
+
onSave: number;
|
|
99
|
+
/** Non-zero when the application is skipped once all of these bits are set. */
|
|
100
|
+
inForce: number;
|
|
101
|
+
/** Whether a later step reads one of `grants` — what `fireProbability` counts. */
|
|
102
|
+
effective: boolean;
|
|
26
103
|
}
|
|
104
|
+
/** Whether `outcome`, drawn in a state whose pre-draw group codes are `codes`, applies `app`. */
|
|
105
|
+
export declare function grantApplies(app: GrantApplication, outcome: StepOutcome, codes: readonly number[]): boolean;
|
|
27
106
|
/**
|
|
28
|
-
* One step of the turn.
|
|
29
|
-
* advances the groups listed in `updates`;
|
|
107
|
+
* One step of the turn. A non-empty `variants` ⇒ the step rolls its own attack and
|
|
108
|
+
* advances the groups listed in `updates`; empty ⇒ it is pure damage whose amount
|
|
30
109
|
* depends only on the mode it fires in.
|
|
31
110
|
*/
|
|
32
111
|
export interface Step {
|
|
33
112
|
id: string;
|
|
34
113
|
/** Declared attacks always fire; riders consult their trigger. */
|
|
35
114
|
trigger: Trigger | null;
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Every precomputed draw set for this step; index 0 is the plain one. A step with
|
|
117
|
+
* no state dependence has exactly one. Nothing is transformed inside the walk:
|
|
118
|
+
* {@link Step.select} picks one of these at the draw site.
|
|
119
|
+
*/
|
|
120
|
+
variants: readonly StepVariant[];
|
|
121
|
+
/**
|
|
122
|
+
* Which variant to draw from, given the state's group codes, its fire slots with
|
|
123
|
+
* this step's own firing already recorded, and its flag bits before this step
|
|
124
|
+
* consumes any.
|
|
125
|
+
*/
|
|
126
|
+
select: (codes: readonly number[], fired: readonly FireMode[], flags: number) => number;
|
|
42
127
|
/** Pure-damage payloads, mass 1 each. */
|
|
43
128
|
damage: {
|
|
44
129
|
hit: PMF;
|
|
@@ -48,8 +133,26 @@ export interface Step {
|
|
|
48
133
|
updates: readonly number[];
|
|
49
134
|
/** Group index this step's trigger reads, or -1 for `not-fired` / always-fires. */
|
|
50
135
|
reads: number;
|
|
51
|
-
/**
|
|
136
|
+
/** Fire slot this step writes when it fires, or -1 for a declared attack. */
|
|
137
|
+
slot: number;
|
|
138
|
+
/** For `not-fired`: the fire slot of the rider or substitute being negated. */
|
|
52
139
|
negates: number;
|
|
140
|
+
/** Fire slot a `spends` draw sets — the watching substitute's — or -1. */
|
|
141
|
+
spendSlot: number;
|
|
142
|
+
/** `next-attack` flag bits this step reads, and so clears when it rolls. */
|
|
143
|
+
consumes: number;
|
|
144
|
+
/** Conditions this step's outcome may apply, in declaration order. */
|
|
145
|
+
grants: readonly GrantApplication[];
|
|
53
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Decide whether `step` fires in state `codes`, and in which mode.
|
|
149
|
+
*
|
|
150
|
+
* Every group a trigger reads is fully determined before its own step runs
|
|
151
|
+
* (sources always precede dependents), so a trigger can be evaluated once, at
|
|
152
|
+
* its step, and again at the end for `Turn.fireProbability` — both give the
|
|
153
|
+
* same answer. The walk calls this at every step; a threshold policy's
|
|
154
|
+
* look-ahead calls it for the steps still to come.
|
|
155
|
+
*/
|
|
156
|
+
export declare function fireMode(step: Step, codes: readonly number[], fired: readonly FireMode[]): FireMode;
|
|
54
157
|
export declare function buildPlan(spec: TurnSpec, eps?: number): TurnPlan;
|
|
55
158
|
//# sourceMappingURL=plan.d.ts.map
|
package/dist/turn/plan.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/turn/plan.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/turn/plan.ts"],"names":[],"mappings":"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"}
|
package/dist/turn/state.d.ts
CHANGED
|
@@ -3,21 +3,35 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A "group" is one distinct set of source ids referenced by a trigger. Everything
|
|
5
5
|
* any trigger needs to know about a group is which outcome landed *first*, whether
|
|
6
|
-
* anything crit,
|
|
7
|
-
*
|
|
6
|
+
* anything crit, whether anything missed, whether anything's dice matched, and
|
|
7
|
+
* whether a match came from a crit — so the whole turn state stays small per group.
|
|
8
8
|
*
|
|
9
|
-
* Layout: `first << 2 | anyCrit << 1 | anyMiss
|
|
10
|
-
* {@link FIRST_NONE} / {@link FIRST_HIT} / {@link FIRST_CRIT}.
|
|
11
|
-
* `code >> 2`, `code & CRIT_BIT`, `code & MISS_BIT
|
|
9
|
+
* Layout: `first << 2 | anyCrit << 1 | anyMiss | anyMatch << 4 | anyCritMatch << 5`,
|
|
10
|
+
* where `first` is one of {@link FIRST_NONE} / {@link FIRST_HIT} / {@link FIRST_CRIT}.
|
|
11
|
+
* Readers decode with `code >> 2 & 0b11`, `code & CRIT_BIT`, `code & MISS_BIT`,
|
|
12
|
+
* `code & MATCH_BIT`, `code & CRIT_MATCH_BIT`.
|
|
12
13
|
*/
|
|
13
14
|
export declare const FIRST_NONE = 0;
|
|
14
15
|
export declare const FIRST_HIT = 1;
|
|
15
16
|
export declare const FIRST_CRIT = 2;
|
|
16
17
|
export declare const CRIT_BIT = 2;
|
|
17
18
|
export declare const MISS_BIT = 1;
|
|
18
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Bit 4 — `FIRST_CRIT` (2) shifted left by 2 is `0b1000` (bit 3), so bits 0-3 are
|
|
21
|
+
* occupied by first/crit/miss.
|
|
22
|
+
*/
|
|
23
|
+
export declare const MATCH_BIT = 16;
|
|
24
|
+
/**
|
|
25
|
+
* Bit 5 — set with {@link MATCH_BIT} when the matching landing was a crit, so a
|
|
26
|
+
* damage-shaped `dice-match` rider doubles its dice exactly when a crit's dice
|
|
27
|
+
* matched. Max reachable code is `8|2|1|16|32 = 59`, still < 256, so `turn.ts`'s
|
|
28
|
+
* `String.fromCharCode` per-group state key is unaffected.
|
|
29
|
+
*/
|
|
30
|
+
export declare const CRIT_MATCH_BIT = 32;
|
|
31
|
+
/** A group that has seen nothing yet: no first landing, no crit, no miss, no match. */
|
|
19
32
|
export declare const START_CODE: number;
|
|
20
33
|
export type StepOutcome = "hit" | "crit" | "miss";
|
|
21
|
-
/** Fold one source outcome into a group's state.
|
|
22
|
-
|
|
34
|
+
/** Fold one source outcome into a group's state. `matched` only has an effect on
|
|
35
|
+
* `"hit"`/`"crit"` — a miss rolls no dice, so it can never match. */
|
|
36
|
+
export declare function advance(code: number, outcome: StepOutcome, matched?: boolean): number;
|
|
23
37
|
//# sourceMappingURL=state.d.ts.map
|
package/dist/turn/state.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/turn/state.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/turn/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,eAAO,MAAM,SAAS,IAAI,CAAC;AAC3B,eAAO,MAAM,UAAU,IAAI,CAAC;AAE5B,eAAO,MAAM,QAAQ,IAAI,CAAC;AAC1B,eAAO,MAAM,QAAQ,IAAI,CAAC;AAC1B;;;GAGG;AACH,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B;;;;;GAKG;AACH,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,uFAAuF;AACvF,eAAO,MAAM,UAAU,QAAkB,CAAC;AAE1C,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAElD;qEACqE;AACrE,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,UAAQ,GAAG,MAAM,CAWnF"}
|
package/dist/turn/turn.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { PMF } from "../pmf/pmf.js";
|
|
2
2
|
import { DiceQuery } from "../pmf/query.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ConditionOptions, Grant, Transform } from "./effects.js";
|
|
4
|
+
import type { Attack, AttackOptions, Damage, Rider, RiderDamage, RiderOptions, Source, TurnSpec } from "./types.js";
|
|
5
|
+
/** What a trigger verb accepts: damage, grants, or both in one list. */
|
|
6
|
+
type Effect = RiderDamage | Grant | readonly (Damage | Grant)[];
|
|
7
|
+
/**
|
|
8
|
+
* The number of trigger groups `t` tracks and, per step, the number of distinct
|
|
9
|
+
* walk states after it. Resolves `t` if it has not been resolved yet.
|
|
10
|
+
*/
|
|
11
|
+
export declare function inspectTurn(t: Turn): {
|
|
12
|
+
groupCount: number;
|
|
13
|
+
stateCounts: readonly number[];
|
|
14
|
+
};
|
|
4
15
|
/**
|
|
5
16
|
* A turn of attacks plus conditional damage riders, resolved to one **exact**
|
|
6
17
|
* joint distribution.
|
|
@@ -18,8 +29,7 @@ import type { Attack, Rider, RiderDamage, RiderOptions, Source, TurnSpec } from
|
|
|
18
29
|
*/
|
|
19
30
|
export declare class Turn {
|
|
20
31
|
private readonly eps;
|
|
21
|
-
private readonly
|
|
22
|
-
private readonly riders;
|
|
32
|
+
private readonly state;
|
|
23
33
|
private readonly plan;
|
|
24
34
|
private resolved?;
|
|
25
35
|
private constructor();
|
|
@@ -27,22 +37,30 @@ export declare class Turn {
|
|
|
27
37
|
* Builds a turn from plain data, throwing {@link TurnSpecError} if it is
|
|
28
38
|
* malformed. Use this from a UI, where `error.code` maps to the field state to
|
|
29
39
|
* show.
|
|
40
|
+
*
|
|
41
|
+
* A rider, substitute or condition with no `of` watches every declared attack
|
|
42
|
+
* plus the attack-shaped `any-miss` / `first-miss` rerolls — for a rider, those
|
|
43
|
+
* listed before it — the same sources the chaining spelling snapshots.
|
|
30
44
|
*/
|
|
31
45
|
static from(spec: TurnSpec, eps?: number): Turn;
|
|
46
|
+
private with;
|
|
47
|
+
/** What a chaining call's omitted `of` means: the attacks so far, plus any reroll so far. */
|
|
48
|
+
private defaultOf;
|
|
32
49
|
/**
|
|
33
50
|
* Appends an attack, throwing {@link TurnSpecError} if that makes the turn
|
|
34
|
-
* invalid.
|
|
51
|
+
* invalid. Pass an id (or `{ id, tag }`) to name it in `of`; a `tag` can be
|
|
52
|
+
* shared by several attacks and names all of them at once.
|
|
35
53
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* `of` is added, or the build fails `unknown-id`.
|
|
54
|
+
* Declare attacks before the riders that watch them. A rider added without an
|
|
55
|
+
* `of` watches the attacks declared *so far*, so appending one after it throws
|
|
56
|
+
* `attack-after-rider` rather than silently leaving the new attack out. Give
|
|
57
|
+
* that rider an explicit `of` to pin it to the attacks it already saw.
|
|
41
58
|
*/
|
|
42
|
-
attack(source: Source,
|
|
59
|
+
attack(source: Source, options?: string | AttackOptions): Turn;
|
|
43
60
|
/**
|
|
44
61
|
* Appends `count` copies of the same attack — the Extra Attack case, which is
|
|
45
|
-
* most of 5e. Argument order mirrors `roll(count, die)`.
|
|
62
|
+
* most of 5e. Argument order mirrors `roll(count, die)`. `tag` names every copy
|
|
63
|
+
* at once in a later `of`.
|
|
46
64
|
*
|
|
47
65
|
* ```ts
|
|
48
66
|
* turn().attacks(4, greatsword).onEveryHit(d6); // fighter 20 + hunter's mark
|
|
@@ -50,35 +68,89 @@ export declare class Turn {
|
|
|
50
68
|
*
|
|
51
69
|
* @throws {RangeError} if `count` is not a positive integer.
|
|
52
70
|
*/
|
|
53
|
-
attacks(count: number, source: Source
|
|
71
|
+
attacks(count: number, source: Source, options?: {
|
|
72
|
+
tag?: string;
|
|
73
|
+
}): Turn;
|
|
74
|
+
private refuseAttackAfterRider;
|
|
54
75
|
/**
|
|
55
76
|
* Appends a rider, throwing {@link TurnSpecError} if that makes the turn
|
|
56
77
|
* invalid. The `onX` methods below are the readable way to call this.
|
|
78
|
+
*
|
|
79
|
+
* An omitted `of` is filled in here, not when the plan is built: the attacks
|
|
80
|
+
* declared so far, plus any attack-shaped `any-miss` / `first-miss` rider
|
|
81
|
+
* declared so far. Nothing else joins — not `every-hit` riders, not
|
|
82
|
+
* damage-shaped riders, not a list of several attacks, not `dice-match` beams
|
|
83
|
+
* (name those explicitly).
|
|
57
84
|
*/
|
|
58
85
|
rider(rider: Rider): Turn;
|
|
86
|
+
/**
|
|
87
|
+
* Adds what one trigger-verb call describes: its damage as a rider, its grants as
|
|
88
|
+
* a condition over the same `of`. The gate (`save` / `chance` / `onSave`) applies
|
|
89
|
+
* to the grants only; the damage lands whatever the save does. With both, `id`
|
|
90
|
+
* names the rider and the condition takes the default `condition N`.
|
|
91
|
+
*/
|
|
92
|
+
private addEffect;
|
|
59
93
|
/**
|
|
60
94
|
* Fires once, on the first source that lands, in that source's mode — so a
|
|
61
95
|
* crit on the first landing attack doubles the rider's dice. Sneak Attack.
|
|
96
|
+
*
|
|
97
|
+
* Given a {@link Transform} such as `keepBestDamage()` instead of damage, it
|
|
98
|
+
* adds a once-per-turn substitute: the first watched landing its policy accepts
|
|
99
|
+
* draws the transformed base payload instead. One call covers every watched
|
|
100
|
+
* attack; a second over any of the same attacks is `duplicate-substitute`. A
|
|
101
|
+
* substitute allocates no trigger group. `fireProbability(id)` is P(spent).
|
|
102
|
+
*
|
|
103
|
+
* Given a {@link Grant}, the first landing applies it once — a passed save is
|
|
104
|
+
* final for the turn:
|
|
105
|
+
*
|
|
106
|
+
* ```ts
|
|
107
|
+
* turn([fist, fist, fist]).onFirstHit(advantage().critOnHit().untilEndOfTurn(), { chance: 0.4 });
|
|
108
|
+
* ```
|
|
62
109
|
*/
|
|
63
|
-
onFirstHit(
|
|
110
|
+
onFirstHit(effect: Effect | Transform, options?: ConditionOptions): Turn;
|
|
64
111
|
/**
|
|
65
112
|
* Fires once if any source crit, always in crit mode. Divine Smite: nothing is
|
|
66
113
|
* lost by holding it for a crit, so this is "any", not "first".
|
|
114
|
+
*
|
|
115
|
+
* Given a {@link Grant}, every crit applies it.
|
|
67
116
|
*/
|
|
68
|
-
onAnyCrit(
|
|
117
|
+
onAnyCrit(effect: Effect, options?: ConditionOptions): Turn;
|
|
69
118
|
/**
|
|
70
|
-
* Fires once if any source missed.
|
|
71
|
-
*
|
|
119
|
+
* Fires once if any source missed. A reroll is a fresh attack, so pass one as
|
|
120
|
+
* the damage.
|
|
121
|
+
*
|
|
122
|
+
* Its step runs after every declared attack. That is exact when the reroll is
|
|
123
|
+
* identically distributed to the attacks after the one it replaces; when it is
|
|
124
|
+
* not, or a later rider reads the order things landed in, use
|
|
125
|
+
* {@link Turn.onFirstMiss}. A reroll that reads a granted modifier or applies a
|
|
126
|
+
* condition's grants throws `unsupported-trigger`: use {@link Turn.onFirstMiss}.
|
|
72
127
|
*/
|
|
73
128
|
onAnyMiss(damage: RiderDamage, options?: RiderOptions): Turn;
|
|
129
|
+
/**
|
|
130
|
+
* Fires once, on the first source that misses, and resolves directly after that
|
|
131
|
+
* attack — so a reroll lands in turn order, before the attacks that follow it,
|
|
132
|
+
* and reads the grants in force at that point. The exact model of "when you
|
|
133
|
+
* miss, you may reroll": Kensei's Unerring Accuracy, Lucky.
|
|
134
|
+
*/
|
|
135
|
+
onFirstMiss(damage: RiderDamage, options?: RiderOptions): Turn;
|
|
74
136
|
/**
|
|
75
137
|
* Fires once per source that lands, in that hit's mode — so it can fire several
|
|
76
138
|
* times in a turn. Hunter's Mark, Hex, Rage.
|
|
139
|
+
*
|
|
140
|
+
* Given a {@link Grant}, every landing applies it, and a `save` or `chance` is
|
|
141
|
+
* rolled again on each landing until it takes:
|
|
142
|
+
*
|
|
143
|
+
* ```ts
|
|
144
|
+
* turn([sword, sword, sword]).onEveryHit(advantage().untilNextAttack());
|
|
145
|
+
* turn([axe, axe]).onEveryHit(advantage().untilEndOfTurn(), { save: d20.plus(2).dc(15) });
|
|
146
|
+
* ```
|
|
77
147
|
*/
|
|
78
|
-
onEveryHit(
|
|
148
|
+
onEveryHit(effect: Effect, options?: ConditionOptions): Turn;
|
|
79
149
|
/**
|
|
80
150
|
* Damage for the turns where the rider added just before this one did *not*
|
|
81
|
-
* fire: "flurry of blows if I didn't smite".
|
|
151
|
+
* fire: "flurry of blows if I didn't smite". After a transform, it fires when
|
|
152
|
+
* the transform was never spent — under the default policy, when no watched
|
|
153
|
+
* attack landed.
|
|
82
154
|
*
|
|
83
155
|
* ```ts
|
|
84
156
|
* turn([dagger, dagger])
|
|
@@ -87,13 +159,38 @@ export declare class Turn {
|
|
|
87
159
|
* ```
|
|
88
160
|
*
|
|
89
161
|
* Always binds to the *immediately* preceding rider, so the two are branches of
|
|
90
|
-
* one decision and can never both land.
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* triggers against the right one.
|
|
162
|
+
* one decision and can never both land. Chaining it therefore alternates rather
|
|
163
|
+
* than laddering: `a.otherwise(b).otherwise(c)` makes `c` fire whenever `b` did
|
|
164
|
+
* not, which is exactly when `a` did. For a genuine three-way priority chain,
|
|
165
|
+
* name the riders and use explicit `not-fired` triggers against the right one.
|
|
95
166
|
*/
|
|
96
167
|
otherwise(damage: RiderDamage, options?: Omit<RiderOptions, "of">): Turn;
|
|
168
|
+
/**
|
|
169
|
+
* Fires once if any of `of`'s named sources' own damage dice matched (showed a
|
|
170
|
+
* duplicate value) on hit or crit — Chromatic Orb's bounce. Unlike the other
|
|
171
|
+
* `onX` triggers, `of` is required: "the dice matched" has no coherent meaning
|
|
172
|
+
* defaulted across every declared attack. Each named source must expose a
|
|
173
|
+
* dice-match descriptor (an `AttackBuilder`-shaped source does); naming one
|
|
174
|
+
* that doesn't is a `TurnSpecError("no-dice-descriptor", ...)`.
|
|
175
|
+
*
|
|
176
|
+
* Most callers want {@link bounce} instead of calling this directly — it
|
|
177
|
+
* builds the whole depth-capped chain of attack-shaped riders.
|
|
178
|
+
*/
|
|
179
|
+
onDiceMatch(of: readonly string[], damage: RiderDamage, options?: Omit<RiderOptions, "of">): Turn;
|
|
180
|
+
/**
|
|
181
|
+
* The same turn against a target with armor class `ac`: every attack with an
|
|
182
|
+
* AC — declared, or carried by a rider (a reroll, a bonus attack) — is rebuilt
|
|
183
|
+
* through `withCheck`. Saves, attacks with no AC to rebind, and bare PMFs pass
|
|
184
|
+
* through unchanged, so a mixed attack/save turn still sweeps.
|
|
185
|
+
*
|
|
186
|
+
* ```ts
|
|
187
|
+
* const base = turn([sword, sword]).onFirstHit(roll(3, d6));
|
|
188
|
+
* [12, 14, 16, 18].map((ac) => base.vsAC(ac).mean());
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
* @throws {TurnSpecError} `no-rebindable-source` if nothing in the turn has an AC.
|
|
192
|
+
*/
|
|
193
|
+
vsAC(ac: number): Turn;
|
|
97
194
|
/**
|
|
98
195
|
* The exact joint distribution: mass 1, outcome-labelled. Resolved once and
|
|
99
196
|
* cached.
|
|
@@ -125,12 +222,27 @@ export declare class Turn {
|
|
|
125
222
|
* defaults. These are the names {@link Turn.fireProbability} accepts.
|
|
126
223
|
*/
|
|
127
224
|
get riderIds(): readonly string[];
|
|
225
|
+
/**
|
|
226
|
+
* Substitute ids in declaration order, including the `substitute 1`, … defaults.
|
|
227
|
+
* {@link Turn.fireProbability} accepts these too.
|
|
228
|
+
*/
|
|
229
|
+
get substituteIds(): readonly string[];
|
|
230
|
+
/**
|
|
231
|
+
* Condition ids in declaration order, including the `condition 1`, … defaults.
|
|
232
|
+
* {@link Turn.fireProbability} accepts these too.
|
|
233
|
+
*/
|
|
234
|
+
get conditionIds(): readonly string[];
|
|
128
235
|
/**
|
|
129
236
|
* P(this rider fired). For an `every-hit` rider it is P(at least one source
|
|
130
|
-
* hit), since that rider can fire more than once in a turn.
|
|
237
|
+
* hit), since that rider can fire more than once in a turn. For a substitute it
|
|
238
|
+
* is P(it was spent). For a condition it is P(its grants were applied at least
|
|
239
|
+
* once where a later attack roll reads them) — on the fail branch of its save,
|
|
240
|
+
* not the `onSave` one. Two attacks with "a hit gives the next attack
|
|
241
|
+
* advantage" give P(attack 1 landed), since nothing reads attack 2's grant.
|
|
131
242
|
*
|
|
132
|
-
* @throws {TurnSpecError} `unknown-id` if `id` is not a rider
|
|
133
|
-
* included, since attacks always happen and have no
|
|
243
|
+
* @throws {TurnSpecError} `unknown-id` if `id` is not a rider, substitute or
|
|
244
|
+
* condition — attack ids included, since attacks always happen and have no
|
|
245
|
+
* firing probability.
|
|
134
246
|
*/
|
|
135
247
|
fireProbability(id: string): number;
|
|
136
248
|
private resolve;
|
|
@@ -146,4 +258,28 @@ export declare class Turn {
|
|
|
146
258
|
* ```
|
|
147
259
|
*/
|
|
148
260
|
export declare function turn(attacks?: Attack | readonly Attack[], eps?: number): Turn;
|
|
261
|
+
/**
|
|
262
|
+
* `bounce({ source, max })` — sugar for a depth-capped chain of attack-shaped
|
|
263
|
+
* `dice-match` riders, so no caller hand-writes the chain by hand: beam 1 is
|
|
264
|
+
* `source` itself as the turn's only declared attack; beam `i + 1` (for
|
|
265
|
+
* `i = 1..max`) is `source` again, as a rider fired only when beam `i`'s own
|
|
266
|
+
* dice matched. Every beam shares `source`'s exact profile (same to-hit, same
|
|
267
|
+
* damage dice) — the natural reading of "the orb leaps to a new target, same
|
|
268
|
+
* damage profile, and repeats."
|
|
269
|
+
*
|
|
270
|
+
* `max` is required and MUST be a non-negative integer: match probability alone
|
|
271
|
+
* does not terminate the recursion (Chromatic Orb bounces are not literally
|
|
272
|
+
* unbounded — DMs cap them by fiat or table size — and something has to).
|
|
273
|
+
*
|
|
274
|
+
* Each beam after the first is its own {@link MAX_TRIGGER_GROUPS}-counted trigger
|
|
275
|
+
* group (`of: [<previous beam's id>]` is a distinct source set every time), so
|
|
276
|
+
* `max` is effectively capped at the turn's remaining group budget — a chain
|
|
277
|
+
* asking for more throws `TurnSpecError("too-many-groups", ...)` the same way
|
|
278
|
+
* any other over-budget turn would, not a silent truncation.
|
|
279
|
+
*/
|
|
280
|
+
export declare function bounce({ source, max }: {
|
|
281
|
+
source: Source;
|
|
282
|
+
max: number;
|
|
283
|
+
}): Turn;
|
|
284
|
+
export {};
|
|
149
285
|
//# sourceMappingURL=turn.d.ts.map
|
package/dist/turn/turn.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turn.d.ts","sourceRoot":"","sources":["../../src/turn/turn.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"turn.d.ts","sourceRoot":"","sources":["../../src/turn/turn.ts"],"names":[],"mappings":"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"}
|