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

Displayed protection values are misleading for multi-material armor in edge cases #78754

Open
moxian opened this issue Dec 24, 2024 · 10 comments
Labels
(S1 - Need confirmation) Report waiting on confirmation of reproducibility

Comments

@moxian
Copy link
Contributor

moxian commented Dec 24, 2024

Describe the bug

Specifically for Kevlar Jumpsuit (left):
20241224-040915-cataclysm-tiles

The armor is made out of (ignoring the weak cloth): Kevlar 1.7mm (100% coverage), Kevlar 1.0mm (90%) and Kevlar 1.0mm (90%), but the protection values displayed imply that only 81% of the body has any protection.

Compare that to Light Kevlar Jumpsuit (right) which has fewer layers (missing an extra Kevlar 1.0mm (90%)), but has much higher coverage (100%).

It looks like some code gets confused about the math somewhere

Attach save file

debug-world-trimmed.tar.gz

Steps to reproduce

Look at a Kevlar Jumpsuit info, get confused.

Expected behavior

81% of high protection and 19% of lower protection, instead of the current 1% + 80%

Screenshots

No response

Versions and configuration

  • OS: Windows
    • OS Version: 10.0.19045.5247 (22H2)
  • Game Version: 0.G-15959-g0f016e4050 [64-bit]
  • Graphics Version: Tiles
  • Game Language: System language []
  • Mods loaded: [
    Dark Days Ahead [dda],
    Disable NPC Needs [no_npc_food],
    Portal Storms Ignore NPCs [personal_portal_storms],
    Slowdown Fungal Growth [no_fungal_growth]
    ]

Additional context

No response

@moxian moxian added the (S1 - Need confirmation) Report waiting on confirmation of reproducibility label Dec 24, 2024
@moxian
Copy link
Contributor Author

moxian commented Dec 24, 2024

Oh, I see now, this more a presentation issue.
The code is here:

// go through each material and contribute its values
float tempbest = 1.0f;
float tempworst = 1.0f;
for( const part_material &mat : armor_data.materials ) {
// the percent chance the material is not hit
float cover = mat.cover * .01f;
float cover_invert = 1.0f - cover;
tempbest *= cover;
// just interested in cover values that can fail.
// multiplying by 0 would make this value pointless
if( cover_invert > 0 ) {
tempworst *= cover_invert;
}
}
// if tempworst is 1 then the item is homogenous so it only has a single option for damage
if( tempworst == 1 ) {
tempworst = 0;
}
// if not exactly 0 it should display as at least 1
if( tempworst > 0 ) {
armor_data.worst_protection_chance = std::max( 1.0f, tempworst * 100.0f );
} else {
armor_data.worst_protection_chance = 0;
}
if( tempbest > 0 ) {
armor_data.best_protection_chance = std::max( 1.0f, tempbest * 100.0f );
} else {
armor_data.best_protection_chance = 0;
}

What that window shows is that:

  • in the absolute worst possible case you will have 2.2 bash resist. This absolute worst case happens 1% of the time (10% to miss first 1mm layer times 10% to miss the second 1mm, and 100% to land on the third 1.7mm)
  • in the absolute best possible case you will have 4.2 bash resist. This absolute best case happens 81% of the time (90% times 90% times 100%), but it somehow gets displayed as 80% because of rounding or something.

The problem is that item info does not mention what happens in the other 18% cases at all, so I've always been reading that as "well, the other 18% doesn't have any protection at all!". But turns out this is not what it's trying to say...

There's a lot of empty space in that window, so perhaps we can fit in some breadcrumb implying that the unaccounted percentage is not lost, but rather "has some intermediate protection values between those two extremes". But I can't think of any way to actually present that right now...

@IdleSol
Copy link
Contributor

IdleSol commented Dec 24, 2024

Imagine an armor of 5 layers with 90% coverage at each layer. How many variations is that? I don't think it's worth the effort to try to display all the possible values. They just won't fit in this window.

Here's an example if the armor has 3 layers with 90% and 1 layer with 100% coverage (so the game doesn't swear at errors).
1

If I'm not mistaken, that's 2^3 options? For 4 layers, that's already 16 values.

@moxian
Copy link
Contributor Author

moxian commented Dec 25, 2024

Yes, displaying all the variants is unreasonable.
Perhpas using a literal ... would work well enough. Something like:

Protection:   1%, 27%, 72%
  Bash:     1.00, ..., 4.00
  Cut:      1.00, ..., 6.00

@IdleSol
Copy link
Contributor

IdleSol commented Dec 25, 2024

I was thinking about how it could be done. And a thought occurred to me. Do these calculations correspond to the real state of affairs?

Unfortunately, I can't check the code (because I can't read it). And I will have a request to you. Could you please find the code responsible for the damage calculation.

I need the part that interacts with the armor.

My assumption is that it rolls a 1d100 die once. And this value is used subsequently for all layers. (Possibly for all armor worn on that body part). In other words, for layers with the same coverage, we can't have events of the form: “hit the first layer, but didn't hit the second layer”.

So the kevlar suit from the screenshot, has bash armor:

  • 4.20 90% of the time.
  • 2.20 10% of the time.

And if I'm right, then things are much simpler.

  1. Take the armor and coverage values.
  2. For layers with the same coverage, sum up the armor value
  3. We get an array of a pair of values: armor and coverage. Sort the array in ascending order of the coverage value. The number of pairs is less than or equal to the number of layers in the object. (Conditionally N pairs)
  4. For the first element of the array (corresponds to the minimum value of coverage), we take all layers and sum up the values of armor, the chance is equal to coverage.
  5. Take the second element of the array. Sum up the armor values for 2...N elements. The chance is equal to the coverage minus the coverage value of the first element.
  6. For element i. Sum up the armor indices for i..N elements. The chance is equal to the coverage of element i minus the coverage of element i-1.
  7. Repeat until we run out of elements.

For clarity. Layers L1..L5 and their coverage values. A 1d100 cube throws 65. And the line shows the layers that are involved in the damage calculation.
1

Of course this is true only if the dice are rolled once. If it is rolled for each layer, it is not true.

Back on topic. Suggestion. Do not calculate percentages, but display the value of armor for each layer. Additionally showing the minimum and maximum value.
2
The Kevlar suit is taken as an example:
3
From the number of layers, the height of the window will increase, which is not a problem. And if we specify armor for each hit option, it won't cause placement problems.

@moxian
Copy link
Contributor Author

moxian commented Dec 25, 2024

My assumption is that it rolls a 1d100 die once. And this value is used subsequently for all layers.

I am honestly a bit struggling reading through this code in fullness, but the intent is that no, each material is calculated individually:

// reduce the damage
// -1 is passed as roll so that each material is rolled individually
armor.mitigate_damage( du, sbp, -1 );

( The "single roll" concept is also there, but that one determines whether the armor was hit at all. My reading is that it's only "single roll" for technical reasons to stop us flip-flopping between "yeah you hit us" and "no, I lied, you didn't actually" for the same body part. )


Side-note, looking at the code again, I see that we do already have neat infrastructure for displaying intermediate damage values (median, specifically), but it only kicks in when both the high-rolls and the low-rolls are sub-50% likely. It's neat, but does not address my original concern - even if we force-enable it for the suit we'd get displayed something like (specific numbers made up):

Protection:   1%, Median, 80%
  Bash:     1.00, 2.30, 4.00
  Cut:      1.00, 3.40, 6.00

which still implies that 19% of the time armor does nothing

@moxian moxian changed the title Multi-material armor protection values are very off in edge-cases Displayed protection values are misleading for multi-material armor in edge cases Dec 25, 2024
@IdleSol
Copy link
Contributor

IdleSol commented Dec 26, 2024

From what I understand, the roll value is calculated somewhere else. This value goes as an argument to the Character::armor_absorb function. Perhaps this function is called for each layer. But the roll value doesn't change. (Or it changes, but in the place where this function is called).

So I still have doubts about the correctness of the chance calculation.

@moxian
Copy link
Contributor Author

moxian commented Dec 26, 2024

The roll value that's calculated somewhere else and then passed to armor_absorb is only used here:

// if the core armor is missed then exit
if( roll > armor.get_coverage( sbp, ctype ) ) {
return false;
}
where armor.get_coverage() returns the "coverage" of the amor as seen in the UI for the corresponding (sub)body part. The coverage is 100 for both kevlar jumpsuits and it's 5 for something like a holster
20241225-183524-cataclysm-tiles

The roll value is reused between different armor items - so if an attack missed your boxer briefs that have 20% hip coverage, then it will also miss the holster that only covers 5% of the hips (and vice versa - everything hitting the holster will also hit the boxer briefs). But it has no bearing on the material layering within a single item.

@RenechCDDA
Copy link
Member

The problem is that item info does not mention what happens in the other 18% cases at all, so I've always been reading that as "well, the other 18% doesn't have any protection at all!". But turns out this is not what it's trying to say...

I would love to see this info moved to a table with clear separators. Hint hint, wink wink?

@IdleSol
Copy link
Contributor

IdleSol commented Dec 26, 2024

I was wrong. I had to run tests in the game to make sure, though.

I see that we do already have neat infrastructure for displaying intermediate damage values ...

1

This option gives little information if the armor coverage is close to 50%. However, you have already noted that:

which still implies that 19% of the time armor does nothing

UPD. And here you can see the error. The armor for the median is equal to 0, although it is not really equal.

UPD2. Just noticed.

0.9 x 0.9 = 0.81
0.1 x 0.9 = 0.09
0.9 x 0.1 = 0.09
0.1 x 0.1 = 0.01

Maximum armor is obtained 81% of the time. Minimum in 1%. Intermediate in 9 + 9 = 18%.

But the game says 80% for maximum armor? A screenshot of the Kevlar suit.

@IdleSol
Copy link
Contributor

IdleSol commented Dec 26, 2024

If I'm not mistaken anywhere, we need something like this:

# Take an item of clothing/armor.
# Create an array of covered_by_mat values and armor values (an array of armor values?).
# Sort it by descending covered_by_mat.
# For the same covered_by_mat, sum up the armor values
# We get an array of N elements. Each element L (from the word layer) represents a layer with the same covered_by_mat and associated armor value.

total_chance = 1.00
total_armor = 0

for L1 = 0 to 1 do
    total_chance *= (covered_by_mat_for_L1 - L1)
    if L1 = 0 then
        armor += armor_for_L1
    end
    for L2 = 0 to 1 do
        total_chance *= (covered_by_mat_for_L2 - L2)
        if L2 = 0 then
          armor += armor_for_L2
        end
....
            for LN = 0 to 1 do
                total_chance *= (covered_by_mat_for_LN - LN)
                if LN = 0 then
                  armor += armor_for_LN
                end
                total_chance = abs(total_chance)
#               Add total_chance and armor to the array
            end
        end
    end
end

# Sort the array by decreasing or increasing armor.
# If the armor values of different pairs are the same, combine the elements into a single element by adding up their chances.
# Output the values from the array

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
(S1 - Need confirmation) Report waiting on confirmation of reproducibility
Projects
None yet
Development

No branches or pull requests

3 participants