Skip to main content

dicebag/
lib.rs

1//! Dice rolling!
2//! 
3//! In most cases, anything from `i8`/`u8` up to `i128`/`u128`
4//! and `usize` is supported (alongside `f32`/`f64` for a few
5//! functions).
6//! 
7//! # The Extensions
8//! 
9//! ## `DiceExt`
10//! 
11//! Covers the core intent dice rollings, e.g. `3.d6()`, `2.d10()`.
12//! 
13//! ```
14//! use dicebag::DiceExt;
15//! let a = 3.d6();
16//! let b = 2_u8.d4();
17//! let c = 5.d(a-3); // FYI: zero as dice size results in 0, no matter the number of dice...
18//! ```
19//! 
20//! ## `HiLo`
21//! 
22//! Coin flipping, using whatever datatype you implement it for…
23//! Comes with two convenience macros so that you don't need to
24//! write those yourself:
25//! ```
26//! use dicebag::{DiceExt, HiLo, lo, hi};
27//! if lo!() {/* do something if result was "low" */}
28//! if hi!() {/* do something if result was "high" */}
29//! ```
30//! 
31//! ## `InclusiveRandomRange`
32//! 
33//! To get a random value within given range.
34//! ```
35//! use dicebag::InclusiveRandomRange;
36//! let range = 6..=12;
37//! let v = range.random_of();
38//! ```
39//! 
40//! ## `RandomOf<T>`
41//! 
42//! A trait to get some random entry of e.g. [Vec].
43//! 
44//! Just make sure your container has at least one entry in it as otherwise
45//! things will catch fire (panic). `.random_of()` really can't choose
46//! a random element out of nothing given…
47//! ```
48//! use dicebag::RandomOf;
49//! let v = vec![2,4,6,8,10];
50//! let x: i32 = v.random_of();
51//! 
52//! #[derive(Clone)]
53//! struct Abc { tag: String };
54//! let abc = vec![Abc{tag:"a".into()}, Abc{tag:"b".into()}, Abc{tag:"c".into()}];
55//! let x = abc.random_of();
56//! assert!(x.tag == "a" || x.tag == "b" || x.tag == "c");
57//! ```
58//! 
59use std::{collections::{HashMap, HashSet}, hash::Hash};
60
61use num::{ Float, Integer };
62use paste::paste;
63use serde::{Deserialize, Serialize, de::{Error, MapAccess, SeqAccess, Visitor}, ser::{SerializeSeq, SerializeStruct}};
64
65pub type DiceT = (i32,i32);
66
67/// Dice roll matrix mod for e.g. serde parsing, etc.
68#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
69pub enum DiceRollMatrixMod {
70    Add(u8),
71    /// `Div` ignores decimals entirely.
72    Div(u8),
73    /// `DivUp` "rounds" upward if dividing ends up with decimals.
74    DivUp(u8),
75    Mul(u8),
76    Sub(u8),
77}
78
79impl <'de> Deserialize<'de> for DiceRollMatrixMod {
80    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
81    where D: serde::Deserializer<'de>
82    {
83        struct ModVis;
84        impl<'de> Visitor<'de> for ModVis {
85            type Value = DiceRollMatrixMod;
86
87            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
88                formatter.write_str("a case-insensitive dice modifier thingy")
89            }
90
91            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
92            where A: MapAccess<'de>,
93            {
94                if let Some((key, value)) = map.next_entry::<String, u8>()? {
95                    match key.to_lowercase().as_str() {
96                        "add" => Ok(DiceRollMatrixMod::Add(value)),
97                        "div" => Ok(DiceRollMatrixMod::Div(value)),
98                        "divup"|"div_up" => Ok(DiceRollMatrixMod::DivUp(value)),
99                        "mul" => Ok(DiceRollMatrixMod::Mul(value)),
100                        "sub" => Ok(DiceRollMatrixMod::Sub(value)),
101                        _ => Err(A::Error::unknown_field(&key, &["add","div","divup","div_up","mul","sub"]))
102                    }
103                } else {
104                    Err(A::Error::custom("empty modifier thingy"))
105                }
106            }
107        }
108
109        deserializer.deserialize_map(ModVis)
110    }
111}
112
113trait DiceRollMatrixModifier {
114    fn drmm(&self, drmm: DiceRollMatrixMod) -> i32;
115}
116
117impl DiceRollMatrixModifier for i32 {
118    fn drmm(&self, drmm: DiceRollMatrixMod) -> i32 {
119        match drmm {
120            DiceRollMatrixMod::Add(v) => self + v as i32,
121            DiceRollMatrixMod::Div(v) => self / v as i32,
122            DiceRollMatrixMod::DivUp(v) => {
123                let v = v as i32;
124                (self + v - 1) / v
125            }
126            DiceRollMatrixMod::Mul(v) => self * v as i32,
127            DiceRollMatrixMod::Sub(v) => self - v as i32,
128        }
129    }
130}
131
132/// Dice roll matrix for e.g. serde parsing, etc.
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub enum DiceRollMatrix {
135    Exact { value: i32 },
136    Percentage(u8), // de: "123%" -> 123_u8, ser: 123u8 -> "123%"
137    Chance(u8, Box<DiceRollMatrix>),
138    Multi(u8, u8),
139    MultiWithMod(u8, u8, DiceRollMatrixMod),
140}
141
142impl DiceRollMatrix {
143    pub fn roll(&self) -> i32 {
144        match self {
145            Self::Exact { value } => *value as i32,
146            Self::Multi(num, sides) => (*num).d(*sides as usize) as i32,
147            Self::MultiWithMod(num, sides, drmm) =>
148                Self::Multi(*num, *sides).roll().drmm(*drmm),
149            Self::Percentage(p) => if 1.d100() <= *p { 1 } else { 0 },
150            Self::Chance(p, drm) =>
151                if 1.d100() > *p { 0 }
152                else { drm.roll() },
153        }
154    }
155}
156
157impl From<i32> for DiceRollMatrix {
158    fn from(value: i32) -> Self {
159        Self::Exact { value }
160    }
161}
162
163impl From<u8> for DiceRollMatrix {
164    fn from(value: u8) -> Self {
165        Self::Exact { value: value as i32 }
166    }
167}
168
169impl <'de> Deserialize<'de> for DiceRollMatrix {
170    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
171    where
172        D: serde::Deserializer<'de>
173    {
174        struct DRMVis;
175        impl <'de> Visitor<'de> for DRMVis {
176            type Value = DiceRollMatrix;
177
178            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
179                formatter.write_str("dice roll matrix thingy")
180            }
181
182            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
183            where E: Error,
184            {
185                if let Some(percstr) = v.strip_suffix('%') {
186                    let n = percstr.parse::<u8>().map_err(E::custom)?;
187                    return Ok(DiceRollMatrix::Percentage(n));
188                }
189                Err(E::custom(format!("'{v}' is an invalid string from DiceRollMatrix")))
190            }
191
192            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
193            where E: Error,
194            {
195                if v <= u8::MAX as u64 {
196                    Ok(DiceRollMatrix::Exact { value: v as i32 })
197                } else {
198                    Err(E::custom(format!("'{v}' is too large for u8")))
199                }
200            }
201
202            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
203            where A: MapAccess<'de>,
204            {
205                let (key, raw): (String, serde_json::Value) =
206                    map.next_entry()?.ok_or_else(|| A::Error::custom("expceted a single-field object"))?;
207
208                match key.as_str() {
209                    "value" => {
210                        let v: u8 = serde_json::from_value(raw).map_err(A::Error::custom)?;
211                        Ok(DiceRollMatrix::Exact { value: v as i32 })
212                    }
213
214                    "chance" => {
215                        // expect: [pct, <DiceRollMatrix>]
216                        let arr: Vec<serde_json::Value> = serde_json::from_value(raw).map_err(A::Error::custom)?;
217                        if arr.len() != 2 {
218                            return Err(A::Error::custom("chance must be [pct, matrix]"));
219                        }
220
221                        let pct: u8 = serde_json::from_value(arr[0].clone()).map_err(A::Error::custom)?;
222                        let inner: DiceRollMatrix = serde_json::from_value(arr[1].clone()).map_err(A::Error::custom)?;
223                        Ok(DiceRollMatrix::Chance(pct, Box::new(inner)))
224                    }
225
226                    "range" => {
227                        let arr: Vec<serde_json::Value> = serde_json::from_value(raw).map_err(A::Error::custom)?;
228                        if arr.len() != 2 {
229                            return Err(A::Error::custom("range must be [a, b]"));
230                        }
231                        let a: i32 = serde_json::from_value(arr[0].clone()).map_err(A::Error::custom)?;
232                        let b: i32 = serde_json::from_value(arr[1].clone()).map_err(A::Error::custom)?;
233                        let (modf, delta) = if a > b {
234                            log::warn!("Range values require an U-turn from [{a},{b}] to [{b},{a}]. \
235                            See to changing that although we'll let that pass… for now.");
236                            let delta = a - b;
237                            (a - 1 - delta, delta as u8)
238                        } else {
239                            let delta = b - a;
240                            (b - 1 - delta, delta as u8)
241                        };
242                        if modf.abs() > u8::MAX as i32 {
243                            return Err(A::Error::custom(format!("Modifier {modf} doesn't fit into u8…")));
244                        }
245                        Ok(if delta == 0 {
246                            DiceRollMatrix::Exact { value: 0 }
247                        } else {
248                            DiceRollMatrix::MultiWithMod(1, delta, if modf < 0 { DiceRollMatrixMod::Sub(modf as u8)} else { DiceRollMatrixMod::Add(modf as u8) })
249                        })
250                    }
251
252                    _ => Err(A::Error::custom(format!("unknown offender '{key}' in DiceRollMatrix")))
253                }
254            }
255
256            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
257            where A: SeqAccess<'de>,
258            {
259                let a: u8 = seq.next_element()?.ok_or_else(|| A::Error::custom("missing 1st element"))?;
260                let b: u8 = seq.next_element()?.ok_or_else(|| A::Error::custom("missing 2nd element"))?;
261                if let Some(modf) = seq.next_element::<DiceRollMatrixMod>()? {
262                    Ok(DiceRollMatrix::MultiWithMod(a,b,modf))
263                } else {
264                    Ok(DiceRollMatrix::Multi(a,b))
265                }
266            }
267        }
268
269        deserializer.deserialize_any(DRMVis)
270    }
271}
272
273impl Serialize for DiceRollMatrix {
274    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
275    where S: serde::Serializer
276    {
277        match self {
278            Self::Exact { value } => {
279                let mut st = serializer.serialize_struct("Exact", 1)?;
280                st.serialize_field("value", value)?;
281                st.end()
282            }
283
284            Self::Multi(a,b) => {
285                let mut seq = serializer.serialize_seq(Some(2))?;
286                seq.serialize_element(a)?;
287                seq.serialize_element(b)?;
288                seq.end()
289            }
290
291            Self::MultiWithMod(a,b,m) => {
292                let mut seq = serializer.serialize_seq(Some(3))?;
293                seq.serialize_element(a)?;
294                seq.serialize_element(b)?;
295                seq.serialize_element(m)?;
296                seq.end()
297            }
298
299            Self::Percentage(p) => serializer.serialize_str(&format!("{p}%")),
300
301            Self::Chance(p, drm) => {
302                let mut st = serializer.serialize_struct("Chance", 1)?;
303                st.serialize_field("chance", &(p, drm))?;
304                st.end()
305            }
306        }
307    }
308}
309
310/// Dice extensions.
311/// 
312/// Note that there is no safeguard against overflows — it's up to you
313/// to ensure your dice rolls will fit into the datatype you're using.
314///
315/// # Example Usage
316/// ```
317/// use dicebag::DiceExt;
318/// let roll = 3.d6();    // i32 in, i32 out
319/// let roll = 2_u8.d8(); // u8 in, u8 out
320/// // hypothetical 15-sided die:
321/// let roll = 5.d(15);
322/// ```
323pub trait DiceExt {
324    /// Roll any D.
325    fn d(&self, sides: usize) -> Self;
326    /// Roll a D2.
327    fn d2(&self) -> Self;
328    /// Roll a D3.
329    fn d3(&self) -> Self;
330    /// Roll a D4.
331    fn d4(&self) -> Self;
332    /// Roll a D5.
333    fn d5(&self) -> Self;
334    /// Roll a D6.
335    fn d6(&self) -> Self;
336    /// Roll a D8.
337    fn d8(&self) -> Self;
338    /// Roll a D10.
339    fn d10(&self) -> Self;
340    /// Roll a D12.
341    fn d12(&self) -> Self;
342    /// Roll a D20.
343    fn d20(&self) -> Self;
344    /// Roll a D100.
345    fn d100(&self) -> Self;
346}
347
348/// Couple "coin-flip" extensions…
349pub trait HiLo {
350    /// Value is considered "high"?
351    fn hi(&self) -> bool;
352    /// Value is considered "low"?
353    fn lo(&self) -> bool;
354}
355
356/// Percentage amount value variator(s).
357pub trait PercentageVariance {
358    #[deprecated(since = "0.3.11", note = "Use `jitter_percentage` instead.")]
359    fn delta(&self, percentage: i32) -> Self;
360    /// Take a number and alter it by up to (or less, of course) ±X%.
361    fn jitter_percentage(&self, percentage: f64) -> Self;
362}
363
364/// Fixed value value variator(s).
365pub trait FixedNumberVariance<T: Float> {
366    #[deprecated(since="0.3.10", note="Use `jitter_within` insted. This will be removed soon.")]
367    fn upto_delta(&self, upto: T) -> T;
368    /// Take a number and alter it ± by \[**0 .. *upto***\].
369    fn jitter_within(&self, upto: T) -> T;
370}
371
372/// "It's just one, isn't it?"…
373pub trait IsOne {
374    /// A convenience extension…
375    /// 
376    /// `if something.is_one() {..}` vs `if something == 1 {..}`.
377    fn is_one(&self) -> bool;
378}
379
380macro_rules! implement_isone_prim {
381    ($prim:expr) => {paste!{
382        impl IsOne for [<i $prim>] { #[inline(always)] fn is_one(&self) -> bool {*self == 1 }}
383        impl IsOne for &[<i $prim>] { #[inline(always)] fn is_one(&self) -> bool {**self == 1 }}
384        impl IsOne for [<u $prim>] { #[inline(always)] fn is_one(&self) -> bool {*self == 1 }}
385        impl IsOne for &[<u $prim>] { #[inline(always)] fn is_one(&self) -> bool {**self == 1 }}
386    }};
387}
388// Implement `IsOne` for all primitive integer types.
389implement_isone_prim!(8);
390implement_isone_prim!(16);
391implement_isone_prim!(32);
392implement_isone_prim!(64);
393implement_isone_prim!(128);
394implement_isone_prim!(size);
395
396pub trait InclusiveRandomRange<T> {
397    fn random_of(&self) -> T;
398}
399
400impl InclusiveRandomRange<i32> for std::ops::RangeInclusive<i32> {
401    /// Generate random value within the given range.
402    /// 
403    /// ```
404    /// use dicebag::InclusiveRandomRange;
405    /// let range = 6..=12;
406    /// let roll = range.random_of();
407    /// ```
408    fn random_of(&self) -> i32 {
409        let (mut start, mut end) = (*self.start(), *self.end());
410        // in case someone fed a range like 12..=6 ... play along and just inverse the ends.
411        if start > end {
412            std::mem::swap(&mut start, &mut end);
413        }
414        let start = start as i64;
415        let end = end as i64;
416        let sides = end - start + 1;
417        // engine.roll(X) gives 1..=X value. Shift it down.
418        (start + (engine::GLOBAL_REACTOR_U64.roll(sides as u64) - 1) as i64) as i32
419        // rand::rng().random_range(start..=end)
420    }
421}
422
423impl InclusiveRandomRange<f64> for std::ops::RangeInclusive<f64> {
424    fn random_of(&self) -> f64 {
425        let (mut start, mut end) = (*self.start(), *self.end());
426        if start > end { std::mem::swap(&mut start, &mut end); }// swap endpoints if needed…
427
428        let raw_bits = engine::GLOBAL_REACTOR_U64.roll(u64::MAX);
429        let max_m = (1u64 << 53) - 1; // use 53 bits of mantissa of the f64
430        start + ((raw_bits & max_m) as f64 / max_m as f64) * (end - start)
431        // rand::rng().random_range(start..=end)
432    }
433}
434
435impl InclusiveRandomRange<f32> for std::ops::RangeInclusive<f32> {
436    fn random_of(&self) -> f32 {
437        let (mut start, mut end) = (*self.start(), *self.end());
438        if start > end { std::mem::swap(&mut start, &mut end); }// swap endpoints if needed…
439
440        let raw_bits = engine::GLOBAL_REACTOR_U64.roll(u64::MAX);
441        let max_m = (1u32 << 24) - 1; // use 24 bits of mantissa of the f32
442        start + ((raw_bits as u32 & max_m) as f32 / max_m as f32) * (end - start)
443        // rand::rng().random_range(start..=end)
444    }
445}
446
447impl InclusiveRandomRange<char> for std::ops::RangeInclusive<char> {
448    fn random_of(&self) -> char {
449        let (mut start, mut end) = (*self.start(), *self.end());
450        if start > end { std::mem::swap(&mut start, &mut end); }// swap endpoints if needed…
451        
452        let u_start = start as u32;
453        let u_end = end as u32;
454        let range = u_end - u_start + 1;
455        loop {
456            let offt = engine::GLOBAL_REACTOR_U64.roll(range as u64) as u32;
457            let maybe = u_start + offt;
458            // skip the illegal surrogate gap
459            if !(0xD800..=0xDFFF).contains(&maybe) {
460                return unsafe { char::from_u32_unchecked(maybe) }
461            }
462        }
463    }
464}
465
466pub trait RandomOf<T> : Clone {
467    type Output;
468    fn random_of(&self) -> Self::Output;
469}
470
471pub trait PlainRandomOf : Clone {
472    type Output;
473    fn random_of() -> Self::Output;
474}
475
476pub trait KeyedRandomOf<K,T> : Clone {
477    type Output;
478    fn random_of(&self) -> Self::Output;
479}
480
481impl<T> RandomOf<T> for Vec<T>
482where T: Clone
483{
484    type Output = T;
485    /// Get a random item from some vector.
486    /// 
487    /// # Panic
488    /// An empty `Vec` will cause a panic.
489    fn random_of(&self) -> Self::Output {
490        if self.is_empty() { panic!("Empty Vec - can't pick a random from that. Anyway… Ta-ta 'til that's fixed.")}
491        T::clone(&self[1.d(self.len())-1])
492    }
493}
494
495impl<T> RandomOf<T> for HashSet<T>
496where T: Clone
497{
498    type Output = T;
499    /// Get a random item from some vector.
500    /// 
501    /// # Panic
502    /// An empty `HashSet` will cause a panic.
503    fn random_of(&self) -> Self::Output {
504        if self.is_empty() { panic!("Empty HashSet - can't pick a random from that. Anyway… Ta-ta 'til that's fixed.")}
505        let Some(ent) = self.iter().nth((1_usize.d(self.len()) - 1) as usize) else {
506            panic!("For some reason the HashSet has less entries in it than .len() suggests?!");
507        };
508        T::clone(ent)
509    }
510}
511
512impl <T> RandomOf<T> for HashMap<String, T>
513where T: Clone
514{
515    type Output = T;
516    fn random_of(&self) -> Self::Output {
517        if self.is_empty() { panic!("Empty HashMap - can't pick a random from that. Anyway… Ta-ta 'til that's fixed.")}
518        let Some((_,ent)) = self.iter().nth((1_usize.d(self.len()) - 1) as usize) else {
519            panic!("For some reason the HashMap has less entries in it than .len() suggests?!");
520        };
521        T::clone(ent)
522    }
523}
524
525impl <K,T> KeyedRandomOf<K,T> for HashMap<K,T>
526where T: Clone, K: Hash + Clone
527{
528    type Output = T;
529    fn random_of(&self) -> Self::Output {
530        if self.is_empty() { panic!("Empty HashMap - can't pick a random from that. Anyway… Ta-ta 'til that's fixed.")}
531        let Some((_,ent)) = self.iter().nth((1_usize.d(self.len()) - 1) as usize) else {
532            panic!("For some reason the HashMap has less entries in it than .len() suggests?!");
533        };
534        T::clone(ent)
535    }
536}
537
538#[macro_export]
539/// Roll some arbitrary dice and see if their result is "low".
540macro_rules! lo {() => {{ use dicebag::{DiceExt, HiLo}; 1_i32.d2().lo() }}}
541
542#[macro_export]
543/// Roll some arbitrary dice and see if their result is "high".
544macro_rules! hi {() => {{ use dicebag::{HiLo, lo}; !lo!() }}}
545
546#[macro_export]
547/**
548 `$chance`% of `$v`, otherwise `0`.
549
550 ## Usage
551 ```
552    use dicebag::{DiceExt, percentage_chance_of};
553    // 90% chance of x ending up being 10, otherwise 0.
554    let x = percentage_chance_of!(90, 10);
555 ```
556 */
557macro_rules! percentage_chance_of {
558    ($chance:expr, f $v:expr) => {{
559        use dicebag::DiceExt;
560        if 1_i32.d100() <= $chance { $v } else { 0.0 }
561    }};
562
563    ($chance:expr, $v:expr) => {
564        if 1_i32.d100() <= $chance { $v } else { 0 }
565    };
566}
567
568macro_rules! implement_sign_dependant_diceext {
569    ($t:ty, signed) => {paste! {
570        #[inline(always)] fn [<diceabs _ $t>](num: $t) -> $t {num.abs()}
571        #[inline(always)] fn [<dicerev _ $t>](num: $t) -> $t {-num}
572        #[inline(always)] fn [<dicelt0 _ $t>](num: $t) -> bool { num < 0 }
573    }};
574    ($t:ty, unsigned) => {paste! {
575        #[inline(always)] fn [<diceabs _ $t>](num: $t) -> $t {num}
576        #[inline(always)] fn [<dicerev _ $t>](num: $t) -> $t {num}
577        #[inline(always)] fn [<dicelt0 _ $t>](_: $t) -> bool { false }
578    }};
579}
580
581implement_sign_dependant_diceext!(i8, signed);
582implement_sign_dependant_diceext!(i16, signed);
583implement_sign_dependant_diceext!(i32, signed);
584implement_sign_dependant_diceext!(i64, signed);
585implement_sign_dependant_diceext!(i128, signed);
586implement_sign_dependant_diceext!(isize, signed);
587implement_sign_dependant_diceext!(u8, unsigned);
588implement_sign_dependant_diceext!(u16, unsigned);
589implement_sign_dependant_diceext!(u32, unsigned);
590implement_sign_dependant_diceext!(u64, unsigned);
591implement_sign_dependant_diceext!(u128, unsigned);
592implement_sign_dependant_diceext!(usize, unsigned);
593
594mod engine {
595    use std::{cell::UnsafeCell, sync::atomic::AtomicBool};
596    use paste::paste;
597
598    macro_rules! const_chaos_engine_crng_vals {
599        (for $([$t:ty, $bits:literal bits, $init:literal, $mul:expr, $add:literal]),+ $(,)?) => {$(paste! {
600            const [<CE_CRNG_U $bits _INIT>]: $t = $init;
601            const [<CE_CRNG_U $bits _MUL>]: $t = $mul;
602            const [<CE_CRNG_U $bits _ADD>]: $t = $add;
603            pub(crate) static [<REACTOR_U $bits _WARMED>]: AtomicBool = AtomicBool::new(false);
604        })+};
605    }
606    macro_rules! core_chaos_engine_struct {
607        ($t:ty, $u:ty, $bits:literal) => {paste!{
608            pub(crate) struct [<ChaosEngine $t>] {
609                state: UnsafeCell<$t>,
610            }
611
612            unsafe impl Sync for [<ChaosEngine $t>] {}
613
614            impl [<ChaosEngine $t>] {
615                pub const fn new(seed: $t) -> Self {
616                    Self { state: UnsafeCell::new(seed) }
617                }
618
619                pub fn roll(&self, max: $t) -> $t {
620                    if max == 0 { return 0; }
621                    unsafe {
622                        let stack_entropy = &max as *const $t as usize as [<u $bits>];
623                        let ptr = self.state.get();
624                        let next = (*ptr as [<u $bits>])
625                            .wrapping_mul([<CE_CRNG_U $bits _MUL>])
626                            .wrapping_add([<CE_CRNG_U $bits _ADD>])
627                            ^ (max as [<u $bits>])
628                            ^ stack_entropy
629                            ^ {
630                                let inst = std::time::Instant::now();
631                                &inst as *const _ as usize as [<u $bits>]
632                            }
633                            ;
634                        *ptr = next as $t;
635                        ((next % (max as [<u $bits>])) + 1) as $t
636                    }
637                }
638
639                pub fn core_chaos_engine_struct_clobber_(&self) {
640                    unsafe {
641                        let ptr = self.state.get();
642                        *ptr = (*ptr as [<u $bits>]).wrapping_add([<CE_CRNG_U $bits _MUL>]).wrapping_add(13) as $t;
643                    }
644                }
645            }
646
647            pub(crate) static [<GLOBAL_REACTOR_U $bits>]: [<ChaosEngine $t>] = [<ChaosEngine $t>]::new([<CE_CRNG_U $bits _INIT>]);
648        }};
649    }
650    macro_rules! implement_chaos_engine_struct {
651        // (for $($t:ty => $u:ty),+ $(,)?) => {$(paste! {
652        //     core_chaos_engine_struct!($t, $u);
653        // })+};
654
655        (for $(($t:ty, $bits:literal bits)),+ $(,)?) => {$(paste! {
656            core_chaos_engine_struct!($t, $t, $bits);
657        })+};
658    }
659
660    const_chaos_engine_crng_vals!(for
661        // [i8, 9, 85, 33],
662        // [i16, 17, 25173, 13849],
663        // [i32, 33, 1664525, 1013904223],
664        // [i64, 65, 6364136223846793005, 1442695040888963407],
665        // [i128, 129, 22695477 as i128, 1],
666        // [isize, 321, 6364136223846793005, 1442695040888963407],
667        // [u8, 11, 85, 33],
668        // [u16, 19, 25173, 13849],
669        // [u32, 35, 1664525, 1013904223],
670        [u64, 64 bits, 67, 6364136223846793005, 1442695040888963407],
671        [u128, 128 bits, 131, 22695477 as u128, 1],
672        // [usize, 747, 6364136223846793005, 1442695040888963407],
673    );
674    // implement_chaos_engine_struct!(for
675        // i8 => u8,
676        // i16 => u16,
677        // i32 => u32,
678        // i64 => u64,
679        // i128 => u128,
680        // isize => u64,
681        // usize => u64
682    // );
683    implement_chaos_engine_struct!(for
684        // u8,
685        // u16,
686        // u32,
687        (u64, 64 bits),
688        (u128, 128 bits),
689    );
690}
691
692macro_rules! implement_diceext {
693    ( for $(($t:ty, $bits:literal bits)),+ $(,)?) => {$(paste! {
694        impl DiceExt for $t {
695            #[inline(always)] fn d(&self, sides: usize) -> Self { [<any _ $t>](*self, sides) }
696            #[inline(always)] fn d2(&self) -> Self { [<any _ $t>](*self, 2)}
697            #[inline(always)] fn d3(&self) -> Self { [<any _ $t>](*self, 3)}
698            #[inline(always)] fn d4(&self) -> Self { [<any _ $t>](*self, 4)}
699            #[inline(always)] fn d5(&self) -> Self { [<any _ $t>](*self, 5)}
700            #[inline(always)] fn d6(&self) -> Self { [<any _ $t>](*self, 6)}
701            #[inline(always)] fn d8(&self) -> Self { [<any _ $t>](*self, 8)}
702            #[inline(always)] fn d10(&self) -> Self { [<any _ $t>](*self, 10)}
703            #[inline(always)] fn d12(&self) -> Self { [<any _ $t>](*self, 12)}
704            #[inline(always)] fn d20(&self) -> Self { [<any _ $t>](*self, 20)}
705            #[inline(always)] fn d100(&self) -> Self { [<any _ $t>](*self, 100)}
706        }
707
708        /// Throw given `num` of dice, each with x `sides`.
709        fn [<any _ $t>](num: $t, sides: usize) -> $t {
710            if engine::[<REACTOR_U $bits _WARMED>].compare_exchange(false, true, std::sync::atomic::Ordering::Relaxed, std::sync::atomic::Ordering::Relaxed).is_ok() {
711                // chaos seed
712                let x = std::time::Instant::now();
713                let ptr = &x as *const _ as u64;
714                let b = std::time::Instant::now().elapsed().as_nanos() as u64;
715                let z = ptr ^ b.rotate_left(7);
716                for _ in 0..(z.rotate_left((ptr & 0xF) as u32)).wrapping_rem(512).max(128) {
717                    engine::[<GLOBAL_REACTOR_U $bits>].roll(sides as [<u $bits>]);
718                }
719                std::thread::spawn(|| {
720                    let sleep_dur = std::time::Duration::from_micros(25);
721                    loop {
722                        std::hint::black_box(engine::[<GLOBAL_REACTOR_U $bits>].core_chaos_engine_struct_clobber_());
723                        std::thread::sleep(sleep_dur);
724                    }
725                });
726            }
727            let mut result: $t = 0;
728            for _ in 0..[<diceabs _ $t>](num) {
729                result += std::hint::black_box(engine::[<GLOBAL_REACTOR_U $bits>].roll(sides as [<u $bits>]) as $t);
730            }
731            if [<dicelt0 _ $t>](num) {[<dicerev _ $t>](result)} else {result}
732        }
733    }
734
735    impl HiLo for $t {
736        #[inline(always)] fn hi(&self) -> bool { self.is_even() }
737        #[inline(always)] fn lo(&self) -> bool { self.is_odd() }
738    }
739    )+};
740}
741
742macro_rules! implement_float_diceext {
743    ( for $($t:ty),+) => {paste!{
744        $(
745            impl FixedNumberVariance<$t> for $t {
746                fn upto_delta(&self, upto: Self) -> Self {self.jitter_within(upto)}
747                fn jitter_within(&self, upto: Self) -> Self {
748                    if upto <= 0.0 { return *self; }
749                    let raw_bits = engine::GLOBAL_REACTOR_U64.roll(u64::MAX);
750                    let scale = (raw_bits & (([<$t>]::MANTISSA_DIGITS as u64) -1)) as $t
751                                / (([<$t>]::MANTISSA_DIGITS as u64) - 1) as $t;
752                    self + ((scale * 2.0 * upto ) - upto)
753                }
754            }
755
756            impl PercentageVariance for $t {
757                fn delta(&self, percentage: i32) -> Self { self.jitter_percentage(percentage as f64) }
758                fn jitter_percentage(&self, percentage: f64) -> Self {
759                    let p = 0.01 * percentage;
760                    let upto = self.abs() * p as $t;
761                    self.jitter_within(upto)
762                }
763            }
764        )+
765    }};
766}
767
768implement_diceext!(for
769    (i8, 64 bits),
770    (i16, 64 bits),
771    (i32, 64 bits),
772    (i64, 64 bits),
773    (i128, 128 bits),
774    (isize, 64 bits),
775    (u8, 64 bits),
776    (u16, 64 bits),
777    (u32, 64 bits),
778    (u64, 64 bits),
779    (u128, 128 bits),
780    (usize, 64 bits),
781);
782implement_float_diceext!(for f32, f64);//f128 unstable at time of writing... July 6, 2025.
783
784#[cfg(test)]
785mod tests {
786    use super::*;
787    use std::thread;
788    use std::time::Duration;
789
790    #[test]
791    fn test_dicebag_is_completely_non_deterministic() {
792        _ = env_logger::try_init();
793        // seq of 10,000 dice rolls (with d10000 for high variancy)
794        let sample_size = 10_000;
795        let sides = 10_000;
796        
797        // 1st seq
798        let mut seq_a = Vec::with_capacity(sample_size);
799        for _ in 0..sample_size {
800            seq_a.push(1_i32.d(sides));
801            // snooze a moment
802            thread::sleep(Duration::from_micros(10));
803        }
804
805        // 2nd seq
806        let mut seq_b = Vec::with_capacity(sample_size);
807        for _ in 0..sample_size {
808            seq_b.push(1_i32.d(sides));
809            thread::sleep(Duration::from_micros(10));
810        }
811
812        // how many elements match at the same index?
813        let mut matches = 0;
814        for i in 0..sample_size {
815            if seq_a[i] == seq_b[i] {
816                matches += 1;
817            }
818        }
819
820        // with 10k sides, back-to-back mirrors are statistically a farce.
821        // Matches should be near zero. If they aren't, the machine lied to us!
822        log::debug!("Identical rolls at matching positions: {}/{}", matches, sample_size);
823        assert!(
824            matches < (sample_size / 10), 
825            "Sequences are too similar! We have a determinist amongst us!"
826        );
827        
828        assert_ne!(seq_a, seq_b, "Something gone really, really wrong - seq B perfectly mirrored A!");
829    }
830
831    #[test]
832    fn test_concurrent_clobbering() {
833        // Pester the engine from all directions to make sure it mangles
834        // output without screaming in panic, deadlocking, or having any
835        // other dreadful issues …
836        let mut handles = vec![];
837        
838        for _ in 0..8 {
839            handles.push(thread::spawn(|| {
840                for _ in 0..50 {
841                    let roll = 1_i64.d(100);
842                    assert!(roll >= 1 && roll <= 100);
843                }
844            }));
845        }
846
847        for handle in handles {
848            handle.join().unwrap();
849        }
850    }
851
852    // #[test]
853    // fn mid_range_u8() {
854    //     for round in 1..=10 {
855    //     let mut ones = 0;
856    //     for _ in 0..100_000 {
857    //         let r = engine::GLOBAL_REACTOR_U8.roll(100) - 1;
858    //         ones += if r < 50 { 1 } else { 0 };
859    //     }
860    //     _ = env_logger::try_init();
861    //     log::debug!("round#{round} .. ones = {ones}; of 100,000");
862    //     }
863    // }
864}