As requested in the comments, here's my optimized versionhere's my optimized version of Magician's code, using the die-relabeling trick instead of simply counting:
\ roll AdX + BdY, target 4, where X is one size less than Y and A+B = 6 \
DICE: {4,6,8,10,12,20}
loop I over {12..(#DICE-1)} {
X: I@DICE(I-1)@DICE
Y: (I+1)@DICEI@DICE
output 6d{0:3,(dX 1:X-3}>= 4) named "6d[X]"
loop B over {1..5} {
A: 6-B
output Ad{0:3,(dX 1:X-3}>= 4) + Bd{0:3,(dY 1:Y-3}>= 4) named "[A]d[X] and [B]d[Y]"
}
}
output 6d{0:3,(dY 1:Y-3}>= 4) named "6d[Y]"
Looking at the averagesthe averages, it's pretty clear that going up from d4 to d6 in the beginning, and from d12 straight to d20 in the end, give the most rapid increases in the success rate, while there's relatively little difference between, say, a d10 and a d12 in the middle of the progression.
For some purposes this kind of a curve, with rapid progress in the beginning that gradually slows down, before speeding up again at the final levels, might be exactly what you want. That said, if you'd prefer something a bit more linear, one way to speed up the middle part of the curve would be to drop the d10drop the d10 from the progression, or, alternatively, to keep the d10 but drop both d8 and d12.drop both d8 and d12.
Ps. What's "the die-relabeling trick", you ask? Well, it turns out that, if you're rolling NdX and counting successes against some target number T, by far the most efficient way to do that in AnyDice is to first create a custom X-sided die with all the sides less than T relabeled as "0" and the rest as "1", and then roll that die N times.
The easiest way to do that is to take a single normal X-sided die and let AnyDice relabel it for you by applying a function or a comparison operator to it.
you should instead do thisthis:
output 6d6d[count {0:34..20} in d20] named "6d20, 1:17}target 4"
or just this:
output 6d(d20 >= 4) named "6d20, target 4"
Here, d{0:3,[count 1:17{4..20} in d20] (or, equivalently, d20 >= 4) is a custom 20-sided die with threethe sides labeled1–3 relabeled as "0" and 17the sides labeled4–20 relabeled as "1",.
The last example above works because AnyDice comparison operators like >= return 1 for "true" and 0 for "false" and because comparing a totaldie with a number applies the comparison to each roll of 20the die, relabeling the sides according to the result.
If the custom die notation above looks too messyIt's also possible to youconstruct such relabeled dice "manually", you can also do the relabelingwithout starting with a helper functionnormal dX at all, e.g. like thisthis:
function: relabel SIDE:n target TARGET:n {
if SIDE < TARGEToutput 6d{ result: 0 }
else { result:3, 1 }
}
RELABELED: [relabel d20 target 4]
output 6dRELABELED17} named "6d20, target 4"
Using the helper function is more verboseHere, but it can also bed{0:3, 1:17} is a lot more readabledie with three sides labeled "0" and 17 sides labeled "1", especially if the relabeling involvesfor a bunchtotal of special cases like autofail on natural 1, autocrit on natural 20, etc. In particular
I used to recommend this method in older versions of this answer, notebut I now feel that the relabeling function can itself return a (relabeled) dieit's uglier and more prone to simulatemistakes than just starting with a rerollnormal die and letting AnyDice relabel it for you.
You can check for yourself that all of these programs produce the same results, but the first one takes up to several seconds to run (and might time out, if the server is busy), while the second one finishesother three finish instantly. And the bigger you make the dice, or the more dice you add to the pool, the more obvious the difference becomes. Trying to calculate, say, 8d20 vs. 4 in the naïve way is pretty much guaranteed to time out, whereas with relabeled dice the code will run pretty much instantly even for, say, 100d20 vs. 4.
The reason for this is that, with the counting methodif you pass a large dice pool to a function like [count ... in ...], AnyDice will waste a lot of time rolling all the possible outcomes of, say, 6d20, and then counting the number of successes in each of them — it's not smart enough to realize that, say, (2, 7, 8, 14, 16, 18) is just as good a roll as (2, 7, 8, 14, 16, 19), and that those cases don't really need to be considered separately. Using a relabeled dice tells AnyDice that all the sides of the die equal to or greater than the target number really are equivalent, and so are all those less than the target. And
Pps. One situation where the "manual" relabeling method can be useful is if you need a more complex relabeling scheme like, better yetsay, eliminating thehaving a natural 20 count [count ... in ...] function call lets AnyDice skip enumerating all the possible results ofas two successes:
output 6d{0:3, 1:16, 2:1} named "6d20, target 4, double success on nat 20"
However, you can also achieve the roll entirelysame result with a custom relabeling function, and instead just use its fast math routines for summing up die rollse.g. like this:
function: relabel SIDE:n target TARGET:n {
if SIDE = 20 { result: 2 }
if SIDE >= TARGET { result: 1 }
result: 0
}
output 6d[relabel d20 target 4] named "6d20, target 4, double success on nat 20"
Of courseUsing a helper function is more verbose, this trickbut it can also be generalized in many ways. For examplea lot more readable, especially if you wantedthe relabeling involves a bunch of special cases like autofail on natural 1, autocrit on natural 20 to count as a double success, you could relabeletc. In particular, note that side of the d20 as "2" instead of "1", i.e. userelabeling function can itself return a d{0:3, 1:16, 2:1} instead of(relabeled) die to simulate a reroll, or even the "empty die" d{0:3, 1:17}), like in to make AnyDice this answer (for Nd10)ignore that particular roll entirely, as if it never happened. For
For even more control over the results (e.g. if certain rolls count as critical successes or fumbles that modify the outcome in some non-additive manner) you can also pass the results of rolling the relabeled dice into a function for further processing, like here. Often it's still useful to start with relabeling a single die, though, since letting AnyDice know that certain sides of the die are equivalent can make it handle large pools of such dice a lot faster.