codex: mining-f2p — executable GE purchase loop, structured copper/tin below-15 fallback, a2 target restrict (bounce-fix 5)

Governor bounce 5 (2026-08-28) repaired finding by finding:
1. Tool purchase is now executable end to end: new a10 ge_open before any
   ge_buy; a12 wait-ge-fill is BOUNDED (one 3-6 min fill window) and on
   no-fill/partial-fill TIMEOUT cancels the resting offer -> a13
   collects/refunds -> fail closed via recover (3 loops -> failed); a resting
   offer is never re-placed indefinitely. Actions renumbered a11..a17.
2. min_level 1 copper/tin below-15 path now has STRUCTURED fallback rows
   (mine_sites se-varrock-copper / se-varrock-tin, level 1, 17.5 xp, safe
   East Varrock) added to mining_modes.fast-xp.sites; level-1 and level-14
   selections are pinned in a new mining_selections block and recomputed by a
   new select_fast_site() validator gate (+ 3 committed selftest fixtures).
3. a2 no longer advertises silver/coal/gold/mithril banked targets: it
   restricts banked-income to the declared rows (iron @ se-varrock-bank,
   adamantite @ al-kharid-south-adamantite); superheat-coal technique is
   marked informational-only. New ge_open/ge_offers_done/ge_collect
   validator gate (+ 1 fixture). Modeled below-15 rate 6,300 XP/hr (was
   the unbacked '~8,800' prose) recomputed from the bronze cadence midpoint.

Validator: 2 entries, 0 errors, 1 pre-existing WARN; --selftest 17 PASS;
git diff --check clean.
This commit is contained in:
Claude 2026-08-28 15:49:44 -05:00
parent 765505ed0f
commit 64b5e4890e
3 changed files with 295 additions and 56 deletions

View File

@ -3,7 +3,7 @@ title: Mining (F2P)
domain: training
tags: [mining, gathering, f2p, skill-training]
status: verified
version: 5 # 5: bounce-fix 4 2026-08-28 — structured mine_sites/pickaxe_tiers/pickaxe_examples/mining_modes data model (validated exactly by the validator: no phrase-search); per-site respawn/depletion modeling (adamantite = respawn-bound + world-hop policy, runite NOT selectable — Wilderness PK risk); success-model rolls_per_ore; executable tool prep (check/withdraw/buy/wait/collect/equip via Wield); hop_world action; sub-29 fast-xp stays fast-xp at a safe drop site; modeled rates recomputed (28,000 xp/hr fast-xp drop; 70,080 gp/hr adamantite w/ hop; 49,900 iron banked)
version: 6 # 6: bounce-fix 5 2026-08-28 — executable GE purchase loop (new a10 ge_open action; bounded fill/no-fill timeout -> cancel/refund/collect -> fail closed, never re-place a resting offer); structured copper/tin below-15 fallback sites (mine_sites se-varrock-copper / se-varrock-tin, level 1, 17.5 xp, safe) pinned in mining_selections (levels 1 & 14) with validator fixtures; a2 restricted to the DECLARED banked-income target rows (iron @ se-varrock-bank, adamantite @ al-kharid-south-adamantite only — silver/coal/gold/mithril no longer advertised)
updated: "2026-08-28" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date
summary: >
@ -11,10 +11,13 @@ summary: >
validated against the structured mine_sites table; the entry never
silently picks "the highest available rock":
- fast-xp: powermining for XP. Iron (level 15, 35 xp) from level 15
onward, copper/tin (17.5 xp) below 15; every full inventory is DROPPED
(drop route explicit — drop_run). Sub-combat-29 accounts run fast-xp at
the safe South-east Varrock drop site; combat 29+ may use the Al Kharid
3-rock powermine. Mode NEVER switches to banking.
onward; copper/tin (17.5 xp) below 15 run on STRUCTURED fallback
mine_sites rows (se-varrock-copper / se-varrock-tin, East Varrock,
level 1, safe — selection pinned in mining_selections at levels 1 and
14). Every full inventory is DROPPED (drop route explicit — drop_run).
Sub-combat-29 accounts run fast-xp at the safe South-east Varrock drop
site; combat 29+ may use the Al Kharid 3-rock powermine. Mode NEVER
switches to banking.
- banked-income: ONE deterministic target mine_sites row chosen and
validated at session start (Mining level >= ore level; the row's
access/safety gates; inventory policy = bank) and every load is BANKED
@ -37,6 +40,7 @@ accounts:
- "Pickaxe: two SEPARATE gates per tier (pickaxe_tiers table): MINING level decides which pickaxe can be USED (bronze 1, iron 1, steel 6, mithril 21, adamant 31, rune 41); ATTACK level decides ONLY whether it can be EQUIPPED (steel 5, mithril 20, adamant 30, rune 40). Select by Mining, equip only when Attack permits, otherwise CARRY the same selected tool — a 1-Attack/41-Mining account selects, carries, and uses a rune pickaxe (pickaxe_examples)"
- "Run mode chosen at session start: fast-xp (powermine, drop every load) or banked-income (deterministic target mine_sites row, bank every load) — mining_modes table"
- "Bank route from the chosen mine (fast-xp drops instead when no bank leg is wanted)"
- "Below-15 fast-xp (Mining 1-14): copper/tin (17.5 xp) at the structured East Varrock fallback rows (mine_sites se-varrock-copper / se-varrock-tin — no aggressive NPCs, safe at any combat level); the deterministic selection is pinned in mining_selections (levels 1 and 14) and recomputed by the validator"
- "Al Kharid powermine HARD GATE: combat level >= 29 (a monster attacks players at <= 2x its level; the scorpion is level 14, so its aggro ceiling is combat 28 and 29+ is safe). Sub-combat-29 accounts run fast-xp at the South-east Varrock DROP site (no aggressive NPCs) — the mode does not change to banking"
- "adamantite @ Al Kharid south REQUIRES world hopping (mine_sites row: world_hop true, hop_world action) — respawns are 4 min and the wiki's 120+ ores/hr assumes hopping F2P worlds"
@ -53,7 +57,7 @@ best_practice:
why: "the quest reward alone takes Mining from 1 to 10; the materials (6 clay, 4 copper, 2 iron) mine at level 1 except iron, which can be bought on the GE"
source: "OSRS wiki — Free-to-play Mining training (Level 1-10: Doric's Quest)"
- name: "powermine iron for maximum XP (fast-xp only)"
why: "iron (level 15, 35 xp) is the fastest F2P training ore; Al Kharid is the only F2P site with three iron rocks around one tile (combat 29+ gate) and South-east Varrock's two iron rocks serve sub-29 accounts with the same drop behaviour — the mode is unchanged. Modeled fast-xp rate ~28,000 XP/hr at 61+ with rune (success model + drop loop + breaks pinned in economy); drop the ores, banking costs XP"
why: "iron (level 15, 35 xp) is the fastest F2P training ore; Al Kharid is the only F2P site with three iron rocks around one tile (combat 29+ gate) and South-east Varrock's two iron rocks serve sub-29 accounts with the same drop behaviour — the mode is unchanged. Below 15 the structured copper/tin fallback (se-varrock-copper / se-varrock-tin, 17.5 xp, safe East Varrock) keeps the same drop behaviour for level 1-14 (mining_selections pins levels 1 and 14). Modeled fast-xp rate ~28,000 XP/hr at 61+ with rune (success model + drop loop + breaks pinned in economy); drop the ores, banking costs XP"
source: "OSRS wiki — Free-to-play Mining training (Levels 15-99: Iron ore); OSRS wiki — Aggressiveness; OSRS wiki — Iron rocks (respawn ~5.4 s)"
- name: "bank iron at the South-east Varrock mine (banked-income anchor)"
why: "the guide names this F2P banking location explicitly; the eastern Varrock bank is the closest bank leg (~30 s per load), and the ores feed Smithing. Modeled ~49,900 gp/hr with rune (midpoint policy model, see economy comments)"
@ -64,15 +68,18 @@ best_practice:
- name: "runite is NOT selectable in this entry"
why: "F2P runite sits in the Wilderness with a ~12-minute respawn — modelling it would require Wilderness travel and PK survival plus heavy world hopping; the mine_sites row is selectable: false and the entry refuses it rather than hand-waving the hazard"
source: "OSRS wiki — Runite ore (respawn ~12 min; F2P rocks in the Wilderness); mine_sites row runite-wilderness"
- name: "superheat coal (optional banked variant): West Lumbridge Swamp mine"
why: "coal (level 30, 50 xp) banks reasonably from the Lumbridge Swamp mine; with 43 Magic, Superheat Item converts coal into bars without a furnace, cutting bank trips (staff of fire + nature runes)"
source: "OSRS wiki — Free-to-play Mining training (Levels 30-99: Superheat Item)"
- name: "superheat coal (informational only — NOT a selectable banked-income target)"
why: "the West Lumbridge Swamp mine holds coal (level 30, 50 xp); with 43 Magic, Superheat Item converts coal to bars without a furnace. This entry does NOT declare a coal mine_sites row: banked-income targets are iron and adamantite ONLY, so superheat-coal is background knowledge (staff of fire + nature runes), never an advertised or executable target — a2's target set matches mining_modes.banked-income.targets exactly"
source: "OSRS wiki — Free-to-play Mining training (Levels 30-99: Superheat Item, background note only)"
- name: "worked example — fast-xp session (preparation -> first full inventory -> first break -> resume)"
why: "Account: Mining 41, Attack 1, combat 35 (passes the Al Kharid gate). (1) prepare: mode fast-xp; ore policy iron 15+; pickaxe by MINING level -> rune (41 <= 41); equip gate Attack 1 < 40 -> CARRY the same tool (27 ore slots); the rune pickaxe is owned, so acquire = a9 withdraw-tool (bank_inventory); initial inventory = pickaxe + empty ore space. (2) travel: walk to Al Kharid (combat 35 >= 29 OK) and position at the 3-rock cluster. (3) mining rotations: click a fresh iron rock (a2, rune cadence [1800, 3600] ms) -> wait_rock: ore gained after ~1.33 rolls (success model) -> mine; repeat until the inventory is full. (4) first full-inventory boundary: 27 ores -> drop_run: 27 executions of a6 (right_click Drop, [200, 800] ms each) -> count_item ore == 0 -> mine. (5) first scheduled break: after the 8th-14th rotation the break counter trips -> break_state: take_break 60-180 s (a15). (6) resume: reposition at the cluster (a16) -> mine; the loop repeats until the XP target or session cap -> complete. Modeled policy rate: 803 ores/hr = 28,105 XP/hr -> pin 28,000 (economy)"
why: "Account: Mining 41, Attack 1, combat 35 (passes the Al Kharid gate). (1) prepare: mode fast-xp; ore policy iron 15+; pickaxe by MINING level -> rune (41 <= 41); equip gate Attack 1 < 40 -> CARRY the same tool (27 ore slots); the rune pickaxe is owned, so acquire = a9 withdraw-tool (bank_inventory); initial inventory = pickaxe + empty ore space. (2) travel: walk to Al Kharid (combat 35 >= 29 OK) and position at the 3-rock cluster. (3) mining rotations: click a fresh iron rock (a2, rune cadence [1800, 3600] ms) -> wait_rock: ore gained after ~1.33 rolls (success model) -> mine; repeat until the inventory is full. (4) first full-inventory boundary: 27 ores -> drop_run: 27 executions of a6 (right_click Drop, [200, 800] ms each) -> count_item ore == 0 -> mine. (5) first scheduled break: after the 8th-14th rotation the break counter trips -> break_state: take_break 60-180 s (a16). (6) resume: reposition at the cluster (a17) -> mine; the loop repeats until the XP target or session cap -> complete. Modeled policy rate: 803 ores/hr = 28,105 XP/hr -> pin 28,000 (economy)"
source: "derived from this entry's policy model (state_machine prepare/travel/mine/wait_rock/drop_run/break_state/complete + actions + run_control.break + mine_sites/rolls_per_ore), synthesized 2026-08-28"
- name: "worked example — banked-income adamantite session (preparation -> first full inventory -> first break -> resume)"
why: "Account: Mining 70, Attack 40, combat 60. Target: 500,000 gp banked or a 2 h session cap. (1) prepare: mode banked-income; target row = al-kharid-south-adamantite (mine_sites: ore level 70 <= Mining 70 OK; access/route validated; inventory policy bank) — the row id is logged to activity state; pickaxe by MINING level -> rune (41 <= 70); equip gate Attack 40 >= 40 -> EQUIP (28 ore slots); tool owned, withdraw from bank (a9). (2) travel: walk to the Al Kharid south adamantite spot. (3) mining rotations: click a fresh adamantite rock (rune roll 1.8 s, delay [1800, 3600] ms) -> wait_rock: ore gained -> mine; when BOTH rocks are depleted -> hop: hop_world (a14) to a fresh F2P world (world_hop row) and continue — this is where the wiki's 120 ores/hr comes from. (4) first full-inventory boundary: 28 ores -> bank_run: walk to the Al Kharid bank (a3), deposit all 28 (a4; 28 x 584 = 16,352 gp logged), walk back (a5) -> mine. (5) first scheduled break: after 8-14 rotations -> break_state: take_break 60-180 s (independent of the bank trips). (6) resume: reposition (a16) -> mine; complete when the banked value >= 500,000 gp or the session cap hits"
why: "Account: Mining 70, Attack 40, combat 60. Target: 500,000 gp banked or a 2 h session cap. (1) prepare: mode banked-income; target row = al-kharid-south-adamantite (mine_sites: ore level 70 <= Mining 70 OK; access/route validated; inventory policy bank) — the row id is logged to activity state; pickaxe by MINING level -> rune (41 <= 70); equip gate Attack 40 >= 40 -> EQUIP (28 ore slots); tool owned, withdraw from bank (a9). (2) travel: walk to the Al Kharid south adamantite spot. (3) mining rotations: click a fresh adamantite rock (rune roll 1.8 s, delay [1800, 3600] ms) -> wait_rock: ore gained -> mine; when BOTH rocks are depleted -> hop: hop_world (a15) to a fresh F2P world (world_hop row) and continue — this is where the wiki's 120 ores/hr comes from. (4) first full-inventory boundary: 28 ores -> bank_run: walk to the Al Kharid bank (a3), deposit all 28 (a4; 28 x 584 = 16,352 gp logged), walk back (a5) -> mine. (5) first scheduled break: after 8-14 rotations -> break_state: take_break 60-180 s (independent of the bank trips). (6) resume: reposition (a17) -> mine; complete when the banked value >= 500,000 gp or the session cap hits"
source: "derived from this entry's policy model (state_machine prepare/travel/mine/wait_rock/hop/bank_run/break_state/complete + actions + mine_sites.al-kharid-south-adamantite), synthesized 2026-08-28"
- name: "worked example — level-1 fast-xp copper/tin session (preparation -> first full inventory -> first break -> resume)"
why: "Account: Mining 1, Attack 1, combat 3. (1) prepare: mode fast-xp; below-15 selection pinned at level 1 (mining_selections) -> se-varrock-copper / se-varrock-tin; pickaxe by MINING -> the canonical selection at Mining 1 (bronze; iron shares the Mining-1 gate and can be substituted once owned); equip gate Attack 1 -> no equip, CARRY; the tool is owned, so acquire = a9 withdraw-tool (bank_inventory). (2) travel: walk to the East Varrock copper/tin rocks (safe — no aggressive NPCs) and position at the cluster. (3) mining rotations: click a fresh copper or tin rock (a2, bronze cadence [4800, 6600] ms) -> wait_rock: ore gained after ~1.6 rolls (success model) -> mine; both ores are 17.5 xp each. (4) first full-inventory boundary: 27 ores -> drop_run: a6 drops ONE slot per execution (right_click Drop, [200, 800] ms), looping until count_item ore == 0 -> mine. (5) first scheduled break: after the 8th-14th rotation the break counter trips -> break_state: take_break 60-180 s (a16). (6) resume: reposition at the cluster (a17) -> mine; complete on the XP/level target. Modeled policy rate: ~360 ores/hr = 6,300 XP/hr at the bronze cadence midpoint (economy)."
source: "derived from this entry's policy model (mining_selections levels 1/14 + mine_sites se-varrock-copper / se-varrock-tin + state_machine + actions a9/a2/a6/a16/a17), synthesized 2026-08-28"
pickaxe_tiers:
bronze-pickaxe:
@ -120,13 +127,26 @@ pickaxe_examples:
expected_tier: mithril-pickaxe
expected_equipped: false # 30 Mining selects mithril (21); Attack 5 < 20 -> CARRY
# Deterministic mode-selection proof (governor bounce 5, 2026-08-28): the
# validator recomputes every row from mining_modes + mine_sites (eligible =
# selectable fast-xp site whose mining_level <= level; choose the highest
# ore xp, tie-break lexicographic site id) and compares. The level-1 and
# level-14 rows prove a below-15 account can always arm a structured
# copper/tin fast-xp run (mirrors the pickaxe_examples recompute pattern).
mining_selections:
fast_xp:
- level: 1
expected_site: se-varrock-copper # copper & tin share the East Varrock rocks (17.5 xp)
- level: 14
expected_site: se-varrock-copper # iron needs 15, so 1-14 stays on the copper/tin fallback
# Mode contract (owner decision 2026-08-28): both modes explicit; each mode's
# sites/targets reference mine_sites rows; inventory policies are fixed.
mining_modes:
fast-xp:
inventory_policy: drop # powermine: every load dropped, never banked
ore_policy: "iron (level 15, 35 xp) from 15 onward; copper/tin (17.5 xp) below 15 — never highest-available; sub-combat-29 accounts DROP at the South-east Varrock site (safe), combat 29+ may use the Al Kharid powermine — the mode never switches to banking"
sites: [al-kharid-powermine, se-varrock-drop]
ore_policy: "Mining 15+: iron (level 15, 35 xp) — never highest-available; Mining 1-14: copper/tin (17.5 xp) at the structured fallback rows (mine_sites se-varrock-copper / se-varrock-tin, level 1, safe); sub-combat-29 accounts DROP at the South-east Varrock site (safe), combat 29+ may use the Al Kharid powermine — the mode never switches to banking; site selection pinned in mining_selections (levels 1 and 14)"
sites: [al-kharid-powermine, se-varrock-drop, se-varrock-copper, se-varrock-tin]
banked-income:
inventory_policy: bank # every load banked until the GP target
targets: [se-varrock-bank, al-kharid-south-adamantite]
@ -168,6 +188,36 @@ mine_sites:
access: "no aggressive NPCs — safe at any combat level (the sub-29 fast-xp site)"
safety: "none required"
route: "drop in place — no bank leg"
se-varrock-copper:
ore: Copper ore
ore_id: 436
mining_level: 1
xp: 17.5
rock_count: 2
respawn_seconds: 5.4
rolls_per_ore: 1.6 # below-15 success model (lower than iron 61+ = 1.33)
modeled_ores_per_hour: 360 # bronze cadence midpoint 5.7 s: 1.6 x 5.7 + 0.5 + 0.39 = 10.01 s/ore (economy)
world_hop: false
selectable: true
inventory_policy: drop
access: "East Varrock copper/tin rocks — no aggressive NPCs; safe at any combat level"
safety: "none required"
route: "drop in place; tin stands at the same rocks (se-varrock-tin) — a below-15 fast-xp run mines both copper and tin (17.5 xp each)"
se-varrock-tin:
ore: Tin ore
ore_id: 438
mining_level: 1
xp: 17.5
rock_count: 2
respawn_seconds: 5.4
rolls_per_ore: 1.6
modeled_ores_per_hour: 360
world_hop: false
selectable: true
inventory_policy: drop
access: "East Varrock tin/copper rocks — no aggressive NPCs; safe at any combat level"
safety: "none required"
route: "drop in place; copper stands at the same rocks (se-varrock-copper) — a below-15 fast-xp run mines both copper and tin (17.5 xp each)"
se-varrock-bank:
ore: Iron ore
ore_id: 440
@ -192,7 +242,7 @@ mine_sites:
respawn_seconds: 240 # wiki F2P adamantite guide: ~4-min respawn
rolls_per_ore: 2.0 # conservative success input at 70 Mining (rate is respawn-bound, not success-bound)
modeled_ores_per_hour: 120 # Sourced measured baseline: wiki money guide ~120 ores/hr WITH world hopping; no-hop ceiling ~30 ores/hr
world_hop: true # hop_world required (a14); the modelled rate assumes it
world_hop: true # hop_world required (a15); the modelled rate assumes it
selectable: true
inventory_policy: bank
access: "Al Kharid south rocks (no aggressive NPCs at this cluster)"
@ -241,8 +291,10 @@ economy:
# banked adamantite @ Al Kharid south (hop): 120 ores/hr (sourced
# measured baseline) x 584 = 70,080 gp/hr; no-hop ceiling ~30 ores/hr
# = 17,520 gp/hr — the entry models ONLY the hop rate.
# bronze path (below 15, copper/tin 17.5 xp): cadence midpoint 5.7 s ->
# ~8,800 XP/hr.
# copper/tin path (Mining 1-14, 17.5 xp): cadence midpoint 5.7 s
# (bronze [4800, 6600]) -> 1.6 rolls x 5.7 + 0.5 drop + 0.39 break =
# 10.01 s/ore -> 360 ores/hr -> 6,300 XP/hr (mine_sites
# se-varrock-copper / se-varrock-tin).
# EXTERNAL BENCHMARK (NOT the modeled rate): the wiki's 50,000 XP/hr
# (41+) / 52,500 XP/hr (61+) iron powermine figures assume a continuous
# 3-tick rotation with zero delay/drop/break overhead; the configured
@ -265,6 +317,7 @@ economy:
- "F2P ore chain (wiki guide): clay 1/5xp, copper 1/17.5, tin 1/17.5, iron 15/35, silver 20/40, coal 30/50, gold 40/65, mithril 55/80, adamantite 70/95, runite 85/125"
- "Sell prices 2026-08-27 (low edge): iron 70, coal 151, silver 73, gold 154, mithril 159, adamantite 584, runite 10,265"
- "fast-xp: powermine iron 15+ and drop every load — XP is the product, realized gp 0; modeled 803 ores/hr -> 28,105 XP/hr (pin 28,000) at 61+ rune"
- "fast-xp 1-14 (copper/tin 17.5 xp): modeled ~360 ores/hr -> 6,300 XP/hr at the bronze cadence midpoint (economy comment) — structured rows se-varrock-copper / se-varrock-tin, pinned at levels 1 and 14 in mining_selections"
- "banked-income iron @ SE Varrock: modeled 713 ores/hr -> 49,910 gp/hr (pin 49,900); deterministic target row (mine_sites.se-varrock-bank)"
- "banked-income adamantite @ Al Kharid south: respawn-BOUND site (2 rocks, 4-min respawn) — modeled 120 ores/hr ONLY with world hopping (hop_world) = 70,080 gp/hr; without hopping the ceiling is ~30 ores/hr = 17,520 gp/hr (wiki F2P adamantite money guide)"
- "runite is NOT a selectable target in this entry — F2P runite sits in the Wilderness (PK risk, ~12-min respawn); the mine_sites row is selectable: false"
@ -277,22 +330,25 @@ state_machine:
goal: >-
Session start. 1) Choose the run mode (mining_modes): fast-xp
(powermine, drop) or banked-income (deterministic target mine_sites
row, bank). 2) fast-xp ore policy: iron 15+ (copper/tin below 15),
site from mining_modes.fast-xp.sites (combat 29+ -> Al Kharid
powermine, else SE Varrock drop — SAME mode, never banking).
banked-income: pick ONE row from mining_modes.banked-income.targets
and validate: level_is Mining >= row mining_level, the row's access
gates hold (e.g. adamantite requires the hop policy), inventory
policy == bank; the row id is logged to activity state. 3) Pickaxe:
select the highest pickaxe_tiers row whose mining_level the account
meets (rune is usable at 41 Mining and equipable at 40 Attack).
4) Equip ONLY when level_is Attack >= the row's attack_level;
otherwise carry the same selected tool (a 1-Attack/41-Mining account
selects and CARRIES a rune pickaxe). 5) Acquire the tool if missing
(a8-a12: check / withdraw-owned / buy / wait / collect) and build
the initial inventory: pickaxe (equipped or carried) + empty ore
space; no food (all routes gate-safe). 6) Record mode + target row
id in activity state.
row, bank). 2) fast-xp ore/site selection (deterministic; pinned in
mining_selections at levels 1 and 14): Mining 15+ -> iron site from
mining_modes.fast-xp.sites (combat 29+ -> Al Kharid powermine, else
SE Varrock drop — SAME mode, never banking); Mining 1-14 -> the
structured copper/tin fallback (mine_sites se-varrock-copper /
se-varrock-tin, 17.5 xp, safe). banked-income: pick ONE row from
mining_modes.banked-income.targets and validate: level_is Mining >=
row mining_level, the row's access gates hold (e.g. adamantite
requires the hop policy), inventory policy == bank; the row id is
logged to activity state. 3) Pickaxe: select the highest
pickaxe_tiers row whose mining_level the account meets (rune is
usable at 41 Mining and equipable at 40 Attack). 4) Equip ONLY when
level_is Attack >= the row's attack_level; otherwise carry the same
selected tool (a 1-Attack/41-Mining account selects and CARRIES a
rune pickaxe). 5) Acquire the tool if missing (a8-a13: check /
withdraw-owned / open GE / place offer / bounded wait+collect) and
build the initial inventory: pickaxe (equipped or carried) + empty
ore space; no food (all routes gate-safe, incl. the copper/tin
fallback). 6) Record mode + target row id in activity state.
next: [travel, complete, failed]
travel:
goal: "walk/teleport to the selected mine_sites location (fast-xp: Al Kharid 3-rock powermine at combat 29+, else South-east Varrock; banked-income: the session's validated target row) and position at the target cluster; arrive with the pickaxe equipped or carried per the prepare gate"
@ -301,9 +357,10 @@ state_machine:
goal: >-
Mine the session target rock until the inventory is full or the run
target is reached. Rock selection is MODE-DRIVEN and deterministic:
fast-xp mines iron (15+) or copper/tin (<15); banked-income mines
ONLY the session's target mine_sites row (never the highest
available rock). Every a2 click is followed by the wait_rock outcome
fast-xp mines iron (15+) or copper/tin (<15, the structured
se-varrock fallback rows); banked-income mines ONLY the session's
target mine_sites row (never the highest available rock). Every a2
click is followed by the wait_rock outcome
wait — never chain clicks on the same rock. A world_hop site
(adamantite) hops when both rocks are depleted.
next: [wait_rock, drop_run, bank_run, hop, recover, survive, complete]
@ -311,7 +368,7 @@ state_machine:
goal: "after clicking a rock (a2), wait for the mining outcome — ore gained (may take several rolls: rolls_per_ore), rock depleted, or roll timeout (2x the equipped tier's roll) — before any re-click; never re-click the active rock while its roll is unresolved; respawns of the site's rock_count rocks are absorbed here (wiki respawn figures in mine_sites)"
next: [mine, hop, drop_run, bank_run, recover]
hop:
goal: "world_hop sites ONLY (al-kharid-south-adamantite): both target rocks are depleted -> hop_world (a14) to a fresh F2P world and continue mining its fresh rocks; hop count tracked; after 8 consecutive hops with no ore gained, go to recover (possible world lockout / price-stale)"
goal: "world_hop sites ONLY (al-kharid-south-adamantite): both target rocks are depleted -> hop_world (a15) to a fresh F2P world and continue mining its fresh rocks; hop count tracked; after 8 consecutive hops with no ore gained, go to recover (possible world lockout / price-stale)"
next: [mine, recover]
drop_run:
goal: "fast-xp ONLY: drop every occupied ore slot — one right-click Drop per slot (a6 re-executed), looping until count_item ore == 0. Slot count is DYNAMIC: inventory capacity minus non-ore items (equipped pickaxe = 28 ore slots, carried = 27, any session load = fewer) — never a hardcoded loop count"
@ -320,10 +377,10 @@ state_machine:
goal: "banked-income ONLY: walk to the nearest bank, deposit all ores (activity state: target row id + load value logged), walk back to the mine; the pickaxe stays (equipped or carried) and the inventory is rebuilt as empty ore space for the next load"
next: [mine, break_state, complete]
break_state:
goal: "scheduled break independent of incidental banking: after every run_control.break.every rotations (8-14) take_break (60-180 s AFK, a15), then reposition at the cluster (a16) and resume mining"
goal: "scheduled break independent of incidental banking: after every run_control.break.every rotations (8-14) take_break (60-180 s AFK, a16), then reposition at the cluster (a17) and resume mining"
next: [mine, complete]
recover:
goal: "diagnose and fix: no fresh rock of the target tier for 2 respawn cycles (rotate cluster / wait respawn / hop for world_hop rows); invalid location (is_at fails — wrong mine or wrong ore) -> re-travel to the correct mine_sites location; missing tool (has_item pickaxe false) -> acquire (a8-a12) and re-prepare; path failure (move_to on_fail) -> retry travel. Persistent after 3 recoveries -> failed"
goal: "diagnose and fix: no fresh rock of the target tier for 2 respawn cycles (rotate cluster / wait respawn / hop for world_hop rows); invalid location (is_at fails — wrong mine or wrong ore) -> re-travel to the correct mine_sites location; missing tool (has_item pickaxe false) -> acquire (a8-a13) and re-prepare; path failure (move_to on_fail) -> retry travel. Persistent after 3 recoveries -> failed"
next: [prepare, travel, mine, failed]
survive:
goal: "defensive fallback only (the 29+ gate means scorpions never aggro at Al Kharid): if attacked anyway (gate bypassed/griefed), retreat out of the aggro radius and re-enter the cluster when safe; no food loop is modeled"
@ -421,7 +478,7 @@ state_machine:
when: "session cap reached during the break"
to: complete
- from: recover
when: "tool missing -> re-acquired (a8-a12) or withdrawn from bank"
when: "tool missing -> re-acquired (a8-a13) or withdrawn from bank"
to: prepare
- from: recover
when: "wrong location -> re-routed to the correct mine_sites location"
@ -458,7 +515,7 @@ actions:
verb: click_object
target: "fresh rock of the session target tier — never the rock whose roll is in progress"
params:
rock: "MODE-DRIVEN: fast-xp -> iron rock (level 15+; copper/tin below 15); banked-income -> ONLY the session's target mine_sites row (silver 20, coal 30, gold 40, mithril 55, adamantite 70) — never highest-available"
rock: "MODE-DRIVEN: fast-xp -> Mining 15+: iron rock (Al Kharid 3-rock / SE Varrock 2-rock); Mining 1-14: copper/tin rock at the East Varrock fallback (mine_sites se-varrock-copper / se-varrock-tin, 17.5 xp); banked-income -> ONLY the session's declared target row from mining_modes.banked-income.targets (iron @ se-varrock-bank or adamantite @ al-kharid-south-adamantite — the ONLY declared/validated banked targets; no other ore is advertised) — never highest-available"
gate: "level_is Mining >= the site row's mining_level (e.g. adamantite = 70), and for banked-income the rock must be at the session's validated mine_sites location"
mode_visible: "activity state logs mode + target row id + ore name on every rotation — the target is visible, never implicit"
rotation: "the site's rock_count cluster (Al Kharid 3-rock / SE Varrock 2-rock / adamantite 2-rock); click a fresh rock only after the wait_rock outcome wait (rolls_per_ore success model)"
@ -530,7 +587,7 @@ actions:
verb: has_item
target: "the selected pickaxe tier (Mining-gated: pickaxe_tiers)"
params:
branch: "owned -> a9 withdraw-tool (or straight to a13 equip-tool if already in the inventory); missing -> a10 buy-tool (GE purchase path)"
branch: "owned -> a9 withdraw-tool (or straight to a14 equip-tool if already in the inventory); missing -> the GE purchase branch: a10 open-ge -> a11 buy-tool -> a12 wait (bounded fill) -> a13 collect (verify)"
verify: "inventory holds exactly one pickaxe of the selected tier — never a second pickaxe"
on_fail: skip
anti_ban:
@ -548,6 +605,17 @@ actions:
delay: [500, 1200]
- id: a10
step: open-ge
verb: ge_open
target: grand-exchange
params:
location: "the GE booth (bank side) — REQUIRED before any ge_buy / ge_collect: the exchange interface must be open before an offer is placed or items are collected"
note: "only the purchase branch reaches the GE (a8 missing-tool); owned tools go straight to a9 withdraw-tool"
on_fail: retry
anti_ban:
delay: [600, 1600]
- id: a11
step: buy-tool
verb: ge_buy
target: grand-exchange
@ -555,32 +623,34 @@ actions:
item: "the selected pickaxe tier (Mining-gated)"
qty: 1
max_price: "GE mid + 10% cap"
pair_with: "a11 wait-ge-fill (offer fill wait) then a12 collect-tool (collect + verify)"
note: "place exactly ONE buy offer; the fill waits at most the BOUNDED a12 window — a resting offer is never retried indefinitely"
pair_with: "a10 open-ge (before) then a12 wait-ge-fill (bounded) then a13 collect-tool (collect + verify)"
on_fail: retry
anti_ban:
delay: [1200, 3000]
- id: a11
- id: a12
step: wait-ge-fill
verb: ge_offers_done
target: "the pickaxe purchase offer"
params:
until: "the pickaxe offer fills or times out — never proceed with a pending offer"
on_fail: retry
until: "the pickaxe offer FILLS (has_item pickaxe) — poll for exactly ONE bounded fill window (3-6 min); on no-fill / partial-fill TIMEOUT: CANCEL the resting offer, collect the item if it finally filled or REFUND the gp (a13), then verify — fail CLOSED via recover (3 loops -> failed); never re-place the same resting offer"
on_fail: alert
anti_ban:
delay: [900, 2400]
- id: a12
- id: a13
step: collect-tool
verb: ge_collect
target: grand-exchange
params:
verify: "has_item pickaxe == true after collection; otherwise recover (a8 path) — a failed purchase or equip is diagnosed, never assumed"
collect: "finished GE transactions — the bought pickaxe if the offer filled, and the gp refund if it was cancelled on timeout; collection closes the offer slot"
verify: "has_item pickaxe == true after collection; if still missing (no-fill / cancelled), diagnose via recover (a8 path, 3 loops -> failed) — a failed purchase is diagnosed, never silently re-queued"
on_fail: retry
anti_ban:
delay: [600, 1400]
- id: a13
- id: a14
step: equip-tool
verb: menu_option
target: "the selected pickaxe tier in the inventory"
@ -591,7 +661,7 @@ actions:
anti_ban:
delay: [600, 1400]
- id: a14
- id: a15
step: hop-world
verb: hop_world
target: "a fresh F2P world"
@ -602,7 +672,7 @@ actions:
anti_ban:
delay: [900, 2200]
- id: a15
- id: a16
step: take-break
verb: take_break
target: "self"
@ -613,7 +683,7 @@ actions:
anti_ban:
delay: [300, 900]
- id: a16
- id: a17
step: reposition-cluster
verb: walk_to
target: "cluster center / fresh rock of the target tier"
@ -629,11 +699,12 @@ run_control:
max_iterations: null
break:
every: [8, 14] # rotations; the break counter is independent of incidental banking and hops
for: [60, 180] # seconds of idle AFK (take_break, a15)
for: [60, 180] # seconds of idle AFK (take_break, a16)
stop_conditions:
- "fast-xp: Mining XP/level target reached (session target); banked-income: banked GP target reached or session cap"
- "3 consecutive failed rock clicks (client issue — do not loop)"
- "recover looped 3 times: persistent missing tool, invalid location, or path failure"
- "GE purchase fail-closed: the bounded fill window expired and the tool is still missing after cancel/refund/collect (recover looped 3 times) — never re-place a resting buy offer"
- "death: hard failure — the Al Kharid hard gate (combat 29+) and the survive (retreat) fallback prevent it in normal operation; if is_dead anyway (gate bypassed/griefed) log ERROR + alert and stop — no respawn loop"
risk:
@ -642,13 +713,17 @@ risk:
- "Mining is a staple low-risk method: long, repetitive, low-input behaviour that matches normal player sessions — the guide itself recommends hours of drop-mining."
- "The powermine rotation is the detection vector if executed perfectly for hours — keep the a2 tier-aware cadence (wait_rock outcome wait + roll_table pause; anchor rune 1.8-3.6 s, never below the equipped tier's full roll) and take the break schedule."
- "Al Kharid mine has aggressive level-14 scorpions (they attack players at combat level <= 28 — wiki Aggressiveness). The entry HARD-gates Al Kharid to combat 29+; sub-29 accounts run fast-xp at the South-east Varrock DROP site (no aggressive NPCs, same mode). No food is part of either route; the survive state (retreat) is a defensive fallback only."
- "Below-15 fast-xp stays at the safe East Varrock copper/tin rocks (se-varrock-copper / se-varrock-tin — no aggressive NPCs), so no food or combat gate applies; the mode is unchanged (drop every load)."
- "World hopping (adamantite row only) is pace-sensitive: hop at the depletion trigger, not on a timer, and back off after 8 fruitless hops. The modelled 120 ores/hr is the wiki's hopping baseline — never claim it without the hop policy."
- "Bottom line on rates: 28,000 XP/hr (fast-xp) and 70,080 gp/hr (adamantite w/ hop) are this entry's modelled policy rates derived in economy; the wiki's 52,500 XP/hr is a labeled external benchmark, not an achieved rate."
- "Bottom line on rates: 28,000 XP/hr (fast-xp iron 15+) and 6,300 XP/hr (fast-xp copper/tin below 15) are this entry's modelled fast-xp policy rates; 70,080 gp/hr (adamantite w/ hop) is the banked-income modelled rate — all derived in economy; the wiki's 52,500 XP/hr is a labeled external benchmark, not an achieved rate."
sources:
- "OSRS wiki — Active/passive aggressive monsters: Aggressiveness (a monster attacks players whose combat level is <= 2x its level; a level-14 scorpion is safe from combat level 29) — verified 2026-08-27"
- "OSRS wiki — Scorpion (combat level 14, hitpoints 17; Al Kharid mine) — verified 2026-08-27"
- "OSRS wiki — Free-to-play Mining training (ore level/xp table — all rows F2P; pickaxe tier table with tick intervals (bronze 8 -> rune 3); Doric's Quest 1,300 xp; iron 50,000 xp/hr at 41+ / 52,500 at 61+ cited as zero-overhead external benchmarks only — the configured midpoint cadence caps the ideal at ~46,667; Al Kharid 3-rock spot; SE Varrock banking; Superheat coal; scorpions noted — this entry hard-gates the mine to combat 29+ instead, so food is not modeled) — verified 2026-08-27"
- "OSRS wiki — Copper ore / Tin ore (level 1, 17.5 XP each, F2P) — the below-15 fast-xp fallback rows (se-varrock-copper / se-varrock-tin) — verified 2026-08-28"
- "OSRS wiki — East of Varrock mine (copper/tin/iron rocks; no aggressive NPCs) — the safe below-15 and sub-29 fast-xp/SE Varrock location — verified 2026-08-28"
- "OSRS wiki — Grand Exchange (buy offers: place at mid, cancel/refund on timeout) — a10-a13 purchase loop — wiki mechanics, verified 2026-08-28"
- "OSRS wiki — Money making guide/Mining adamantite ore (F2P) (4-minute respawn; about 120+ ores/hour by hopping worlds only; at the committed 584 gp that is <=17,520 gp/hr without hopping or ~70,080 gp/hr at 120 ores/hr) — verified 2026-08-28"
- "OSRS wiki — Iron rocks (respawn approximately 5.4 seconds) — verified 2026-08-28"
- "OSRS wiki — Runite ore (respawn about twelve minutes; F2P rocks in the Wilderness) — verified 2026-08-28"

View File

@ -123,6 +123,26 @@
}
},
"mining_selections": {
"type": "object",
"additionalProperties": false,
"properties": {
"fast_xp": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["level", "expected_site"],
"additionalProperties": false,
"properties": {
"level": { "type": "integer", "minimum": 1, "maximum": 126 },
"expected_site": { "type": "string", "minLength": 2 }
}
}
}
}
},
"mine_sites": {
"type": "object",
"minProperties": 1,

View File

@ -92,6 +92,25 @@ def select_pickaxe(mining_level, tiers):
return best
def select_fast_site(mining_level, modes, sites):
"""Deterministic fast-xp site selection (mining_selections recompute):
highest ore XP among eligible selectable fast-xp sites whose
mining_level <= level; tie-break lexicographic site id (copper/tin share
an XP+level tie). Returns the site id or None."""
best_xp, cands = -1, []
for sid in (modes.get("fast-xp", {}).get("sites", []) or []):
sdef = sites.get(sid)
if not isinstance(sdef, dict) or sdef.get("selectable") is not True:
continue
ml, xp = sdef.get("mining_level"), sdef.get("xp")
if isinstance(ml, int) and isinstance(xp, (int, float)) and ml <= mining_level:
if xp > best_xp:
best_xp, cands = xp, [sid]
elif xp == best_xp:
cands.append(sid)
return sorted(cands)[0] if cands else None
def mining_model_errors(rel, data):
"""STRUCTURED mining-model gates (owner guidance 2026-08-28; replaces the
earlier phrase-search checks, which could pass on comments alone)."""
@ -230,6 +249,88 @@ def mining_model_errors(rel, data):
f"(3600 / (rolls_per_ore x 2.7 s)) — the rate exceeds what "
f"the declared click policy can produce")
# --- 4b. GE tool purchase must be executable end to end ---
gev = set(v for v in verbs if v in {
"ge_open", "ge_buy", "ge_offers_done", "ge_collect", "ge_sell"})
if "ge_buy" in gev:
for must, why in (("ge_open", "open the exchange before placing an offer"),
("ge_offers_done", "wait one BOUNDED fill window"),
("ge_collect", "collect the item / refund the gp on timeout")):
if must not in gev:
errors.append(
f"{rel}: ge_buy must be part of an executable GE loop — "
f"missing '{must}' ({why}); a resting offer must never be "
f"retried indefinitely")
elif gev:
if "ge_open" not in gev:
errors.append(
f"{rel}: GE verbs {sorted(gev)} require a ge_open action — "
f"the GE flow must start by opening the exchange")
# --- 4c. below-15 copper/tin path must be structurally arming-able ---
min_level = (data.get("accounts") or {}).get("min_level")
fx_levels = []
for sid in (modes.get("fast-xp", {}).get("sites", []) or []):
sdef = sites.get(sid)
if isinstance(sdef, dict) and sdef.get("selectable") is True:
ml = sdef.get("mining_level")
if isinstance(ml, int):
fx_levels.append(ml)
if isinstance(min_level, int) and min_level < 15:
if not fx_levels:
errors.append(
f"{rel}: min_level {min_level} (<15) but fast-xp has no "
f"structured selectable site")
else:
fmin = min(fx_levels)
if fmin > 14:
errors.append(
f"{rel}: fast-xp sites all require Mining >= {fmin} but "
f"the entry claims a copper/tin below-15 path (min_level "
f"{min_level}) — add a structured low-level (<=14) "
f"fallback site or raise min_level")
if fmin > min_level:
errors.append(
f"{rel}: the lowest fast-xp site requires Mining {fmin} "
f"but min_level is {min_level} — a level-{min_level} "
f"account cannot arm a fast-xp run")
# --- 4d. mining_selections pins (deterministic, recomputed) ---
sel = data.get("mining_selections")
if not isinstance(sel, dict) or not isinstance(sel.get("fast_xp"), list) \
or not sel["fast_xp"]:
errors.append(
f"{rel}: mining entries must declare mining_selections.fast_xp "
f"(level -> expected_site pins, recomputed by the validator)")
else:
rows = {}
for r in sel["fast_xp"]:
lvl = r.get("level") if isinstance(r, dict) else None
if not isinstance(lvl, int) or lvl < 1:
errors.append(
f"{rel}: mining_selections.fast_xp rows need a positive "
f"integer 'level'")
continue
rows[lvl] = r
if isinstance(min_level, int) and min_level <= 14:
for lvl in (1, 14):
if lvl not in rows:
errors.append(
f"{rel}: mining_selections.fast_xp must pin level "
f"{lvl} (a below-15 account must have a structured, "
f"selectable fast-xp site)")
for lvl, r in rows.items():
got = select_fast_site(lvl, modes, sites)
if got is None:
errors.append(
f"{rel}: no eligible selectable fast-xp site at level "
f"{lvl} — the low-level (copper/tin) fallback is missing")
elif r.get("expected_site") != got:
errors.append(
f"{rel}: mining_selections level {lvl} expects site "
f"'{r.get('expected_site')}' but the selection rule "
f"picks '{got}'")
# --- 5. fast-xp headline xp rate consistent with the modelled sites ---
eco = data.get("economy")
xph = eco.get("xp_per_hour") if isinstance(eco, dict) else None
@ -291,6 +392,16 @@ def run_selftest(verbose=False):
"modeled_ores_per_hour": 803, "world_hop": False, "selectable": True,
"inventory_policy": "drop", "access": "combat 29+", "safety": "gate",
"route": "drop in place"},
"se-varrock-copper": {"ore": "Copper ore", "ore_id": 436, "mining_level": 1,
"xp": 17.5, "rock_count": 2, "respawn_seconds": 5.4, "rolls_per_ore": 1.6,
"modeled_ores_per_hour": 360, "world_hop": False, "selectable": True,
"inventory_policy": "drop", "access": "safe", "safety": "none",
"route": "drop in place"},
"se-varrock-tin": {"ore": "Tin ore", "ore_id": 438, "mining_level": 1,
"xp": 17.5, "rock_count": 2, "respawn_seconds": 5.4, "rolls_per_ore": 1.6,
"modeled_ores_per_hour": 360, "world_hop": False, "selectable": True,
"inventory_policy": "drop", "access": "safe", "safety": "none",
"route": "drop in place"},
"se-varrock-bank": {"ore": "Iron ore", "ore_id": 440, "mining_level": 15,
"xp": 35, "rock_count": 2, "respawn_seconds": 5.4, "rolls_per_ore": 1.33,
"modeled_ores_per_hour": 713, "world_hop": False, "selectable": True,
@ -302,14 +413,20 @@ def run_selftest(verbose=False):
"selectable": True, "inventory_policy": "bank", "access": "south rocks",
"safety": "banked locally", "route": "bank 25s + hop_world"},
}
good = {"pickaxe_tiers": canon, "pickaxe_examples": good_examples,
good = {"accounts": {"min_level": 1},
"pickaxe_tiers": canon, "pickaxe_examples": good_examples,
"mining_modes": {"fast-xp": {"inventory_policy": "drop",
"ore_policy": "iron 15+",
"sites": ["al-kharid-powermine"]},
"ore_policy": "iron 15+ / copper/tin under 15",
"sites": ["al-kharid-powermine",
"se-varrock-copper",
"se-varrock-tin"]},
"banked-income": {"inventory_policy": "bank",
"targets": ["se-varrock-bank",
"al-kharid-south-adamantite"],
"stop": "gp target"}},
"mining_selections": {"fast_xp": [
{"level": 1, "expected_site": "se-varrock-copper"},
{"level": 14, "expected_site": "se-varrock-copper"}]},
"mine_sites": sites,
"actions": [{"id": "a1", "step": "hop-world", "verb": "hop_world",
"on_fail": "retry"}],
@ -396,6 +513,33 @@ def run_selftest(verbose=False):
check("xp_per_hour 28,000 within 2% of the modelled fast-xp rate 28,105 passes",
errs == [])
# below-15 fallback + mining_selections pins (governor bounce 5)
no_fallback = {**good, "mining_modes": {
"fast-xp": {"inventory_policy": "drop", "ore_policy": "iron 15+ only",
"sites": ["al-kharid-powermine"]},
"banked-income": {"inventory_policy": "bank", "targets": ["se-varrock-bank",
"al-kharid-south-adamantite"], "stop": "gp target"}}}
errs = mining_model_errors("f14.yaml", no_fallback)
check("min_level 1 with iron-only fast-xp sites (no copper/tin fallback) is rejected",
any("below-15" in e for e in errs))
wrongpin = {**good, "mining_selections": {"fast_xp": [
{"level": 1, "expected_site": "se-varrock-tin"},
{"level": 14, "expected_site": "se-varrock-copper"}]}}
errs = mining_model_errors("f15.yaml", wrongpin)
check("mining_selections level-1 expected site mismatching the recomputed selection is rejected",
any("selection rule picks" in e for e in errs))
no_pin14 = {**good, "mining_selections": {"fast_xp": [
{"level": 1, "expected_site": "se-varrock-copper"}]}}
errs = mining_model_errors("f16.yaml", no_pin14)
check("mining_selections missing the level-14 pin is rejected when min_level < 15",
any("pin level 14" in e for e in errs))
ge_no_open = {**good, "actions": [
{"id": "a1", "step": "buy-pick", "verb": "ge_buy", "on_fail": "retry"},
{"id": "a2", "step": "hop-world", "verb": "hop_world", "on_fail": "retry"}]}
errs = mining_model_errors("f17.yaml", ge_no_open)
check("ge_buy without ge_open / ge_offers_done / ge_collect is rejected",
any("ge_open" in e for e in errs))
return errors