Repository navigation
Description
What you were modeling
D&D 5.2 (5e-srd-5.2) Cunning Strike, adopted as attack parameters in #249: cunningStrike (and cunningStrike2 from Improved Cunning Strike) each pick an effect that costs Sneak Attack dice (Poison, Trip, Withdraw 1; Daze 2; Obscure 3; Knock Out 6). The pair is used only when the rogue can use both and their cost fits in the Sneak Attack; then the dice come off the Sneak Attack and the target saves at the rogue's DC (content/features/rogue/cunning-strike.yaml, rules/class-rogue.yaml).
What's missing
- A value worked out from a run's parameters. The cost of the chosen pair depends on
params.cunningStrikeandparams.cunningStrike2. Fields can't readparams, and an action has no named value derived from its parameters, so every formula that needs the cost restates it: the rules'sneakAttacksituational effect (twice: the fit test and the dice removed) and the two strikeactioneffects'when(once each). That is the same nested ternary four times, in two files. - Effects reading computed values. The feature declares
computed: { strikeDc }for its card, but itsop: actioneffects can't readself.strikeDc(unknown-nameat load: content.md, Computed values lists effects among the places that can't). So the DC is a field (rogue.strikeDc) set by an effect, and the computed value only mirrors it.
Workaround
The cost ternary is repeated in the rules' sneakAttack effect and both strike effects; a fix to the costs has to touch all four. The DC lives in the rogue.strikeDc field. A Trip against a Huge or larger target still spends its die, because the cost can't see the target either (README).
Options
- (recommended) Derived parameters: a parameter (an action's own or one a
paramseffect adds) may declareformulainstead of being given. It is evaluated once every given parameter has its value, before bounds, in declaration order (reading those before it), and read asparams.<name>everywhere params are: Cunning Strike addscunningStrikeCostand the rules' effect and the strike effects read it. The app never passes it (invalid-paramif it does); describeAction lists it as derived. In aneachstep it could also seeparticipant, which would cover the Trip size check. - Let effects that run inside an action (roll,
action,changes,paramsbounds) read the content's computed values asself.<name>, with computed values seeing the running action'sparamsthere. CoversstrikeDc, but not the rules'sneakAttackeffect, which belongs to no content. - Leave it: a lookup in a map field (
rogue.strikeCost[params.cunningStrike]) shortens each copy, but the fit test and the pair sum stay repeated.
Option 1 and the second half of option 2 are independent; the computed-value read is small on its own.
Activity
5e-srd-5.2 README: spec 0.4, section order, strikeDc field, link #251 #…
Pack READMEs: spec 0.4, open-issue links, app-facing ids, one section…
Spec: derived parameters, and computed values in action-time effects (#…
Derived parameters: the build reports an action step giving one, and …
Spec: derived parameters, computed values in action-time effects, bou…
mpigsley commentedon Nov 2, 2026
Resolved in #262.
Packs: derived Cunning Strike cost and Healing Grace's bound on spend (…