atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
aef6f6498ea2ef0c39d1079a0ca0bc99afdd9bac
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git

Blame: src/tests/rolls.rs

8b8e83329a1datagen2026-10-12 12:01 UTC1
use super::*;
8b8e83329a1datagen2026-10-12 12:01 UTC2
8b8e83329a1datagen2026-10-12 12:01 UTC3
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC4
async fn holding_calms_and_sometimes_reflects() {
8b8e83329a1datagen2026-10-12 12:01 UTC5
    let mut calmed = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC6
    let mut reflected = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC7
    let mut ruptured = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC8
    for seed in 0..600u64 {
8b8e83329a1datagen2026-10-12 12:01 UTC9
        let mut run = Run::new("cafebabe".into(), "cafe".into(), seed);
8b8e83329a1datagen2026-10-12 12:01 UTC10
        run.body.eq = 75;
8b8e83329a1datagen2026-10-12 12:01 UTC11
        match run.hold() {
8b8e83329a1datagen2026-10-12 12:01 UTC12
            Ok(_) if run.body.eq == 0 => reflected += 1,
8b8e83329a1datagen2026-10-12 12:01 UTC13
            Ok(_) => calmed += 1,
8b8e83329a1datagen2026-10-12 12:01 UTC14
            Err(_) => ruptured += 1,
8b8e83329a1datagen2026-10-12 12:01 UTC15
        }
8b8e83329a1datagen2026-10-12 12:01 UTC16
    }
8b8e83329a1datagen2026-10-12 12:01 UTC17
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC18
        (25..=80).contains(&reflected),
8b8e83329a1datagen2026-10-12 12:01 UTC19
        "one HOLD in twelve should reflect, about 50 of 600, got {reflected} and {calmed} calm"
8b8e83329a1datagen2026-10-12 12:01 UTC20
    );
8b8e83329a1datagen2026-10-12 12:01 UTC21
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC22
        ruptured <= 7,
8b8e83329a1datagen2026-10-12 12:01 UTC23
        "the flat 1 in 250 ruptured {ruptured} of 600 holds, about 2.4 expected"
8b8e83329a1datagen2026-10-12 12:01 UTC24
    );
8b8e83329a1datagen2026-10-12 12:01 UTC25
}
8b8e83329a1datagen2026-10-12 12:01 UTC26
8b8e83329a1datagen2026-10-12 12:01 UTC27
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC28
async fn the_burn_shifts_the_rupture_tiers() {
8b8e83329a1datagen2026-10-12 12:01 UTC29
    async fn ruptures(burned: bool) -> u32 {
8b8e83329a1datagen2026-10-12 12:01 UTC30
        let mut gone = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC31
        for seed in 0..400u64 {
8b8e83329a1datagen2026-10-12 12:01 UTC32
            let app = app_with(Floor::Bedroom, seed, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC33
                run.body.warmed_up = true;
8b8e83329a1datagen2026-10-12 12:01 UTC34
                run.body.eq = 60;
8b8e83329a1datagen2026-10-12 12:01 UTC35
                run.items.lubes.baby_oil = 9;
8b8e83329a1datagen2026-10-12 12:01 UTC36
                run.body.conditions.friction_burn = burned;
8b8e83329a1datagen2026-10-12 12:01 UTC37
            });
8b8e83329a1datagen2026-10-12 12:01 UTC38
            let lube = [("content-type", "application/baby-oil")];
8b8e83329a1datagen2026-10-12 12:01 UTC39
            let jelq = send(
8b8e83329a1datagen2026-10-12 12:01 UTC40
                &app,
8b8e83329a1datagen2026-10-12 12:01 UTC41
                "JELQ",
8b8e83329a1datagen2026-10-12 12:01 UTC42
                "/runs/cafebabe?technique=wet&intensity=5",
8b8e83329a1datagen2026-10-12 12:01 UTC43
                &lube,
8b8e83329a1datagen2026-10-12 12:01 UTC44
                "",
8b8e83329a1datagen2026-10-12 12:01 UTC45
            )
8b8e83329a1datagen2026-10-12 12:01 UTC46
            .await;
8b8e83329a1datagen2026-10-12 12:01 UTC47
            if jelq.status() == StatusCode::INTERNAL_SERVER_ERROR {
8b8e83329a1datagen2026-10-12 12:01 UTC48
                gone += 1;
8b8e83329a1datagen2026-10-12 12:01 UTC49
            }
8b8e83329a1datagen2026-10-12 12:01 UTC50
        }
8b8e83329a1datagen2026-10-12 12:01 UTC51
        gone
8b8e83329a1datagen2026-10-12 12:01 UTC52
    }
8b8e83329a1datagen2026-10-12 12:01 UTC53
    let burned = ruptures(true).await;
8b8e83329a1datagen2026-10-12 12:01 UTC54
    let calm = ruptures(false).await;
8b8e83329a1datagen2026-10-12 12:01 UTC55
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC56
        burned >= 8 && calm <= 4,
8b8e83329a1datagen2026-10-12 12:01 UTC57
        "burned {burned} ruptures against {calm}"
8b8e83329a1datagen2026-10-12 12:01 UTC58
    );
8b8e83329a1datagen2026-10-12 12:01 UTC59
}
8b8e83329a1datagen2026-10-12 12:01 UTC60
8b8e83329a1datagen2026-10-12 12:01 UTC61
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC62
async fn the_top_tier_rolls_a_single_third() {
8b8e83329a1datagen2026-10-12 12:01 UTC63
    let mut ruptures = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC64
    let mut early = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC65
    for seed in 0..400u64 {
8b8e83329a1datagen2026-10-12 12:01 UTC66
        let app = app_with(Floor::Bedroom, seed, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC67
            run.body.warmed_up = true;
8b8e83329a1datagen2026-10-12 12:01 UTC68
            run.body.eq = 96;
8b8e83329a1datagen2026-10-12 12:01 UTC69
            run.items.lubes.baby_oil = 9;
8b8e83329a1datagen2026-10-12 12:01 UTC70
            run.items.kit.blinds = true;
8b8e83329a1datagen2026-10-12 12:01 UTC71
        });
8b8e83329a1datagen2026-10-12 12:01 UTC72
        let lube = [("content-type", "application/baby-oil")];
8b8e83329a1datagen2026-10-12 12:01 UTC73
        let jelq = send(
8b8e83329a1datagen2026-10-12 12:01 UTC74
            &app,
8b8e83329a1datagen2026-10-12 12:01 UTC75
            "JELQ",
8b8e83329a1datagen2026-10-12 12:01 UTC76
            "/runs/cafebabe?technique=wet&intensity=5",
8b8e83329a1datagen2026-10-12 12:01 UTC77
            &lube,
8b8e83329a1datagen2026-10-12 12:01 UTC78
            "",
8b8e83329a1datagen2026-10-12 12:01 UTC79
        )
8b8e83329a1datagen2026-10-12 12:01 UTC80
        .await;
8b8e83329a1datagen2026-10-12 12:01 UTC81
        match jelq.status() {
8b8e83329a1datagen2026-10-12 12:01 UTC82
            StatusCode::INTERNAL_SERVER_ERROR => ruptures += 1,
8b8e83329a1datagen2026-10-12 12:01 UTC83
            StatusCode::TOO_EARLY => early += 1,
8b8e83329a1datagen2026-10-12 12:01 UTC84
            other => panic!("seed {seed} ended in {other}"),
8b8e83329a1datagen2026-10-12 12:01 UTC85
        }
8b8e83329a1datagen2026-10-12 12:01 UTC86
    }
8b8e83329a1datagen2026-10-12 12:01 UTC87
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC88
        (90..=175).contains(&ruptures),
8b8e83329a1datagen2026-10-12 12:01 UTC89
        "ruptures {ruptures} of 400 against {early} early"
8b8e83329a1datagen2026-10-12 12:01 UTC90
    );
8b8e83329a1datagen2026-10-12 12:01 UTC91
    assert!(
8b8e83329a1datagen2026-10-12 12:01 UTC92
        (225..=310).contains(&early),
8b8e83329a1datagen2026-10-12 12:01 UTC93
        "early {early} of 400 against {ruptures} ruptures"
8b8e83329a1datagen2026-10-12 12:01 UTC94
    );
8b8e83329a1datagen2026-10-12 12:01 UTC95
}
8b8e83329a1datagen2026-10-12 12:01 UTC96
8b8e83329a1datagen2026-10-12 12:01 UTC97
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC98
async fn holding_rolls_a_flat_rupture_chance() {
8b8e83329a1datagen2026-10-12 12:01 UTC99
    async fn ruptures(eq: i32, stamina: i32) -> u32 {
8b8e83329a1datagen2026-10-12 12:01 UTC100
        let mut gone = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC101
        for seed in 0..600u64 {
8b8e83329a1datagen2026-10-12 12:01 UTC102
            let app = app_with(Floor::Bedroom, seed, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC103
                run.body.warmed_up = true;
8b8e83329a1datagen2026-10-12 12:01 UTC104
                run.body.eq = eq;
8b8e83329a1datagen2026-10-12 12:01 UTC105
                run.body.stamina = stamina;
8b8e83329a1datagen2026-10-12 12:01 UTC106
            });
8b8e83329a1datagen2026-10-12 12:01 UTC107
            let held = send(&app, "HOLD", "/runs/cafebabe", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC108
            if held.status() == StatusCode::INTERNAL_SERVER_ERROR {
8b8e83329a1datagen2026-10-12 12:01 UTC109
                gone += 1;
8b8e83329a1datagen2026-10-12 12:01 UTC110
            }
8b8e83329a1datagen2026-10-12 12:01 UTC111
        }
8b8e83329a1datagen2026-10-12 12:01 UTC112
        gone
8b8e83329a1datagen2026-10-12 12:01 UTC113
    }
8b8e83329a1datagen2026-10-12 12:01 UTC114
    let high = ruptures(90, 10).await;
8b8e83329a1datagen2026-10-12 12:01 UTC115
    let low = ruptures(30, 2).await;
8b8e83329a1datagen2026-10-12 12:01 UTC116
    assert!((0..=7).contains(&high), "high holds ruptured {high}/600");
8b8e83329a1datagen2026-10-12 12:01 UTC117
    assert!((0..=7).contains(&low), "low holds ruptured {low}/600");
8b8e83329a1datagen2026-10-12 12:01 UTC118
}
8b8e83329a1datagen2026-10-12 12:01 UTC119
8b8e83329a1datagen2026-10-12 12:01 UTC120
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC121
async fn the_kitchen_pays_out_at_the_documented_odds() {
8b8e83329a1datagen2026-10-12 12:01 UTC122
    let mut oil = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC123
    let mut drinks = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC124
    let mut packs = 0;
f16ab17c8ff6atagen2026-10-13 11:14 UTC125
    let mut raids = 0;
f16ab17c8ff6atagen2026-10-13 11:14 UTC126
    for seed in 0..800u64 {
8b8e83329a1datagen2026-10-12 12:01 UTC127
        let mut run = Run::new("cafebabe".into(), "cafe".into(), seed);
8b8e83329a1datagen2026-10-12 12:01 UTC128
        let held = (
8b8e83329a1datagen2026-10-12 12:01 UTC129
            run.items.lubes.coconut_oil,
8b8e83329a1datagen2026-10-12 12:01 UTC130
            run.items.stores.energy_drinks,
8b8e83329a1datagen2026-10-12 12:01 UTC131
            run.items.stores.ice_packs,
8b8e83329a1datagen2026-10-12 12:01 UTC132
        );
f16ab17c8ff6atagen2026-10-13 11:14 UTC133
        if run.raid_kitchen().is_err() {
f16ab17c8ff6atagen2026-10-13 11:14 UTC134
            continue;
f16ab17c8ff6atagen2026-10-13 11:14 UTC135
        }
f16ab17c8ff6atagen2026-10-13 11:14 UTC136
        raids += 1;
8b8e83329a1datagen2026-10-12 12:01 UTC137
        oil += u32::from(run.items.lubes.coconut_oil > held.0);
8b8e83329a1datagen2026-10-12 12:01 UTC138
        drinks += u32::from(run.items.stores.energy_drinks > held.1);
8b8e83329a1datagen2026-10-12 12:01 UTC139
        packs += u32::from(run.items.stores.ice_packs > held.2);
8b8e83329a1datagen2026-10-12 12:01 UTC140
    }
f16ab17c8ff6atagen2026-10-13 11:14 UTC141
    let rate = |found: u32| f64::from(found) / f64::from(raids);
f16ab17c8ff6atagen2026-10-13 11:14 UTC142
    assert!(
f16ab17c8ff6atagen2026-10-13 11:14 UTC143
        (0.28..=0.39).contains(&rate(oil)),
f16ab17c8ff6atagen2026-10-13 11:14 UTC144
        "coconut {oil} in {raids}"
f16ab17c8ff6atagen2026-10-13 11:14 UTC145
    );
f16ab17c8ff6atagen2026-10-13 11:14 UTC146
    assert!(
f16ab17c8ff6atagen2026-10-13 11:14 UTC147
        (0.15..=0.25).contains(&rate(drinks)),
f16ab17c8ff6atagen2026-10-13 11:14 UTC148
        "energy {drinks} in {raids}"
f16ab17c8ff6atagen2026-10-13 11:14 UTC149
    );
f16ab17c8ff6atagen2026-10-13 11:14 UTC150
    assert!(
f16ab17c8ff6atagen2026-10-13 11:14 UTC151
        (0.08..=0.17).contains(&rate(packs)),
f16ab17c8ff6atagen2026-10-13 11:14 UTC152
        "ice {packs} in {raids}"
f16ab17c8ff6atagen2026-10-13 11:14 UTC153
    );
8b8e83329a1datagen2026-10-12 12:01 UTC154
}
8b8e83329a1datagen2026-10-12 12:01 UTC155
8b8e83329a1datagen2026-10-12 12:01 UTC156
#[tokio::test]
8b8e83329a1datagen2026-10-12 12:01 UTC157
async fn the_kitchen_rolls_the_normal_tiers() {
8b8e83329a1datagen2026-10-12 12:01 UTC158
    async fn ruptures(eq: i32) -> u32 {
8b8e83329a1datagen2026-10-12 12:01 UTC159
        let mut gone = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC160
        for seed in 0..600u64 {
8b8e83329a1datagen2026-10-12 12:01 UTC161
            let app = app_with(Floor::Bedroom, seed, |run| run.body.eq = eq);
8b8e83329a1datagen2026-10-12 12:01 UTC162
            let raid = send(&app, "POST", "/runs/cafebabe/kitchen", &[], "").await;
8b8e83329a1datagen2026-10-12 12:01 UTC163
            if raid.status() == StatusCode::INTERNAL_SERVER_ERROR {
8b8e83329a1datagen2026-10-12 12:01 UTC164
                gone += 1;
8b8e83329a1datagen2026-10-12 12:01 UTC165
            }
8b8e83329a1datagen2026-10-12 12:01 UTC166
        }
8b8e83329a1datagen2026-10-12 12:01 UTC167
        gone
8b8e83329a1datagen2026-10-12 12:01 UTC168
    }
8b8e83329a1datagen2026-10-12 12:01 UTC169
    let hot = ruptures(96).await;
8b8e83329a1datagen2026-10-12 12:01 UTC170
    let cool = ruptures(40).await;
8b8e83329a1datagen2026-10-12 12:01 UTC171
    assert!((40..=80).contains(&hot), "hot kitchen ruptured {hot}/600");
8b8e83329a1datagen2026-10-12 12:01 UTC172
    assert!((0..=9).contains(&cool), "cool kitchen ruptured {cool}/600");
8b8e83329a1datagen2026-10-12 12:01 UTC173
}
8b8e83329a1datagen2026-10-12 12:01 UTC174
8b8e83329a1datagen2026-10-12 12:01 UTC175
#[tokio::test]
4b1f5f88827datagen2026-10-13 10:18 UTC176
async fn degloving_takes_one_hard_dry_stroke_in_twenty() {
8b8e83329a1datagen2026-10-12 12:01 UTC177
    let mut degloved = 0;
8b8e83329a1datagen2026-10-12 12:01 UTC178
    for seed in 0..1200u64 {
8b8e83329a1datagen2026-10-12 12:01 UTC179
        let app = app_with(Floor::Hotel, seed, |run| {
8b8e83329a1datagen2026-10-12 12:01 UTC180
            run.here.wifi_accepted = true;
8b8e83329a1datagen2026-10-12 12:01 UTC181
            run.body.warmed_up = true;
8b8e83329a1datagen2026-10-12 12:01 UTC182
            run.body.eq = 55;
8b8e83329a1datagen2026-10-12 12:01 UTC183
            run.body.foreskin = Some(1.0);
8b8e83329a1datagen2026-10-12 12:01 UTC184
        });
8b8e83329a1datagen2026-10-12 12:01 UTC185
        let dry = send(
8b8e83329a1datagen2026-10-12 12:01 UTC186
            &app,
8b8e83329a1datagen2026-10-12 12:01 UTC187
            "JELQ",
8b8e83329a1datagen2026-10-12 12:01 UTC188
            "/runs/cafebabe?technique=dry&intensity=8",
8b8e83329a1datagen2026-10-12 12:01 UTC189
            &[],
8b8e83329a1datagen2026-10-12 12:01 UTC190
            "",
8b8e83329a1datagen2026-10-12 12:01 UTC191
        )
8b8e83329a1datagen2026-10-12 12:01 UTC192
        .await;
8b8e83329a1datagen2026-10-12 12:01 UTC193
        if dry.status() == StatusCode::GONE
8b8e83329a1datagen2026-10-12 12:01 UTC194
            && json(dry).await["type"]
8b8e83329a1datagen2026-10-12 12:01 UTC195
                .as_str()
8b8e83329a1datagen2026-10-12 12:01 UTC196
                .unwrap()
8b8e83329a1datagen2026-10-12 12:01 UTC197
                .ends_with("degloved")
8b8e83329a1datagen2026-10-12 12:01 UTC198
        {
8b8e83329a1datagen2026-10-12 12:01 UTC199
            degloved += 1;
8b8e83329a1datagen2026-10-12 12:01 UTC200
        }
8b8e83329a1datagen2026-10-12 12:01 UTC201
    }
8b8e83329a1datagen2026-10-12 12:01 UTC202
    assert!(
4b1f5f88827datagen2026-10-13 10:18 UTC203
        (35..=90).contains(&degloved),
4b1f5f88827datagen2026-10-13 10:18 UTC204
        "{degloved} degloved in 1200 hard dry strokes, about 60 expected"
4b1f5f88827datagen2026-10-13 10:18 UTC205
    );
4b1f5f88827datagen2026-10-13 10:18 UTC206
}
4b1f5f88827datagen2026-10-13 10:18 UTC207
4b1f5f88827datagen2026-10-13 10:18 UTC208
#[tokio::test]
4b1f5f88827datagen2026-10-13 10:18 UTC209
async fn degloving_takes_one_cold_stroke_in_twenty() {
4b1f5f88827datagen2026-10-13 10:18 UTC210
    let mut degloved = 0;
4b1f5f88827datagen2026-10-13 10:18 UTC211
    for seed in 0..1200u64 {
4b1f5f88827datagen2026-10-13 10:18 UTC212
        let app = app_with(Floor::Hotel, seed, |run| {
4b1f5f88827datagen2026-10-13 10:18 UTC213
            run.here.wifi_accepted = true;
4b1f5f88827datagen2026-10-13 10:18 UTC214
            run.body.eq = 55;
4b1f5f88827datagen2026-10-13 10:18 UTC215
            run.body.foreskin = Some(1.0);
4b1f5f88827datagen2026-10-13 10:18 UTC216
        });
4b1f5f88827datagen2026-10-13 10:18 UTC217
        let cold = send(&app, "JELQ", "/runs/cafebabe?technique=stretch", &[], "").await;
4b1f5f88827datagen2026-10-13 10:18 UTC218
        if cold.status() == StatusCode::GONE
4b1f5f88827datagen2026-10-13 10:18 UTC219
            && json(cold).await["type"]
4b1f5f88827datagen2026-10-13 10:18 UTC220
                .as_str()
4b1f5f88827datagen2026-10-13 10:18 UTC221
                .unwrap()
4b1f5f88827datagen2026-10-13 10:18 UTC222
                .ends_with("degloved")
4b1f5f88827datagen2026-10-13 10:18 UTC223
        {
4b1f5f88827datagen2026-10-13 10:18 UTC224
            degloved += 1;
4b1f5f88827datagen2026-10-13 10:18 UTC225
        }
4b1f5f88827datagen2026-10-13 10:18 UTC226
    }
4b1f5f88827datagen2026-10-13 10:18 UTC227
    assert!(
4b1f5f88827datagen2026-10-13 10:18 UTC228
        (35..=90).contains(&degloved),
4b1f5f88827datagen2026-10-13 10:18 UTC229
        "{degloved} degloved in 1200 cold strokes, about 60 expected"
8b8e83329a1datagen2026-10-12 12:01 UTC230
    );
8b8e83329a1datagen2026-10-12 12:01 UTC231
}