package main import ( "math" "testing" ) func approx(a, b float64) bool { return math.Abs(a-b) < 0.01 } // Wand: OD = (buffedElemVsMonsters - tableMaxElemVsMonsters) * 100. // Live "Frost Baton": skill 34 (war), wieldreq 385, ElemVsMonsters 1.40, // no spirit-drinker spells. Table max for (34,*,*,385) = 1.18 → OD = +22. func TestOD_Wand(t *testing.T) { raw := map[string]any{ "ObjectClass": 31.0, "IntValues": map[string]any{"159": 34.0, "160": 385.0}, "DoubleValues": map[string]any{"152": 1.40, "167772169": 8.0}, "Spells": []any{}, "ActiveSpells": []any{}, } od, ok := computeOD(raw) if !ok || !approx(od, 22.0) { t.Fatalf("wand OD = %v ok=%v, want +22", od, ok) } } // Wand with an ACTIVE Prodigal Spirit Drinker (3735, change .15, bonus 0): // shown ElemVsMonsters 1.55 un-buffs to 1.55 − .15 = 1.40 → OD +22, same // underlying weapon as TestOD_Wand. func TestOD_WandActiveProdigalSpiritDrinker(t *testing.T) { raw := map[string]any{ "ObjectClass": 31.0, "IntValues": map[string]any{"159": 34.0, "160": 385.0}, "DoubleValues": map[string]any{"152": 1.55, "167772169": 8.0}, "Spells": []any{}, "ActiveSpells": []any{3735.0}, } od, ok := computeOD(raw) if !ok || !approx(od, 22.0) { t.Fatalf("wand w/ active Prodigal SD OD = %v ok=%v, want +22", od, ok) } } // Wand carrying a mastery value (353) with no matching wand table row: the // exact-match lookup on (34,5,0,385) misses, and the mastery-0 retry must hit // the (34,0,0,385) row → OD +22 as in TestOD_Wand. func TestOD_WandMasteryFallback(t *testing.T) { raw := map[string]any{ "ObjectClass": 31.0, "IntValues": map[string]any{"159": 34.0, "353": 5.0, "160": 385.0}, "DoubleValues": map[string]any{"152": 1.40, "167772169": 8.0}, "Spells": []any{}, "ActiveSpells": []any{}, } od, ok := computeOD(raw) if !ok || !approx(od, 22.0) { t.Fatalf("wand mastery-fallback OD = %v ok=%v, want +22", od, ok) } } // Non-loot (no workmanship) → no OD. func TestOD_NonLootNull(t *testing.T) { raw := map[string]any{ "ObjectClass": 31.0, "IntValues": map[string]any{"159": 34.0, "160": 385.0}, "DoubleValues": map[string]any{"152": 1.40}, "Spells": []any{}, "ActiveSpells": []any{}, } if _, ok := computeOD(raw); ok { t.Fatal("no-workmanship item must have no OD") } } // Table lookup miss (unknown tier) → no OD. func TestOD_UnknownTierNull(t *testing.T) { raw := map[string]any{ "ObjectClass": 31.0, "IntValues": map[string]any{"159": 34.0, "160": 999.0}, "DoubleValues": map[string]any{"152": 1.40, "167772169": 8.0}, "Spells": []any{}, "ActiveSpells": []any{}, } if _, ok := computeOD(raw); ok { t.Fatal("unknown wieldreq tier must have no OD") } } // Melee: OD = buffedMaxDmg - varianceTinks - tableMaxDmg, // varianceTinks = round(log(tableMaxVar/variance)/log(0.8), 2). // Heavy (44) sword (2) non-MS, wieldreq 430: tableMaxDmg=71, tableMaxVar=0.47. // A perfect retail-max item: maxDmg 71, variance 0.47 → varianceTinks=0 → OD 0. func TestOD_MeleeRetailMaxIsZero(t *testing.T) { raw := map[string]any{ "ObjectClass": 1.0, "IntValues": map[string]any{"159": 44.0, "353": 2.0, "160": 430.0, "218103842": 71.0}, "DoubleValues": map[string]any{"167772171": 0.47, "167772169": 10.0}, "Spells": []any{}, "ActiveSpells": []any{}, } od, ok := computeOD(raw) if !ok || !approx(od, 0.0) { t.Fatalf("melee retail-max OD = %v ok=%v, want 0", od, ok) } } // Melee with tighter variance (0.376 = one granite tink below the 0.47 baseline): // the table MaxVar is the BASELINE variance for the tier, and lower variance than // baseline is better — it contributes positively to OD. Per UB ItemInfo.cs:1096 // (Math.Log(perfectVariance / Variance, 0.8)): // varianceTinks = round(log(0.47/0.376)/log(0.8), 2) = round(-1.00,2) = -1 // → OD = 71 - (-1) - 71 = +1. func TestOD_MeleeOneTinkVariance(t *testing.T) { raw := map[string]any{ "ObjectClass": 1.0, "IntValues": map[string]any{"159": 44.0, "353": 2.0, "160": 430.0, "218103842": 71.0}, "DoubleValues": map[string]any{"167772171": 0.376, "167772169": 10.0}, "Spells": []any{}, "ActiveSpells": []any{}, } od, ok := computeOD(raw) if !ok || !approx(od, 1.0) { t.Fatalf("melee OD = %v ok=%v, want +1", od, ok) } } // Melee buffed: innate Legendary Blood Thirst (id 6089, +10 MaxDamage). // maxDmg 61 + 10 = 71, variance 0.47 → OD 0 (vs 71 table). func TestOD_MeleeBuffedByBloodThirst(t *testing.T) { raw := map[string]any{ "ObjectClass": 1.0, "IntValues": map[string]any{"159": 44.0, "353": 2.0, "160": 430.0, "218103842": 61.0}, "DoubleValues": map[string]any{"167772171": 0.47, "167772169": 10.0}, "Spells": []any{6089.0}, "ActiveSpells": []any{}, } od, ok := computeOD(raw) if !ok || !approx(od, 0.0) { t.Fatalf("melee buffed OD = %v ok=%v, want 0", od, ok) } } // Exact SpellInfo(change, bonus) semantics (UB Dictionaries.cs:356): innate // Spells add `bonus`, ActiveSpells subtract `change`. Incantation of Blood // Drinker (5183) has change=24 but bonus=0 (the 2012 aura conversion), so: // - innate 5183 grants NOTHING → maxDmg 71 + 0 = 71 → OD 0; // - active 5183 on an enchanted maxDmg 95 (71+24) subtracts 24 → 71 → OD 0. func TestOD_MeleeInnateIncantBDGrantsNothing(t *testing.T) { innate := map[string]any{ "ObjectClass": 1.0, "IntValues": map[string]any{"159": 44.0, "353": 2.0, "160": 430.0, "218103842": 71.0}, "DoubleValues": map[string]any{"167772171": 0.47, "167772169": 10.0}, "Spells": []any{5183.0}, "ActiveSpells": []any{}, } od, ok := computeOD(innate) if !ok || !approx(od, 0.0) { t.Fatalf("innate Incant BD OD = %v ok=%v, want 0 (bonus is 0)", od, ok) } active := map[string]any{ "ObjectClass": 1.0, "IntValues": map[string]any{"159": 44.0, "353": 2.0, "160": 430.0, "218103842": 95.0}, "DoubleValues": map[string]any{"167772171": 0.47, "167772169": 10.0}, "Spells": []any{}, "ActiveSpells": []any{5183.0}, } od, ok = computeOD(active) if !ok || !approx(od, 0.0) { t.Fatalf("active Incant BD OD = %v ok=%v, want 0 (change 24 subtracted)", od, ok) } } // Missile golden test, modeled on the live Fire Compound Bow: // bow (mastery 8), wieldreq 385, maxDmg 32, elem bonus 21, DamageBonus 2.73, // no "171" key → tinks 0 → remaining 9 (the imbue tink is reserved). // dmgMod = 2.73·100−100 = 173; arrowMax = 40; maxMod = (140+136)/100 = 2.76; // OD = (1+(173+4·9)/100)·(21+32+40)/2.76 − (22+24+40) = 3.09·93/2.76 − 86 ≈ 18.12. func TestOD_MissileBow(t *testing.T) { raw := map[string]any{ "ObjectClass": 9.0, "IntValues": map[string]any{"159": 47.0, "353": 8.0, "160": 385.0, "218103842": 32.0, "204": 21.0}, "DoubleValues": map[string]any{"167772174": 2.73, "167772169": 6.0}, "Spells": []any{}, "ActiveSpells": []any{}, } od, ok := computeOD(raw) if !ok || !approx(od, 18.12) { t.Fatalf("missile bow OD = %v ok=%v, want 18.12", od, ok) } }