Skip to content

[Gear] Fix Bite of Zul'jan review findings#1

Open
NN0323 wants to merge 1 commit into
bite-of-zuljanfrom
fix/bite-of-zuljan-review
Open

[Gear] Fix Bite of Zul'jan review findings#1
NN0323 wants to merge 1 commit into
bite-of-zuljanfrom
fix/bite-of-zuljan-review

Conversation

@NN0323

@NN0323 NN0323 commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Fixes from code review of the Bite of Zul'jan set (Venomfang weapons + Zul'jin's Guillotine Technique trinket + 2pc). All changes are in engine/player/unique_gear_midnight.cpp.

Critical

Venomfang Burst fired once instead of per instance. The async debuff used set_expire_callback, which only fires when the buff fully clearsdecrement() calls expire() only on the last stack, while intermediate expiries just decrement. With N overlapping applications the burst fired once (N→…→0), not N times, defeating the purpose of the ASYNCHRONOUS rework. Now driven by set_stack_change_callback firing on each lost stack (the final stack still clears via expire(), which also invokes the callback). Matches the async idiom used by reavers_mark (DH, sc_demon_hunter.cpp:9362) and the paladin templar buffs.

⚠️ This assumes the intended behavior is one burst per instance, as stated by the code comment ("each instance expiry fires the burst"). That is the one thing not verifiable from code alone — please confirm against actual game behavior.

Important

Guillotine ricochet could drift onto the primary target. secondary_targets_only() excludes ricochet->target, but the ricochet is single-target, so execute_on_target() repoints its target to the last bounce victim without invalidating the target cache (set_target only invalidates when n_targets() != 0). On any add/death the cache is rebuilt by the target_non_sleeping_list callback, now excluding the drifted target — re-admitting the primary and yielding a double Guillotine + double Venomfang on it. Fixed by re-pointing at the primary and forcing a cache recompute each cast. (font_of_venomous_rage is safe with the same helper only because its splatter is AoE, where execute_on_target does invalidate the cache.)

Minor

  • Debuff duration: read the refreshed DoT's remains() instead of recomputing calculate_dot_refresh_duration on the already-refreshed dot, which (for DOT_REFRESH_DURATION = time_to_next_full_tick + triggered_duration) overshot by ~1 tick on the first application (≈7s vs the 6s DoT). Also makes the per-instance burst fire on DoT expiry, matching the comment.
  • composite_ta_multiplier: zero tick damage when no instance debuff is active, matching root_rot (defensive; the DoT and debuff could otherwise desync by a tick).
  • Version gate: gate the Venomfang weapon driver (1291718) behind 12.1.0, consistent with its Bite of Zul'jan set siblings (the trinket 1291728 and 2pc driver 1291726 are already gated).

Deliberately not changed

The review suggested asserting on find_action("venomfang") in the 2pc path. I kept the existing if ( venomfang ) guard instead: set_bonus= overrides can force the 2pc without equipping the weapons, where the action is legitimately null — an assert would crash those sims. This matches the file's idiom for cross-item set dependencies (e.g. torments_duality uses a soft find_special_effect check, not an assert).

Verification

Syntax-checked the translation unit with MSVC (cl /Zs /std:c++17 /utf-8, exit 0). All touched APIs (set_stack_change_callback(buff_t*,int,int), dot_t::remains(), set_target, public target_cache) were verified against the engine headers and existing usages. No sim was run; the right end-to-end check is the review's suggested multi-target sim watching Venomfang Burst frequency, which is what surfaces the Critical fix.

🤖 Generated with Claude Code

Address code review of the Bite of Zul'jan set (Venomfang weapons +
Zul'jin's Guillotine Technique trinket + 2pc):

- Venomfang Burst now fires per instance. The async debuff's
  expire_callback only fires when the whole debuff clears, so overlapping
  applications produced a single burst instead of one per instance. Drive
  the burst from a stack-change callback on each lost stack (the final
  stack clears via expire(), which also invokes it).
- Guillotine ricochet no longer drifts onto the primary. secondary_targets_only()
  excludes ricochet->target, but a single-target action's execute_on_target()
  does not invalidate the target cache (set_target only does so when
  n_targets() != 0), so an add/death cache refresh could re-admit the
  primary, causing a double Guillotine + Venomfang. Re-point at the primary
  and invalidate the cache each cast.
- Venomfang debuff duration reads the refreshed DoT's remains() instead of
  recomputing the refresh duration, which overshot by a tick on the first
  application.
- composite_ta_multiplier zeroes tick damage when no instance debuff is
  active, matching root_rot.
- Gate the Venomfang weapon driver (1291718) behind 12.1.0 for consistency
  with its Bite of Zul'jan set siblings (trinket + 2pc driver).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@NN0323 NN0323 requested a review from criller7070 June 29, 2026 20:56
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.

1 participant