MosswartOverlord/go-services/inventory-go/od_test.go
2026-07-15 08:00:22 +02:00

180 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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·100100 = 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)
}
}