From 90e9c7ed21d13c5fa798f8e49a326d37ad538fab Mon Sep 17 00:00:00 2001 From: Mathih13 Date: Fri, 4 Sep 2026 22:54:57 +0200 Subject: [PATCH] perf(module): index fear aura candidates --- module/src/creatures/cycle/ctx.rs | 10 ++++------ module/src/creatures/cycle/harness.rs | 4 ++-- module/src/spell/tables.rs | 3 +++ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/module/src/creatures/cycle/ctx.rs b/module/src/creatures/cycle/ctx.rs index d12ef6f78..27f1e5cda 100644 --- a/module/src/creatures/cycle/ctx.rs +++ b/module/src/creatures/cycle/ctx.rs @@ -857,18 +857,16 @@ impl RoutSink for CtxWorld<'_> { } impl FearSink for CtxWorld<'_> { - // KNOWN DEBT, inherited verbatim with the pass: `game_aura` carries no by-mechanic index, so the - // fear rows are found by scanning the aura table, which is itself small — fear uptime is rare. - // ponytail: one row per AURA, not per feared creature, so a doubly-feared creature is resolved - // twice and the phase collapses it. Ceiling: a second `fear_source` scan for a rare case. + // One row per aura, not per feared creature, so a doubly-feared creature is resolved twice and + // the phase collapses it. A second `fear_source` probe preserves the first-caster tie break. fn panicked(&self, scope: &TickScope) -> Vec { let entities = self.ctx.db.game_world_entity(); let melee = self.ctx.db.game_melee_attack(); self.ctx .db .game_aura() - .iter() - .filter(|a| a.eff_kind == crate::spell::A_CONTROL && a.eff_p0 == crate::spell::M_FEAR) + .by_kind_param() + .filter((crate::spell::A_CONTROL, crate::spell::M_FEAR)) .filter_map(|a| { let c = tick::movable_creature(self.ctx, a.target_guid, scope)?; // Re-resolved rather than read off the row above, so which caster wins when a diff --git a/module/src/creatures/cycle/harness.rs b/module/src/creatures/cycle/harness.rs index 2d207a1c3..9fe8975e9 100644 --- a/module/src/creatures/cycle/harness.rs +++ b/module/src/creatures/cycle/harness.rs @@ -6081,8 +6081,8 @@ fn the_production_adapter_is_the_pass_through_the_harness_assumes() { concat!( "{ fn panicked(&self, scope: &TickScope) -> Vec { let entities = ", "self.ctx.db.game_world_entity(); let melee = self.ctx.db.game_melee_attack(); ", - "self.ctx .db .game_aura() .iter() .filter(|a| a.eff_kind == ", - "crate::spell::A_CONTROL && a.eff_p0 == crate::spell::M_FEAR) .filter_map(|a| { ", + "self.ctx .db .game_aura() .by_kind_param() .filter((crate::spell::A_CONTROL, ", + "crate::spell::M_FEAR)) .filter_map(|a| { ", "let c = tick::movable_creature(self.ctx, a.target_guid, scope)?; let source = ", "crate::spell::fear_source(self.ctx, c.guid)?; let at = ", "entities.guid().find(source).or_else(|| { melee .attacker_guid() .find(c.guid) ", diff --git a/module/src/spell/tables.rs b/module/src/spell/tables.rs index 97fffb2ce..368d5f956 100644 --- a/module/src/spell/tables.rs +++ b/module/src/spell/tables.rs @@ -186,6 +186,9 @@ pub struct SpellProcEvent { public, index(accessor = by_target, btree(columns = [target_guid])), index(accessor = by_expiry, btree(columns = [expires_at])), + // Creature fear movement runs every firing. This exact pair finds its sparse candidates without + // reading unrelated buffs, passive effects, periodic effects, or Procs. + index(accessor = by_kind_param, btree(columns = [eff_kind, eff_p0])), // Perf catalog 1.9: the same recipe as `by_expiry`, for the PERIODIC pass. `next_tick_micros == 0` // is the non-periodic sentinel, so a `1..=now` range skips every buff/passive for free. index(accessor = by_next_tick, btree(columns = [next_tick_micros]))