Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reactive Armor Hardener resistance calculation incorrect #680

Closed
MrNukealizer opened this issue Jul 14, 2016 · 37 comments
Closed

Reactive Armor Hardener resistance calculation incorrect #680

MrNukealizer opened this issue Jul 14, 2016 · 37 comments
Labels
discussion This is mostly a discussion thread

Comments

@MrNukealizer
Copy link
Contributor

MrNukealizer commented Jul 14, 2016

In pyfa the Reactive Armor Hardener always sets its resistance profile equal to the incoming damage profile. That's not how it works in game though.
In game the RAH adjusts to match the ship's overall resistances to the incoming damage, rather than matching its own resistances. What this means is that when taking 50% EM and 50% Thermal damage in a ship with 80% EM resistance and 50% Thermal resistance, the RAH will converge on 0% EM and 60% Thermal, not 30% EM and 30% Thermal. Another example: when taking 25%/25%/25%/25% damage in a ship with resists of 60%/50%/40%/30% the RAH will converge on 0%/5.8%/21.5%/32.7%, not 25%/25%/25%/25%.

There are certain case with one or two attackers doing mixed damage where the RAH will shift to 30%/30% or 20%/20%/20%, but it's not possible to figure out whether that will happen based solely on a damage profile, so that can be disregarded.

I would fix the calculation and put in a pull request, but I'm not familiar enough with pyfa's codebase to make it work. The calculation would need to know the ship's resistances with the effects of all other modules except Damage Control, and I'm not sure how to get that.

@blitzmann
Copy link
Collaborator

blitzmann commented Jul 14, 2016

I know that the RAH calculation is not supposed to be completely accurate due to some limitations and the simple fact that you cannot completely simulate a combat situation. It's supposed to represent a rough idea of what to expect. However, these numbers do seem to be off.

That being said, I know almost nothing on the RAH mechanics. @Ebag333 Thoughts?

@Ebag333
Copy link
Contributor

Ebag333 commented Jul 14, 2016

In game the RAH adjusts to match the ship's overall resistances to the incoming damage, rather than matching its own resistances. What this means is that when taking 50% EM and 50% Thermal damage in a ship with 80% EM resistance and 50% Thermal resistance, the RAH will converge on 0% EM and 60% Thermal, not 30% EM and 30% Thermal. Another example: when taking 25%/25%/25%/25% damage in a ship with resists of 60%/50%/40%/30% the RAH will converge on 0%/5.8%/21.5%/32.7%, not 25%/25%/25%/25%.

I'm 99% sure that this is not true.

I've done tests with mixed damage types, and always found it adjusted to match the damage type not based of my base resists. Now, granted, all my fits tend to have fairly uniform (though not exactly perfect) resist profiles, but I'll make a point of testing this tonight.

If this ends up being true, we should be able to do this inside the effects file (where we're already running the calcs). It'll be pretty fugly and a lot more complex, but doable.

Also, if you're running tests and comparing your in game resists VS Pyfa resists, there is a few percent difference between the two sometimes. I suspect it's a rounding issue (Pyfa or EVE's rounding issue, who knows), but it also could simply be that the in game client is displaying it incorrectly (there's plenty of that with DPS and cap numbers, for example). So that can throw off expected results between the two.

This makes it more difficult to troubleshoot issues like these, but I can setup a scenario where I'm getting shot at with 50/50 ammo (probably Void) and have either Therm or Kin with much higher resists than the other. I'll try and do that tonight.

Another thing is that I've never seen the RAH in game adjust to sub 1% values. In fact, it seems to only adjust in 3% increments (probably to speed up how fast it adjusts, as it adjusts 3% per cycle). It also doesn't adjust exactly, for example when getting hit with antimatter (which is 41.7% therm and 58.3% kin) , Pyfa will show 25% Therm and 35% Kin, but the RAH (in game) will adjust to 30% Therm and 30% Kin...even though with 3% steps it should be 27% Therm and 33% Kin or even 24% Therm and 36% Kin. So CCP doesn't even follow their own rules some of the time.

After all those numbers I need a drink.

So, between:

  1. Rounding
  2. 3% steps
  3. The RAH flattening off and not adjusting exactly (via some undetermined logic)

I could see plenty of scenarios where it looks like it's adjusting based off base resists...but it's really not.

@MrNukealizer If you can give me the following:

  1. link a fit
  2. List the ammo being shot at it
  3. The game resists that the RAH actually ends up with
    I can more easily replicate the scenario and figure out what is happening.

@blitzmann (Just so you don't think you can dump this all on me!)

I suspect that there's some logic going on that takes the incoming damage profile and adjusts it to some sort of stepped values. For example antimatter is 41.7% therm and 58.3% kin, but the RAH rounds it to 50/50. Can you think of a fairly straight forward way to do this? I don't think we can replicate CCP's formula exactly, but maybe if we did a rounding to the nearest quarter? So AM becomes 50/50, while some (purely theoretical) damage profile of 29/61 becomes 25/75. I think that if we could implement this that we'd end up with much closer to in game behavior. I might start up a separate issue to track this.

OTOH, I have seen things like the RAH being set to 3% therm, 57% kin, so this isn't universal. Grrr CCP.

@MrNukealizer
Copy link
Contributor Author

MrNukealizer commented Jul 14, 2016

There are certain case with one or two attackers doing mixed damage where the RAH will shift to 30%/30% or 20%/20%/20%, but it's not possible to figure out whether that will happen based solely on a damage profile, so that can be disregarded.

@Ebag333 It seems like most of your testing was under conditions that cause the above effect. I've found that when taking split damage from only 1 type of weapon, it will go with the even split and disregard the resistance profile.

At some number of damage sources or types the RAH instead keeps track of the damage taken during the last cycle and adjusts resistances from whatever type(s) of damage you took less of into the type(s) that did more damage. That's why it often fluctuates between cycles: because it often slightly overreacts, and the damage taken during the last cycle varies depending on cycle time of the weapons shooting you.

Another thing is that I've never seen the RAH in game adjust to sub 1% values. In fact, it seems to only adjust in 3% increments (probably to speed up how fast it adjusts, as it adjusts 3% per cycle).

The game doesn't show sub 1% resists, but you can tell it does by the rounding errors. The resistances shown can add up to 60%, 61%, or very rarely 59%. It adjusts resistances by up to 3%, but it can do less. Normally the overcompensation and variance in incoming damage for a cycle are enough to cause the full 3% change, but not always.
rah

It also doesn't adjust exactly, for example when getting hit with antimatter (which is 41.7% therm and 58.3% kin) , Pyfa will show 25% Therm and 35% Kin, but the RAH (in game) will adjust to 30% Therm and 30% Kin...even though with 3% steps it should be 27% Therm and 33% Kin or even 24% Therm and 36% Kin. So CCP doesn't even follow their own rules some of the time.

That sounds like a situation of one damage source doing split damage, causing the RAH to go to the preset 30%/30% or 20%/20%/20%. If you use drones too, it should correct the resistances. For example, a Cerberus with ungrouped launchers can easily force the RAH to correct itself:
2016 07 14 22 22 29

@MrNukealizer
Copy link
Contributor Author

MrNukealizer commented Jul 14, 2016

Hmm, on second thought, the rules for determining whether the RAH accurately compensates or does 60%, 30%/30% or 20%/20%/20% seem a bit more complicated than they did at first glance. When taking all 4 types of damage or being attacked by numerous enemies, the RAH responds appropriately, but it seems to bug out when only taking two types of damage.

For example, I just shot that ship with a Cerb using half thermal, half explosive, and the RAH went to 30%/30%. Then I put out Warriors and it stayed the same. When the missiles had to reload, the RAH went to 60% explosive because only Warriors were shooting. Then when I started using missiles again, it stayed at 60% explosive and completely ignored the thermal damage.

This will definitely take more testing to determine where it draws the line between using the simple algorithm and accurately balancing out resistances.

Edit: It seems to reliably use the accurate calculation when taking all 4 types of damage or when being attacked by 5+ rats doing 2 types. More tests are needed to see the threshold with 3 damage types and how it responds to player ships and drones. The point remains though, you can't tell which mode will be used solely based on the pyfa damage profile when it has 2 or 3 types. Perhaps it would be ideal to have the option to choose between 30%/30% or 20%/20%/20% and the calculated pattern when the damage profile has 2-3 types.

@Ebag333
Copy link
Contributor

Ebag333 commented Jul 15, 2016

Okay, so did some testing.

Test 1
Resists (with RAH off): 74% EM, 88% Therm, 70% Kin, 84% Expl
Incoming Damage Profile: Void (0/50/50/0)
Final RAH: 0/30/30/0

Test 2
Resists (with RAH off): 74% EM, 88% Therm, 70% Kin, 84% Expl
Incoming Damage Profile: Depleted Uranium (0/37.5/25/37.5)
Final RAH: 0/0/39/21

What's interesting about this one is it went initially to 0/0/30/30, THEN switched to the final listed above. What's especially interesting is that it didn't pick the two strongest resists.

After the RAH adjusted, the person shooting at me put 5 hobs on me. Even though they were doing very little damage, the RAH wanted to adjust mostly to therm damage. So clearly the way to beat a RAH is to use a high volley ship, with 5 light drones.

However, I have seen the RAH get "stuck" where it stops adjusting (until you stop and restart it).

So what does all this mean?

  1. RAH wants to adjust to even numbers if it's close. We could code that in to account for stuff like antimatter, but the EHP and HP/s difference is so small that it's not really worth it.
  2. The RAH doesn't care about base resists. If it did, then the drones after the second test shouldn't have mostly switched to therm (as that was a very high resist).
  3. RAH should adjust to new incoming damage types. There is a common belief that once it adjusts, it will stop adjusting. (This sometimes breaks for , which is probably why the belief that it stops adjusting exists.)
  4. With 3 (and possibly 4?) incoming damage types from a single volley, RAH will only pick two to adjust to. (This is a fairly rare scenario.)
  5. With 3 incoming damage types from multiple volleys, RAH will adjust across all three.
  6. RAH will adjust up/down based on the damage dealt during the last cycle. Volume of damage (number of hits) is more important than absolute damage. In fact, based on the numbers I suspect the amount of damage matters not at all, as volleys of 150-200 arty cannons were keeping the Kin/Exp in low percentages (single digits) while the multiple 3-5 damage Hobs were keeping Therm in the 50s.

So what about the way we handle it with Pyfa?

Clearly it's not perfect. But without the exact code CCP is using for the RAH, it would be impossible for us to replicate it perfectly. Additionally, there are MANY edge cases for this that are going to create scenarios that would be impossible for us to calculate.

The one scenario where we COULD adjust it is we could tell it to ignore the third damage type. At a guess, CCP is dropping the third listed damage type (alphabetically), but this is far from confirmed. It's not based on damage as it dropped the higher damage one.

This isn't a perfect representation of what's in game, but it's vastly better than the previous implementation (which made RAH look far worse than a DCU in every scenario).

Regarding your scenarios specifically, I suspect that the RAH doesn't care who is shooting, just the number of shots. So by splitting your launchers and using different damage types, you're going to emulate a number of people shooting, not a 1v1 scenario.

At it's best, the RAH adjustment will only be able to emulate very simple limited scenarios, against a specific set of incoming damage type. Once you get beyond a very simple scenario, all bets are out the window and we simply can't handle those complex scenarios with where we're at now.

So, for now, I don't think it's worth messing with. The formula is pretty simple and straight forward, and I'm worried if we start adding edge cases that it's going to quickly get unmanageable.

@blitzmann unless @MrNukealizer comes up with something else, I don't think there's anything to change here. While there's some plot twists to the way RAH works in Pyfa, I don't think we have a reliable way to capture that. My vote is for keeping the formula as is, and calling it good enough.

@MrNukealizer
Copy link
Contributor Author

MrNukealizer commented Jul 15, 2016

Test 1
Resists (with RAH off): 74% EM, 88% Therm, 70% Kin, 84% Expl
Incoming Damage Profile: Void (0/50/50/0)
Final RAH: 0/30/30/0

When taking two types of damage from one attacker it will always go to 30%/30%. That's quite reliable; the issue is that it switches to normal computation when there are more than a certain number of attackers.

Test 2
Resists (with RAH off): 74% EM, 88% Therm, 70% Kin, 84% Expl
Incoming Damage Profile: Depleted Uranium (0/37.5/25/37.5)
Final RAH: 0/0/39/21

What's interesting about this one is it went initially to 0/0/30/30, THEN switched to the final listed above. What's especially interesting is that it didn't pick the two strongest resists.

The reason the RAH went to 0/0/30/30 first is because of the way it modifies the resistances. Every cycle it checks how much damage of each type you took during the last cycle, then it transfers up to 3% from resistances that took less damage to those that took more. I don't know how it calculates the amount to transfer to what, but the cycles go like this:

  1. Resistances 15/15/15/15, damage taken 0/25.4/40.5/34.0; 3% EM -> Kin, 3% EM -> Exp, 3% Therm -> Kin, 3% Therm -> Exp.
  2. Resistances 9/9/21/21, damage taken 0/28.2/39.0/32.8; 3% EM -> Kin, 3% EM -> Exp, 3% Therm -> Kin, 3% Therm -> Exp.
  3. Resistances 3/3/27/27, damage taken 0/31.2/37.4/31.4; 1.5% EM -> Kin, 1.5% Therm -> Kin, 1.5% Therm -> Kin, 1.5% Therm -> Exp.
  4. Resistances 0/0/30/30, damage taken 0/32.7/36.6/30.7; 3% Exp -> Therm, 3% ExP -> Kin.
  5. Resistances 0/3/33/24, damage taken 0/31.7/35.0/33.3; 1.5% Therm -> Kin, 1.5% Therm -> Exp.
  6. Resistances 0/0/34.5/25.5, damage taken 0/32.86/34.35/32.79; 3% Exp -> Therm, 3% Exp -> Kin.
  7. Resistances 0/3/37.5/19.5, damage taken 0/31.8/32.7/35.4; 1.5% Therm -> Kin, 1.5% Therm -> Exp.
  8. Resistances 0/0/39/21, damage taken 0/33.0/32.1/34.9; 3% Kin -> Therm, 3% Kin -> Exp.
  9. Same as 5. Repeat 5-8 Indefinitely.

Test 2
Resists (with RAH off): 74% EM, 88% Therm, 70% Kin, 84% Expl
Incoming Damage Profile: Depleted Uranium (0/37.5/25/37.5)
Final RAH: 0/0/39/21
...
After the RAH adjusted, the person shooting at me put 5 hobs on me. Even though they were doing very little damage, the RAH wanted to adjust mostly to therm damage.

I replicated this test to the best of my ability using a target with 74.1%/87.8%/70.8%/83.6% resistances and an attacker with 300 DPS from Depleted Uranium and 94 DPS from Hobgoblins.
When the Hobgoblins started hitting, the RAH started oscillating more than normal but staying around 0%/31%/24%/5%. That's the ideal profile to match my resistances to incoming damage.

So what does all this mean?

  1. RAH wants to adjust to even numbers if it's close. We could code that in to account for stuff like antimatter, but the EHP and HP/s difference is so small that it's not really worth it.
  2. The RAH doesn't care about base resists. If it did, then that second test shouldn't have mostly switched to therm (as that was a very high resist).
  3. RAH should adjust to new incoming damage types. (This sometimes breaks for .)
  4. With 3 (and possibly 4?) incoming damage types from a single volley, RAH will only pick two to adjust to. (This is a fairly rare scenario.)
  5. With 3 incoming damage types from multiple volleys, RAH will adjust across all three.
  6. RAH will adjust up/down based on the damage dealt during the last cycle. Volume of damage (number of hits) is more important than absolute damage.
  1. It's not about being close. When you have less than a certain number of attackers doing two types of damage, it will go to 30%/30%. I'm positive I've seen it do 20%/20%/20% with few attackers doing three damage types at some point, but the test with Depleted Uranium suggests that's not the case.
  2. The RAH does care about base resists and adjusts to minimize the damage taken. That's why it switched a lot into thermal.
  3. Yeah, it seems a little buggy. Like when I had warriors on a target and then hit it with thermal and explosive, but the RAH stayed at 60% explosive.
  4. There's something a little odd about how it transfers resistances, but the profile it eventually converges on is correct and easy to simulate.
  5. Correct.
  6. Only absolute damage is considered. It just happened to coincide with number of hits in your test.

So what about the way we handle it with Pyfa?

If there's a way to get the ship's resistances with all tank mods except the RAH and Damage Control, it would be an easy matter to calculate the resistance profile the RAH would converge on. As for choosing whether to use it or not, it's pretty simple except in the case of two incoming damage types. With one type it always goes to 60%. With four types, and apparently with three, it always calculates the profile to minimize damage taken. With two damage types, however, the formula depends on the number of attackers. Since that's impossible to know from the damage profile, that's a bit of a dilemma. For most PvP situations it would go to 30%/30% (if you're somehow only getting hit by two damage types), whereas for PvE it would almost always use the correct profile due to the number of attackers.

Like I said, I can code it, but I don't know how to get the ship's resistances before RAH and DC.

@blitzmann
Copy link
Collaborator

blitzmann commented Jul 15, 2016

I don't understand anything in this thread, so you guys figure it out. :p

@MrNukealizer, as for getting the ship resists after other modules but before DC, there isn't an easy way. You could pull it off by fucking with the effect runtimes (so that DC run after RAH, which runs after all other tank mods), but we only have three levels of runtime, and that might get messy with other effects that might depend on those effects.

Alternatively, and this is really janky, but you could check to see if the ship has been "affected by" the damage control. If it has, do the reverse calculation that the damage control performed. You would probably have to set the RAH to runtime = 'late' to ensure all other tank modules got a chance to apply.

I just thought about resist boosts via fleet booster. Yeah, you'll have to determine what to do there. YMMV

All in all, it's my personal belief that the RAH is a complex module in the game, and cannot accurately be represented pyfa no matter how hard we try try. What we have now is a best effort type of thing. If we can better it, great, but I know fuck all about it, which is why I deferred to @Ebag333 :)

@Ebag333
Copy link
Contributor

Ebag333 commented Jul 15, 2016

When taking two types of damage from one attacker it will always go to 30%/30%. That's quite reliable; the issue is that it switches to normal computation when there are more than a certain number of attackers.

Except you're defining "attacker" in a way that doesn't necessarily make sense.

If you define "one attacker" as "one volley from one weapon, then...this is kinda true. If you define "attacker" as one person, who may be using multiple damage types (not even accounting for drones, which the majority of ships carry at least 1), then this is nowhere near to being true.

It also won't go to 30/30 against stuff like say Scorch, which has very high EM, and some (but low) therm. (This I've tested). The estimated Pyfa adjustment for Scorch is 49% | 11%, and the actual in game numbers were within 2-3% of that.

but the cycles go like this:

Your numbers and logic look reasonable, but it's still simply a guess. We can't code of "this is the way it could be."

  1. The RAH does care about base resists and adjusts to minimize the damage taken. That's why it switched a lot into thermal.

  2. Only absolute damage is considered. It just happened to coincide with number of hits in your test.

This is incorrect, and clearly shown to be incorrect in the drone + arty test. Thermal was my highest resist, and it was the lowest incoming total amount of damage (by a massive margin). The RAH adjusted almost entirely into thermal despite being the smallest absolute damage (and even continued to adjust thermal up even during the cycles when the Arty hits were landing, which there were plenty, and plenty of back to back ones).

While the damage did bounce around a bit, the trend was absolutely clear.

Kadesh (who used to maintain Pyfa) did their own testing on this, and reported:

Then when I started using missiles again, it stayed at 60% explosive and completely ignored the thermal damage

(That is, using Warrior drones and shooting thermal missiles)
It's quite clear that the RAH simply doesn't care about the damage numbers, just the number of hits.

Your own comment even confirms this:

  1. Yeah, it seems a little buggy. Like when I had warriors on a target and then hit it with thermal and explosive, but the RAH stayed at 60% explosive.

Only this isn't a bug but a...uh....we'll call it "by design."

Regardless, this is getting out of damage profile and into multiple damage sources territory, which is not (and probably never will be) covered by Pyfa.

With four types, and apparently with three, it always calculates the profile to minimize damage taken.

This just isn't true. I've ran into multiple scenarios where the resists adjusted maximized my damage taken (given the incoming damage types). This is the primary complaint about the RAH, though the exact technical bits are often ignored for "it doesn't adjust the way I want it to."

With two damage types, however, the formula depends on the number of attackers.

Also doesn't seem to be true. Tested using multiple damage types with one attacker with split weapon (that is, ammo with multiple damage types), or multiple attackers.

For every test I've done so far, incoming damage from a single source (not just a single attacker) only accounts for 2 of the 3 damage types.

If you split up the guns/launchers (or add drones), then the RAH will treat a single attacker as multiple attackers. It still seems to follow the rule above (where individual volleys ignore one of the damage types), but the resists fluctuating makes it more difficult to track.

The more sources of incoming damage that there are (and the more varying the damage type), the more easily the RAH gets "confused".

To give an example, I used a ship with almost perfectly even armor resists.

(1 attacker, 2 launchers)
Incoming damage (ratio): 44%/56%/0%/0%
RAH: 18%/42%/0%/0%

By your theory of "minimal damage" this is clearly bad, because the thermal is being adjusted too high and the EM far too low (by 14%).

(1 attacker, 3 launchers)
Incoming damage (ratio): 16%|54%|30%|0%
RAH: 0%|48%|12%|0%

Again, by "minimal damage" metrics this isn't very good. While it's reducing the damage to the highest damage amount, it's completely ignoring the EM damage.

(1 attacker, 2 drones)
'Integrated' Infiltrator 111|71|0|0 61%|39%|0%|0%
Caldari Navy Vespa 0|0|108|0 0%|0%|100%|0%
Total Damage:38%|24%|37%|0%
RAH: 10/0/50/0

So again, not the optimal resists (not by a long shot). Not even the optimal resists based off the number of hits, since the drones were basically hitting one for one.

What I did notice was that the RAH started out with all three damage types being set (though never hit that "optimal" resist profile), but slowly migrated toward the final one listed above.

What has all this testing shown me?

  1. Even under extremely similar circumstances, when said circumstances are more complex than a single damage source, RAH doesn't always behave the exact same (and sometimes wildly different).
  2. When there is a single damage source, the in game RAH end resists are very similar to Pyfa (sub 10 percent).
  3. Exception to the rule above, when a single damage source has 3 types of damage, only two are used (and it's not necessarily the highest two).
  4. Base armor resists have no effect on this, as shooting a ship with 18% higher Thermal than Kinetic base resists still ends up with a 30|30 split on the RAH.
  5. The RAH is extremely complex, probably a bit buggy, doesn't always behave "as expected," and other shenanigans.

Okay, enough debating about complex scenarios involving multiple damage sources. Whether or not it's theoretically possible to reverse engineer those scenarios is pretty much out of scope as it's going to be complex as all get out.

Now, regarding a single source of damage (and ungrouping your missiles doesn't count), if you can show me a fit, ammo type used, and screenshot of the RAH resists and it can be replicated (not because I'm doubting you, but because RAH is complex and doesn't always work as expected), then we can look at adjusting the formula used.

To be fair, your thought that it fills holes isn't unique. Ivy links a Reddit thread where this is claimed (though without any CCP confirmation). Ivy also claims that it adjusts in 6% jumps, when it's actually 3%, so there is that.
https://www.reddit.com/r/Eve/comments/260fx4/reactive_armor_hardener_question/chmhvxs

I've tested the RAH against all four ammo classes now, and have had no evidence at all that this is true. I'm willing to entertain the idea if you can show me proof, but it needs to be a very simple scenario as that is all that we can handle in Pyfa.

Now regarding the comments above by Mr @blitzmann , trying to run it to figure out a way to get resists sans DCU (and links?) is going to be virtually impossible to do without making it incredibly complex (and lots of points of potential error). We would likely be better off simply accepting the current resists and running off those, even if they are off slightly. As all the resists would be bumped equally by DCU/Links, the impact on the RAH adjustment would be minimal at best. It would amount to accepting a very small known amount of error versus lots of room for a completely unknown amount of error.

Okay, and perhaps we can clarify things a bit more by listing out the scenarios that we support.

Scenario: Single damage type
Example ammo: Missiles
Expected results: 60% in one damage type
Actual results: 60% in one damage type

Scenario: Two damage types
Example Ammo: Anti-matter, Lasers
Expected Results: RAH resists match damage profile
Actual Results:
Void: Matches damage profile
Antimatter: 30|30 instead of the expected 41.7%|58.3%
Scorch: Matches damage profile (within 3%)

Scenario: Three damage types
Example Ammo: Projectiles
Expected Results: RAH resists match damage profile
Actual Results: Matches damage profile of two (not three) of the damage types

Scenario: Four damage types
Example Ammo: Gecko, Fighters
Expected Results: RAH resists match damage profile
Actual Results: Untested

Regarding the last scenario, theoretically since it's omni damage the RAH shouldn't change. If it follows the same pattern as scenario three, then the first two would get adjusted to 30. I'm not aware of any ammo/drones that use all four damage types, but have it uneven.

Anything outside those four scenarios would be completely unsupported by Pyfa (since damage profiles can't really cover that either), so let's just stick with those four.

@MrNukealizer
Copy link
Contributor Author

MrNukealizer commented Jul 15, 2016

Except you're defining "attacker" in a way that doesn't necessarily make sense.
You're right. I just discovered there was an error in my testing. In all the situations where there were a lot of "attackers" and it seemed like I was getting hit by two damage types, there was actually a third type doing very little DPS but causing the RAH to act as if there were three damage types. Just ignore the attacker bit; it seems that whole idea was flawed.

It also won't go to 30/30 against stuff like say Scorch, which has very high EM, and some (but low) therm.

Are you sure there was only EM and Thermal damage applied since the RAH was started? I just tried Scorch against a target with fairly even resists, one with much lower EM, and one with much lower Thermal, and in all cases the RAH split 30%/30%. I've also used Scorch in several other tests and IIRC they all went 30%/30%.

but the cycles go like this:

Your numbers and logic look reasonable, but it's still simply a guess. We can't code of "this is the way it could be."

That's exactly how my test went. I have screenshots of the resistances for the first 20 cycles and they fit that pattern perfectly. I'm still not sure what the exact rules are for shifting the resistances, but it doesn't matter since the convergence point is where each type of incoming damage does the same amount. That's quite easy to calculate from the resistances and damage profile without simulating the exact algorithm.

  1. The RAH does care about base resists and adjusts to minimize the damage taken. That's why it switched a lot into thermal.

  2. Only absolute damage is considered. It just happened to coincide with number of hits in your test.

This is incorrect, and clearly shown to be incorrect in the drone + arty test. Thermal was my highest resist, and it was the lowest incoming total amount of damage (by a massive margin). The RAH adjusted almost entirely into thermal despite being the smallest absolute damage (and even continued to adjust thermal up even during the cycles when the Arty hits were landing, which there were plenty, and plenty of back to back ones).

I said that wrong. I was under the impression that matching the ship's resistance profile to the damage profile would minimize damage taken. After doing the math, I can see that's wrong.
If you were getting shot by Depleted Uranium and Hobgoblins, you were getting hit by more thermal damage than any other type. Since the RAH tries to adjust so each type of incoming damage does the same amount, it would try to make your thermal resistance higher than the others by the same ratio the thermal damage was higher than the others. Since you mention artillery and that you got hit multiple cycles in a row, I'm assuming not large artillery.
The highest DPS medium artillery platform I know of is a Sleipnir, which gets 429 Depleted Uranium DPS with 4 faction gyros and all 5 skills. Continuing to assume all 5 skills, you'd be getting hit by 5 Hobgoblin IIs for 99 DPS. That would give an expected RAH profile of 0/25/26/9. If you were getting shot by a Cynabal with three T2 gyros for 266 turret DPS and 99 drone DPS, the expected pattern would be roughly 0/35/23/4.

I'm not sure what to say about your test without knowing the turret/drone DPS of the attacking ship and what kind of numbers you got on the RAH. In my tests it has always responded properly to drones, including a Nightmare with a flight of thermal drones and Tachyon Beams, which have a low rate of fire.

Then when I started using missiles again, it stayed at 60% explosive and completely ignored the thermal damage
(That is, using Warrior drones and shooting thermal missiles)

It's quite clear that the RAH simply doesn't care about the damage numbers, just the number of hits.

Your own comment even confirms this:

  1. Yeah, it seems a little buggy. Like when I had warriors on a target and then hit it with thermal and explosive, but the RAH stayed at 60% explosive.

Yes, it gets very weird when it's doing the two type calculation and you change things. That's not related to the way it works with 3+ damage types though, and there's no way to simulate changing damage profiles mid fight. It would be much more reasonable to simulate the behavior when taking the same proportion of damage the whole fight, which for two types seems to always be 30%/30%.

With four types, and apparently with three, it always calculates the profile to minimize damage taken.

This just isn't true. I've ran into multiple scenarios where the resists adjusted maximized my damage taken (given the incoming damage types). This is the primary complaint about the RAH, though the exact technical bits are often ignored for "it doesn't adjust the way I want it to."
I phrased that a bit wrong. The RAH tries to make all types of incoming damage do the same amount, effectively matching the ship's resistances to the incoming damage profile. In many cases that's not ideal for taking minimal damage.

(1 attacker, 3 launchers)
Incoming damage (ratio): 16%|54%|30%|0%
RAH: 0%|48%|12%|0%

Again, by "minimal damage" metrics this isn't very good. While it's reducing the damage to the highest damage amount, it's completely ignoring the EM damage.

You're correct that it's not minimizing damage in this case, however you're wrong about why not. In that situation with truly even resists like on a Gnosis, it would be most effective to put all 60% in thermal and forget about kinetic too. It is however doing its best to equalize the damage taken from each type; your incoming damage profile after resists is 22.7%/39.8%/37.5%/0%.

(1 attacker, 2 drones)
'Integrated' Infiltrator 111|71|0|0 61%|39%|0%|0%
Caldari Navy Vespa 0|0|108|0 0%|0%|100%|0%
Total Damage:38%|24%|37%|0%
RAH: 10/0/50/0
That's very interesting. I would've expected it to be more like 31/0/29/0. I'll have to test that and see what's up.

  1. Exception to the rule above, when a single damage source has 3 types of damage, only two are used (and it's not necessarily the highest two).

I'm not sure where you're getting that, but I didn't notice any such pattern in your results.

  1. Base armor resists have no effect on this, as shooting a ship with 18% higher Thermal than Kinetic base resists still ends up with a 30|30 split on the RAH.

Yes, because doing two types of damage does tend to cause a 30/30 split, ignoring a lot of things that affect the outcome against 3-4 damage types.

Now, regarding a single source of damage (and ungrouping your missiles doesn't count), if you can show me a fit, ammo type used, and screenshot of the RAH resists and it can be replicated (not because I'm doubting you, but because RAH is complex and doesn't always work as expected), then we can look at adjusting the formula used.

Here you go: http://imgur.com/a/4hGHH

trying to run it to figure out a way to get resists sans DCU (and links?) is going to be virtually impossible to do

Links don't matter one way or the other. The only reason I wanted to exclude the DC is because of stacking penalty calculations. If those will work correctly, it should be fine.

@Ebag333 sorry for the wall of text. We seem to have a difference of opinion on certain bits though, and it's important we both understand how it works.

@Ebag333
Copy link
Contributor

Ebag333 commented Jul 15, 2016

Are you sure there was only EM and Thermal damage applied since the RAH was started? I just tried Scorch against a target with fairly even resists, one with much lower EM, and one with much lower Thermal, and in all cases the RAH split 30%/30%. I've also used Scorch in several other tests and IIRC they all went 30%/30%.

Yup, and this is what makes RAH so much fun. Tests that are replicated do not always give the same results. (I tried to replicate your results with missiles, for example, and got different results.)

I suspect that getting stuck at 30|30 isn't actually intended.

@Ebag333 sorry for the wall of text. We seem to have a difference of opinion on certain bits though, and it's important we both understand how it works.

https://xkcd.com/386/

I'd love to fully understand how RAH works, and I've learned several things about it that I didn't know before while testing it. Unfortunately, unless someone figures out how to extract the relevant code from the client, or CCP posts the logic publicly, we're left with trying to reverse engineer it, which is sketchy at best.

Here's where I see that we're at:

  1. Two similar/identical situations doesn't always give the same results.
  2. RAH is hard
  3. It's not perfect for single source damage types...but it's mostly close.
  4. It's not perfect for multiple source damage types...but it's mostly close.

I don't think there's an easy solution here, because of the various "features"/bugs with RAH such as:

  1. it stops adjusting when it shouldn't (often at 30|30)
  2. it doesn't adjust to the incoming damage the same way every time

Clearly the solution in place is better than what we had before. It's not perfect, and not 100% accurate, but it's never going to be.

As the smart person in this conversation said:

All in all, it's my personal belief that the RAH is a complex module in the game, and cannot accurately be represented pyfa no matter how hard we try try. What we have now is a best effort type of thing.

@MrNukealizer
Copy link
Contributor Author

MrNukealizer commented Jul 17, 2016

Ok, I think I've figured out the exact logic. At the end of each cycle the RAH looks at how much damage of each type was taken during the cycle, after resists. Then it does one the following:

  1. If no damage was taken, it does nothing.
  2. If there was only one type of incoming damage, it shifts 6% (or whatever is remaining if less than 6%) from each of the other three resistances into the one that took damage.
  3. If two or more types of damage were taken, it shifts 6% (or whatever is remaining if less than 6%) from both of the resistances that took the least damage evenly into the two that took the most damage. If multiple resistances took the exact same amount of damage, it seems to break the tie by preferring to take from resistances in the following order: EM>Exp>Kin>Therm and give to resistances in the opposite order, Therm>Kin>Exp>EM.

That's why it locks at 30/30 when taking two types of damage. The two resistances taking the least (0) damage evenly shift into the two taking the most. Those two resistances never shift between eachother because it doesn't shift resistances away from either of the two taking the most damage, and they're always taking more damage than the two resistances not getting hit.

It also explains the bit about the RAH shifting to 60% when taking one type of damage but not adjusting when a second type is added. The type with 60% resistance may be taking significantly less damage than the other, but it's still one of the top two and won't shift resistances away.

I redid my test involving shooting the Megathron Navy Issue with Depleted Uranium ammo, and it turns out that adding even 1% more thermal resistance causes the RAH to lock at 0/0/30/30. With the resistances I had for the first test, the thermal resistance was just barely low enough to take more damage than explosive and cause a shift to 0/3/33/24, but a slight increase in thermal resistance is enough to keep kinetic and explosive as the top two damage types and stop them from optimizing between eachother.

(1 attacker, 2 drones)
'Integrated' Infiltrator 111|71|0|0 61%|39%|0%|0%
Caldari Navy Vespa 0|0|108|0 0%|0%|100%|0%
Total Damage:38%|24%|37%|0%
RAH: 10/0/50/0

I tried this setup with a Gila shooting a Gnosis. The Em/Therm balance behaved as expected, but the randomness in damage between hits made it very hard to predict the balance between EM/Therm and Kinetic damage. The RAH generally bounced around near 30/0/30/0, often locking in for several cycles until one drone got much better hits than the other. I wouldn't be too surprised at your result of 10/0/50/0 if the Vespa got significantly better hits than the Infiltrator for a couple cycles, because kinetic would almost always be in the top two damage types and thus never go down.

Scenario: Four damage types
Example Ammo: Gecko, Fighters
Expected Results: RAH resists match damage profile
Actual Results: Untested

I did that with a Gecko hitting a Gnosis, and the RAH kept switching between 15/15/15/15 and 9/21/21/9. Against other ships it evens out the resistances as expected.

@Ebag333
Copy link
Contributor

Ebag333 commented Jul 17, 2016

This is all good testing, I appreciate the time you've put into this.

I'm doing some additional digging myself on this, so we can leave this open for a while.

@blitzmann blitzmann added the discussion This is mostly a discussion thread label Jul 18, 2016
@Ebag333
Copy link
Contributor

Ebag333 commented Jul 18, 2016

Confirmed by sources that the RAH works off magic.

Unfortunately, despite all the testing we've done, I don't think we're necessarily any closer to figuring out the actual behavior. While we've definitely nailed some aspects (such as the RAH only adjusting for 2 damage types out of 3/4 damage type volleys), the bigger question is should we implement those?

If the damage profile is a single source, then absolutely. Problem is, there's nothing that enforces that, so if someone creates a damage profile with a fleet battle in mind, it no longer behaves as expected (even though we don't necessarily support this scenario, there's nothing to stop users from using it this way).

You have good arguments/tests showing that the RAH very well may operate in a way that takes base resists into account. Unfortunately, there's also scenarios when it clearly doesn't.

While the solution we have in place isn't ideal, unless someone can find the actual logic behind it I don't foresee us being able to implement anything better. There are too many use cases, and too many "well, when you do this" scenarios. (And by actual logic I mean actual logic, not simply "here's a scenario where it behaves as my theorycrafted logic says it should.")

We could make it a lot more complex and start attaching options for "single damage source" vs "multiple damage sources," but then we need to explain to a player that his ship fit with turrets and missiles and 5 drones (with 3 different sizes/damage types) doesn't count as a "single damage source."

TL;DR: RAH is a largely unknown module with complex mechanics, and what's in place today counts as a best effort.

@blitzmann
Copy link
Collaborator

blitzmann commented Jul 18, 2016

I always love a good discussion, and encourage it continuing here. However, for now, the RAH mechanics within pyfa will stay the same unless some sort of logic can be figured out that makes it more useful. It was never meant to be accurate per se, but rather to give a better idea of how it might settle instead of giving 15 across the board. It is a best effort calculation.

Again, I defer judgement on the formula to @Ebag333 as I know fuck all about it, and this thread has already turned my brain to mush. Please feel free to continue discussion; if anything this is good information for others who might be googling how RAH actually works. =)

@ghost
Copy link

ghost commented Jul 25, 2016

Hi Space Friends,

The RAH works out all the damage you've taken at the end of each cycle. This is damage after resistances are applied. So the same damage you see on your logs. It also only counts Armor damage, ignoring shield/hull damage. It only counts the damage taken since the start of the module cycle.

If you've taken 1 type of damage, it will lower the other 3 attributes to buff that 1.
If you've taken more than 1 type of damage, it will lower the lowest 2 to buff the highest 2.

I hope that clears things up a little :)

Cheers,
CCP Larrikin

@Ebag333
Copy link
Contributor

Ebag333 commented Jul 27, 2016

I hope that clears things up a little :)

Chatted with @ccplarrikin some in Slack. There were statements made that make it seem to me that it's not quite as cleared up as I would have liked. :)

BUT....As I understand it....it does not adjust to fill your resistances (it doesn't care if you have 99% EM or 1%). But it does adjust in the damage calc post resists.

So order of operations is:

  1. Raw incoming damage
  2. Incoming damage is reduced by resists. (There's some other reductions, but we'll ignore those for the moment.)
  3. RAH looks at the damage profile of the incoming damage (post resist reduction).
  4. RAH adjusts to match that damage profile.

This makes sense why people swore that the RAH adjusts to fill your holes. It doesn't. But your holes will leave the most incoming damage left for the damage types you're weakest in, and remove the most damage for the types your are highest in.

So, if we use a theoretical example of resists of 75% Therm and 25% Kin, and incoming damage of 100 Therm and 100 Kin (ignoring all the special exceptions for the moment), it looks something like this:

  1. Raw Damage: 100/100 Therm/Kin
  2. Damage after Resists: 25/75 Therm/Kin
  3. RAH Adjusts: 0/15/45/0 EM/Therm/Kin/Expl

Okay, now for special exceptions.

  1. RAH doesn't handle 3/4 damage volleys very well. To quote @MrNukealizer:

    take from resistances in the following order: EM>Exp>Kin>Therm and give to resistances in the opposite order, Therm>Kin>Exp>EM.

  2. Volume matters more than damage (illustrated by the various tests done with drones). Basically you'll cause the RAH to skew toward the low damage but high volume damage types, while the high damage but low volume hits get mostly ignored.

So. How do we handle all this?

First off, officially we can only support a single source of damage, so we can ignore exception 2 entirely. Now, by that metric we should follow exception number 1. The problem is that there's nothing stopping people from entering damage profiles for multiple damage sources/types, and we can't simply assume that a damage profile is a single volley from a single weapon.

I believe that if we followed exception number 1, it'd confuse the heck out of a lot of people, and it'd also be completely broken if the damage profile being used was for multiple incoming weapons/drones/etc. (For example, someone doing a Worm damage profile with EM drones and mixed therm/kin rockets would adjust completely wrong, as the EM drone damage would be entirely ignored even though it'd be 75% of the DPS.)

So to walk the tightrope, I think our solution is to ignore exception number 1 entirely, and simply move the RAH calculations to after the armor resists are taken into account (or rather, we use the armor resists to appropriately modify the damage profile).

This means that the RAH won't be right for single damage sources with 3 or 4 damage types, and it won't be right for fleet fights....but it'll handle single/double damage types fairly well and be a fair approximation of fleet fights.

And finally....

Incoming damage is reduced by resists. (There's some other reductions, but we'll ignore those for the moment.)

Well....crap.

This is where things get complicated. Assuming (and this is a pure assumption on my part, no CCP confirmation here) that RAH applies after ALL the reductions, then sig and speed would play parts.

Fortunately for me, Pyfa doesn't account for this one bit. So I can safely ignore any potential other reductions, and just worry about the reductions from resists. (Unless someone can think of another reduction that would come into play?)

I'll play around with the effects file and see if I can get it to play nice with the logic above.

@MrNukealizer
Copy link
Contributor Author

MrNukealizer commented Jul 28, 2016

So order of operations is:

  1. Raw incoming damage
  2. Incoming damage is reduced by resists. (There's some other reductions, but we'll ignore those for the moment.)
  3. RAH looks at the damage profile of the incoming damage (post resist reduction).
  4. RAH adjusts to match that damage profile.

That's about right, but with a couple clarifications:

2: AFAIK the other reductions can be ignored completely since they affect all resistances evenly and don't have stacking penalties with modules.

4: That depends on your definition of "match that damage profile." It does adjust based on the incoming damage profile post resists, but the "match" part can be complicated due to the way it refuses to shift the resistance taking the second most damage into the one taking the most.

So, if we use a theoretical example of resists of 75% Therm and 25% Kin, and incoming damage of 100 Therm and 100 Kin (ignoring all the special exceptions for the moment), it looks something like this:

  1. Raw Damage: 100/100 Therm/Kin
  2. Damage after Resists: 25/75 Therm/Kin
  3. RAH Adjusts: 0/15/45/0 EM/Therm/Kin/Expl

In that case it would adjust to 0/30/30/0 because it shifts from EM/Exp to Therm/Kin but never reduces the thermal resistance because it's taking the second most damage.

Okay, now for special exceptions.

  1. RAH doesn't handle 3/4 damage volleys very well. To quote @MrNukealizer: >take from resistances in the following order: EM>Exp>Kin>Therm and give to resistances in the opposite order, Therm>Kin>Exp>EM.
  2. Volume matters more than damage (illustrated by the various tests done with drones). Basically you'll cause the RAH to skew toward the low damage but high volume damage types, while the high damage but low volume hits get mostly ignored.
  1. That quote needs more context. The only time it shuffles resistances around in a fixed order is in the very, very rare case where multiple types of damage did exactly the same amount of damage. Realistically the only times it would happen is when a Gecko is shooting a Gnosis or possibly Svipul. The rest of the time it works very predictably, shifting resistances from the two types that took the least damage into the two types that took the most.
  2. As CCP Larrikin and I have said, the number of hits is irrelevant, only the total damage during the cycle. I'm convinced your drone test worked as expected and you just had slightly less thermal resistance than I did when replicating it, or the guns were doing less damage than mine were. Since you didn't say the gun/drone DPS or exact resistances, it's hard to replicate that test.

So to walk the tightrope, I think our solution is to ignore exception number 1 entirely, and simply move the RAH calculations to after the armor resists are taken into account (or rather, we use the armor resists to appropriately modify the damage profile).

Calculating after armor resistances does seem like the way to go, though I got to thinking: After learning more about the way the RAH works, it doesn't seem like there's a simple mathematical solution. Thus the RAH cycles would need to be simulated till it stops changing or enters a loop. That should never take more than 5-10 cycles, but I have slight concerns about handling the stacking penalties with Damage Controls or performance issues recalculating the whole ship's resistances.

There's also the issue of what resistances to use if it enters a constantly cycling loop like it did for a few of my tests. I'd probably go with the average of the profiles it cycles through, but I have a feeling the effective resistance profile over time is slightly more complicated than a simple average of the cycles.

This means that the RAH won't be right for single damage sources with 3 or 4 damage types, and it won't be right for fleet fights....but it'll handle single/double damage types fairly well and be a fair approximation of fleet fights.

It would be accurate for a single damage source with 3-4 types as well as for fleet fights. There's no difference in performance based on the number of damage sources, except where the weapon timing and RAH cycle time, or variations in hit quality, cause fluctuations in the damage taken from each source. Since pyfa can't take that into account, there's nothing to be done there.

@Ebag333
Copy link
Contributor

Ebag333 commented Jul 28, 2016

In that case it would adjust to 0/30/30/0 because it shifts from EM/Exp to Therm/Kin but never reduces the thermal resistance because it's taking the second most damage.

This is not the way the formula works, as I understand it.

To be fair, there's a bit of a telephone game going on here. I haven't seen the magical formula with my very own eyes, but I have talked with CCP Larrakin while he was looking at the code. (He's also very well aware that there's further conversation going on in this thread. :) )

Now, that's not to say that there isn't a bug or other issue, but that's the way it should work. (Some caveats below, but close enough for our purposes.)

That quote needs more context. The only time it shuffles resistances around in a fixed order is in the very, very rare case where multiple types of damage did exactly the same amount of damage. Realistically the only times it would happen is when a Gecko is shooting a Gnosis or possibly Svipul. The rest of the time it works very predictably, shifting resistances from the two types that took the least damage into the two types that took the most.

I don't believe that this is true, as I ran into this with projectile ammo doing 3 damage types (and while it was nearly even starting resists, it was certainly not even damage done). Now your order of which it prefers may not be exact, but the point was there there is something at play that 3/4 damage type volleys only seems to account for 2 damage types.

But regardless, this is something that we can safely ignore for the reasons listed above. So no need to further beat that particular dead horse.

As CCP Larrikin and I have said, the number of hits is irrelevant, only the total damage during the cycle. I'm convinced your drone test worked as expected and you just had slightly less thermal resistance than I did when replicating it, or the guns were doing less damage than mine were. Since you didn't say the gun/drone DPS or exact resistances, it's hard to replicate that test.

And CCP Larrikin agrees with you. I believe I did list the resists (if I didn't, it was a Strat with resists that varied by less than a percent, so about as even as a non-Gnosis can get). I did put in a bug report about this, as even going back to the logs and adding up the damage, looking at times/cycles, it doesn't quite add up.

This is one of the better known/documented "problems" with RAH, there's a good number of threads/posts about it (in fact it's one of the very first things @blitzmann pointed out to me when arguing not to open the can of worms by making it reactive, because of testing Kadesh did back in the day).

REGARDLESS, I will shamelessly quote myself:

this is something that we can safely ignore for the reasons listed above. So no need to further beat that particular dead horse.

Moving on.

After learning more about the way the RAH works, it doesn't seem like there's a simple mathematical solution.

Well, CCP makes it work with a simple mathematical solution. It's just everything around that mathematical solution that makes it hard, as we keep getting caught up on edge cases and probably buggy behavior. (At least half this thread is stuff that's very clearly things we shall never deal with in Pyfa. :D )

Thus the RAH cycles would need to be simulated till it stops changing or enters a loop.
There's also the issue of what resistances to use if it enters a constantly cycling loop like it did for a few of my tests. I'd probably go with the average of the profiles it cycles through, but I have a feeling the effective resistance profile over time is slightly more complicated than a simple average of the cycles.

This goes back to the limitations that we have. Simply put, we cannot have the RAH simulate anything more complex than a single volley from a single weapon (at least officially, unofficially we try and will get mostly right).

We absolutely cannot simulate the RAH behavior over time. This means we can only show the a simplistic representation of RAH values, and not how it behaves over time. There's no unofficial try on this one, but it's no different than many other values we expose, such as passive shield HP/s or your cap regen GJ/s. These only show your peak values, and not the whole range.

You are absolutely correct that the RAH will be "slightly more complicated than a simple average of the cycles" for mixed damage. For one, averages have nothing to do with it, the RAH (in game) has space Alzheimers and forgets about anything longer than 1 cycle ago. What we are simulating with the RAH is a single weapon hitting the RAH with no stepping in play.

The reason it goes through a cycle is because it's forgetful. To use some numbers as an example:

Take the following base values:
Damage Profile: 0/100/100/0
Resist Profile: 50/25/75/50
RAH Profile: 15/15/15/15

Assuming you could take the stepping out of the equation and have it fully instantly react, the behavior would be as follows:

Shot 1
Damage Post Resists: 0/75/25/0
RAH sees this, and reacts by adjusting to: 0/45/15/0
This changes the resist profile to: 35/55/75/0

Shot 2
Damage post resists: 0/45/25/0
RAH sees this and reacts by adjusting to: 0/39/21/0
This changes the resist profile to 35/49/81/0

Shot 3
Damage post resists: 0/51/19/0

You now have the cause of the RAH "bouncing" or "cycling" in game. Because of the stepping that they do, it often makes it impossible to finalize on a single value,

I have slight concerns about handling the stacking penalties with Damage Controls

Remember that this is a very simplistic representation of the RAH. You are absolutely correct in that it won't handle stacking penalties the way it does in game (though those are fairly minimal, to be honest, as you can't stack more than 2 deep). It also can't show the final final FINAL (really I mean it this time) RAH adjustment.

What we will be showing will be what the RAH would do, if there was a single volley, from a single weapon, and the RAH could instantly fully adjust to that without any stepping/cycles in play.

Man, I love that we actually have it that well defined.

To use the example above, the RAH in Pyfa (with the yet-to-be-written future formula) would show a resist profile of 0/45/15/0, when the actual resists (once it finished bouncing around) would be more like 0/42/18/0 (I didn't actually play out the scenario to the end, but close enough).

Is what we show (0/45/15/0) perfectly dead on? Nope.
Is it far closer to accurate than 15/15/15/15? Absolutely.

performance issues recalculating the whole ship's resistances.

There won't be any (further) performance hit because we're not going to be doing multiple cycles. While it's certainly doable (it's just a loop) it still won't take into account things like stacking penalties, and it's terribly complicated to build/maintain/troubleshoot for what amounts to a handful of percent difference.

@MrNukealizer
Copy link
Contributor Author

MrNukealizer commented Jul 28, 2016

Well, CCP makes it work with a simple mathematical solution. It's just everything around that mathematical solution that makes it hard, as we keep getting caught up on edge cases and probably buggy behavior.

The actual functionality is iterative, thus it's hard or impossible to reproduce with a simple formula using the resistances and incoming damage. If you think of it as one step like that, it's no wonder there seem to be so many edge cases and bugs. If you instead evaluate the logic in a loop until it starts repeating, it's possible to perfectly simulate it for the assumed constant stream of damage with no variance or timing issues. Real performance would vary slightly based on the timing and quality of hits, but the damage profile doesn't say anything about that so it need not be considered.

With the logic I mentioned a while back and will repeat below, the only "edge case" that needs consideration is when taking only one type of damage instead of 2+.

We absolutely cannot simulate the RAH behavior over time. This means we can only show the a simplistic representation of RAH values, and not how it behaves over time. There's no unofficial try on this one, but it's no different than many other values we expose, such as passive shield HP/s or your cap regen GJ/s. These only show your peak values, and not the whole range.

Sure we can simulate the RAH behavior over time. Python can do loops, and it's fairly trivial to simulate the 3-12 cycles the RAH would go through before it stops on one profile or starts repeating. Since we can only use one value for the final RAH resistances, that would be either the profile it stops on or the average of the few it loops through (the effective resistances across cycles are actually a simple average).

You are absolutely correct that the RAH will be "slightly more complicated than a simple average of the cycles" for mixed damage. For one, averages have nothing to do with it, the RAH (in game) has space Alzheimers and forgets about anything longer than 1 cycle ago. What we are simulating with the RAH is a single weapon hitting the RAH with no stepping in play.

Ok, the way I see it, the damage profile is the amount of DPS that's constantly hitting your ship for as long as you're in combat, not one single hit. If you're getting hit by constant damage for more than a minute or two, the RAH will either find a profile where it stops changing, or loop through a repeating series of profiles. If it stops, that's the obvious resistance profile to use. If it loops, the effective resistances over time are the average of the resistances during each cycle of the loop. Some cycles you'll take more of one type of damage and less of another, but by the end of the loop it all evens out. Given the assumption that the damage profile is constant DPS hitting you till the battle is over, the RAH would continue to loop an undetermined number of times, evening out the effective resistances to the average of the profiles it loops through.

The reason it goes through a cycle is because it's forgetful. To use some numbers as an example:

Take the following base values:
Damage Profile: 0/100/100/0
Resist Profile: 50/25/75/50
RAH Profile: 15/15/15/15

Assuming you could take the stepping out of the equation and have it fully instantly react, the behavior would be as follows:

Shot 1
Damage Post Resists: 0/75/25/0
RAH sees this, and reacts by adjusting to: 0/45/15/0
This changes the resist profile to: 35/55/75/0

Shot 2
Damage post resists: 0/45/25/0
RAH sees this and reacts by adjusting to: 0/39/21/0
This changes the resist profile to 35/49/81/0

Shot 3
Damage post resists: 0/51/19/0

That's a nice scenario, but the real RAH would just go to 0/30/30/0 and not change any more, so using a simulation like that would be quite inaccurate.

You now have the cause of the RAH "bouncing" or "cycling" in game. Because of the stepping that they do, it often makes it impossible to finalize on a single value,

Given an indeterminate amount of time, normally under 40 seconds, the RAH will always repeat the same few cycles over and over again. While it would be pointless to use any one of the values it cycles through, if you get hit by the same damage during each cycle the effective resistances would be the average of all the profiles it cycles through.

It also can't show the final final FINAL (really I mean it this time) RAH adjustment.
What we will be showing will be what the RAH would do, if there was a single volley, from a single weapon, and the RAH could instantly fully adjust to that without any stepping/cycles in play.

And that will be inaccurate 100% of the time, occasionally getting close but never actually correct.

Just find a way to get the ship's resistances with all tank modules active, and I'll provide the code to give an accurate value. The logic is as follows and very easy to implement:

At the end of each cycle the RAH looks at how much damage of each type was taken during the cycle, after resists. Then it does one of the following:

  1. If no damage was taken, it does nothing.
  2. If there was only one type of incoming damage, it shifts 6% (or whatever is remaining if less than 6%) from each of the other three resistances into the one that took damage.
  3. If two or more types of damage were taken, it shifts 6% (or whatever is remaining if less than 6%) from both of the resistances that took the least damage evenly into the two that took the most damage. If multiple resistances took the exact same amount of damage, it seems to break the tie by preferring to take from resistances in the following order: EM>Exp>Kin>Therm and give to resistances in the opposite order, Therm>Kin>Exp>EM.

I literally just did that in about 30 lines of C# code (it would be a lot less if it was possible to use references or pointers in that context) for another project and it perfectly matches every test I've recorded.

@ghost
Copy link

ghost commented Jul 28, 2016

Hi,

Ok, I've done a bunch of tests on this. I added some logging to the RAH to show exactly what its doing every cycle. In all cases the testing procedure was the same. Drones/Weapons were ordered to attack. After a short while, the RAH was activated.

Scenario 1

Vagabond (5x 650mm Artillery Cannon II w' Quake M, 3x Gyrostabilizer II, 5x Hornet II) shooting a Gnosis (1x Reactive Armor Hardener, hacked up armor HP).
Cycle time on the Arty is 4.74s
Cycle time on the Drones is 4s
Cycle time on the RAH is 5s
The drones are doing Kin damage, while the guns do Therm/Kin

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.15/0.15/0.15/0.15 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/886/809 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.09/0.09/0.21/0.21 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/587/712 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.03/0.03/0.27/0.27 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/503/569 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/440/473 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/636/509 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/464/462 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/475/494 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/600/627 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/665/572 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/405/420 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/460/515 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/432/528 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/691/597 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/528/625 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/525/594 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/495/620 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/728/644 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/453/515 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.0/0.0/0.3/0.3 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/948/1422 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/Exp
RAH LOG> Attributes to Decrease EM/Therm/

Scenario 2

Vagabond (5x 650mm Artillery Cannon II w' EMP M, 3x Gyrostabilizer II, 5x Hornet II) shooting a Gnosis (1x Reactive Armor Hardener, hacked up armor HP).
Cycle time on the Arty is 4.74s
Cycle time on the Drones is 4s
Cycle time on the RAH is 5s
The drones are doing Kin damage, while the guns do EM/Kin/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.15/0.15/0.15/0.15 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 622/0/287/138 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.21/0.09/0.21/0.09 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 682/0/259/174 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.27/0.03/0.27/0.03 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1111/0/271/328 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Exp
RAH LOG> Attributes to Decrease Therm/Kin/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.315/0.0/0.21/0.075 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 620/0/305/186 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.345/0.0/0.24/0.015 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 684/0/437/228 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.3525/0.0/0.2475/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 630/0/253/216 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.3525/0.0/0.2475/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 682/0/307/234 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.3525/0.0/0.2475/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 575/0/240/197 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.3525/0.0/0.2475/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 490/0/406/168 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.3525/0.0/0.2475/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 658/0/275/226 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.3525/0.0/0.2475/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 529/0/235/181 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.3525/0.0/0.2475/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 570/0/236/195 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

Scenario 3

Vagabond (5x 720mm Artillery Cannon II w' EMP M, 3x Gyrostabilizer II, 5x Hornet II) shooting a Gnosis (1x Reactive Armor Hardener, hacked up armor HP).
Cycle time on the Arty is 7.43s
Cycle time on the Drones is 4s
Cycle time on the RAH is 5s
The drones are doing Kin damage, while the guns do EM/Kin/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.15/0.15/0.15/0.15 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/382/0 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/
RAH LOG> Attributes to Decrease EM/Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.09/0.09/0.33/0.09 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1289/0/254/286 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Exp
RAH LOG> Attributes to Decrease Therm/Kin/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.15/0.03/0.27/0.15 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1092/0/306/242 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.195/0.0/0.315/0.09 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/157/0 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/
RAH LOG> Attributes to Decrease EM/Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.135/0.0/0.435/0.03 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1335/0/356/332 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.15/0.0/0.45/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1537/0/237/402 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Exp
RAH LOG> Attributes to Decrease Therm/Kin/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.18/0.0/0.39/0.03 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/126/0 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/
RAH LOG> Attributes to Decrease EM/Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.12/0.0/0.48/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1208/0/247/305 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Exp
RAH LOG> Attributes to Decrease Therm/Kin/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.15/0.0/0.42/0.03 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1279/0/332/324 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.165/0.0/0.435/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/150/0 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/
RAH LOG> Attributes to Decrease EM/Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.105/0.0/0.495/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1311/0/200/325 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Exp
RAH LOG> Attributes to Decrease Therm/Kin/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.135/0.0/0.435/0.03 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1054/0/244/262 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Exp
RAH LOG> Attributes to Decrease Therm/Kin/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.165/0.0/0.375/0.06 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/295/0 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/
RAH LOG> Attributes to Decrease EM/Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.105/0.0/0.495/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1419/0/221/352 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Exp
RAH LOG> Attributes to Decrease Therm/Kin/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.135/0.0/0.435/0.03 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1157/0/239/288 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Exp
RAH LOG> Attributes to Decrease Therm/Kin/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.165/0.0/0.375/0.06 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/179/0 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/
RAH LOG> Attributes to Decrease EM/Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.105/0.0/0.495/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1164/0/314/289 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.105/0.0/0.495/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1410/0/199/350 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Exp
RAH LOG> Attributes to Decrease Therm/Kin/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.135/0.0/0.435/0.03 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/136/0 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/
RAH LOG> Attributes to Decrease EM/Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.075/0.0/0.525/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1455/0/182/349 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Exp
RAH LOG> Attributes to Decrease Therm/Kin/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.105/0.0/0.465/0.03 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1083/0/342/260 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Kin/
RAH LOG> Attributes to Decrease Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.12/0.0/0.48/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/102/0 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/
RAH LOG> Attributes to Decrease EM/Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.06/0.0/0.54/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1474/0/196/348 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Exp
RAH LOG> Attributes to Decrease Therm/Kin/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.09/0.0/0.48/0.03 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 952/0/177/225 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Exp
RAH LOG> Attributes to Decrease Therm/Kin/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.12/0.0/0.42/0.06 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/302/0 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/
RAH LOG> Attributes to Decrease EM/Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.06/0.0/0.54/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1428/0/200/337 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Exp
RAH LOG> Attributes to Decrease Therm/Kin/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.09/0.0/0.48/0.03 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 1513/0/232/358 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase EM/Exp
RAH LOG> Attributes to Decrease Therm/Kin/

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.12/0.0/0.42/0.06 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/125/0 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/
RAH LOG> Attributes to Decrease EM/Therm/Exp

RAH LOG> *** Start of Cycle ***
RAH LOG> Applying Resists 0.06/0.0/0.54/0.0 (EM/Therm/Kin/Exp)
RAH LOG> *** End of Cycle ***
RAH LOG> Removing Resists
RAH LOG> Incoming Armor Damage this Cycle 0/0/242/0 (EM/Therm/Kin/Exp)
RAH LOG> Attributes to Increase Kin/
RAH LOG> Attributes to Decrease EM/Therm/Exp

The RAH is working as intended. The rule of thumb for the RAH is as follows >

  • During the cycle, if the RAH takes 1 type of damage, it will move resistances from the other 3 into that 1 type.
  • During the cycle, if the RAH takes more than 1 type of damage, it will move resistances from the lowest two resists into the highest two resists.

The damage is calculated after resists.
Number of hits, source, etc, are not important.
Only damage to armor is counted.

I hope that helps!

Cheers,
CCP Larrikin

@Ebag333
Copy link
Contributor

Ebag333 commented Jul 28, 2016

@ccplarrikin I owe you a beer. Or .3/.3 of one. :D

So after an hour long discussion with @ccplarrikin I think we got this figured out.

  1. RAH only cares about the highest two damage types. (Setting aside 1 damage type scenario for the moment.)
  2. RAH doesn't add resists, it removes resists from the lowest two incoming damage types.
  3. Whatever resists it has leftover is added to the highest two incoming damage types, in a 50/50 split.
  4. RAH will never ever steal resists from the second highest damage type (as long as that damage type has more than 0 damage dealt that cycle).

The reason why we get resists other than 30/30? Because damage is variable, so if one of the bottom two damage types ends up being (for a cycle or three) the second or top damage type. This causes RAH to adjust as it will, and because of the "never steal from second highest damage type" rule, will cause it to go lopsided.

This is also why drones break the RAH. Their damage type hits every single cycle, but using a weapon with longer than 5s cycle time only hits every other cycle. So chances are the damage type for drones is always first or second highest, and resists will eventually go 100% to that damage type (and stay there).

So for our programming needs, we don't need cycles or loops, because as long as the damage is static (doesn't change, which it never changes in Pyfa), you will always end up with either:

  1. 60% resists in one damage type (single damage type profile)
  2. 30%/30% resists in two damage types. (2, 3, or 4 damage type profiles)

Single damage types are pretty easy (and already handled today).

For two+ damage type profiles:

Well, this just became incredibly simple.

@MrNukealizer
Copy link
Contributor Author

MrNukealizer commented Jul 29, 2016

Ok, this is starting to get a bit frustrating. I've been saying things for over a week and you just disregard them until Larrikin says the same thing, then you still disregard the part I said but he didn't.

  1. RAH only cares about the highest two damage types. (Setting aside 1 damage type scenario for the moment.)
  2. RAH doesn't add resists, it removes resists from the lowest two incoming damage types.
  3. Whatever resists it has leftover is added to the highest two incoming damage types, in a 50/50 split.
  4. RAH will never ever steal resists from the second highest damage type (as long as that damage type has more than 0 damage dealt that cycle).
  1. Correct, but keep in mind that as it shifts resistances the balance of incoming damage changes as well, so the highest two can change between cycles.
  2. If it doesn't add resistances how do some get higher? It shuffles resistances around, adding and removing.
  3. So it adds resistances even though it doesn't add resistances?
  4. That sounds familiar:

That's why it locks at 30/30 when taking two types of damage. The two resistances taking the least (0) damage evenly shift into the two taking the most. Those two resistances never shift between eachother because it doesn't shift resistances away from either of the two taking the most damage, and they're always taking more damage than the two resistances not getting hit.

Moving on,

The reason why we get resists other than 30/30? Because damage is variable, so if one of the bottom two damage types ends up being (for a cycle or three) the second or top damage type.

Or perhaps the RAH can overcompensate for one of the top two damage types and let a third surpass it for a cycle.

This is also why drones break the RAH. Their damage type hits every single cycle, but using a weapon with longer than 5s cycle time only hits every other cycle.

Keep in mind most people don't have Resistance Phasing V so the cycle time could easily be 6-10 seconds. Also when getting shot by multiple people who don't coordinate their fire, there's a much higher chance of getting hit by the weapons every cycle.

So for our programming needs, we don't need cycles or loops, because as long as the damage is static (doesn't change, which it never changes in Pyfa), you will always end up with either:

  1. 60% resists in one damage type (single damage type profile)
  2. 30%/30% resists in two damage types. (2, 3, or 4 damage type profiles)

Please hop on SiSi with this fit:

[Megathron Navy Issue, Target]

Reactive Armor Hardener
Armor Thermal Hardener II
Armor Thermal Hardener II
Armor Kinetic Hardener II
Armor Explosive Hardener II
Prototype Armor Explosive Hardener I
Prototype Armor Explosive Hardener I

Large Anti-Thermal Pump II
Large Anti-EM Pump I
Large Anti-EM Pump I

Then shoot it with a projectile weapon using Depleted Uranium ammo. That does three types of damage, and it most certainly doesn't stay at 0/0/30/30. It goes to 0/0/30/30, but then the thermal resistance is low enough to take more damage than explosive, so it loops between 0/3/33/24, 0/0/34.5/25.5, 0/3/37.5/19.5, and 0/0/39/21.

@Ebag333
Copy link
Contributor

Ebag333 commented Jul 29, 2016

Ok, this is starting to get a bit frustrating. I've been saying things for over a week and you just disregard them until Larrikin says the same thing, then you still disregard the part I said but he didn't.

Disregard? No.

Did I pay more attention to the person who was looking at the actual code (and hacked it to add debugging)? Yes.

Correct, but keep in mind that as it shifts resistances the balance of incoming damage changes as well, so the highest two can change between cycles.

True, and there's other edge cases where changing resists will change the incoming damage and change which damage types are the top two. There's nothing we can do to accommodate that, however, as we can only deal with fixed values.

If it doesn't add resistances how do some get higher? It shuffles resistances around, adding and removing.
So it adds resistances even though it doesn't add resistances?

It's more an order of operations thing. The point is that the RAH subtracts resists from the weakest two resists, then adds the amount subtracted to the highest resists. (Equally.)

Everyone thinks of RAH as trying to add to the highest two resists, when in fact it's subtracting from the weakest two (and then just dumping whatever extra it has). It's a subtle difference, but explains exactly why these various scenarios that we ran into happened the way they did.

It might be a subtle difference, but it's insanely important.

Or perhaps the RAH can overcompensate for one of the top two damage types and let a third surpass it for a cycle.

And in mixed damage scenarios this absolutely happens. It's actually the normal damage stuff that tends to cause this more than the resists changing (as damage fluctuates quite a lot if not using missiles, and even if using missiles with speed/sig).

However, either one of these scenarios requires multiple cycles, which gets into times, complex values, and other things that Pyfa simply cannot handle.

Keep in mind most people don't have Resistance Phasing V so the cycle time could easily be 6-10 seconds. Also when getting shot by multiple people who don't coordinate their fire, there's a much higher chance of getting hit by the weapons every cycle.

And again, mixed damage scenarios are something we cannot account for due to the vast complexity of Pyfa.

It may be able to cover some of it through the use of the projected tab...but it still wouldn't cover large chunks of it.

Then shoot it with a projectile weapon using Depleted Uranium ammo. That does three types of damage, and it most certainly doesn't stay at 0/0/30/30. It goes to 0/0/30/30, but then the thermal resistance is low enough to take more damage than explosive, so it loops between 0/3/33/24, 0/0/34.5/25.5, 0/3/37.5/19.5, and 0/0/39/21.

This absolutely happens. It's also not a scenario we can cover because of the timing/cycle issue that Pyfa cannot handle with the current engine.

@MrNukealizer MrNukealizer mentioned this issue Jul 29, 2016
@MrNukealizer
Copy link
Contributor Author

Ok, I took the file in your pull request and modified it to use the algorithm I've found to be accurate. Now it's just up to @blitzmann to choose which to use.

@blitzmann
Copy link
Collaborator

Gonna open this again as there are now pull requests associated with this discussion.

I'll be honest, I still don't know what you guys are on about, but I'll take a look. From what I gather, cycling the RAH causes issues because each cycle will change the incoming damage. However, RAH cycling isn't currently supported in pyfa. I was discussing the idea of doing some sort of cycle like we do with cap simulation to find some sort of average, but it's not something that's on the radar for now.

I'll take a peak at both PRs. Please do not expect either of them to be merged any time soon due to RL being very busy and no easy way for me to quickly test these in game. =)

@blitzmann
Copy link
Collaborator

Just realized that you actually did implement cycling into your code. Well done! I don't like that it's in the effect file itself due to performance reasons, but if it works well it is not a big deal (considering you can only ever have one on a ship, so this effect file will should only run once per fitting calculation)

@Ebag333
Copy link
Contributor

Ebag333 commented Jul 29, 2016

I don't like that it's in the effect file itself due to performance reasons, but if it works well it is not a big deal (considering you can only ever have one on a ship, so this effect file will should only run once per fitting calculation)

As we discussed on Slack, it runs 3 times for the fit when its opened, once each time it's refreshed (which is every time you click anything at all), and once per booster. (Boosters only seem to run once regardless of open vs refresh.)

So theoretically, worst case scenario, it could run 6 times.

@MrNukealizer
Copy link
Contributor Author

It doesn't seem like the cycling would cause performance issues, but it can be optimized a bit if it does. I'm not sure how efficiently Python runs it, but very similar code in C# could always resolve in under a millisecond.

If it does need to be optimized, the number of damage types could be checked before the loop to handle one or two types in one step, since those always go to 60% or 30%/30%. I also think I probably did some stuff inefficiently since it was the first time I've touched Python.

@MrNukealizer
Copy link
Contributor Author

...And now that I think of it, I shouldn't have left the logging in for my pull request. The code itself should perform fine, but the logging might slow it down a lot.

@blitzmann
Copy link
Collaborator

blitzmann commented Jul 29, 2016

Naw, logging wouldn't usually affect performance as I believe it's async. Could be wrong, but I've never had performance issues because of logging. But yeah, if this were to be merged, I'd take the statement out / comment them out.

@Ebag333
Copy link
Contributor

Ebag333 commented Jul 30, 2016

We need more logging in Pyfa. Almost nothing gets logged, so when we have a problem, it's difficult to troubleshoot (especially when we can't replicate it).

As long as it set for debug and not info, it only pops up if debugging is turned on, so the impact is incredibly small.

It's not the debugging. And while the code could be optimized, at the heart it's not really the code. It's the inefficient nature of Pyfa, and the extremely complex fitting engine behind it.

Unfortunately this sometimes means that we can't do things. There are a few things I've tried to implement, but had to put aside because of the impact it had.

Pyfa already runs very sluggish, we simply need to be very careful about what we add.

@Ebag333
Copy link
Contributor

Ebag333 commented Jul 30, 2016

Unfortunately this sometimes means that we can't do things. There are a few things I've tried to implement, but had to put aside because of the impact it had.

Thinking about this further, I would suggest that we look at merging RAH v2, but leave the v3 PR open. v2 is closer to real life scenarios than the current version, so it's a step in the right direction.

When we can get the v3 code optimized enough to bring it down to sub second times for that initial open, and/or we can optimize the code around it, we can have it ready to drop in.

That won't be today or next week (unless someone comes up with a brilliant plan to optimize the RAH code), but it'll be waiting and ready to drop in, and we can continue to iterate on it as we go along.

@blitzmann blitzmann reopened this Jul 30, 2016
@MrNukealizer
Copy link
Contributor Author

MrNukealizer commented Jul 30, 2016

Ok, I made a couple minor tweaks to the v3 code and commented out the logging, and it seems to run in a millisecond or less. Toggling the RAH in debug mode seems to change the time spent in the "late" runtime from 2-3ms to 3-4ms. I can personally (sample size: 1 on a good machine) load a fit with an RAH, three different boosters with RAHs, and two projected fits with RAHs, in well under a second:

2016-07-29 22:46:29,450 eos.saveddata.fit        DEBUG    Starting fit calculation on: Fit(ID=396, ship=Nestor, name=Nestor fit) at 0x9f23af0, withBoosters: True
2016-07-29 22:46:29,451 eos.saveddata.fit        DEBUG    Fleet is set, gathering gang boosts
2016-07-29 22:46:29,453 eos.saveddata.fit        DEBUG    Starting fit calculation on: Fit(ID=393, ship=Astarte, name=RAH booster) at 0x999c630, withBoosters: False
2016-07-29 22:46:29,618 eos.saveddata.fit        DEBUG    Timer - Fit: 393, RAH booster - Done with runtime: early - 165.00ms (165.00ms elapsed)
2016-07-29 22:46:29,647 eos.saveddata.fit        DEBUG    Timer - Fit: 393, RAH booster - Done with runtime: normal - 29.00ms (194.00ms elapsed)
2016-07-29 22:46:29,657 eos.saveddata.fit        DEBUG    Timer - Fit: 393, RAH booster - Done with runtime: late - 11.00ms (205.00ms elapsed)
2016-07-29 22:46:29,658 eos.saveddata.fit        DEBUG    Timer - Fit: 393, RAH booster - Done with fit calculation - 1.00ms (206.00ms elapsed)
2016-07-29 22:46:29,661 eos.saveddata.fit        DEBUG    Starting fit calculation on: Fit(ID=394, ship=Astarte, name=RAH booster copy) at 0x9e44f50, withBoosters: False
2016-07-29 22:46:29,673 eos.saveddata.fit        DEBUG    Timer - Fit: 394, RAH booster copy - Done with runtime: early - 10.00ms (10.00ms elapsed)
2016-07-29 22:46:29,686 eos.saveddata.fit        DEBUG    Timer - Fit: 394, RAH booster copy - Done with runtime: normal - 14.00ms (24.00ms elapsed)
2016-07-29 22:46:29,690 eos.saveddata.fit        DEBUG    Timer - Fit: 394, RAH booster copy - Done with runtime: late - 4.00ms (28.00ms elapsed)
2016-07-29 22:46:29,690 eos.saveddata.fit        DEBUG    Timer - Fit: 394, RAH booster copy - Done with fit calculation - 1.00ms (29.00ms elapsed)
2016-07-29 22:46:29,694 eos.saveddata.fit        DEBUG    Starting fit calculation on: Fit(ID=395, ship=Astarte, name=RAH booster copy 2) at 0x9c8f0b0, withBoosters: False
2016-07-29 22:46:29,704 eos.saveddata.fit        DEBUG    Timer - Fit: 395, RAH booster copy 2 - Done with runtime: early - 11.00ms (11.00ms elapsed)
2016-07-29 22:46:29,720 eos.saveddata.fit        DEBUG    Timer - Fit: 395, RAH booster copy 2 - Done with runtime: normal - 15.00ms (26.00ms elapsed)
2016-07-29 22:46:29,723 eos.saveddata.fit        DEBUG    Timer - Fit: 395, RAH booster copy 2 - Done with runtime: late - 3.00ms (29.00ms elapsed)
2016-07-29 22:46:29,726 eos.saveddata.fit        DEBUG    Timer - Fit: 395, RAH booster copy 2 - Done with fit calculation - 3.00ms (32.00ms elapsed)
2016-07-29 22:46:29,730 eos.saveddata.fit        DEBUG    Timer - Fit: 396, Nestor fit - Done calculating gang boosts for Fit(ID=396, ship=Nestor, name=Nestor fit) at 0x9f23af0 - 280.00ms (280.00ms elapsed)
2016-07-29 22:46:29,732 eos.saveddata.fit        DEBUG    Applying gang boosts in `early` runtime for Fit(ID=396, ship=Nestor, name=Nestor fit) at 0x9f23af0
2016-07-29 22:46:29,744 eos.saveddata.fit        DEBUG    Timer - Fit: 396, Nestor fit - Done with runtime: early - 14.00ms (294.00ms elapsed)
2016-07-29 22:46:29,746 eos.saveddata.fit        DEBUG    Applying gang boosts in `normal` runtime for Fit(ID=396, ship=Nestor, name=Nestor fit) at 0x9f23af0
2016-07-29 22:46:29,773 eos.saveddata.fit        DEBUG    Timer - Fit: 396, Nestor fit - Done with runtime: normal - 28.00ms (322.00ms elapsed)
2016-07-29 22:46:29,773 eos.saveddata.fit        DEBUG    Applying gang boosts in `late` runtime for Fit(ID=396, ship=Nestor, name=Nestor fit) at 0x9f23af0
2016-07-29 22:46:29,779 eos.saveddata.fit        DEBUG    Timer - Fit: 396, Nestor fit - Done with runtime: late - 7.00ms (329.00ms elapsed)
2016-07-29 22:46:29,780 eos.saveddata.fit        DEBUG    Starting fit calculation on: Fit(ID=374, ship=Megathron Navy Issue, name=sfda) at 0x504a990, withBoosters: True
2016-07-29 22:46:29,780 eos.saveddata.fit        DEBUG    Applying projections to target: Fit(ID=396, ship=Nestor, name=Nestor fit) at 0x9f23af0
2016-07-29 22:46:29,782 eos.saveddata.fit        DEBUG    ProjectionInfo: ProjectedFit(sourceID=374, victimID=396, amount=1, active=True) at 0x9f23050
2016-07-29 22:46:29,786 eos.saveddata.fit        DEBUG    Timer - Fit: 374, sfda - Done with runtime: early - 5.00ms (5.00ms elapsed)
2016-07-29 22:46:29,801 eos.saveddata.fit        DEBUG    Timer - Fit: 374, sfda - Done with runtime: normal - 15.00ms (20.00ms elapsed)
2016-07-29 22:46:29,802 eos.saveddata.fit        DEBUG    Timer - Fit: 374, sfda - Done with runtime: late - 2.00ms (22.00ms elapsed)
2016-07-29 22:46:29,802 eos.saveddata.fit        DEBUG    Timer - Fit: 374, sfda - Done with fit calculation - 0.00ms (22.00ms elapsed)
2016-07-29 22:46:29,803 eos.saveddata.fit        DEBUG    Starting fit calculation on: Fit(ID=391, ship=Stratios, name=RAH test) at 0x9930510, withBoosters: True
2016-07-29 22:46:29,803 eos.saveddata.fit        DEBUG    Applying projections to target: Fit(ID=396, ship=Nestor, name=Nestor fit) at 0x9f23af0
2016-07-29 22:46:29,805 eos.saveddata.fit        DEBUG    ProjectionInfo: ProjectedFit(sourceID=391, victimID=396, amount=1, active=True) at 0x9f23f50
2016-07-29 22:46:29,812 eos.saveddata.fit        DEBUG    Timer - Fit: 391, RAH test - Done with runtime: early - 8.00ms (8.00ms elapsed)
2016-07-29 22:46:29,826 eos.saveddata.fit        DEBUG    Timer - Fit: 391, RAH test - Done with runtime: normal - 15.00ms (23.00ms elapsed)
2016-07-29 22:46:29,828 eos.saveddata.fit        DEBUG    Timer - Fit: 391, RAH test - Done with runtime: late - 2.00ms (25.00ms elapsed)
2016-07-29 22:46:29,828 eos.saveddata.fit        DEBUG    Timer - Fit: 391, RAH test - Done with fit calculation - 0.00ms (25.00ms elapsed)
2016-07-29 22:46:29,890 service.fit              DEBUG    ==========recalc==========
2016-07-29 22:46:29,890 eos.saveddata.fit        DEBUG    Starting fit calculation on: Fit(ID=395, ship=Astarte, name=RAH booster copy 2) at 0x9c8f0b0, withBoosters: True
2016-07-29 22:46:29,892 eos.saveddata.fit        DEBUG    Timer - Fit: 395, RAH booster copy 2 - Done with runtime: early - 3.00ms (3.00ms elapsed)
2016-07-29 22:46:29,905 eos.saveddata.fit        DEBUG    Timer - Fit: 395, RAH booster copy 2 - Done with runtime: normal - 13.00ms (16.00ms elapsed)
2016-07-29 22:46:29,907 eos.saveddata.fit        DEBUG    Timer - Fit: 395, RAH booster copy 2 - Done with runtime: late - 2.00ms (18.00ms elapsed)
2016-07-29 22:46:29,907 eos.saveddata.fit        DEBUG    Timer - Fit: 395, RAH booster copy 2 - Done with fit calculation - 0.00ms (18.00ms elapsed)
2016-07-29 22:46:29,913 service.fit              DEBUG    ==========recalc==========
2016-07-29 22:46:29,914 eos.saveddata.fit        DEBUG    Starting fit calculation on: Fit(ID=395, ship=Astarte, name=RAH booster copy 2) at 0x9c8f0b0, withBoosters: True
2016-07-29 22:46:29,915 eos.saveddata.fit        DEBUG    Timer - Fit: 395, RAH booster copy 2 - Done with runtime: early - 2.00ms (2.00ms elapsed)
2016-07-29 22:46:29,930 eos.saveddata.fit        DEBUG    Timer - Fit: 395, RAH booster copy 2 - Done with runtime: normal - 14.00ms (16.00ms elapsed)
2016-07-29 22:46:29,931 eos.saveddata.fit        DEBUG    Timer - Fit: 395, RAH booster copy 2 - Done with runtime: late - 2.00ms (18.00ms elapsed)
2016-07-29 22:46:29,931 eos.saveddata.fit        DEBUG    Timer - Fit: 395, RAH booster copy 2 - Done with fit calculation - 0.00ms (18.00ms elapsed)

@Ebag333 please do your testing with the new version and see if it's still sluggish. I have the distinct feeling it was the logging causing issues, since my logs were being written to an SSD and I had no such issues.

@Ebag333
Copy link
Contributor

Ebag333 commented Sep 12, 2016

@MrNukealizer @blitzmann I completely rewrote the function from the group up. It seems to run much faster now, and will loop through to dynamically adjust (so we catch the edge scenarios where the lowest damage might switch).

Seems to run in a couple hundred milliseconds, even for fits that have boosters.

If you want to test it:
#688

@Ebag333 Ebag333 mentioned this issue Sep 15, 2016
@MrNukealizer
Copy link
Contributor Author

MrNukealizer commented Oct 8, 2016

@blitzmann any chance of merging v3(#689) or v4(#737) soon? Both work well at the moment and are significantly better than the current RAH implementation. Below are the remaining concerns that may need to be dealt with:

  1. Neither version compensates for the effect of stacking penalties with Damage Controls. In many situations that makes no difference, and when it does the inaccuracy isn't significant (worst I've seen is 1.5% difference on two resistances). Since v4 is an approximation anyway, that's less of a concern there.
  2. v3 has no exception for the uniform damage profile but v4 does. @Ebag333 seems to think the RAH shouldn't calculate for the uniform damage profile and I think it should.
  3. v4 isn't perfectly accurate, but it's a good enough approximation to be a reasonable choice.
  4. There may be some performance concerns. @Ebag333's tests show v3 being quite slow, and my tests show both being an order of magnitude faster than his tests but v4 a fair bit slower than v3. In my tests neither version has ever taken more than 15ms, and the average is about 0.1ms for simple situations and under 2ms for loops. If @Ebag333's times are accurate there's cause for concern though, since he was seeing numbers over 200ms.

@blitzmann
Copy link
Collaborator

I still need to review the changes, but I will definitely merge one or the other with the next patch update. (yes I'm procrastinating :P)

@blitzmann
Copy link
Collaborator

Closing discussion as I've finally merged one of the PRs. Feel free to continue discussion about it here if desired. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion This is mostly a discussion thread
Projects
None yet
Development

No branches or pull requests

3 participants