Skip to main content

Mining System API

The mining system implements OSRS-accurate mechanics including level-dependent success rates, pickaxe speed bonuses, and 100% rock depletion.

Success Rate Calculation

Location: packages/shared/src/systems/shared/entities/gathering/SuccessRateCalculator.ts

computeSuccessRate()

Calculate mining success rate based on player level.
computeSuccessRate(
  skill: string,
  level: number,
  successRateData: { low: number; high: number } | null
): number
Parameters:
  • skill - The gathering skill (“mining”, “woodcutting”, “fishing”)
  • level - Player’s skill level (1-99)
  • successRateData - Success rate bounds from manifest (or null for 100% success)
Returns: Success probability (0.0 to 1.0) Formula:
P(Level) = (1 + floor(low × (99 - L) / 98 + high × (L - 1) / 98 + 0.5)) / 256
Example:
// Copper ore at level 1
const successRate = computeSuccessRate("mining", 1, { low: 100, high: 256 });
// Returns: ~0.395 (39.5%)

// Copper ore at level 62
const successRate = computeSuccessRate("mining", 62, { low: 100, high: 256 });
// Returns: 1.0 (100%)

computeCycleTicks()

Compute gathering cycle in ticks (OSRS-accurate, skill-specific).
computeCycleTicks(
  skill: string,
  baseCycleTicks: number,
  toolData: GatheringToolData | null,
  bonusRollTriggered: boolean = false
): number
Parameters:
  • skill - The gathering skill (“mining”, “woodcutting”, “fishing”)
  • baseCycleTicks - Base cycle ticks from resource manifest
  • toolData - Tool data from tools.json manifest (may have rollTicks for mining)
  • bonusRollTriggered - Whether dragon/crystal pickaxe bonus speed triggered (caller rolls this server-side)
Returns: Number of ticks between gathering attempts
Deterministic Function: This function contains NO randomness. For dragon/crystal pickaxe bonus speed, the caller must roll and pass bonusRollTriggered.
Example:
// Bronze pickaxe (8 ticks)
const ticks = computeCycleTicks("mining", 8, { rollTicks: 8 }, false);
// Returns: 8

// Dragon pickaxe with bonus triggered (2 ticks instead of 3)
const ticks = computeCycleTicks("mining", 3, { 
  rollTicks: 3, 
  bonusRollTicks: 2,
  bonusTickChance: 0.167 
}, true);
// Returns: 2

Mining Constants

Location: packages/shared/src/constants/GatheringConstants.ts

Success Rates

MINING_SUCCESS_RATES: {
  ore_copper: { low: 100, high: 256 },    // 39.5% at L1, 100% at L62
  ore_tin: { low: 100, high: 256 },       // Same as copper
  ore_iron: { low: 133, high: 256 },      // 52% at L15, 100% at L63
  ore_coal: { low: 42, high: 101 },       // 16.4% at L30, 39.5% at L99
  ore_mithril: { low: 30, high: 51 },     // 11.7% at L55, 19.9% at L99
  ore_adamant: { low: 19, high: 26 },     // 7.4% at L70, 10.2% at L99
  ore_runite: { low: 17, high: 19 },      // 6.64% at L85, 7.42% at L97+
}

Depletion

MINING_DEPLETE_CHANCE: 1.0  // OSRS: Always depletes after one ore
100% Depletion: Mining rocks ALWAYS deplete after yielding one ore. This matches OSRS behavior. The only exception would be Mining gloves (not currently implemented).

Pickaxe Tool Data

Location: packages/shared/src/data/DataManager.ts

GatheringToolData Interface

interface GatheringToolData {
  itemId: string;
  levelRequired: number;
  rollTicks?: number;           // For mining: ticks between roll attempts
  bonusTickChance?: number;     // Dragon/Crystal: chance for bonus speed roll
  bonusRollTicks?: number;      // Dragon/Crystal: tick count when bonus triggers
  priority: number;             // Lower = better (1 = best)
}

Pickaxe Speeds

PickaxeRoll TicksBonus ChanceBonus TicksAverage Speed
Bronze8--8 ticks
Iron7--7 ticks
Steel6--6 ticks
Mithril5--5 ticks
Adamant4--4 ticks
Rune3--3 ticks
Dragon31/6 (0.167)22.83 ticks
Crystal31/4 (0.25)22.75 ticks
Example Tool Data:
{
  "itemId": "dragon_pickaxe",
  "levelRequired": 61,
  "rollTicks": 3,
  "bonusTickChance": 0.167,
  "bonusRollTicks": 2,
  "priority": 1
}

Server-Side Implementation

Location: packages/shared/src/systems/shared/entities/ResourceSystem.ts

computeCycleTicks() (Server Wrapper)

Server-side wrapper that rolls for dragon/crystal pickaxe bonus speed:
private computeCycleTicks(
  skill: string,
  tuned: { levelRequired: number; baseCycleTicks: number },
  toolData: GatheringToolData | null
): number {
  // Roll for dragon/crystal pickaxe bonus speed server-side
  const bonusRollTriggered =
    toolData?.bonusTickChance !== undefined &&
    Math.random() < toolData.bonusTickChance;

  return computeCycleTicksUtil(
    skill,
    tuned.baseCycleTicks,
    toolData,
    bonusRollTriggered
  );
}
Server Authority: The random roll happens ONLY on the server. The computed cycle time is sent to the client via the gathering session state, ensuring perfect sync.

Model Loading

Location: packages/shared/src/utils/rendering/ModelCache.ts

normalizeScales()

Normalize non-uniform scales in GLTF model hierarchy.
private normalizeScales(scene: THREE.Object3D): void
Purpose: Some 3D modeling tools export models with non-uniform scales baked into internal nodes (e.g., scale (2, 0.5, 1)). This causes models to appear “squished” or “stretched”. Behavior:
  • Traverses all nodes in the scene
  • Resets non-uniform scales to (1, 1, 1)
  • Preserves uniform scales (intentional sizing)
  • Called ONCE when model is first loaded, before caching
Example:
// Detect non-uniform scale
const isNonUniform =
  Math.abs(s.x - s.y) > 0.001 ||
  Math.abs(s.y - s.z) > 0.001 ||
  Math.abs(s.x - s.z) > 0.001;

if (isNonUniform) {
  node.scale.set(1, 1, 1);
  node.updateMatrix();
}

Resource Entity

Location: packages/shared/src/entities/world/ResourceEntity.ts

Depleted Models

Resources can define depleted models in their manifest:
{
  "id": "rock_copper",
  "depletedModelPath": "asset://models/rocks/copper-rock-depleted.glb",
  "depletedModelScale": 0.8
}
Behavior:
  • When resource depletes, swaps to depleted model
  • When resource respawns, swaps back to full model
  • Works for all resource types (trees, rocks, fishing spots)
Methods:
private async swapToStump(): Promise<void>
private async swapToFullModel(): Promise<void>

Testing

Success Rate Tests

// Test OSRS-accurate success rates
it("copper ore has ~39.5% success at level 1", () => {
  const rate = computeSuccessRate("mining", 1, { low: 100, high: 256 });
  expect(rate).toBeCloseTo(0.395, 2);
});

it("copper ore has 100% success at level 62", () => {
  const rate = computeSuccessRate("mining", 62, { low: 100, high: 256 });
  expect(rate).toBe(1.0);
});

Pickaxe Bonus Tests

// Test dragon pickaxe bonus speed
it("dragon pickaxe uses 2 ticks when bonus triggers", () => {
  const ticks = computeCycleTicks("mining", 3, {
    rollTicks: 3,
    bonusRollTicks: 2,
    bonusTickChance: 0.167
  }, true);  // bonusRollTriggered = true
  
  expect(ticks).toBe(2);
});