MosswartOverlord/docs/superpowers/specs/2026-07-15-weapon-od-loot-profile-design.md
Erik 7a11122a26 docs: spec + plan to recompute OD from VTank loot profile (0-10 scale)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 19:06:02 +02:00

77 lines
3.8 KiB
Markdown
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.

# Weapon OD — recompute from the VTank loot profile (010 scale)
**Date:** 2026-07-15
**Status:** Approved (user directed the rebuild)
## Problem
The shipped OD (`od.go`) ports UtilityBelt's unbounded "over-retail damage"
float (16 … +43). The user's actual in-game OD is a **010** (casters 07)
integer they see on ident. Investigation (this session) proved:
- Mag-Tools has **no** OD calculation. Its `ItemInfoPrinter` prints the item
info **plus the matching VirindiTank loot-rule name** (`GetLootRuleInfoFromItemInfo`).
- The user's `VirindiTank\Loot.utl` defines a ladder of rules named
`(H) Axe (OD +10)`, `(OD +9)`, … per weapon bucket. Mag-Tools prints whichever
matched — **that** is the OD the user sees.
The thresholds are the same across all the user's profiles (user-confirmed), so
one table drives every weapon and caster.
## The rule (decoded from Loot.utl)
Each bucket is `OD = clamp(stat baseline, 1, cap)`, where the bucket is matched
by **weapon skill + mastery + (optional) weapon-name filter**, and `stat` is a
raw captured value. Ladders step by 1 per tier.
- **Melee** (object_class 1): `stat = MaxDamage` (int key `218103842`), cap 10.
27 buckets (skill `218103840`, mastery `353`, name filter, baseline) — see the
table in the plan. Example: 2H Cleaving baseline 45 → Tetsubo (maxdmg 85) = OD
**+10** (matches in-game); Heavy Axe baseline 74.
- **Crossbow** (object_class 9, mastery `353`=9): `stat = int key 218103839`
("effective missile damage"), baseline **77**, cap 10 (OD+10=87 … OD+1=78,
verified on Fire Compound Crossbow k839=89 → +10).
- **Casters** (object_class 31): `stat = ElementalDamageVersusMonsters` (double
key `152`), baseline **1.18**, ×100, cap **7**. War bucket: skill `159`=34,
name `Baton|Sceptre|Staff`; Void bucket: skill 43, name `Nether`. OD+7 at
elemVsMon 1.25, OD+1 at 1.19. Frost Baton (1.40) → +7.
- **No match / stat below baseline+1 → NULL** (item shows no OD, exactly like
in-game when no OD rule matches).
## Deferred: Bows & Thrown (mastery 8 / 10)
Their OD rules threshold a **computed decimal** damage stat (bow OD+10 = 78.6,
thrown = 85.3) on a key not reliably identifiable from the profile alone (bow
`218103839` values 4570 don't reach 78.6; the rule uses a DoubleValKeyGE the
`.utl` encodes indirectly). To avoid showing a *wrong* number (the very problem
we're fixing), **bows and thrown get NULL OD** in this pass. Reopened once the
user supplies one in-game bow OD reading to calibrate the stat.
## Design
- **`od.go` rewritten** around an ordered bucket table (extracted from
`Loot.utl`, committed as data): `{objectClass, skill, mastery, nameAlts[],
statKey, baseline, cap}`. `computeOD` finds the first bucket whose
objectClass+skill+mastery+name match, then `OD = clamp(stat baseline, 1,
cap)`; returns null if none match or stat ≤ baseline. Name match = any
alternative is a substring of the item name (VTank substring semantics).
- **Type**: `od_rating` stays `DOUBLE PRECISION NULL` but now holds an integer
110 (or 17). Frontend already renders it; drop the `+`/decimals for the new
integer (show plain `7`, `10`, `—`).
- Search `min_od`/`max_od`/sort unchanged. Backfill via existing
`POST /admin/backfill-od`.
- The best-values table, spell-effect dicts, and variance math in the old
`od.go` are **deleted** (wrong metric).
## Testing
Go golden tests from real items: Tetsubo → 10, Frost Baton → 7, Fire Compound
Crossbow → 10, a mid melee → its expected tier, a below-baseline weapon → null,
a bow → null (deferred). Then live backfill + spot-checks; the column must read
010/07 and the user confirms against ident.
## Out of scope
- Bows/thrown OD (deferred, null).
- The loot-rule name filters are reproduced as substring lists; exotic renamed
items may differ from a live VTank match — acceptable.