Equipment stats audit — the gear dataset & how item data actually works
Phase 0 of the combat/items track: a standing audit that proves the equipment stats our
Phase 0 of the combat/items track: a standing audit that proves the equipment stats our server serves match live OSRS, for ~490 commonly used PvM items. Green since 2026-07-07.
TL;DR — where item stats come from
- Combat bonuses ship in the cache. Every item's stab/slash/crush/magic/ranged
attack & defence, melee/ranged strength, magic damage and prayer bonus are obj
params (ids 0–12, 299, 189) decoded from the vanilla cache (opcode 249). Nothing
server-side authors them;
WornBonusessums them off worn slots and the combat formulas consume them. A spawned Twisted bow is already correct. - Magic damage units: wiki shows whole percent; param 299 stores tenths
(occult necklace 5% →
param_299=50). - The cache does NOT ship weapon categories.
weaponCategory(drives attack styles/stances/anims in the combat tab) is injected by the cache enricher fromapi/cache-enricher/.../objs.toml. A weapon missing there behaves as Unarmed (punch styles, default 4t speed). Attack speed (param_14) is usually in the cache, but the enricher overrides stale values (e.g.dragonhunter_wandcache=4 → toml=5, which matches live). Runtime truth = cache + enricher.
The audit harness
scripts/gear-audit/
curated_gear.txt # ~340 wiki pages: metal tiers, barrows, GWD, nex, cox/tob/toa,
# void, moons, hueycoatl, colosseum, DT2, wildy, jewellery...
gen_expected_gear.py # wiki mirror sqlite -> ExpectedGearStats.kt (489 items)
check_against_dump.py # instant offline diff vs osrs-dumps/config/dump.obj
api/player/src/integration/.../worn/gear/
ExpectedGearStats.kt # GENERATED - do not hand-edit
EquipmentStatsAuditTest.kt # 8 integration tests- The oracle is the local OSRS wiki mirror (
mcp/wiki-mcp/cache/osrs-wiki.db,bucket_infobox_bonuses⋈bucket_item_id). Regenerate after editing the curated list:python scripts/gear-audit/gen_expected_gear.py(any Python 3.10+; the wiki-mcp venv works). curated_gear.txtline syntax:Page[#Version][ | field=value, ...]. Overrides pin a field to our rev-239 cache value where live has since diverged, with a comment saying why. Version pick rules: poisonables#(unp), imbued jewellery#Nightmare Zone, degradables#New, void/capes#Normal(the generator has a preference list and errors on ambiguity).- Tests (
:api:player:integration --tests 'org.rsmod.api.player.worn.gear.*'):- all 489 items' params vs expected (collected mismatch report),
- wearpos + two-handed flags,
- weapon-category resolution for every curated weapon,
WornBonusesaggregation on benchmark loadouts — max melee, max ranged (quiver ammo counted), Tumeken's shadow (×3 offensive magic + ×3 damage multiplier), elite mage void (+50 magic damage) — expected totals computed from the dataset, not hardcoded.
- Plus an informational cache-wide sweep printing every equipable right-hand item with no weapon category.
Findings (2026-07-07)
- All 489 curated items are correct — stats, speeds, wearpos, 2h flags.
- Keris partisan is the only genuine weapon-category gap in common PvM gear:
the
WeaponCategoryenum has noPartisanvalue andobjs.tomlhas no entry, so partisans behave as Unarmed. Fix = enum value +weapon_attack_styles/weapon_attack_typesrows + toml entry. (Cache-wide, 156/1235 right-hand equipables are uncategorized, but the rest are quest/holiday props.) - Charged dragonfire shields don't get their +50 defences. Live applies charge-state
bonuses dynamically; the cache carries uncharged-equal stats on the charged ids
(
dragonfire_shield11283,dragonfire_ward22002,wyvern_shield21633). Needs aWornBonusesspecial case (same pattern as Tumeken's shadow ×3). Expected values are pinned to cache in the curated list until then. - Post-rev-239 live rebalances exist (steel warhammer str 18→16, steel halberd defences, ursine chainmace slash def). We target rev-239 parity → cache values pinned via overrides.
- Generated Kotlin files are excluded from spotless (
formatter-conventions.gradle.ktstargetExclude).
Extending
New set drops / new tier → add page names to curated_gear.txt, rerun the generator, run
check_against_dump.py for an instant offline sanity check, then the integration tests.
The generator fails loudly on unknown pages/ambiguous versions and reports wiki items whose
ids are missing from our cache (post-239 content).