Imagine the following scenario:
- A weapon does D points of (constant) damage per hit.
- An armor has R damage resistance to that weapon (e.g., R = 50 means only 50% of damage taken).
- How much damage will the armor take?
In the current algorithm, we use integer division and round-off (as opposed to truncation):
damage-taken = floor(((D * R) + 50) / 100)
For large values of D, this equation leads to reasonable damage, but if D is small, then the effect of round-off becomes apparent.
Let D = 5 and R = 50. At floating point precision, we expect 2.5 points of damage per hit. But when plugged into the formula above, we get:
damage-taken = floor(((5 * 50) + 50) / 100) = floor((250 + 50) / 100) = 3
However, if D = 5 and R = 49, we expect 2.45, but end up with 2 after round off. This is exactly how round-off is supposed to work.
If R were randomly distributed, then this effect would cancel out. Unfortunately, because damage resistance is much more likely to be 50 than 49, we end up with a bias towards higher damage for low-damage weapons.
The obvious solution is to use floating-point numbers for hit points and damage, but that would require large-scale changes all through the code. Instead, we need to use a stochastic round off:
First we compute both the truncated integer value of the division, and the remainder. We then randomly add 1 to the result with frequency proportional to remainder. Something like:
damage-int = floor(D * R / 100) damage-remainder = mod(D * R, 100) if random(1, 100) <= damage-remainder damage-taken = damage-int + 1 else damage-taken = damage-int
Edit: I should mention that the error here is small. The error is worst when damage adj is around a multiple of 25 (25, 50, 75, or their partners: 24, 49, 74). At those values the error is about 15%, meaning that a low-damage weapon will do 15% more damage than it should.
But other damage adj values don't have errors that large. For something like reactive armor, which has 44 damage adj against kinetic, the error is only 2%.
Nevertheless, this is a bug in a core mechanic, and thus worth fixing.