-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Feral rock throwing target distribution still has room for improvement #80151
Comments
Also while rock throwing draws the most attention, possibly having to do with how it is perceived to behave outside the expectations of the average player, this may possibly be indicative of deeper problems with target body part selection expectations across other ranged attack situations as well. |
Tl;dr 90%+ hit rate to torso is totally what we expect, the rest of this explains why. Rule of nines is surface area, the silhouette that we're concerned about in ballistics is different. Surface area overstates the proportion of smaller limbs relative to torso. The actual process we're modeling is bullet dispersion relative to an aim point, which is aggressively normal, i.e. the vast majority of hits are very close to the target point and the number of hits that land further and further from the target point drop off very rapidly. This means if you are close enough to get reliable hits, nearly all of them will be in the center of the target rather than in the edges, despite the edges having a larger overall surface area. Putting this another way: Hits are not distributed by surface area, they are distributed based on distance from the target point. The most extreme example here is torso vs legs. Putting both legs together the surface area is about the same as the torso, but the legs are so far from the aim point we are using that only incredibly wild shots are hitting the legs at all, and not many of those. If you want to change this, you need to present a ballistics oriented argument for what those proportions actually should be. What this roughly looks like is draw concentric circles around the aim point (roughly center of mass or upper middle torso), and for each section, evaluate both what proportion of rounds fall within that area, then evaluate what proportion of that proportion is allocated to each body part. |
Thanks for the detailed explanation! I get the gist of it, but I'm still having trouble reconciling that with the .36 overall hit rate. Besides that, there's still a huge difference between even 90% and 95% and 97%. I'll try to look into this more and see if I can fit the data into an improved model. |
For distance = 5, n = 10000, I see a 90% torso hit rate with 5532 shots hitting, with the following distribution of
data: out.csv |
Upgraded my little tool a bit. Can now be biased and draw data points inward towards the target point. The bias controls both the percentage of dots affected and also the percentage lerp from their original position to the target position. With zero bias, points are distributed with a random angle and random distance up to the defined (precision) radius, which already favors/concentrates shots close to the target naturally. At max bias, all points are equal to the target point. After adding significant bias, with a 90% torso rate (torso hits/total hits), there's no way the overall accuracy (total hits/attempts) is only 36%. When trying to fit an overall accuracy of 36%, with or without bias, the torso rate isn't going above 70% In conclusion, a 36% overall accuracy rate should not result in 97% of the hits being torso hits. Anothersimulacrum, how did you get 90%? When I used the modified monster throwing sanity test, I consistently got over 95%. So if it's supposed to be 90% of hits are torso hits, that's not what is happening in the sanity test. And if 90% of hits being torso hits is desired, the overall hit rate should to be closer to 80% to fit, instead of sub 40%. Then it wouldn't fit the existing hit rate/damage expectations. To preserve the existing damage expectation, the existing hit rate should remain as is, and the proportion of torso hits be reduced to 65%-75% of all hits. Tool is here - https://oosyrag.github.io/c3ex/ |
I have a theory regarding the monster throwing sanity test - do limbs deflect at a higher rate than torso? Deflections are counted as misses in that test, as hits are counted when damage is done. |
The cause of the discrepancy is that Also, your hit percentage logic is wrong. I get the following:
When my logging reports 5414 calls to
data: out.csv With this patchdiff --git a/src/ballistics.h b/src/ballistics.h
index a1394f43a5..9f62199add 100644
--- a/src/ballistics.h
+++ b/src/ballistics.h
@@ -136,6 +136,7 @@ class targeting_graph
}
T select( const double range_min, const double range_max, double value ) const {
+ FILE *fp = fopen( "out.csv", "a" );
// First, find the path we will follow
// That is, what body parts we will hit with less and less accurate shots
std::list<const node *> path;
@@ -157,6 +158,7 @@ class targeting_graph
// Or, if range_min is -0.5, range_max is 1.5, this will put value into the range 0-2
// And below, we'll have a scale factor of 2 / total_weight
value -= range_min;
+ fprintf( fp, "%.02f,", value );
// Now, find the total weight along our path
double total_weight = 0.0;
@@ -169,6 +171,8 @@ class targeting_graph
double scale_factor = ( range_max - range_min ) / total_weight;
// Then, just walk along the path
double accumulated_weight = 0.0;
+ fprintf( fp, "%.02f,%.02f,", total_weight, scale_factor );
+ fclose( fp );
for( const node *nd : path ) {
accumulated_weight += nd->weight * scale_factor;
// And quit when we've gone far enough that we can't make it to the next part
@@ -177,9 +181,12 @@ class targeting_graph
}
}
+
// And if we made it all the way here, we (somehow) hit with a very inaccurate shot
return path.back()->val;
}
};
+extern bool MYTEST;
+
#endif // CATA_SRC_BALLISTICS_H
diff --git a/src/creature.cpp b/src/creature.cpp
index 79806c92a1..58789f80a9 100644
--- a/src/creature.cpp
+++ b/src/creature.cpp
@@ -1128,10 +1128,13 @@ struct projectile_attack_results {
}
};
+bool MYTEST = true;
+
projectile_attack_results Creature::select_body_part_projectile_attack(
const projectile &proj, const double goodhit, const bool magic,
const double missed_by, const weakpoint_attack &attack ) const
{
+ MYTEST = true;
projectile_attack_results ret( proj );
const float crit_multiplier = proj.critical_multiplier;
const float std_hit_mult = std::sqrt( 2.0 * crit_multiplier );
@@ -1141,6 +1144,7 @@ projectile_attack_results Creature::select_body_part_projectile_attack(
// 40% true when goodhit = 0, 20% true when goodhit = 0.2
bool crit_roll = hit_roll * 0.5 < accuracy_critical;
const monster *mon = as_monster();
+ FILE *fp = fopen( "out.csv", "a" );
if( mon ) {
fatal_hit = ret.max_damage * crit_multiplier > get_hp_max();
ret.wp = mon->type->weakpoints.select_weakpoint( attack );
@@ -1155,6 +1159,7 @@ projectile_attack_results Creature::select_body_part_projectile_attack(
}
// Range is -0.5 to 1.5 -> missed_by will be [1, 0], so the rng addition to it
// will push it to at most 1.5 and at least -0.5
+ fprintf( fp, "%.02f,", hit_value );
ret.bp_hit = get_anatomy()->select_body_part_projectile_attack( -0.5, 1.5, hit_value );
crit_mod = get_crit_factor( ret.bp_hit );
fatal_hit = ret.max_damage * crit_multiplier > get_hp_max( ret.bp_hit );
@@ -1192,6 +1197,10 @@ projectile_attack_results Creature::select_body_part_projectile_attack(
// ( 0.05, 0.25 ) when goodhit = 0.8, 0.05 when nears 1.0
ret.damage_mult *= 1.05 - hit_roll;
}
+
+ fprintf( fp, "%.02f,%s\n", missed_by, ret.bp_hit.id().c_str() );
+ fclose( fp );
+
add_msg_debug( debugmode::DF_CREATURE, "crit_damage_mult: %f", ret.damage_mult );
return ret;
}
diff --git a/tests/monster_attack_test.cpp b/tests/monster_attack_test.cpp
index 22e40ec366..25bbf5862a 100644
--- a/tests/monster_attack_test.cpp
+++ b/tests/monster_attack_test.cpp
@@ -5,6 +5,7 @@
#include <string>
#include <vector>
+#include "ballistics.h"
#include "bodypart.h"
#include "calendar.h"
#include "cata_catch.h"
@@ -226,6 +227,8 @@ TEST_CASE( "monster_throwing_sanity_test", "[throwing],[balance]" )
REQUIRE( rl_dist( test_monster.pos_abs(), target->pos_abs() ) <= 5 );
statistics<int> damage_dealt;
statistics<bool> hits;
+ statistics<bool> misses;
+ statistics<bool> torso_hits;
epsilon_threshold threshold{ expected_damage, 2.5 };
do {
you.set_all_parts_hp_to_max();
@@ -234,20 +237,34 @@ TEST_CASE( "monster_throwing_sanity_test", "[throwing],[balance]" )
you.clear_effects();
you.set_dodges_left( 1 );
int prev_hp = you.get_hp();
+ int prev_torso_hp = you.get_hp( bodypart_id( "torso" ) );
// monster shoots the player
+ FILE *fp = fopen( "out.csv", "a" );
+ if( MYTEST ) {
+ fprintf( fp, "%d,", distance );
+ MYTEST = false;
+ }
+ fclose( fp );
REQUIRE( attack->call( test_monster ) == true );
// how much damage did it do?
// Player-centric test in throwing_test.cpp ranges from 2 - 8 damage at point-blank range.
int current_hp = you.get_hp();
+ int torso_hp = you.get_hp( bodypart_id( "torso" ) );
hits.add( current_hp < prev_hp );
damage_dealt.add( prev_hp - current_hp );
+ misses.add( current_hp == prev_hp );
+ torso_hits.add( torso_hp < prev_torso_hp );
test_monster.ammo[ itype_rock ]++;
- } while( damage_dealt.n() < 100 || damage_dealt.uncertain_about( threshold ) );
+ } while( damage_dealt.n() < 10000 || damage_dealt.uncertain_about( threshold ) );
clear_creatures();
CAPTURE( expected_damage );
CAPTURE( distance );
- INFO( "Num hits: " << damage_dealt.n() );
+ INFO( "Attempts: " << damage_dealt.n() );
+ INFO( "Hits: " << hits.sum() );
+ INFO( "Misses: " << misses.sum() );
INFO( "Hit rate: " << hits.avg() );
+ INFO( "Torso hits: " << torso_hits.sum() );
+ INFO( "% Torso hits: " << torso_hits.sum() / hits.sum() << " (expected 0.7222?)" );
INFO( "Avg total damage: " << damage_dealt.avg() );
INFO( "Dmg Lower: " << damage_dealt.lower() << " Dmg Upper: " << damage_dealt.upper() );
CHECK( damage_dealt.test_threshold( threshold ) ); |
It is possible target bp selection should use goodhit instead, but that has a lower bound of 0.19, producing the same problem from the opposite direction. |
The chain producing that: Cataclysm-DDA/src/ballistics.cpp Lines 215 to 228 in 21b6691
Cataclysm-DDA/src/ballistics.cpp Lines 266 to 276 in 21b6691
Cataclysm-DDA/src/ballistics.cpp Lines 516 to 529 in 21b6691
For any projectile attack that has a chance of missing, if we perform enough trials, then we should see the whole range (perfect hit to near miss) of the hit values we pass to the body part selection. Right now with the bounds we assume we only see the better 80% of that range, not the full range. We need to somehow ensure the bounds are correct (do a bunch of math and understand the chain of random rolls that get us to the missed_by/goodhit values), and ensure the correct bounds are given to body part selection. The good news is most of the rolls seem to happen in dispersion, and we don't care about odds - only possible values, which are clamped at various places to be >= 0 and <= 1. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. Please do not bump or comment on this issue unless you are actively working on it. Stale issues, and stale issues that are closed are still considered. |
Is your feature request related to a problem? Please describe.
Whenever feral rock throwing gets brought up, discussion is usually shut down quickly due to "tests for maintaining the desired outcome" being a thing that exists. After looking at said test (monster_throwing_sanity_test), it seems to test for the average total damage at a given range, with overall hit rate noted. It does not take into account which body part gets hit.
Following that, the next thing that gets mentioned is that data or testing is needed. So I modified the test a bit to track when the torso takes damage, as opposed to when any damage is taken. The result:
At distance 5, nearly 97% (this varied between 95-97% after repeated testing) of hits that did damage (36% overall accuracy) landed on the torso. While it debunks the misconception that ferals can't miss (not even close), and that they always hit the torso (they don't, but it comes really close), the proportion of torso hits is decidedly uncanny. Well above the 72% from #56104, and waaaay above the actual proportion of the torso consists of the body as well (36% from the direct front or back. Note that this number goes down, significantly, at basically any other angle. But lets not worry about that for now). It's exacerbated by the fact that small changes at high percentages are disproportionately perceivable - 75% is 3/4, while 95% is 19/20.
"Aiming for the torso" concentrating hits on the torso doesn't seem to be suitable or apply very well, considering the overall accuracy of 36% of damage dealing hits out of attempts.
As the range gets shorter, the overall accuracy improves (great, as expected), but the proportion of torso shots also increases (also reasonable, just that the base rate was too high to begin with).
My modified test is at master...oosyrag:Cataclysm-DDA:feralthrowtest#diff-1c2e09fb3aaac49cccf9a37b2eff695db19c67a7e6d36caa3f9b406b350db0f6. Maybe I did something wrong or missing something obvious?
Solution you would like.
Damage distributed more reasonably across the body. How to go about implementing this I dunno. Haven't gotten around to thinking about that part yet, especially considering and respecting other "expectations and desired outcomes".
Personal desired result: Less backpack repairs due to rock impacts.
Describe alternatives you have considered.
No response
Additional context
Edit: Removed infographic regarding burn rule of 9s, which is irrelevant here.
The text was updated successfully, but these errors were encountered: