• Log InLog In
  • Register
Liquid`
Team Liquid Liquipedia
EDT 18:46
CEST 00:46
KST 07:46
  • 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
[ASL19] Finals Recap: Standing Tall9HomeStory Cup 27 - Info & Preview18Classic wins Code S Season 2 (2025)16Code S RO4 & Finals Preview: herO, Rogue, Classic, GuMiho0TL Team Map Contest #5: Presented by Monster Energy6
Community News
Flash Announces Hiatus From ASL59Weekly Cups (June 23-29): Reynor in world title form?13FEL Cracov 2025 (July 27) - $8000 live event19Esports World Cup 2025 - Final Player Roster16Weekly Cups (June 16-22): Clem strikes back1
StarCraft 2
General
Program: SC2 / XSplit / OBS Scene Switcher Statistics for vetoed/disliked maps The SCII GOAT: A statistical Evaluation Weekly Cups (June 23-29): Reynor in world title form? PiG Sty Festival #5: Playoffs Preview + Groups Recap
Tourneys
FEL Cracov 2025 (July 27) - $8000 live event RSL: Revival, a new crowdfunded tournament series Korean Starcraft League Week 77 Master Swan Open (Global Bronze-Master 2) [GSL 2025] Code S: Season 2 - Semi Finals & Finals
Strategy
How did i lose this ZvP, whats the proper response Simple Questions Simple Answers
Custom Maps
[UMS] Zillion Zerglings
External Content
Mutation # 480 Moths to the Flame Mutation # 479 Worn Out Welcome Mutation # 478 Instant Karma Mutation # 477 Slow and Steady
Brood War
General
Flash Announces Hiatus From ASL SC uni coach streams logging into betting site BGH Mineral Boosts Tutorial Video Player “Jedi” cheat on CSL Replays question
Tourneys
[Megathread] Daily Proleagues [BSL20] Grand Finals - Sunday 20:00 CET Small VOD Thread 2.0 [BSL20] GosuLeague RO16 - Tue & Wed 20:00+CET
Strategy
Simple Questions, Simple Answers I am doing this better than progamers do.
Other Games
General Games
Stormgate/Frost Giant Megathread Nintendo Switch Thread Path of Exile What do you want from future RTS games? Beyond All Reason
Dota 2
Official 'what is Dota anymore' discussion
League of Legends
Heroes of the Storm
Simple Questions, Simple Answers Heroes of the Storm 2.0
Hearthstone
Heroes of StarCraft mini-set
TL Mafia
TL Mafia Community Thread Vanilla Mini Mafia
Community
General
Russo-Ukrainian War Thread US Politics Mega-thread Trading/Investing Thread Things Aren’t Peaceful in Palestine The Games Industry And ATVI
Fan Clubs
SKT1 Classic Fan Club! Maru Fan Club
Media & Entertainment
Anime Discussion Thread [Manga] One Piece [\m/] Heavy Metal Thread
Sports
2024 - 2025 Football Thread Formula 1 Discussion NBA General Discussion TeamLiquid Health and Fitness Initiative For 2023 NHL Playoffs 2024
World Cup 2022
Tech Support
Computer Build, Upgrade & Buying Resource Thread
TL Community
Blogs
Culture Clash in Video Games…
TrAiDoS
from making sc maps to makin…
Husyelt
Blog #2
tankgirl
StarCraft improvement
iopq
Trip to the Zoo
micronesia
Customize Sidebar...

Website Feedback

Closed Threads



Active: 597 users

miss chance, rng, and some code experiments

Forum Index > BW General
Post a Reply
quaristice
Profile Joined February 2021
112 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
Bulgaria1423 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) ^_._^
ko-fi.com/luckynoob
Mutaller
Profile Blog Joined July 2013
United States1051 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
112 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
Germany1762 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
Next event in 11h 14m
[ Submit Event ]
Live Streams
Refresh
StarCraft 2
Nina 176
NeuroSwarm 93
ProTech84
StarCraft: Brood War
firebathero 263
NaDa 52
Rock 41
LancerX 21
yabsab 6
League of Legends
Grubby3677
Dendi1401
Counter-Strike
Stewie2K100
Heroes of the Storm
Liquid`Hasu543
Khaldor220
Other Games
summit1g10101
tarik_tv5029
fl0m632
RotterdaM319
ViBE102
Organizations
Other Games
BasetradeTV32
StarCraft 2
Blizzard YouTube
StarCraft: Brood War
BSLTrovo
sctven
[ Show 21 non-featured ]
StarCraft 2
• Berry_CruncH303
• davetesta45
• musti20045 30
• tFFMrPink 19
• HeavenSC 13
• OhrlRock 3
• IndyKCrew
• AfreecaTV YouTube
• intothetv
• Kozan
• sooper7s
• LaughNgamezSOOP
• Migwel
StarCraft: Brood War
• Pr0nogo 3
• STPLYoutube
• ZZZeroYoutube
• BSLYoutube
Dota 2
• Ler114
League of Legends
• Doublelift4167
• Jankos1847
Other Games
• imaqtpie1108
Upcoming Events
RSL Revival
11h 14m
Clem vs Classic
SHIN vs Cure
FEL
13h 14m
WardiTV European League
13h 14m
BSL: ProLeague
19h 14m
Dewalt vs Bonyth
Replay Cast
2 days
Sparkling Tuna Cup
2 days
WardiTV European League
2 days
The PondCast
3 days
Replay Cast
4 days
RSL Revival
4 days
[ Show More ]
Replay Cast
5 days
RSL Revival
5 days
FEL
5 days
RSL Revival
6 days
FEL
6 days
FEL
6 days
Liquipedia Results

Completed

BSL 2v2 Season 3
HSC XXVII
Heroes 10 EU

Ongoing

JPL Season 2
BSL Season 20
Acropolis #3
KCM Race Survival 2025 Season 2
CSL 17: 2025 SUMMER
Copa Latinoamericana 4
Championship of Russia 2025
RSL Revival: Season 1
Murky Cup #2
BLAST.tv Austin Major 2025
ESL Impact League Season 7
IEM Dallas 2025
PGL Astana 2025
Asian Champions League '25
BLAST Rivals Spring 2025
MESA Nomadic Masters
CCT Season 2 Global Finals
IEM Melbourne 2025

Upcoming

2025 ACS Season 2: Qualifier
CSLPRO Last Chance 2025
2025 ACS Season 2
CSLPRO Chat StarLAN 3
K-Championship
uThermal 2v2 Main Event
SEL Season 2 Championship
FEL Cracov 2025
Esports World Cup 2025
StarSeries Fall 2025
FISSURE Playground #2
BLAST Open Fall 2025
BLAST Open Fall Qual
Esports World Cup 2025
BLAST Bounty Fall 2025
BLAST Bounty Fall Qual
IEM Cologne 2025
FISSURE Playground #1
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 © 2025 TLnet. All Rights Reserved.