Okay, I've got a solution in place, and I'll share it here. I think a better solution is probably possible, and I think this might be an actual bug that should be discussed with rAthena devs.
I thought similarly about the "propagation" when I noticed it seemed like the status effect of Defender was indeed being applied properly (had icon), but some of the effects of it were not being applied. I confirmed that when I cast Devotion on a target while I have Defender active (in both cases of it being the first devotion cast, and a recast) the target _will_ get the defender status icon, _will_ walk slow, but _will not_ get the damage reduction and _will not_ get the ASPD reduction. When Defender is recast, the damage reduction and aspd reduction are properly applied (I ran some tests with level 1 Defender for the ASPD check). The fact that some effects are being applied is what makes me think this is bug-level. If it was all or nothing, I could think "expected behaviour".
So, this meant the target is properly being devoted, but is not getting the effects properly. So I dug a little deeper. I tried to find where the effects of Defender are applied.
I think the place that the devotion target loses ASPD is also in status.cpp:
if (sc->getSCE(SC_DEFENDER))
bonus -= sc->getSCE(SC_DEFENDER)->val4 / 10;
Since it doesn't work, maybe val4 probably isn't populated correctly.
There's another ASPD related bit lower:
if(sc->getSCE(SC_DEFENDER))
aspd_rate += sc->getSCE(SC_DEFENDER)->val4;
Also relying on val4.
There's a damage related bit using val2 in battle.cpp:
damage -= damage * tsc->getSCE(SC_DEFENDER)->val2 / 100;
I think the place that the devotion target loses speed is in status.cpp:
if( sc->getSCE(SC_DEFENDER) )
speed = max(speed, 200);
Note it does not rely on val2/val3/val4 from the status and is working as it should be.
But when you cast Devotion, the status_change_start call is setting them to 0 (or in the case of val4, (type2 == SC_REFLECTSHIELD ? 1 : 0) for some reason). val2 is being passed as 0 but it's used as the damage reduction variable, val3 is being passed as 0 which is fine since it's deprecated for defender afaik, and I'm not sure what's happening with val4 but that should be the ASPD reduction factor.
status_change_start(d_bl, bl, type2, 10000, d_sc->getSCE(type2)->val1, 0, 0, (type2 == SC_REFLECTSHIELD ? 1 : 0), skill_get_time(status_db.getSkill(type2),d_sc->getSCE(type2)->val1), SCSTART_NOAVOID);
When you cast Defender, you can see these values being properly passed:
case SC_DEFENDER:
if (!(flag&SCSTART_NOAVOID)) {
val2 = 5 + 15*val1; // Damage reduction
val3 = 0; // Unused, previously speed adjustment
val4 = 250 - 50*val1; // Aspd adjustment
if (sd) {
map_session_data *tsd;
int32 i;
for (i = 0; i < MAX_DEVOTION; i++) { // See if there are devoted characters, and pass the status to them. [Skotlex]
if (sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])))
status_change_start(src,&tsd->bl,type,10000,val1,val2,val3,val4,tick,SCSTART_NOAVOID);
}
}
}
Now, for the solution I've just put in place. It's working, but I think there are probably better solutions. You can revert all of your SC_DEVOTION code from the first post back to it's original state, and just modify the status_change_start call to:
status_change_start(d_bl, bl, type2, 10000, d_sc->getSCE(type2)->val1, (type2 == SC_DEFENDER ? d_sc->getSCE(type2)->val2:0), (type2 == SC_DEFENDER ? d_sc->getSCE(type2)->val3:0), (type2 == SC_REFLECTSHIELD ? 1 : (type2 == SC_DEFENDER ? d_sc->getSCE(type2)->val4:0)), skill_get_time(status_db.getSkill(type2),d_sc->getSCE(type2)->val1), SCSTART_NOAVOID);
From here, I think it would also be good to know how val1/val2/val3/val4 are being used by the other status' to be sure they aren't being broken somehow as well. Since I didn't take the time to understand how SC_AUTOGUARD, SC_REFLECTSHIELD and SC_ENDURE are working, I've just separated the DEFENDER portion from those to fix it separately. It might actually be possible to do something like this, instead:
status_change_start(d_bl, bl, type2, 10000, d_sc->getSCE(type2)->val1, d_sc->getSCE(type2)->val2, d_sc->getSCE(type2)->val3,d_sc->getSCE(type2)->val4, skill_get_time(status_db.getSkill(type2),d_sc->getSCE(type2)->val1), SCSTART_NOAVOID);
(Also please note the last argument I changed to give icons for all the passed status', but you can use
(type2 == SC_DEFENDER) ? SCSTART_NOAVOID : SCSTART_NOAVOID|SCSTART_NOICON
if you'd like).