Procs
RPPM with Bad Luck Protection and haste scaling, flat-chance procs, and per-impact proc filtering.
A proc is a random effect that fires off some trigger: a cast, a damage impact, a tick. The engine models two flavours: flat-chance rolls (a fixed probability per trigger) and RPPM (real procs per minute), where the chance scales with how long it has been since the last attempt so that, on average, the proc fires a target number of times per minute regardless of attack speed.
All of the random sampling lives in one file, crates/engine-sim/src/stochastic.rs, layered on the deterministic SimRng. Every primitive takes the RNG as rng: &mut dyn FnMut() -> f64 so combat code can pass its own seeded generator.
Flat-chance procs
The simplest case is proc_chance(rng, chance): one draw, rng() < chance. There is also roll_tier(rng, thresholds) for tiered outcomes, which returns the index of the first ascending cumulative threshold the roll falls under, and shuffle_pick, a partial Fisher-Yates used to pick N random items. These are the building blocks. The interesting one is RPPM.
RPPM
Each RPPM source has an RppmTracker holding its rate (rppm), the time of the last attempt and last successful proc, an accumulator for bad-luck protection (accumulated_blp), and two flags: haste_scales and blp_enabled:
roll_rppm(tracker, now, haste_pct, rng) does the work, and it is worth reading in order because each piece corrects for a real failure mode:
-
Same-time guard. If this attempt is within
SAME_TIME_TOLERANCE_S = 0.001of the last one, it returnsfalsewithout rolling. Two events landing at the same instant must not double-roll the same proc. -
Elapsed, capped.
elapsed = (now - last_attempt).max(0), then capped atMAX_INTERVAL_S = 3.5. The cap stops a long gap (the pull, say, or a movement break) from handing out a near-guaranteed proc on the next attempt. -
Haste scaling. When
haste_scalesis set,haste_factor = 1 + haste_pct/100, otherwise1.0. This is what makes "per minute" hold as attack speed rises. Faster attacks mean more attempts, so each attempt's chance is scaled up by haste to keep the rate constant. -
Base chance.
base_chance = rppm * haste_factor * (elapsed / 60), the rate per minute converted to a probability for this interval. -
Bad Luck Protection. When enabled and the effective rate is positive, the longer you go without a proc, the higher the chance climbs. With
expected_interval = 60 / real_ppmandaccumulated = min(accumulated_blp, MAX_BAD_LUCK_PROT_S):factor = max(1, 1 + (accumulated/expected_interval - 1.5) * 3) chance = clamp(base_chance * factor, 0, 1)The
1.5and3.0constants match the established SimulationCraft BLP factor, per the in-code note. The BLP cap isMAX_BAD_LUCK_PROT_S = 1000. -
Roll and reset.
success = rng() < chance,last_attempt_timealways advances, and on successlast_proc_time = nowandaccumulated_blpresets to zero. The accumulator only grows between procs, so BLP ramps and then snaps back.
A few candid notes. There is no explicit internal-cooldown (ICD) field on RppmTracker. The same-time guard plus the MAX_INTERVAL_S cap are the only time-based limiters, so an ICD'd proc would need to be modelled separately. And BLP here is the standard SimC formula, not Blizzard's exact (undocumented) implementation; it is a faithful reproduction of community-reverse-engineered behaviour, which is the best available reference.
RPPM trackers are registered at build time. Item procs use register_item_rppm, which delegates to rppm to register the tracker, then indexes it by item id so the generated item code can look it up:
Impact procs
Many procs trigger on a damage impact rather than a cast. Those go through fire_impact_procs, called at the end of every deal_damage and every periodic tick. An ImpactProc carries a chance, the function to run, and a set of filters that decide whether this particular hit is eligible:
Before doing any work, fire_impact_procs short-circuits on the cases that can never proc: no registered procs, a pet hit, or zero damage.
Pet damage explicitly does not trigger player impact procs, see pets. Past the guards it iterates the registered procs, applies each proc's filters, and rolls. To avoid cloning the proc vector on every damage event (the hot path), it uses a mem::take and restore against a reusable scratch buffer, the same allocation-avoidance pattern the cast hooks use.
The proc function itself receives a HookCtx, the constrained post-event context that can apply or consume an aura, gain a resource, reduce or reset a cooldown, deal damage, schedule events, and roll RPPM, but cannot reach the raw event queue directly. That constraint is what keeps proc effects composable: a proc can only do things the engine knows how to schedule.
Étapes suivantes
