Page 1 of 1

Spell success breakpoints

PostPosted: Tuesday, 11th April 2017, 20:46
by duvessa
As you may or may not know, spell success only changes at intervals of 0.5 effective spell skill. For example, if your Spellcasting skill is an even number, then having 0.9 Necromancy is exactly the same as having only 0.5 Necromancy for the purposes of Animate Skeleton success. If you have 11 Spellcasting, and 11 Ice Magic, having 12.3 Necromancy is the same as having only 11.5 necromancy for the purposes of Simulacrum success.
This affects my skill training quite a bit, especially at higher levels since skills are very expensive and the differences between breakpoints become quite large. It's also something that very few players are even aware of.
This happens because of the very imprecise calculation of spell power. All the relevant code is in spl-cast.cc:
  Code:
/**
 * Calculate the player's failure rate with the given spell, including all
 * modifiers. (Armour, mutations, statuses effects, etc.)
 *
 * @param spell     The spell in question.
 * @return          A failure rate. This is *not* a percentage - for a human-
 *                  readable version, call _get_true_fail_rate().
 */
int raw_spell_fail(spell_type spell)
{
    int chance = 60;

    // Don't cap power for failure rate purposes.
    chance -= 6 * calc_spell_power(spell, false, true, false);
    chance -= (you.intel() * 2);

    const int armour_shield_penalty = player_armour_shield_spell_penalty();
    dprf("Armour+Shield spell failure penalty: %d", armour_shield_penalty);
    chance += armour_shield_penalty;

    static const int difficulty_by_level[] =
    {
        0,
        3,
        15,
        35,

        70,
        100,
        150,

        200,
        260,
        330,
    };
    const int spell_level = spell_difficulty(spell);
    ASSERT_RANGE(spell_level, 0, (int) ARRAYSZ(difficulty_by_level));
    chance += difficulty_by_level[spell_level];

    int chance2 = chance;

    const int chance_breaks[][2] =
    {
        {45, 45}, {42, 43}, {38, 41}, {35, 40}, {32, 38}, {28, 36},
        {22, 34}, {16, 32}, {10, 30}, {2, 28}, {-7, 26}, {-12, 24},
        {-18, 22}, {-24, 20}, {-30, 18}, {-38, 16}, {-46, 14},
        {-60, 12}, {-80, 10}, {-100, 8}, {-120, 6}, {-140, 4},
        {-160, 2}, {-180, 0}
    };

    for (const int (&cbrk)[2] : chance_breaks)
        if (chance < cbrk[0])
            chance2 = cbrk[1];
    chance2 += get_form()->spellcasting_penalty;

    chance2 -= 2 * you.get_mutation_level(MUT_SUBDUED_MAGIC);
    chance2 += 4 * you.get_mutation_level(MUT_WILD_MAGIC);
    chance2 += 4 * you.get_mutation_level(MUT_ANTI_WIZARDRY);

    if (you.props.exists(SAP_MAGIC_KEY))
        chance2 += you.props[SAP_MAGIC_KEY].get_int() * 12;

    chance2 += you.duration[DUR_VERTIGO] ? 7 : 0;

    // Apply the effects of Vehumet and items of wizardry.
    chance2 = _apply_spellcasting_success_boosts(spell, chance2);

    if (chance2 > 100)
        chance2 = 100;

    return chance2;
}

int stepdown_spellpower(int power)
{
    return stepdown_value(power / 100, 50, 50, 150, 200);
}

int calc_spell_power(spell_type spell, bool apply_intel, bool fail_rate_check,
                     bool cap_power)
{
    int power = 0;

    const spschools_type disciplines = get_spell_disciplines(spell);

    int skillcount = count_bits(disciplines);
    if (skillcount)
    {
        for (const auto bit : spschools_type::range())
            if (disciplines & bit)
                power += you.skill(spell_type2skill(bit), 200);
        power /= skillcount;
    }

    power += you.skill(SK_SPELLCASTING, 50);
    // Brilliance boosts spell power a bit (equivalent to three
    // spell school levels).
    if (!fail_rate_check && you.duration[DUR_BRILLIANCE])
        power += 600;

    if (apply_intel)
        power = (power * you.intel()) / 10;

    // [dshaligram] Enhancers don't affect fail rates any more, only spell
    // power. Note that this does not affect Vehumet's boost in castability.
    if (!fail_rate_check)
        power = apply_enhancement(power, _spell_enhancement(spell));

    // Wild magic boosts spell power but decreases success rate.
    if (!fail_rate_check)
    {
        power *= (10 + 3 * you.get_mutation_level(MUT_WILD_MAGIC));
        power /= (10 + 3 * you.get_mutation_level(MUT_SUBDUED_MAGIC));
    }

    // Augmentation boosts spell power at high HP.
    if (!fail_rate_check)
    {
        power *= 10 + 4 * augmentation_amount();
        power /= 10;
    }

    // Each level of horror reduces spellpower by 10%
    if (you.duration[DUR_HORROR] && !fail_rate_check)
    {
        power *= 10;
        power /= 10 + (you.props[HORROR_PENALTY_KEY].get_int() * 3) / 2;
    }

    power = stepdown_spellpower(power);

    const int cap = spell_power_cap(spell);
    if (cap > 0 && cap_power)
        power = min(power, cap);

    return power;
}
The main culprits are the terrible resolution of raw_spell_fail and the integer division by 100 in stepdown_spellpower. The latter is what results in skills only applying to spell success in intervals of 0.5 effective level. (For breakpoints in spell power itself, the effect of intelligence etc. makes it more complicated; I just want to talk about spell success for now.)

chance_breaks then introduces additional breakpoints. There is a very close cubic fit for the points in chance_breaks:
Image
Polynomial fit calculator that you can conveniently paste this right into:
  Code:
{45, 45}, {42, 43}, {38, 41}, {35, 40}, {32, 38}, {28, 36},
        {22, 34}, {16, 32}, {10, 30}, {2, 28}, {-7, 26}, {-12, 24},
        {-18, 22}, {-24, 20}, {-30, 18}, {-38, 16}, {-46, 14},
        {-60, 12}, {-80, 10}, {-100, 8}, {-120, 6}, {-140, 4},
        {-160, 2}, {-180, 0}
if you want to experiment.

I have tried a local branch with raw_spell_fail using floating point while computing the failure rate (only casting to an int at the end), using a separate power stepdown without the integer division, and with chance_breaks replaced with
  Code:
chance2 = 28.0+chance2*0.32+chance2*chance2*0.0016+chance2*chance2*chance2*0.0000038.
(larger constant than the "best" fit to avoid buffing general spell success).
Success rates are always very close to current ones, but with skill breakpoints gone. One great effect of getting rid of chance_breaks is that spells transition from 100% to 99% to 98% etc. etc. earlier, instead of staying at 100% for ages and then quickly falling. A 99% fail spell is still useless but it makes it much easier for unspoiled players to tell how close they are to getting it castable, compared to seeing both level 5 and level 9 spells as 100% fail.

I'm very pleased with the outcome and I think mainline Crawl should do something similar to get rid of these breakpoints. With the chance_breaks replacement I used, level 9 spells are slightly easier to cast than before but that can easily be changed; a nice thing about using an actual formula instead of a nonsense lookup table is that you can easily adjust the coefficients.

Re: Spell success breakpoints

PostPosted: Tuesday, 11th April 2017, 20:49
by milski
Why are all of Crawl's formulas batfuck crazy nonsense, anyway? Stuff like Airstrike damage is just insane.

Re: Spell success breakpoints

PostPosted: Wednesday, 12th April 2017, 00:05
by tabstorm
milski wrote:Why are all of Crawl's formulas batfuck crazy nonsense, anyway? Stuff like Airstrike damage is just insane.

probably no reason in particular, just adding various hacks like multipliers, floors, additional die etc. to get the desired damage output by whoever was writing the spell

Re: Spell success breakpoints

PostPosted: Wednesday, 12th April 2017, 00:24
by tasonir
Is this related to/will this fix things like it being impossible to have a spell at 5% fail? I always thought it was odd that it jumps from 6 to 4% fail. Regardless of that part, overall a smoother curve would be wonderful. I didn't realize it only counted .5 skill levels. And I think being able to see spells coming down to 99/98% sooner is helpful. I check my spell success rate quite often as I gain exp if I'm currently training for a new spell.

Re: Spell success breakpoints

PostPosted: Wednesday, 12th April 2017, 00:46
by duvessa
tasonir wrote:Is this related to/will this fix things like it being impossible to have a spell at 5% fail? I always thought it was odd that it jumps from 6 to 4% fail.
Yes, that's chance_breaks's doing and would be fixed by changing to a smooth formula.

Re: Spell success breakpoints

PostPosted: Wednesday, 12th April 2017, 04:07
by njvack
duvessa wrote:If you have 11 Spellcasting, and 11 Ice Magic, having 12.3 Necromancy is the same as having only 11.5 necromancy for the purposes of Simulacrum success.

Wow. I have been playing Crawl for yonks and I didn't know this was the case; I just knew spell success seemed "kinda jumpy." There's a bunch of XP between 11.5 necro and 12.3 necro.

Re: Spell success breakpoints

PostPosted: Wednesday, 12th April 2017, 14:39
by watertreatmentRL
njvack wrote:
duvessa wrote:If you have 11 Spellcasting, and 11 Ice Magic, having 12.3 Necromancy is the same as having only 11.5 necromancy for the purposes of Simulacrum success.

Wow. I have been playing Crawl for yonks and I didn't know this was the case; I just knew spell success seemed "kinda jumpy." There's a bunch of XP between 11.5 necro and 12.3 necro.


It's an important deficiency too. The way people get a feel for what they're supposed to do in a game is some variation of gradient descent, so having an apparently continuous parameter depend in a non-smooth, especially "piecewise constant," way on other continuous parameters the player can control hurts discoverability pretty severely.

Re: Spell success breakpoints

PostPosted: Wednesday, 12th April 2017, 15:01
by bel
The OP is pretty good, but it would have been nice if the axes in the graph had some labels and/or values.

Re: Spell success breakpoints

PostPosted: Wednesday, 12th April 2017, 15:44
by Factorialite
I also wish there were labelled axes on the graph, but I get the main thrust of what duvessa is saying. It seems absolutely crazy that the system works the way it does.

Re: Spell success breakpoints

PostPosted: Wednesday, 26th April 2017, 08:41
by tasonir
After all my years of crawl, it finally happened.

Image

Thanks duvessa!