• Log InLog In
  • Register
Liquid`
Team Liquid Liquipedia
EDT 08:54
CET 13:54
KST 21:54
  • Home
  • Forum
  • Calendar
  • Streams
  • Liquipedia
  • Features
  • Store
  • EPT
  • TL+
  • StarCraft 2
  • Brood War
  • Smash
  • Heroes
  • Counter-Strike
  • Overwatch
  • Liquibet
  • Fantasy StarCraft
  • TLPD
  • StarCraft 2
  • Brood War
  • Blogs
Forum Sidebar
Events/Features
News
Featured News
WTL 2023 Summer - Qualifiers Preview + Power Rank5[Interview] Dewalt18Tournament Spotlight: Crowdfunded Pre-Season Events15[ASL15] Ro24 Preview: Welcome Back!24A Tidal Wave in Still Water - Oliveira at IEM Katowice22
Community News
Liquibet SC2 Season 27 Recap11SCboy: 2023 Tournament Plans8ESL Open Cup #166: Dark, MaxPax, ByuN win4Classic, DRG, Nice, and Strange join Mystery Gaming13Team DPG and KZ Merge, rebranded as DKZ.23
StarCraft 2
General
WTL 2023 Summer - Qualifiers Preview + Power Rank Tournament Spotlight: Crowdfunded Pre-Season Events The Death of Korean SC2, and Where We Go From Here Liquibet SC2 Season 27 Recap SCboy: 2023 Tournament Plans
Tourneys
Initial DEX offering-fundraising works like magic [WTL 2023] Summer Qualifier and Code A $10,000 PIG STY FESTIVAL 3.0! (March 15-19) Ukrainian Cup Powered By Hot Headed Gaming Playoff Kung Fu Cup and Master's Coliseum Return for 2023
Strategy
[H] (PvP) WTF Nexus rush into recall probe/zealot Neural parasite on disruptors?
Custom Maps
[A] Proxy Rush [A] SC Real Scale [A] (Minigames) Raynor Party [D] Planning to host a small map tournament
External Content
Mutation # 361 And Drops and Rifts Mutation # 360 Double Trouble Mutation # 359 Enhanced Defenses Mutation # 358 The Ascended
Brood War
General
[Update] ShieldBattery: League Support! BGH auto balance -> http://bghmmr.com/ Jumper vs Julia aka BlackmanPL showmatch March 26 The uncertainty behind FlaSh's Return; In-depth [Interview] Dewalt
Tourneys
[Megathread] Daily Proleagues [BSL16] RO3 - SemiFinals - Sunday 18:00 CET Copa Latinoamericana StarCraft by OliPatrick Small VOD Thread 2.0
Strategy
Carriers or arbiters Starcraft Remastered Build Orders February 2023 Marine rate of fire
Other Games
General Games
Diablo IV Nintendo Switch Thread Frost Giant announce Stormgate Destiny 2 - PC/Xbox/PS4 Path of Exile
Dota 2
Official Dota and Chess General Discussion Lima Major 2023
League of Legends
LiquidLegends to reintegrate into TL.net [Patch Notes] Release General Discussion
Heroes of the Storm
HotS: WP and Funny Moments
Hearthstone
TL Mafia
TL Mafia Community Thread Liquid Arcanon News [0]Paper Team Liquid Maria L TL Mafia Idea Factory Chezinu streak(s) Mafia
Community
General
US Politics Mega-thread Trading/Investing Thread Russo-Ukrainian War Thread The Chess Thread YouTube Thread
Fan Clubs
The Phredxor Fan Club The Scarlett Fan Club The Clem Fan Club
Media & Entertainment
Anime Discussion Thread [Manga] One Piece Korean Music Discussion [\m/] Heavy Metal Thread [TV] HBO The Last Of Us series
Sports
2022 - 2023 Football Thread Formula 1 Discussion 2021 NFL/CFB Season UFC/MMA Discussion Thread NBA General Discussion
World Cup 2022
FIFA World Cup Qatar 2022 Thread
Tech Support
how to play music while streaming with xsplit Computer Build, Upgrade & Buying Resource Thread
TL Community
Recent Gifted Posts Happy Birthday R1CH! Ask TL Staff Anything
Blogs
Return of CranKy Du…
CranKy Ducklings
TL Currency Converter Mafia…
Minely
An ex-schizoid's u…
ApatheticSchizoid
20 years plus!
FuDDx
ASL 15 English Commentary…
namkraft
Teaching StarCraft Blog …
Lovethelord
Why Liquipedia needs Notabi…
FO-nTTaX
Customize Sidebar...

Website Feedback

Closed Threads



Active: 1129 users

miss chance, rng, and some code experiments

Forum Index > BW General
Post a Reply
quaristice
Profile Joined February 2021
83 Posts
January 29 2023 14:23 GMT
#1
TLDR: miss chance basically works correctly/as expected as far as i can tell on deeper investigation? just it's nice to have some confirmation

so i think many people here probably know of the 1/256 chance "airshot" miss chance, as well as the 120/256 miss chance against uphill/under cover (non-stacking), but if you're not familiar, please check out the liquipedia page. TL user StaticNine also tested those numbers ingame in the past on their blog here.

DISCLAIMER: I'M NOT A STATISTICIAN OR ANYTHING RESEMBLING ONE AND PROBABILITY IS HARD

i haven't really had a chance to play much in the past few months because i've been moving continents and my ergonomic situation has been awful (bad furniture). and i don't want to aggravate my RSI. so i guess i am doing this type of shit instead of getting better at the game. but i got a bit curious watching a stream since i saw one of those 1/256 airshots happen. i feel like it's so rare to see them during gameplay, rarer than 1/256. i wanted to investigate and see if i could find any evidence to back this up.

i did this research based on the OpenBW github, so thanks to them.

so the RNG in BW is a linear congruential generator
specifically, it's the same as the LCG RNG from Borland C++.

Here's the relevant bits of code:
+ Show Spoiler +

https://github.com/OpenBW/openbw/blob/master/bwgame.h#L13187

st.lcg_rand_state = st.lcg_rand_state * 22695477 + 1;
return (st.lcg_rand_state >> 16) & 0x7fff;


https://github.com/OpenBW/openbw/blob/master/bwgame.h#L14370

fp8 unit_dodge_chance(const unit_t* u) const {
if (u_flying(u)) return 0_fp8;
if (unit_is_under_dark_swarm(u)) return 255_fp8;
if (st.tiles[tile_index(u->sprite->position)].flags & tile_t::flag_provides_cover) return 119_fp8;
return 0_fp8;
}

fp8 unit_target_miss_chance(const unit_t* u, const unit_t* target) const {
fp8 r = unit_dodge_chance(target);
if (!u_flying(u) && !u_flying(target)) {
if (get_ground_height_at(target->sprite->position) > get_ground_height_at(u->sprite->position)) {
if (r < 119_fp8) r = 119_fp8;
}
}
return r;
}

https://github.com/OpenBW/openbw/blob/master/bwgame.h#L14438

fp8::from_raw(lcg_rand(1) & 0xff) <= unit_target_miss_chance(bullet_owner_unit, target_unit)



the key thing in that code is the <=, and 0 for just normal, and the 119 for shooting up a cliff. the way the RNG is used there, gives a value from [0 to 255]. because of the <= have the equal in there, when the RNG rolls 0, you get a miss no matter what.

i did a bunch of testing by writing some rust code to replicate the rng, with different starting seeds.

the code is in the spoiler below if you want it, or if you just want to fuck around with it online, here's a rust playground link
+ Show Spoiler +


use std::num::Wrapping;

fn main() {
/////////////////////////////////////////////////////////////////
// constants to tweak here
let reroll_count = 65536; // how much should we reroll off a starting seed

// 0 normally, 119 for shooting uphill/under trees
let target = 0;
//let target = 119;

// -trial_range to trial_range as seed
let trial_range = 10000;

// how many misses in a row is weird
let outlier_chain_length = 10;

// the LCG seems to behave very mildly worse centered around initial seed 0
// so this gets added to +-trial_range
let trial_offset = 0;
// no more constants
/////////////////////////////////////////////////////////////////



// finding outliers
let expected_to_find = (reroll_count * (target+1)) / 256;

let found_threshold = expected_to_find / 4;

let found_threshold_high = expected_to_find + found_threshold;
let found_threshold_low = expected_to_find - found_threshold;
//


let mut found_count = 0;
let mut trial_count = 0;
let mut sequential_found_count = 0;
let mut sequential_not_found_count = 0;

let mut unusual_chain_count = 0;
let mut longest_chain = 0;

let mut loop_body = |start_state| {
let prev_found = found_count;

let mut lcg_rand_state = Wrapping(start_state);

let mut previous_roll_was_found = false;
let mut chain_length = 0;

for _ in 0..reroll_count
{
let (new_state, result) = lcg_rand(lcg_rand_state);

lcg_rand_state = new_state;

if (result & Wrapping(0xFF)) <= Wrapping(target)
{
found_count += 1;

if previous_roll_was_found {
sequential_found_count += 1;
}

previous_roll_was_found = true;
chain_length += 1;
} else {
if !previous_roll_was_found {
sequential_not_found_count += 1;
}

previous_roll_was_found = false;

if chain_length > outlier_chain_length {
unusual_chain_count += 1;
}

if chain_length > longest_chain {
longest_chain = chain_length;
}
chain_length = 0;
}
}

// finding outliers
let delta_found = found_count - prev_found;
if delta_found > found_threshold_high || delta_found < found_threshold_low {
println!("unusual number {} found for start seed {} outside range {} to {} centered around {}", delta_found, start_state, found_threshold_low, found_threshold_high, expected_to_find);
}

trial_count += 1;
};

// uncomment this and comment out the loop to test a specific seed
//loop_body(7);

for start_state
in -trial_range+trial_offset..trial_range+trial_offset
{
loop_body(start_state);
}

let total_count = trial_count * reroll_count;
println!(
"{} instances found in {} trials of {} rolls ({} total rolls)",
found_count, trial_count, reroll_count, total_count);
println!("{} were sequential misses", sequential_found_count);
println!();

//let reroll_count = reroll_count as f64;
let found_count = found_count as f64;
let total_count = total_count as f64;
let miss_rate = (found_count / total_count) * 100.0;
let miss_in = total_count / found_count;
let expected_rate = ((target as f64 + 1.0)/256.0) * 100.0;
println!("\n\t{}% miss rate", miss_rate);
println!("or roughly 1 in {}", miss_in as usize);
println!("in contrast to\n\t{}% expected", expected_rate);
println!("off by\n\t{}%", miss_rate - expected_rate);

println!();

let sequential_found_count = sequential_found_count as f64;
let sequential_miss_rate = sequential_found_count/found_count * 100.0;
let sequential_miss_in = found_count / sequential_found_count;

println!("{}% sequential miss rate",sequential_miss_rate);
println!("or roughly 1 in {}", sequential_miss_in as usize);
println!("in contrast to\n\t{}% expected", expected_rate);
println!("off by\n\t{}%", sequential_miss_rate - expected_rate);

println!();
let sequential_not_found_count = sequential_not_found_count as f64;
let sequential_hit_rate = sequential_not_found_count/(total_count-found_count) * 100.0;
println!("{}% sequential hit rate",sequential_hit_rate);
println!("in contrast to\n\t{}% expected", 100.0-expected_rate);
println!("off by\n\t{}%", sequential_hit_rate - (100.0-expected_rate));

println!();
println!("found {} chains of {} or more misses in a row", unusual_chain_count, outlier_chain_length);
println!("longest chain was {} misses in a row", longest_chain);
}


fn lcg_rand(lcg_rand_state : Wrapping<i32>) -> (Wrapping<i32>, Wrapping<i32>) {
let lcg_rand_state = lcg_rand_state * Wrapping(22695477) + Wrapping(1);

(lcg_rand_state, (lcg_rand_state >> 16) & Wrapping(0x7fff))
}


so for just raw percentages, everything seems fine. the biggest unexpected results was the uphill miss results for starting seed 2 being off by 0.37~% in 65535 rolls, which, is really not much at all.

next i examined "probability of a miss if the last roll was a miss" and "probability of a hit if the last roll was a hit", and i had roughly similar results. this assumes that there aren't other RNG rolls in the meantime, which is a pretty significant assumption. again some random seeds are a tiny bit "off" here, for example starting seed 4 with 65535 rolls gives is "off" by 0.72~% repeated successful hits against uphill. pretty mild stuff.

so yeah. idk. seems like it works as described. 1/256 chance and everything. if there's something weird, it might be something more periodic like "once every 4th roll". idk. or something i missed. i just really feel instinctively that 1/256 should happen more often than it actually does, it's real weird. like it's 0.390625%, right? and i compare it to rolling two d20s in a row and getting a 20 on both and that's 0.25%, which, if you've played any of those d20-using tabletop rpgs, i swear is more common than the starcraft "airshot". so what's up with that. maybe it's just that they're more memorable in other contexts and the airshots are usually not super noticeable.
LUCKY_NOOB
Profile Blog Joined June 2013
Bulgaria1016 Posts
January 30 2023 16:08 GMT
#2
StaticNine: "Even during Misses the zergling still received splash damage." Rigged against Zerg!

If you ask most Ts and Ps would say misses happen too often in their opinion.

I hope ur tests confirm nothing is amiss. (pun intended) ^_._^
NO HUNTERS FREE FOR ALL Discord: https://discord.gg/kWNQvnd
Mutaller
Profile Blog Joined July 2013
United States901 Posts
January 30 2023 18:13 GMT
#3
When looking at the air shot there could be a possibility that it feels lower than the dnd two d20s in a row, because how often does a player play with a singular air unit? The single air unit is going to be hard to notice. I love microing mutas and I think I probably have blamed my self for my mutas not all attacking on my micro rather than the mischance
"To practice isn't for you to get better now in the present. Practice will never betray you and will always come back for you in the future." -Jaedong
quaristice
Profile Joined February 2021
83 Posts
January 30 2023 20:42 GMT
#4
On January 31 2023 03:13 Mutaller wrote:
When looking at the air shot there could be a possibility that it feels lower than the dnd two d20s in a row, because how often does a player play with a singular air unit? The single air unit is going to be hard to notice. I love microing mutas and I think I probably have blamed my self for my mutas not all attacking on my micro rather than the mischance

i should've been more clear, the 1/256 airshot miss only occurs on same-height no-tree ground vs ground fighting.
the name is kind of confusing, just the liquipedia page and old TL.net threads from 10+ years ago call it an airshot.
LML
Profile Blog Joined March 2007
Germany1610 Posts
January 30 2023 22:46 GMT
#5
The air shot happens every time an SCV has 10 hp left and your goon fires a shot and you already tell it to go somewhere else while its energy ball is still in the air.
LML
Please log in or register to reply.
Live Events Refresh
World Team League
12:00
Qualifier Day 1
RotterdaM802
CranKy Ducklings214
LiquipediaDiscussion
[ Submit Event ]
Live Streams
Refresh
StarCraft 2
RotterdaM 802
OGKoka 344
IndyStarCraft 146
Ryung 94
BRAT_OK 74
SC2Nice 66
Forgg! 64
Ragnarok 15
StarCraft: Brood War
Sea 12932
Calm 4577
Horang2 1982
Shuttle 1937
Mini 953
Light 819
Hyuk 580
Soma 370
ZerO 328
Stork 237
[ Show more ]
ToSsGirL 193
Larva 173
Leta 172
Rush 131
hero 109
Mong 103
Mind 98
ggaemo 96
Shinee 92
ZerO(Twitch) 83
Hyun 71
Zeus 54
Sharp 37
Barracks 29
Noble 21
zelot 9
Shine 8
ajuk12(nOOB) 7
Hm[arnc] 5
Dota 2
qojqva1170
XcaliburYe691
Attackerdota312
Fuzer 178
febbydoto98
Counter-Strike: Global Offensive
olofmeister2187
Other Games
Stewie2K5492
singsing3252
Liquid`RaSZi1230
Pyrionflax761
Beastyqt619
Livibee448
crisheroes400
Crank 319
XaKoH 221
ToD134
Hui .94
QueenE64
nookyyy 17
Organizations
Dota 2
PGL Dota 2 - Main Stream3258
Other Games
B2W.Neo2153
Counter-Strike: Global Offensive
ESL CS:GO1995
StarCraft: Brood War
Kim Chul Min (afreeca) 730
StarCraft 2
Esl_sc2131
ESL.tv131
StarCraft: Brood War
StarcraftVOD1
StarCraft 2
Blizzard YouTube
StarCraft: Brood War
BSLTrovo
[ Show 16 non-featured ]
StarCraft 2
• Migwel
• Alpha X_
• aXEnki
• intothetv
• Gussbus
• Poblha
• Kozan
• IndyKCrew
• LaughNgamez Trovo
• Laughngamez YouTube
StarCraft: Brood War
• HerbMon 3
• sscaitournament2
• STPLYoutube
• AfreecaTV YouTube
• BSLYoutube
League of Legends
• Lourlo885
Upcoming Events
ESL Pro Tour
4h 6m
ESL Pro Tour
10h 6m
Afreeca Starleague
1d 21h
Royal vs Shine
Jaedong vs Action
Afreeca Starleague
2 days
Rush vs Barracks
Queen vs JyJ
WardiTV Winter Champion…
2 days
RagnaroK vs HonMonO
NightMare vs Kelazhur
PassionCraft
3 days
Korean StarCraft League
3 days
WardiTV Winter Champion…
3 days
HeRoMaRinE vs Gerald
Elazer vs INnoVation
Sniper's StarCraft League
5 days
ESL Pro Tour
5 days
[ Show More ]
BSL: ProLeague
6 days
Bonyth vs TBD
Amantes de StarCraft 2
6 days
Liquipedia Results

Completed

Ultimate Battle: Snow vs BarrackS
PiG Sty Festival 3.0
Tournament by teenyeu #2
CCT Central EU Malta Finals

Ongoing

FS Mania
CWCL Season 6
BWCL Season 58
Copa Latinoamericana
ASL Season 15
Individual Silver League
Spring Cup Season 4: China
KCM Ladies Race Survival 2023 Season 1
KCM Race Survival 2023 Season 1
BSL Season 16
Spring Cup Season 4
WardiTV Winter 2023
NGS Storm Division S6
Calamity Cup Division A - Season 5
META Madness #7
ESL Pro League Season 17
ESL Challenger League S44 NA
ESL Challenger League S44 EU
ESL Challenger League S44 AP

Upcoming

CHN vs KOR Week35
KOR-CHN Invitational League 10: Organ vs Kid
WTL 2023 Summer
WardiTV Korean Royale
LTK Thunderball
BLAST.tv Paris Major 2023
ESL Challenger Melbourne 2023
IEM Rio 2023
BLAST.tv Paris 2023: EU RMR B
BLAST.tv Paris 2023: EU RMR A
BLAST.tv Paris 2023: APAC RMR
BLAST.tv Paris 2023: AME RMR
BLAST Premier Spring AME Showdown
BLAST Premier Spring EU Showdown
TLPD

1. ByuN
2. TY
3. Dark
4. Solar
5. Stats
6. Nerchio
7. sOs
8. soO
9. INnoVation
10. Elazer
1. Rain
2. Flash
3. EffOrt
4. Last
5. Bisu
6. Soulkey
7. Mini
8. Sharp
Sidebar Settings...

Advertising | Privacy Policy | Terms Of Use | Contact Us

Original banner artwork: Jim Warren
The contents of this webpage are copyright © 2023 TLnet. All Rights Reserved.