diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/IMatteryLevel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/IMatteryLevel.kt index 583febb3c..383f1c521 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/IMatteryLevel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/IMatteryLevel.kt @@ -5,13 +5,25 @@ import net.minecraft.world.level.Level import net.neoforged.fml.ModList interface IMatteryLevel { - /** - * OTM provided [RandomSource], which has better statistical parameters - * - * Original Minecraft use LCG, which may show bad behavior when repeatedly sampled *a lot*, - * which is what [Level]'s random is used for. OTM provided PRNG should behave better in this scenario. - */ val otmRandom: RandomSource? } +/** + * OTM provided [RandomSource], which has better statistical parameters + * + * Original Minecraft use LCG, which may show bad behavior when repeatedly sampled *a lot*, + * which is what [Level]'s random is used for. OTM provided PRNG should behave better in this scenario. + * + * The way OTM uses random generator in its code will quickly cause LCG used in Minecraft to show its bias + * because LCG in minecraft samples its highest 48 bits, which gives us at best 2^16 period in the lowest bit returned by LCG. + * Which it doesn't sound bad, it quickly causes RNG become biased the quicker/more it is sampled on each tick, especially considering + * some may use `level.random.nextInt(chance) == 0` to determine chance of something happening, + * which will get extremely biased on heavy RNG congested environment + * If we avoid sampling Level's generator this much, we won't suffer from bias in our own code, as well as avoid biasing other mods this much. + * + * The "2^16 period" problem is also might be the reason why Entities get their own instance of RandomSource, + * and Mob Goals use random exactly the way described above (`nextInt(chance)`), which can and will suffer + * from bias the moment mob exists in world for more than 2^16 ticks (but actual bias will happen sooner + * because RNG is not sampled only once per tick, obviously) + */ val Level.otmRandom: RandomSource get() = (this as IMatteryLevel).otmRandom ?: random