Repository navigation
fix ResolveVariant's arithmetic (HS-1552) - #11
Merged
Merged
Conversation
- ResolveVariant tested the variant's element properties against null, but they are non-nullable and default to None. Any variant with StatModifiers therefore replaced both of the item's elements with its own, None unless it set one. A variant without StatModifiers took the else branches, where the offensive branch assigned the defensive element, and so cleared it. - An element the variant leaves at None is now inherited from the base item; one it sets replaces the base item's. - In world data this restores the defensive element on 56 variants of 12 elemental belts, such as every Enchant1 variant of Fire Belt. No variant sets an element to None explicitly, so none relied on clearing one. Refs: HS-1552
- Physical: `*` bound tighter than `??`, so a variant without <Physical> set value, durability and weight to 1; a missing multiplier now keeps the base value - Damage: SmallMin was computed twice and SmallMax never scaled - Flags: the variant's flags replaced the base item's when it had any, and were dropped when it had none; they are now a union, so a variant keeps Bound, MasterOnly, Tailorable and the rest - Tags: the variant's tags are added only where the base lacks them; before, a tag on both was listed twice - Restrictions: a variant with <Level Min> on a base item with no <Restrictions> threw, and Item.ProcessAll buried the exception - drop the HideBoots copy, a no-op on a clone of the base item Refs: HS-1552, WLD-39
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #11 +/- ##
==========================================
+ Coverage 62.90% 63.12% +0.22%
==========================================
Files 43 43
Lines 1531 1524 -7
Branches 351 346 -5
==========================================
- Hits 963 962 -1
Misses 457 457
+ Partials 111 105 -6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- a base item with no <Equipment>, or a weapon with no <Damage>, threw and lost its variants; scaling now skips them - a scaled damage value over ushort.MaxValue wrapped (80000 became 14464 on net10); it is clamped before the cast - a base level with no Min counts as 1, as the server treats it, so <Level Max="99"/> and a missing <Level> resolve alike - element overrides move inside the StatModifiers block, damage scales the clone's own values, and the dead Tags guards and the element helper go - the element tests fold into VariantResolution as a theory Refs: HS-1552
Contributor
|
Review note, not a blocker for this PR.
The fix is in |
Caeldeth
approved these changes
Oct 23, 2026
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR fixes the defects in
Variant.ResolveVariantlisted on HS-1552.Changes
*binds tighter than??, so a variant without<Physical>set value, durability and weight to 1. A missing multiplier now keeps the base value.None, and replaces one it sets. Previously the non-nullable element properties made every variant with<StatModifiers>overwrite both elements, and theelsebranch cleared the defensive element.SmallMinwas computed twice, andSmallMaxwas never scaled.Tagsis initialized to an empty list. The bug was that a tag on both items was listed twice.<Level Min>on a variant threw when the base item had no<Restrictions>, andItem.ProcessAllswallowed the exception. A base level with no Min now counts as 1, which is how the server treats it, so<Level Max="99"/>and a missing<Level>resolve the same way.<Equipment>, or a weapon with no<Damage>, threw and lost its variants. A scaled damage value overushort.MaxValuewrapped: on net10, 80000 became 14464. Damage is now clamped before the cast.Not changed: the "clamp once at the end" item.
ProcessAllandVariantGroup.ResolveVariantsapply exactly one variant to a base item, so each value is multiplied and clamped once.A 0 damage multiplier stays a literal ×0, by decision. So the world fix has to give an explicit
1for every damage bound it means to leave unchanged, because an omitted bound deserializes to 0.What changes in real data
I built every variant with
mainand with this branch, and compared each field:SmallMaxchangedValue="1234567", now clamped)The flags restored in world are Enchantable 2280, Consecratable 2224, Tailorable 1575, Smithable 292, Bound 70, UniqueInventory 70 and MasterOnly 66. For example, Magic Saffian Boots goes from
Depositable, Exchangeable, Vendorableto that set plus Enchantable and Consecratable. Today the four process flags are read only bySkillAnalyze.lua, for display.This depends on WLD-39. Smithable variants write
<Damage>as child elements, so all four multipliers deserialize to 0. Onmain, the unscaledSmallMaxwas the one damage value those weapons kept (Great Scythe0/0/0/381). With this change it scales by that 0 as well, so the world-data fix should land together with this one.Tests
VariantResolutionhas 17 cases, including the three element cases. Every test for a fixed defect was run against the code before its fix and failed there; the rest cover behaviour that already worked.mainand on this branch, because the suite targets ceridwen's entities.XmlStoreTests.Contains(not touched here) is flaky: it draws castable names from 0-999, so they collide about 0.3% of the time.Review
/code-review(high): its null guards, damage clamp, level default and simplifications were applied; its XSD-default change was left for later./simplify: four cleanup passes, applied where behaviour stays identical.Refs: HS-1552, WLD-39