Skip to content

fix(loot): roll binomial_with_bonus_count without fortune 🤖🤖🤖#2546

Open
DemonFiend wants to merge 1 commit into
Pumpkin-MC:masterfrom
DemonFiend:fix/binomial-bonus-without-fortune
Open

fix(loot): roll binomial_with_bonus_count without fortune 🤖🤖🤖#2546
DemonFiend wants to merge 1 commit into
Pumpkin-MC:masterfrom
DemonFiend:fix/binomial-bonus-without-fortune

Conversation

@DemonFiend

Copy link
Copy Markdown

Description

apply_bonus gated its entire body on enchantment_level > 0, so binomial_with_bonus_count never rolled its base extra trials when no fortune was present. Crops that drop via that formula (wheat/beetroot seeds) always yielded exactly 1 seed instead of 1 + binomial(extra, p) (e.g. wheat = 1–4).

Removed the outer enchantment_level > 0 guard so the formulas always apply. uniform_bonus_count computes 0..=(level * multiplier), a no-op at level 0, and ore_drops self-gates on its own match arm, so neither regresses.

Testing

Broke ~10 mature wheat by hand: seed drops now vary (1–4) instead of always 1; carrots/potatoes (which use SetCount/uniform and were unaffected) still drop 2–5. cargo clippy --all-targets (debug + release), cargo fmt --check, tests, and typos all pass.

Copilot AI review requested due to automatic review settings July 25, 2026 18:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Fixes loot bonus application so binomial_with_bonus_count rolls its base trials even when no Fortune enchantment is present (restoring correct crop seed drop variance).

Changes:

  • Removed the outer enchantment_level > 0 gate so bonus formulas execute at level 0.
  • Added explanatory comments describing the intended behavior at level 0 and why gating was incorrect.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pumpkin/src/world/loot.rs Outdated
Comment on lines +130 to +137
let n = enchantment_level + *extra;
let mut extra_items = 0;
for _ in 0..n {
if rand::rng().random::<f32>() < *probability {
extra_items += 1;
}
stack.item_count = stack.item_count.saturating_add(extra_items as u8);
}
stack.item_count = stack.item_count.saturating_add(extra_items as u8);
Comment thread pumpkin/src/world/loot.rs
Comment on lines +132 to 136
for _ in 0..n {
if rand::rng().random::<f32>() < *probability {
extra_items += 1;
}
stack.item_count = stack.item_count.saturating_add(extra_items as u8);
}
Comment thread pumpkin/src/world/loot.rs Outdated
parameters
{
let extra =
rand::rng().random_range(0..=(enchantment_level * *bonus_multiplier));
Comment thread pumpkin/src/world/loot.rs Outdated
}
}
"minecraft:ore_drops" if enchantment_level > 0 => {
let multiplier = rand::rng().random_range(0..=(enchantment_level + 1));
@DemonFiend
DemonFiend force-pushed the fix/binomial-bonus-without-fortune branch from 508f974 to 70d328a Compare July 25, 2026 19:54
Copilot AI review requested due to automatic review settings July 25, 2026 19:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

pumpkin/src/world/loot.rs:150

  • uniform_bonus_count computes an i32 extra and then casts with as u8, which truncates/wraps for values > 255, producing incorrect bonuses at high levels. Also, enchantment_level * bonus_multiplier can overflow i32. Use saturating_mul, clamp to non-negative, and convert to u8 with a saturating/clamping conversion like in the binomial branch.
                    let extra = rng.random_range(0..=(enchantment_level * *bonus_multiplier));
                    stack.item_count = stack.item_count.saturating_add(extra as u8);

pumpkin/src/world/loot.rs:156

  • ore_drops casts multiplier with as u8, which truncates/wraps if multiplier ever exceeds 255 (and enchantment_level + 1 can overflow i32). Since this PR is already hardening overflow behavior in the binomial branch, consider clamping here as well for consistency and correctness at high levels.
                let multiplier = rng.random_range(0..=(enchantment_level + 1));
                if multiplier > 0 {
                    stack.item_count = stack.item_count.saturating_mul(multiplier as u8);

Comment thread pumpkin/src/world/loot.rs Outdated
if let Some(LootFunctionBonusParameter::Probability { extra, probability }) =
parameters
{
let n = enchantment_level + *extra;
Comment thread pumpkin/src/world/loot.rs
Comment on lines +118 to +125
// These formulas must run even without the enchantment. `binomial_with_bonus_count` rolls
// `enchantment_level + extra` trials, so at level 0 it still rolls `extra` trials — this is what
// gives e.g. wheat/beetroot seeds their base 1 + binomial(3, p) = 1-4 by hand. Gating the whole
// block on `enchantment_level > 0` dropped that base roll (crops always yielded 1 seed).
// `uniform_bonus_count` computes `0..=(level * mult)`, which is a no-op at level 0, and
// `ore_drops` self-gates on its own match arm, so neither regresses.
let mut rng = rand::rng();
for stack in stacks {
apply_bonus gated its whole body on enchantment_level > 0, so
binomial_with_bonus_count never rolled its base `extra` trials without
fortune - crops always dropped exactly 1 seed instead of 1 + binomial(3, p) =
1-4. Removed the outer guard; uniform_bonus_count computes 0..=(level*mult),
a no-op at level 0, and ore_drops self-gates on its match arm, so neither
regresses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@DemonFiend
DemonFiend force-pushed the fix/binomial-bonus-without-fortune branch from 70d328a to f63f7c4 Compare July 25, 2026 20:32
Copilot AI review requested due to automatic review settings July 25, 2026 20:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

pumpkin/src/world/loot.rs:153

  • uniform_bonus_count can overflow/truncate in two places: (enchantment_level * *bonus_multiplier) can overflow i32 (panic in debug, wrap in release), and extra as u8 will wrap for values > 255. Using saturating math plus a checked conversion avoids surprising behavior with malformed/high enchantment levels.
                if let Some(LootFunctionBonusParameter::Multiplier { bonus_multiplier }) =
                    parameters
                {
                    let extra = rng.random_range(0..=(enchantment_level * *bonus_multiplier));
                    stack.item_count = stack.item_count.saturating_add(extra as u8);

pumpkin/src/world/loot.rs:160

  • ore_drops converts multiplier to u8 via as, which wraps for values > 255. Also, enchantment_level + 1 can overflow i32 in debug builds. A saturating add plus checked conversion keeps behavior consistent with the new saturating logic in the binomial branch.
            "minecraft:ore_drops" if enchantment_level > 0 => {
                let multiplier = rng.random_range(0..=(enchantment_level + 1));
                if multiplier > 0 {
                    stack.item_count = stack.item_count.saturating_mul(multiplier as u8);
                }

Comment thread pumpkin/src/world/loot.rs
// Clamp to a non-negative count: a negative `n` would make `0..n` empty and
// silently skip every trial (reintroducing the "always base count" bug), and
// `saturating_add` avoids overflow at implausibly high enchantment levels.
let n = enchantment_level.saturating_add(*extra).max(0);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants