ClockworkRS Docs

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; WornBonuses sums 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 from api/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_wand cache=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_bonusesbucket_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.txt line 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.*'):
    1. all 489 items' params vs expected (collected mismatch report),
    2. wearpos + two-handed flags,
    3. weapon-category resolution for every curated weapon,
    4. WornBonuses aggregation 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)

  1. All 489 curated items are correct — stats, speeds, wearpos, 2h flags.
  2. Keris partisan is the only genuine weapon-category gap in common PvM gear: the WeaponCategory enum has no Partisan value and objs.toml has no entry, so partisans behave as Unarmed. Fix = enum value + weapon_attack_styles/weapon_attack_types rows + toml entry. (Cache-wide, 156/1235 right-hand equipables are uncategorized, but the rest are quest/holiday props.)
  3. 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_shield 11283, dragonfire_ward 22002, wyvern_shield 21633). Needs a WornBonuses special case (same pattern as Tumeken's shadow ×3). Expected values are pinned to cache in the curated list until then.
  4. 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.
  5. Generated Kotlin files are excluded from spotless (formatter-conventions.gradle.kts targetExclude).

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).

On this page