4
\$\begingroup\$

The game Neon City Overdrive uses the following resolution mechanic for checks:

  1. create a pool of Action Dice and (possibly) another pool of differently-colored Danger Dice (all d6, generally up to 5 or 6 dice in each pool)
  2. roll all the dice
  3. each Danger Die cancels out an Action Die with the same value - both are discarded
  4. the highest remaining Action Die (if there is any) is the result (the precise meaning of which is irrelevant for the purposes of this question)
  5. each extra remaining Action Die showing a 6 (i.e. any second, third etc. "6" beyond the first "6" that is read as the result of the roll) provides one critical success (called a boon)

The basic mechanic has already been addressed in this question but I want to know how a slight variation would change the probabilities. What I'm interested in is seeing how the odds work out if danger dice cancel any action dice that is equal to or lower than instead of just equal to (normally a 5 on a danger dice would only cancel another 5, but here it would cancel out a single 1-5 result instead).

Danger dice would eliminate the highest possible action dice first so a danger dice pool of [6] against an action dice pool of [5, 2] would eliminate the 5 and leave the 2 as the final result.

There is one additional twist I'd like to factor in, and that is if all Action Dice are eliminated (which normally gives a result of 1), the value is reduced for each Additional 6 on a danger dice (basically an inversion of the critical success mechanic for extra 6's). So no Action dice and a danger 6=-1, no action dice and two danger 6's=-2, etc. I'm also curious if the odds would be more symmetrical if all action dice being elimated counts as a 1 or a 0 (given equal sized Action and Danger pools I'd like the odds of rolling a zero to be similar to the odds of getting a 7, -1 similar to 8, etc)

I'd prefer the results in anydice form if possible.

\$\endgroup\$
3
  • \$\begingroup\$ The first two paragraphs seem to be nearly equivalent to this question; in particular Dale M's answer does AnyDice specifically; just return the first surviving die rather than P. \$\endgroup\$ May 25, 2022 at 5:32
  • \$\begingroup\$ The third paragraph is a little ambiguous. Is this the raw number of Danger 6s, or the number of Danger 6s that weren't used in cancellation? If the latter, are the Danger 6s used in cancellation first, or is the cancellation done to maximize the number of surviving Danger 6s while still cancelling all the Action dice? E.g. does Action [6, 1] vs. Danger [6, 6, 5] result in -2, -1, or 0? \$\endgroup\$ May 25, 2022 at 5:40
  • \$\begingroup\$ "[I]f all Action Dice are eliminated (which normally gives a result of 1), the value is reduced for each … 6 on a danger dice … [s]o no Action dice and a danger 6=-1 … ." Does that imply that no remaining actions, and no sixes in the danger pool result in a 0 or a 1 in your version? (I'm pretty sure the cited prior solutions assume 0.) \$\endgroup\$
    – posita
    May 25, 2022 at 15:03

2 Answers 2

2
\$\begingroup\$

I will leave it to others with superior knowledge of AnyDice to propose/adapt a solution for that platform. That being said, I adapted my dyce¹-based solution to this question (mentioned by @HighDiceRoller) to augment my prior response to your cited NCO question to explore a comparison of your mod against the base mechanic. It's showcased in the last cell of that notebook and the implementation is captured in the nco_so_dangerous function in neon_city_overdrive.py. I'm sure the implementation could be optimized, but I focused on readability. I also made several assumptions, including:

  1. When no actions were left, the base result is zero;
  2. The highest available danger dice are used to cancel action dice and do not optimize for retaining "dangerous" sixes; and
  3. Danger dice used to cancel action dice are not available to further work against the result.

In other words, in response to @HighDiceRoller's question above, Action [6, 1] vs. Danger [6, 6, 5] results in 0. (First danger six cancels the action six, second danger six cancels the action one, the remaining danger available to work against the result is five (i.e., not a six).

Even so, your proposed mod appears substantially more dangerous than the base mechanic where the danger pool size meets or exceeds the action pool size. While a matter of taste, I find anydyce's² "burst" graphs are well-suited for visualizing the differences. (Screenshot for select comparisons below. Gray outer rings show the base mechanic. Red inner rings show your dangerous mod.)

You can see a more generalized version in action (and play around with it) in your browser: Try dyce [source]

Note that you might have to scroll down to the section that addresses this particular question. Limitations and caveats mentioned elsewhere apply.


¹ dyce is my Python dice probability library.

² anydyce is my visualization layer for dyce meant as a rough stand-in for AnyDice.


anydyce burst graphs

\$\endgroup\$
3
  • \$\begingroup\$ Some of those graphs look a little odd unless I'm reading them wrong, like 5 action and 0 danger should be skewed heavily towards 6 but it's saying 1 is the most likely result? \$\endgroup\$
    – Naothaniel
    May 25, 2022 at 18:58
  • 1
    \$\begingroup\$ Oh! You're right. nd6 vs. 0d6 should probably be identical for both approaches. I'll look into that…. \$\endgroup\$
    – posita
    May 25, 2022 at 22:01
  • 1
    \$\begingroup\$ I found and fixed my bug and updated the burst plots. The two mechanics are more similar than I originally thought where there is a disparity in pool size, but there is a substantial difference where the size of the danger pool meets or exceeds the size of the action pool (which makes intuitive sense). \$\endgroup\$
    – posita
    May 26, 2022 at 17:19
3
\$\begingroup\$

The basic "brute force" solution given at the top of my answer to the earlier question can be easily adjusted to fit your modification to the NCO mechanic simply by replacing = N with >= N in the if condition on the fifth line:

function: modified neon city overdrive ACTION:s DANGER:s {
  BOON: (ACTION = 6) - (DANGER = 6)
  if BOON > 0 { result: 5 + BOON }
  loop N over {5,4,3,2,1} {
    if (ACTION >= N) > (DANGER >= N) { result: N }
  }
  result: 0
}

output [modified neon city overdrive 5d6 5d6]

The beginning of the function above is identical to the unmodified version: if we roll k > 0 more 6s on the action dice than on the danger dice, then the result will still be k + 5. Since there are no higher numbers on the dice that could cancel out a 6, your modification to the mechanic makes no difference.

If all the 6s on the action dice are cancelled out, we then check whether the number of 5s and higher numbers rolled with the action dice exceeds those rolled with the danger dice. If yes, not all the 5s can be cancelled out, so the result is 5. Otherwise we repeat the same check for 4s and higher, then 3s and higher, etc.

(It's actually not quite obvious that this method gives the correct result. To show that, we basically need to prove a lemma saying that all action dice of value n or above can be cancelled by equal or higher valued danger dice if any only if, for every mn, the number of danger dice of value m or above equals or exceeds the number of action dice of value m or above. Proving this by induction is not very hard, but neither is it completely trivial.)

Note that this function, like the one in my answer to the earlier question, return 0 if all the action dice are eliminated. If you'd prefer the result to be 1 in that case, just change the line result: 0 at the end of the function to result: 1.

You can also implement (one interpretation of) the additional modification you suggest at the end of your question by changing that line to result: 1 + BOON. (Note that BOON is always negative or zero at this point.) That way, if all action dice are cancelled out, any 6 rolled on the danger dice that does not cancel a 6 on the action dice will reduce the result by one.


Another possible interpretation of the "anti-boon" mechanic would be that, if all action dice are cancelled out, the result would be 1 (or 0) minus the number of 6s on the danger dice that are not needed to cancel any action die. This can also be calculated, but requires slightly more work, since we actually have to (effectively) pair the cancelled action dice with the danger dice instead of just relying on the lemma mentioned above. Here's one way to do it:

function: modified neon city overdrive ACTION:s DANGER:s {
  BOON: (ACTION = 6) - (DANGER = 6)
  if BOON > 0 { result: 5 + BOON }
  loop N over {5,4,3,2,1} {
    if (ACTION >= N) > (DANGER >= N) { result: N }
  }
  \ all action dice are cancelled: calculate how many danger 6s we had to use \
  DEBT: 0
  loop N over {1..5} {
    DEBT: [highest of 0 and DEBT + (ACTION = N) - (DANGER = N)]
  }
  result: 1 + BOON + DEBT
}

Here, the DEBT variable keeps track of how many action dice of value up to N can not be cancelled by danger dice of value up to N, and thus must be cancelled by higher-valued danger dice. (The fact that we know in advance that all action dice can be cancelled simplifies this calculation somewhat.) Thus, at the end of the loop, we know that DEBT is the number of danger 6s that are needed to cancel lower-valued action dice.


Addendum: FWIW, the optimized recursive solution given later in the same answer can also be tweaked to fit your modification by adding a CARRY parameter that tells the function how many unused higher-valued danger dice are still available to cancel lower action rolls:

function: mnco helper N:n AMAX:n DMAX:n AROLL:n DROLL:n CARRY:n {
  if AROLL > DROLL + CARRY {
    if N = 6 { result: 5 + AROLL - DROLL }
    result: N
  }
  A: AMAX - AROLL
  D: DMAX - DROLL
  C: CARRY + DROLL - AROLL
  if N = 2 { result: A > D + C }
  X: d(N-1) = N-1
  result: [mnco helper N-1 A D AdX DdX C]
}

function: mnco A:n D:n {
  X: d6 = 6
  result: [mnco helper 6 A D AdX DdX 0]
}

loop A over {5} {
  loop D over {0..5} {
    output [mnco A D] named "action [A]d6, danger [D]d6"
  }
}

In fact, we can even optimize this code to get rid of the explicit CARRY parameter and just add it to the danger dice roll instead:

function: mnco helper N:n AMAX:n DMAX:n AROLL:n DROLL:n {
  if AROLL > DROLL {
    if N = 6 { result: 5 + AROLL - DROLL }
    result: N
  }
  if N = 2 { result: AMAX > DMAX }
  A: AMAX - AROLL
  D: DMAX - DROLL
  C: DROLL - AROLL
  X: d(N-1) = N-1
  result: [mnco helper N-1 A D+C AdX DdX+C]
}

function: mnco A:n D:n {
  X: d6 = 6
  result: [mnco helper 6 A D AdX DdX]
}

loop A over {5} {
  loop D over {0..5} {
    output [mnco A D] named "action [A]d6, danger [D]d6"
  }
}

The early abort check at N = 2 can also be optimized slightly by noting that A - (D + C) always equals AMAX - DMAX. Alas, even with these optimizations, this code still seems to be somewhat slower than the equivalent version for the unmodified Neon City Overdrive mechanic.

Further modifying this recursive code to implement the additional modifications in case all action dice are cancelled is left as an exercise. ;)

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .