Compare commits

...

43 Commits

Author SHA1 Message Date
dc9a726ee7 Merge pull request 'Codex entry: woodcutting (F2P) (Closes #3)' (#12) from issue-3 into main 2026-08-30 00:53:38 +02:00
Claude
0c2e38c998 Merge origin/main 5ca8324b into issue-3; reconcile validator gates (governor 10637 / owner guidance 2026-08-29)
Magic/Alchemy PR #10 landed on main; this integrates it into the woodcutting
branch and resolves the predicted tools/validate.py conflict:

- union of all validator gates: magic/alchemy (recipe/rate/tier/loadout,
  supply_limit_conflicts, document_error + _end_to_end_bad_mapping),
  mining (pickaxe suite), GE-flip lifecycle (floor-tax + scheduler +
  conservation + slot-phase + buy-capacity + slot-reuse), and the
  woodcutting gates (canonical axe table, per-mode ladders, per-selected-
  row 27/28 dynamic rates, drop-GP-zero, prepare/failed/drop-gate wiring).
- governor 10637: removed the run_flip_selftest alias duplication — the
  combined GE/Woodcutting suite no longer runs twice. Each suite now runs
  exactly once per invocation: run_selftest (cross-field/alchemy),
  run_flip_selftest (GE lifecycle), run_mining_selftest, and the new
  run_woodcutting_selftest. Truthful unique selftest count: 144 PASS.
- tree scan: 6 entries, 0 errors, 0 warnings; git diff --check clean.
2026-08-29 15:44:34 -05:00
5ca8324b8a Merge pull request 'Citation sweep: researched flag + magic alch handoff entry (#5)' (#10) from issue-5 into main 2026-08-29 22:04:12 +02:00
Claude
f569ce0f2c codex: reconcile issue-5 with current main (mining PR #11) — merge origin/main ccdd0c6, union schema/validator gates (bounce-fix attempt 8, governor mergeable=false finding, owner guidance 2026-08-29T21:45Z) 2026-08-29 15:00:10 -05:00
Claude
1c0322e0ef Merge remote-tracking branch 'origin/main' into issue-3
# Conflicts:
#	tools/validate.py
2026-08-29 14:51:01 -05:00
ccdd0c6d24 Merge pull request 'Codex entry: mining (F2P) (#4)' (#11) from issue-4 into main 2026-08-29 21:43:26 +02:00
Claude
dfa7e00bd0 Merge origin/main into issue-3; bounce-fix 4: executable axe preparation, per-selected-row dynamic rates, drop-GP-zero (governor 10398, owner decision 10499)
- woodcutting-f2p.yaml v5: prepare state (a2-a9 check/withdraw/GE-buy/wait/collect/equip-or-carry/loadout -> travel), unobtainable axe -> failed (never complete), chopping->drop_logs gated to afk-xp, per-row chop/walk/respawn/log_price constants, mode rates = canonical top-row reference, woodcutting_fixtures (level-1 / level-50 quest+no-quest / level-70 / level-30 profiles), drop-mode realized GP 0
- schemas/entry.schema.json: ladder rows require per-row rate fields; model slimmed to drop/deposit/break; woodcutting_fixtures block added
- tools/validate.py: port woodcutting gates onto the economy validator (select_ladder_row + row_rates recompute fixtures at 27/28 capacity, drop-GP-zero gate, prepare/failed/drop-gating wiring checks); selftest now 55 checks
2026-08-29 04:53:03 -05:00
Claude
64ef7b1d38 codex: pin alch-tier restock counters (901/1,802) in handoff worked example (main-side regression gate, post-merge reconciliation)
The merged alch_worked_example_regression gate requires the worked example to pin monotonic restock counters at 901/1,802 as well as breaks at 550/1,100/1,650; the 9-cast SII steel-path pins were already monotonic, this adds the alch-tier batch-cadence pins consistently with the entry's declared model.
2026-08-29 04:15:47 -05:00
Claude
832e8e5048 Merge remote-tracking branch 'origin/main' into issue-5
Resolve schema/validator conflict by preserving both sides:
- main: GE flip entry validator (slot_state_machine, quote_age_policy,
  max-fill fields) + new economy entries (flip-candidates, low-alch-members,
  _f2p-alch-catalog)
- issue-5: rate_model lifecycle derivation, dynamic recipe selection,
  loadout verification, supply-fact consistency gate
2026-08-29 04:14:21 -05:00
Claude
7ee36da445 Merge remote-tracking branch 'origin/main' into issue-4
# Conflicts:
#	schemas/entry.schema.json
#	tools/validate.py
2026-08-29 03:30:20 -05:00
Claude
912b1ae432 codex: executable tier mutual-exclusivity — SII only 43 <= Magic < 55, HLA only >= 55 with target above 55, completion first at target (bounce-fix attempt 7, owner guidance 10529, governor 10477, issue #5) 2026-08-29 03:22:27 -05:00
Claude
2a5cfb30af Merge remote-tracking branch 'origin/main' into issue-4
# Conflicts:
#	tools/validate.py
2026-08-29 03:18:04 -05:00
a19716585b Merge pull request 'GE flipping entry for the OSRS Codex (#1)' (#7) from issue-1 into main 2026-08-29 10:16:46 +02:00
Claude
d19a4d9de0 Merge remote-tracking branch 'origin/main' into issue-1
# Conflicts:
#	tools/validate.py
2026-08-29 03:13:45 -05:00
Claude
a0b03b87c6 codex: mining-f2p — level-1 pickaxe tie-break: Mining 1 selects+equips iron (bounce-fix attempt 6)
Owner guidance 2026-08-29 for governor review 10476 (PR #11), finding by finding:

1. Explicit tier rank/tie-break for equal Mining gates. pickaxe_tiers
   declared bronze and iron both at Mining 1 / Attack 1 while the
   level-1 worked example called bronze canonical and carried it (27
   slots) — internally contradictory under the entry's own
   highest-unlocked acquisition policy. pickaxe_tiers now carries an
   explicit rank (bronze 1 .. rune 6) and the selection rule resolves
   equal Mining gates by rank: Mining 1 deterministically selects IRON
   (7-tick roll, rank 2) over bronze (rank 1), and Attack 1 EQUIPS it
   (iron attack gate = 1) -> 28 ore slots. Stated at every policy
   layer (summary, accounts.requires_other, best_practice.equipment,
   prepare state, economy.supplies, a14 equip-tool gate, pickaxe_tiers
   head comment). An owned-only bronze run is documented as an operator
   choice, never the entry's selection rule.

2. Level-1 lifecycle/rate inputs recomputed. The level-1 worked
   example now uses the equipped iron pickaxe: 28 ore slots per drop
   load, iron cadence [4200, 6000] ms (midpoint 5.1 s), and the
   copper/tin rate re-derived from that cadence: 1.6 rolls x 5.1 + 0.5
   drop + 0.39 break = 9.05 s/ore -> 398 ores/hr -> ~6,960 XP/hr (pin
   6,900), replacing the bronze-midpoint 360 ores/hr / 6,300 XP/hr.
   Updated in mine_sites (se-varrock-copper / se-varrock-tin),
   economy comment block, profit_drivers, worktree example, and risk
   notes; no other rate changes (fast-xp iron 28,000 and banked rates
   untouched).

3. Recomputed 1-Attack/1-Mining fixture + capacity proof. Schema:
   pickaxe_tiers gains required rank (1..6); pickaxe_examples gain
   required expected_slots (28 equipped / 27 carried). Validator:
   select_pickaxe() ties by rank; every pickaxe_examples row is
   recomputed including expected_slots, the 1-Attack/41-Mining
   rune-carry fixture is preserved, and the 1-Attack/1-Mining row is
   now mandatory (selects iron, equipped, 28). Committed negative
   fixtures f18-f22: bronze-at-Mining-1, carried-at-Attack-1, wrong
   slots, missing 1/1 fixture, and a tier missing rank are all
   rejected (22 selftests pass).

Preserved: complete GE tool acquisition loop, structured copper/tin
below-15 fallback, fast-xp/banked-income modes, dynamic 27/28
inventory handling, declared banked targets, respawn/world-hop model,
and all prior rate gates. version 6 -> 7, updated 2026-08-29.

Gate: python3 tools/validate.py -> 2 entries, 0 errors, 1 pre-existing
F2P WARN (low-alch-f2p researched, untouched); --selftest -> 22 PASS;
git diff --check clean.
2026-08-29 03:12:40 -05:00
Claude
671d025d9f fix(flip-candidates): release the physical GE slot after a completed flip via closed -> free (governor 10471)
A successful sell followed sell-filled-awaiting-collect -> closed, and
'closed' had no outgoing transition while the scheduler left closed
slots untouched — every GE slot could perform ONE flip and then became
permanently unusable (no rotations, breaks, or longer sessions).

Fix (finding-by-finding, governor comment 10471):
1. 'closed' is no longer a sink. The completed cycle's ledger is
   reconciled and ARCHIVED at close, and the physical slot is released
   to free via the explicit archival transition closed -> free
   (flip-candidates.yaml slot_transitions, whens, scheduler_iteration,
   a9 archive step, summary, risk, sources). The scheduler pass
   dispatches the release (SLOT_DISPATCH['closed'] = 'release'), so a
   closed slot is only ever transient.
2. A deliberately closed cancelled sell releases identically:
   sell-cancelled-awaiting-collect -> closed -> free (whens updated).
3. Committed two-rotation regression (validator, wired into
   ge_lifecycle_errors for every flipping entry): slot_reuse_violations
   walks one physical slot through free -> buy-open ->
   buy-filled-awaiting-collect -> ready-to-sell -> sell-open ->
   sell-filled-awaiting-collect -> closed -> free -> buy-open (second
   buy in the SAME slot), validating every ledger frame against both
   conservation gates; archived_ledger_preserved_after_second_buy
   proves the rotation-1 archived ledger survives rotation 2 bit-for-bit
   (no loss, duplication, or mutation). Negative fixtures prove a
   closed sink or a closed that does not release to free FAILS.
4. ge_lifecycle_errors now requires closed == ['free'] exactly and
   rejects any machine where a state has no transitions.
5. Worked example: t5/t8 walk the release, t8b runs rotation 2 in the
   same physical slots.

All prior fixtures preserved (tax x4, async-slot scheduler,
collection-box, limit-window, side-aware conservation x25) — now 35
selftest checks.

Evidence: python3 tools/validate.py -> 2 entries, 0 errors, 1
pre-existing warning; --selftest -> 35 PASS, 0 errors; git diff
--check clean.
2026-08-29 03:11:20 -05:00
762a8bb608 Merge pull request 'Codex entry: low alchemy — members (loss-minimizing) (Closes #2)' (#8) from issue-2 into main 2026-08-29 10:06:04 +02:00
Claude
64b5e4890e 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.
2026-08-28 15:51:05 -05:00
Claude
8d85c65801 fix(low-alch/handoff)... 2026-08-28 15:15:23 -05:00
Claude
76f8e5ad27 fix(flip-candidates): GE asset accounting + offer lifetime per governor 10390 (bounce-fix 6)
- Buy-limit reset NEVER cancels an offer: quote_age_policy now aborts
  only on a stale scanned edge or explicit strategy reallocation; the
  4h window is a capacity clock (limit_ledger), not an offer deadline.
  Removed every lapse-as-cancel trigger from best_practice, whens,
  quote_age_policy, a5/a8, worked example, risk, and the a6 verdict.
- Completed offers pass through the collection box: new canonical
  sub-states buy-filled-awaiting-collect / sell-filled-awaiting-collect;
  a full buy cannot sell and a full sell cannot close before ge_collect
  (scheduler dispatches collect for all four *-awaiting-collect states).
- Works the arrowtips example side-aware: a 4,000/10,000 partial BUY
  cancel returns 4,000 units + refunded CASH for the unfilled 6,000 —
  never 'refunded units'; the phantom 10,000-unit withdrawal is gone.
- conservation_violations() is now side-aware (buy: quoted =
  filled + still_open + cancelled_unfilled with acquired items = filled
  only, cash = spent + still_committed + refunded; sell: quoted =
  sold + still_open + returned_unsold) with slot_phase_violations()
  phase gates and committed negative fixtures: phantom refunded buy
  units, a full buy sold before collection, a full sell closed before
  proceeds collection. Existing tax/scheduler/limit fixtures preserved.
- grammar (ge_cancel/ge_collect), schema sub_states minItems, entry
  version 5 + sources updated accordingly.
2026-08-28 14:48:55 -05:00
Claude
889c3076b6 validate: keep skipping underscore-prefixed dataset files (regression fix) 2026-08-28 04:22:01 -05:00
Claude
86c0c896d8 woodcutting-f2p: three explicit modes + corrected axe model (governor 10323 bounce-fix 3, owner decision 10288)
- fast-xp / afk-xp / banked-income modes with deterministic first-match
  ladders (no 'or'), level-1 fallbacks, pinned activity state
- axe model corrected: 41 Woodcutting to USE / 40 Attack to EQUIP;
  equip only when Attack permits, else carry the same axe — committed
  axe_examples incl. the 1-Attack/41-WC rune-axe carry case (owner)
- full per-mode lifecycle: prereqs, axe acquisition, initial inventory,
  travel, depletion/respawn, one chop attempt, inventory-full, complete
  dropping or banking route, breaks independent of incidental banking,
  recovery branches, completion
- per-mode worked examples: prep -> first full-inventory boundary ->
  first scheduled break -> resume (level-50 and level-70 pinned)
- realized rates: logs/hr = model chain (chop x capacity / cycle incl.
  walk/deposit or drop, x respawn duty x break duty); xp/gp derived
  and validator-enforced (axe_tiers / axe_examples / woodcutting_modes
  structured schema blocks + 16 committed selftest fixtures)
- wiki-verified axe table 2026-08-28; prices refreshed 2026-08-28
2026-08-28 04:21:31 -05:00
Claude
589767b195 codex: rate_model lifecycle derivation + executable HLA tier selection (bounce-fix attempt 5, owner guidance 10331, issue #5) 2026-08-28 04:04:03 -05:00
Claude
02b366c7ef flip-candidates: per-slot async sub-state lifecycle (governor 10323 bounce-fix 5)
- slot_state_machine: canonical 8 sub-states (free/buy-open/
  buy-cancelled-awaiting-collect/ready-to-sell/sell-open/
  sell-cancelled-awaiting-collect/ready-to-requote/closed) + global
  scheduler pass that never advances unrelated slots
- ge_cancel moves filled items/refunds/unsold units/coins to the GE
  collection box; ge_collect + ledger reconciliation before reuse
- 4h buy-limit ledger (limit_used/first_fill_ts/limit_resets_at) distinct
  from offer expiry; cancellation never renews the limit
- per-slot conservation invariants w/ committed validator fixtures
- economy.ceiling_gp_per_hour typed ceiling, gp_per_hour_low/high as
  documented realized scenario bounds (no uncited multipliers)
- capped/floored 2% tax + 4 fixtures preserved (owner decision)
- action-grammar: ge_cancel/ge_collect rows corrected
2026-08-28 04:01:20 -05:00
Claude
765505ed0f codex: structured mining data model + respawn/success-correct rates + executable tool prep (bounce-fix 4, owner mode decision)
Governor bounce 4 + owner guidance 2026-08-28 (structured, provable, full lifecycle):

1. site model is data, not prose: new mine_sites table (ore, gates, rock_count,
   respawn_seconds, rolls_per_ore, modeled_ores_per_hour, world_hop, selectable,
   inventory_policy, access, safety, route). Adamantite @ Al Kharid south is now
   respawn-BOUND (2 rocks, 240 s respawn): modeled 120 ores/hr ONLY with world
   hopping (wiki F2P adamantite guide) = 70,080 gp/hr; no-hop ceiling ~30/hr =
   17,520; the 839 ores/hr iron-cadence fantasy is gone and a cadence-ceiling
   gate rejects it. Runite is selectable:false (Wilderness PK risk, ~12 min
   respawn) — refused, not hand-waved. New hop state + hop_world action (a14)
   implement the world-hop policy; long-respawn selectable sites REQUIRE
   world_hop + a hop_world action (validator).
2. fast-xp success model: rolls_per_ore 1.33 (61+, derived from the wiki 52,500
   benchmark: 1,500 ores/hr / 2,000 3-tick rolls/hr); rates recomputed with
   success rolls + respawn math (3 rocks x 3.59 s = 10.8 s > 5.4 s respawn ->
   zero idle, pinned) + drop/bank + breaks: xp_per_hour 28,000 (was 32,400 with
   a flat 0.3 s and 1 ore/click); 52,500 stays a labeled external benchmark.
3. sub-29 fast-xp preserved: South-east Varrock DROP site (safe, same mode) —
   the mode never silently switches to banking.
4. tool prep executable end to end: check-tool (has_item) -> withdraw-tool
   (owned branch, bank_inventory) or buy-tool + wait-ge-fill (ge_offers_done) +
   collect-tool (ge_collect, verify) -> equip-tool via menu_option Wield with
   the Attack gate; recovery on purchase/equip failure (3 -> failed).
5. VALIDATOR PROVES THE CONTRACT (no phrase-search): pickaxe_tiers must match
   the canonical F2P table exactly (Mining=use gate, Attack=equip gate,
   roll_ticks); pickaxe_examples recomputed per row (1-Attack/41-Mining must
   select + CARRY rune; 40-Attack/70-Mining equips; 5-Attack/30-Mining carries
   mithril); both modes declared with fixed inventory policies; mode refs must
   resolve to declared selectable policy-matching sites; Wilderness selectable
   -> error; modeled ores/hr <= 3600/(rolls_per_ore x 2.7 s); xp_per_hour
   within 2% of the modeled fast-xp rate. 13 committed --selftest fixtures
   (negative cases included) wired into every gate run.

Evidence: validate 2 entries 0 errors 1 pre-existing WARN; --selftest 13 PASS;
synthetic 3-way merge main 79cab78 (incl. catalog) + head clean + gate green;
git diff --check clean. version 4 -> 5.
2026-08-28 03:34:48 -05:00
Claude
3572255a11 codex: complete GE-flip lifecycle per owner addendum — partial fills, ge_cancel recovery, ledger, break/resume, realized rates (bounce-fix 4)
Owner lifecycle addendum (2026-08-28) + governor bounce 4 (lifecycle contract):
- state machine now covers the ENTIRE buy -> sell -> collect flow with no
  stranded state: partial fills tracked per slot (filled_qty ledger), 4h-lapse
  buys go to a new cancel_requote state (ge_cancel: units return to inventory,
  cash refunds, slot freed) instead of collect, cancelled units still sell at
  the high edge, lapse sells re-quote or lower rather than strand
- action grammar + schema extended ONCE with the cancellation mutation
  ge_cancel (was missing; ge_clear/ge_offers_done are predicates only) and
  used explicitly in the new a8 cancel-requote action
- concrete runtime state: minimum per-slot capital table (buy x 4h limit),
  members 8-slot vs F2P 3-slot paths, per-slot offer phase + quantities,
  buy-limit window, inventory/collection reconciliation in a9
- breaks connected to an explicit safe/resume path: resting offers persist
  server-side + per-slot ledger survive the pause; resume = re-open GE,
  re-check offers, continue current phase; never break mid-collect/mid-place
- rates qualified against the full lifecycle: 149,500 gp/h is named the 4h
  buy-limit CEILING; realized range = ceiling x fill_factor (~0.5-0.9) x
  duty_cycle (~0.90-0.95) ~ 96,300 gp/h midpoint; gp_per_hour_low 74,750
- pinned worked example: capital 22.5M -> 8 slot quotes -> arrowtips partial
  fill (4,000/10,000 tracked) -> iron bar full fill sell (90,000 net) ->
  cosmic no-fill ge_cancel + requote -> arrowtips partial matures at lapse
  (cancel, sell 4,000, 68,000 net) -> first break after 6-10 rotations ->
  resume -> stop conditions
- version 2 -> 3; evidence: validate 2 entries 0 errors 1 pre-existing WARN,
  selftest 4 PASS (fractional-tax fail, ge non-margin pass, floored pass,
  above-cap pass), synthetic merge main 79cab78 + head clean + gate green,
  git diff --check clean
2026-08-28 03:19:13 -05:00
Claude
126cf7b38a codex: mining-f2p — fast-xp/banked-income modes, Mining-vs-Attack pickaxe gates, modeled rates (bounce-fix attempt 3)
Owner decision 2026-08-28: expose separate fast-xp (powermine, drop) and
banked-income (deterministic target ore + mine, bank) modes; never select
highest-available rocks. Separate pickaxe gates per tier: Mining level
decides usability (rune 41), Attack only equipping (rune 40) — pinned
1-Attack/41-Mining carry-and-use example. Modeled policy rates recomputed
with cadence, respawn idle, drop/bank legs and breaks (fast-xp ~32,400
XP/hr; banked iron ~56,500 gp/hr); 52,500 kept as labeled external
benchmark only. Worked examples per mode covering preparation -> first
full inventory -> first break -> resume. Validator now proves the gate
distinction and both modes for mining entries.
2026-08-28 03:15:04 -05:00
Claude
34f36d2275 codex: dynamic SII recipe selection + realized rates + validator crash fix (bounce fix attempt 3, owner guidance)
Owner decision (2026-08-28): Superheat Item uses a dynamic recipe table
governed by Smithing level — highest UNLOCKED recipe selected, primary ore
(target_input, never coal) is the spell target, fallback to the next lower
unlocked recipe, else skip SII and stay on LA.

- magic-alch-handoff: per-recipe target_input; selection block
  (mode highest-unlocked / eligible_rule / fallback); full-lifecycle
  actions (buy-staff, equip-staff w/ fire-rune failure path, buy-supplies,
  cast-alch, cast-superheat, bank-loop, take-break); deterministic restock
  (LA/HLA carried_batch 1,000 + threshold 100 -> 901-cast batches; SII
  24-cast bank cycles, bars >= 24); breaks [400,700] COMPLETED casts,
  independent of banking; worked example pinned (prep -> first restock @24
  -> break @550 -> resume -> restock @552); stop conditions + recovery block
- rates REALIZED incl. mandatory breaks + banking: LA 24,500 / SII 27,500 /
  HLA 51,400 xp/hr (formulas pinned; loop-only 837/hr and 305/hr regimes
  superseded); low-alch-f2p also switched (xp 24,500, gp -110,500..-74,000)
- validate.py: rows now collected ONLY after schema validation (schema-
  invalid MAPPINGS incl. 'economy: not-a-mapping' produce normal ERRORs,
  never AttributeError tracebacks — end-to-end subprocess regression added);
  new gates: recipe_model_errors (target_input in declared inputs, coal
  never targetable, unique smithing gates, selection block + SII/fallback
  transitions) and rate_errors (xp_per_hour must match the realized cadence,
  tolerance 2%); highest_unlocked_recipe helper + 6 eligibility fixtures
- entry.schema.json: optional selection block + per-recipe target_input
- Evidence: validate 2 entries 0 errors 0 warnings; --selftest 31 PASS;
  governor's exact direct repro supply_limit_conflicts([('bad.yaml',
  {'economy':'not-a-mapping'})]) -> [] no crash; synthetic 3-way merge of
  main 79cab78 (incl. issue-6 catalog) + head: clean, gate green; git
  diff --check clean
2026-08-28 03:11:07 -05:00
Claude
00bfc358cb fix(validate): cap GE floor tax at 5,000,000 GP per item (owner decision A)
Owner decision 2026-08-28: single global GE tax formula for all entries:
min(floor(0.02 * sell_price), 5_000_000). Apply the per-item cap inside
ge_floor_tax_error(); the margin-tag scope (flipping/merchanting) and the
generic-ge non-margin exemption are unchanged.

Regression fixtures now cover:
- fractional-tax flipping entry must FAIL (kept)
- non-margin entry with only ge + processing must PASS (kept)
- correctly-floored flipping entry must PASS (kept)
- high-value flip above the 5,000,000 GP cap (sell 300,000,000 / buy
  294,000,000 / net 1,000,000) must PASS — the old uncapped rule computed
  6,000,000 tax, demanded net 0, and rejected this valid entry (new)

Closes the governor's attempt-3 blocker on PR !7.
2026-08-28 02:35:53 -05:00
Claude
368734da65 codex: fix woodcutting-f2p entry per governor review (bounce fix attempt 2)
- Dragon Slayer I removed from accounts.requires_quest (empty for the
  base entry); it now gates ONLY the optional Maple tier (a1 tier gate,
  a2 maple route, idle transition)
- Dynamic inventory: no hardcoded 28-log assumption — equip the axe only
  when the Attack requirement is met (rune/gilded = 41), otherwise chop
  with the axe carried (27 log slots); inventory-full derived dynamically;
  banking deposits logs ONLY, axe preserved
- version 2 -> 3
2026-08-27 17:06:15 -05:00
Claude
75a9965c9f codex: bounce-fix attempt 2 — additive pacing, cast-unit breaks, concrete SII recipes, validator crash guard (issue #5)
Governor-review (PR #10, attempt 2) findings, same issue-5 branch:

1. Pacing is additive. action-grammar.md defines anti_ban.delay as sleep
   AFTER the action, so a cast takes 1.8s + 1.6-3.4s = 3.4-5.2s (mid
   4.3s), ~837 casts/hr, not 1,400 or 1,800. Both entries now pin the
   midpoint rate with the formula documented: LA 25,953 / SII 44,372 /
   HLA 54,419 xp/hr. low-alch-f2p gp_per_hour bracketed by the delay
   endpoints (-119,000 / -78,000).

2. run_control units agree: schema.md 'every' counts iterations and
   iteration is one cast, so break.every [8, 14] is casts (comment no
   longer claims bank cycles).

3. SII is now a concrete recipe. New top-level recipes: table models all
   eight F2P smithable bars (bronze/iron/silver/steel/gold/mithril/
   adamantite/runite) with every input and quantity (coal 2/4/6/8,
   Smithing gates, bar ids) from the OSRS wiki smelting table. The
   cast-superheat action pins params.recipe (steel-bar anchor) plus a
   recipe_rule: smithing gate + complete input cost + one nature rune <
   bar price; buy/withdraw the exact unnoted inputs; bank the bar.
   entry.schema.json gains the optional 'recipes' section. Validator
   gains recipe/SII checks and a deterministic steel-bar fixture
   (1 iron + 2 coal = 3 inputs; margin 600 - (100 + 2*150 + 145) = 55).

4. Validator no longer crashes on malformed docs: scalar/empty YAML is
   rejected with a normal ERROR (document shape check) and
   supply_limit_conflicts guards non-mapping rows; regression fixtures
   cover (bad.yaml, None), scalar docs, and undeclared recipe ids.

Tests: python3 tools/validate.py -> 2 entries, 0 errors, 0 warnings
(exit 0); --selftest -> 14 PASS; live negatives: scalar + empty docs
error cleanly (exit 1, no traceback), reintroduced 12500 divergence is
caught (supply-fact gate), git diff --check clean.
2026-08-27 17:01:54 -05:00
Claude
ba3a8af9f9 codex: fix mining-f2p entry per governor review (bounce fix attempt 2)
- Dynamic inventory/drop model: no hardcoded 27-slot count; drop loop
  driven by count_item ore + inventory full; equip only when Attack
  requirement met (rune = 40 Attack), else carry pickaxe in inventory
- Outcome wait: new wait_rock state — re-click only on ore gained, rock
  depleted, or roll timeout; never re-click the active rock
- Tier-aware cadence: roll_table per pickaxe tier (bronze 4.8s -> rune
  1.8s), mandatory pause >= full roll + [0,1800]ms; anchor rune [1800,3600]
- Al Kharid hard-gated to combat 29+ (scorpion aggro ceiling 28 per wiki
  Aggressiveness); sub-29 -> SE Varrock, no food in the model, eat action
  removed; survive = retreat-only defensive fallback
- version 2 -> 3
2026-08-27 17:00:42 -05:00
Claude
8a8cc0d6b3 fix(codex): F2P maple path, XP/income mode, executable bank loop, combat gate (issue #3)
Governor-review bounce (PR #12) fixes on issue-3:

1. Maple IS F2P — restored as an optional level-45 tier. The only F2P
   maples are the 4 trees in the Corsair Cove Resource Area (requires
   Dragon Slayer I to enter), per the Free-to-play Woodcutting training
   guide and Maple tree page (members1=No @ Corsair Cove). Entry no
   longer labels Maple itself members-only; the quest gate is in
   accounts.requires_quest and the tier route table.

2. XP-versus-income mode is now explicit: state machine and a1 gate-tier
   take a mode parameter — XP mode stays on Willow (or Maple with Dragon
   Slayer I) after 60; income mode selects Yew at Edgeville. The guide:
   yew logs 'earn a profit' but 'it is recommended to cut willow or
   maple trees for experience'. Default is XP mode; income must be an
   explicit run parameter.

3. Bank loop is now executable and tier-specific: actions split into
   walk-to-bank (a5) + deposit-logs (a6) + walk-back-to-trees (a7), and
   the state machine pins chop -> walk_to_bank -> deposit_logs ->
   walk_back -> chop with dedicated states. Per-tier tree+bank routes
   (Lumbridge/Draynor/Edgeville/Corsair Cove) replace the hardcoded
   Draynor-for-everything; Yew is routed to Edgeville (guide: 'best
   place to chop yew trees is south of Edgeville bank').

4. Combat gate instead of a false no-combat claim: the Draynor willow
   spot sits between two aggressive level-7 Dark Wizards that attack
   players under combat 15 (Draynor Village wiki page). Accounts below
   combat 15 are routed to the single Edgeville willow (bank adjacent);
   relog/respawn N/A now scoped to the gated routes only, and the risk
   notes call out Draynor as a combat zone for low-level accounts.

Tests: python3 tools/validate.py -> 2 entries, 0 errors, 1 pre-existing
warning (researched flag on low-alch-f2p, owned by issue #5's PR);
merged-tree check (main 79cab78 + head) keeps the issue-6 catalog,
gate clean; git diff --check clean.
2026-08-27 12:55:50 -05:00
Claude
b06b168c55 codex: fix mining-f2p entry per governor review (bounce fix attempt 1)
- Powermine drop loop: model per-slot Dropping of non-stackable ores —
  drop_run now self-loops (count_item ore > 0 -> drop_run) until the
  inventory is empty, then back to mine; a6 drops one occupied slot per
  execution and its params state the iteration contract.
- Click cadence: a2 delay was 600-1400ms, below the rune pickaxe 3-tick
  (1.8s) mining roll — replaced with 2000-4200ms and comment distinguishing
  the roll interval from translator re-click cadence (bronze 8 ticks = 4.8s
  note).
- Al Kharid survivability: route gate modeled - scorpion is level 14 and
  attacks players at combat <= 28 (wiki Aggressiveness, 2x rule), so Al
  Kharid powermine requires combat 29+ or food; new survive state with eat/
  retreat actions (a7/a8), hard-failure path (food exhausted + low HP ->
  failed; death = hard failure, no respawn claim). Banked South-east Varrock
  route preserved for sub-29 accounts. version 1 -> 2. Validator: 0 errors,
  1 pre-existing F2P WARN.
2026-08-27 12:49:58 -05:00
Claude
9a2f936c05 fix(codex): pin alch cadence to delay policy, unify nature-rune limit, phase-aware SII (issue #5)
Governor-review bounce (PR #10) fixes on issue-5:

1. Timing units + rates: one OSRS tick = 0.6s so a 3-tick cast is 1.8s,
   not 5.4s as both action comments claimed. The mandatory 1.6-3.4s
   post-action delay (mid ~2.5s) allows ~1,440 casts/hr before overhead;
   rates are now pinned at 1,400 casts/hr: LA 43,400 / SII 74,200 / HLA
   91,000 xp/hr (was 55,800 / 95,400 / 117,000 @ 1,800). The old 1,800
   figures were unreachable under the entries' own pacing.

2. Nature rune limit unified to 18000 (mapping 'limit' + Module:Exchange
   agree, fetched live 2026-08-27): low-alch-f2p 12500 -> 18000, matching
   magic-alch-handoff. tools/validate.py gains a cross-entry supply-fact
   consistency check (same item+source must agree on ge_limit; run on
   every gate invocation) plus --selftest regression fixtures: diverging
   limits must fail, agreeing limits must pass, missing ge_limit is not
   gated.

3. SII tier is now phase-aware and translatable: actions split into
   cast-alch (LA/HLA -> cheapest alch candidate, noted stack) and
   cast-superheat (SII 43-55 -> unnoted ore whose live bar margin is
   positive; never a noted stack, never a price-only pick; on_fail skip
   when no positive-margin ore). State machine pins each tier path
   (LA <43, SII window chosen/skipped 43-55, HLA >=55), bank_supplies
   banks smelted bars, buy-supplies restricts ores to positive-margin
   recipes.

Tests: python3 tools/validate.py -> 2 entries, 0 errors, 0 warnings;
--selftest -> 3 PASS; negative test (reintroducing 12500) caught by the
new check; git diff --check clean.
2026-08-27 12:46:12 -05:00
Claude
f6675ba543 fix(validate): scope GE floor-tax gate to margin tags, add regression selftest
Governor-review bounce (PR #7, attempt 2) fix on issue-1:
- The floor-tax rule previously triggered on any entry carrying the
  generic 'ge' tag (tags & {flipping, merchanting, ge}). A training,
  processing, or procurement entry may legitimately carry 'ge' while
  its income/cost model is unrelated to flip tax, so it could fail the
  global gate unless its economics accidentally satisfied the flip
  equation.
- Rule now gates ONLY the unambiguous margin tags (flipping and/or
  merchanting); the bare 'ge' tag no longer triggers it.
- Rule extracted into ge_floor_tax_error() and a --selftest mode added
  with three regression fixtures, run on every gate invocation:
    * fractional-tax flipping entry  -> must FAIL
    * non-margin entry with only 'ge' + 'processing' (economics
      deliberately violate the flip equation) -> must PASS
    * correctly-floored flipping entry -> must PASS
- Demo (old scope): ge+processing fixture with net 2000 vs floored 1900
  errored under the old tag set; low-alch-f2p's -112 vs -115 model
  would also false-positive if it carried 'ge'.

Tests: python3 tools/validate.py -> 2 entries, 0 errors, 1 pre-existing
warning (researched flag on low-alch-f2p); --selftest -> 3 PASS, 0 errors;
git diff --check clean.
2026-08-27 12:34:08 -05:00
79cab78786 Merge pull request 'Dataset: F2P alch catalog (machine-readable) (Closes #6)' (#9) from issue-6 into main 2026-08-27 19:31:32 +02:00
Claude
864f2c9e1c fix(flip-candidates): floor GE tax to whole coins, pin full sell-cycle, qualify F2P path
Governor-review bounce (PR #7) fixes on issue-1:
1. GE 2% convenience fee rounds DOWN per item: net = sell - floor(0.02*sell) - buy.
   Anchor net 16.12 -> 17 (244-4-223); top-8 members table recomputed with
   floored tax -> 598,000/4h = 149,500 gp/h (was 138,000); gp_per_hour_low/high
   updated. Validator now rejects fractional-tax arithmetic for GE-margin entries.
2. State machine split wait_fill into wait_buy_fill / wait_sell_fill so the full
   buy -> buy-fill -> sell -> sell-fill -> collect path is explicit; sell cycle
   can no longer loop back into sell without collecting.
3. Economy qualified as the members path (8 slots); F2P path added (3 slots,
   F2P-tradeable rows only): Iron bar + Cosmic rune + Silver ore = 227,000/4h
   = 56,750 gp/h. Members/F2P flags cross-checked against wiki item pages.
2026-08-27 05:27:09 -05:00
Claude
0ba4646296 codex: woodcutting (F2P) entry
Verified against OSRS wiki 2026-08-27 + live prices:
- F2P tree ladder: Tree 1/25xp, Oak 15/37.5xp, Willow 30/67.5xp,
  Yew 60/175xp (wiki tree-page infoboxes + F2P location lists)
- Issue draft corrected: '45 Pine' disproven (OSRS pines are scenery
  only, Pine tree page); level 45 is Maple (members) — noted in handoff
- F2P axes verified from wiki axe table + item pages: Bronze(1)..Rune(41)
  + Gilded(41) all F2P; Rune axe = best F2P; Dragon = members
- economy anchored on Draynor Village willows: live log sell prices
  (willow 21, yew 112), xp/hr grounded in wiki per-log XP (hourly
  rate explicitly flagged as estimate)
- bankside spot chosen per Oak/Willow/Yew page location lists; tree
  fall timers cited (willow 30s, yew 114s)
- N/A relog/respawn noted (no combat/death vector)

validate.py exit 0 (0 errors). Closes #3
2026-08-27 03:31:01 -05:00
Claude
d6eb514b3e codex/training/mining-f2p.yaml: F2P mining entry (issue #4)
Wiki-verified 2026-08-27 (Free-to-play Mining training guide):
- F2P ore chain: 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; pickaxe tiers bronze->rune with roll intervals (rune = 3 ticks).
- Rates: iron powermine ~50,000 xp/hr at 41+ with rune pickaxe, 52,500 at
  61+; Al Kharid = only F2P 3-rock spot; Doric's Quest 1,300 xp.
- Money: adamantite (70) banking; runite (85) cited as fastest F2P money.
Prices snapshot 2026-08-27 (prices.runescape.wiki latest): iron 70, coal
151, silver 73, gold 154, mithril 159, adamantite 584, runite 10265 (id 451).
Gate: RESULT 2 entries, 0 errors, 1 warning (pre-existing researched flag
on low-alch-f2p.yaml on main — resolved by PR !10, not this branch).
2026-08-27 03:30:47 -05:00
Claude
ca34e97366 codex: replace researched flag with real sources + magic training handoff entry (issue #5)
- low-alch-f2p.yaml: 'bank at a GE-adjacent bank' source now cites the wiki
  Money making guide (noted-item bulk alching, West Varrock Bank F2P hub) and
  High Level Alchemy page (GE buying below alch value; noted-item casting).
  Researched-flag WARN eliminated.
- Verified handoff levels from the wiki: LA 21 (31xp), SII 43 (53xp), HLA 55
  (65xp), all F2P; LA page explicitly recommends HLA at 55. Recorded as a
  soft-efficiency-choice note on the golden entry and a new self-contained
  entry codex/training/magic-alch-handoff.yaml (id magic-alch-handoff).
- Gate: RESULT 2 entries, 0 errors, 0 warnings; diff --check clean.
2026-08-27 03:23:03 -05:00
Claude
9651691e9f codex: F2P alch catalog dataset (machine-readable)
Generated 2026-08-27 from prices.runescape.wiki mapping + latest:
filter lowalch != null, members=false, GE limit >= 150 -> 303 items,
sorted by buy price asc. Columns: item, id, buy (latest.low), sell
(latest.high), limit, lowalch_price, value, net_per_cycle
(= lowalch_price - buy). Source of truth for least-loss ranking
referenced by low-alch-f2p / low-alch-members (related: f2p-alch-catalog).

Validator already skips underscore-prefixed dataset files (verified:
exit 0, file not counted as an entry).

Closes #6
2026-08-27 03:22:50 -05:00
Claude
04a4af0299 codex/economy/flip-candidates.yaml: GE flipping entry (epic #1 P0 backlog)
Implements the epic's uncovered P0 economy backlog item (flip candidates).
Verified source chain 2026-08-27: prices.runescape.wiki mapping + latest,
all 15 shortlist limits cross-checked against Module:Exchange (agree);
GE tax verified at 2% since 2025-05-29 (1% at 2021 intro), 5M/item cap,
sub-50gp effectively untaxed; Merchanting + Grand Exchange/Buying limits
pages cited for mechanics. Net model: 0.98*sell - buy at band edges.
Gate: python3 tools/validate.py exit 0 (2 entries, 0 errors, 1 pre-existing
warning on the golden entry's researched flag — none added by this entry).
2026-08-27 03:15:59 -05:00
9 changed files with 8782 additions and 72 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,626 @@
id: flip-candidates
title: GE Flipping (margin trading)
domain: economy
tags: [flipping, merchanting, ge, money-making, hybrid]
status: verified
version: 6 # 6: bounce-fix 7 2026-08-29 (governor comment 10471) — 'closed' is no longer a sink: after a completed sell's proceeds are collected and the ledger is reconciled + ARCHIVED, the physical GE slot is released to free via the explicit archival transition `closed -> free` (a deliberately closed cancelled sell releases the same way), and the committed two-rotation regression proves one slot completes buy/collect/sell/collect, archives the first ledger, and opens a SECOND buy in the same slot without losing, duplicating, or mutating the archived record. Previous (v5, bounce-fix 6): the 4h buy-limit reset NEVER cancels an offer (offer lifetime = independent quote_age_policy: stale edge or strategy reallocation only); explicit buy-filled-awaiting-collect / sell-filled-awaiting-collect states so completed offers must pass through the GE collection box (ge_collect) before reuse or closing; SIDE-AWARE per-slot ledgers (buy: quoted units = filled + still_open + cancelled_unfilled, acquired items = filled ONLY, cash = spent + still_committed + refunded; sell: quoted = sold + still_open + returned_unsold) with committed negative fixtures for phantom refunded buy units, a full buy sold before collection, and a full sell closed before proceeds collection. Capped/floored tax model + its 4 selftest fixtures and the asynchronous scheduler behavior preserved (owner decisions 2026-08-28).
updated: "2026-08-28" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date
summary: >-
Buy-and-sell Grand Exchange margin flipping: place resting buy offers at
the low edge of an item's price band and resting sell offers at the high
edge, harvesting the spread on high-volume items. There are no skill or
quest requirements — capital breadth and slot discipline are the whole
game. Members accounts run 8 GE slots over the full candidate set; F2P
accounts get 3 slots and F2P-tradeable rows only (separate F2P path in
profit_drivers). Model (governor 10323 + 10390, 2026-08-28): every GE
slot is an INDEPENDENT asynchronous sub-state machine (free / buy-open /
buy-filled-awaiting-collect / buy-cancelled-awaiting-collect /
ready-to-sell / sell-open / sell-filled-awaiting-collect /
sell-cancelled-awaiting-collect / ready-to-requote / closed) driven by a
global scheduler pass that acts only on dispatchable slots and never
closes or advances unrelated ones; completed offers (full buys and full
sells) land in the GE COLLECTION BOX and must pass through ge_collect +
ledger reconciliation before assets are reused or the slot closes;
'closed' is NOT a sink — the physical GE slot is released to free via
the archival transition `closed -> free` after the completed cycle's
ledger is reconciled + archived, so every slot runs flip after flip
through rotations and breaks (two-rotation reuse regression); the
4h buy-limit ledger (limit_used / first_fill_ts / limit_resets_at /
remaining) is DISTINCT from offer expiry — a limit-window reset NEVER
cancels an offer and cancellation NEVER renews the limit; ge_cancel
moves filled items, refunded CASH, unsold units and proceeds to the box,
and every snapshot satisfies SIDE-AWARE per-slot conservation (buy:
quoted units == filled + still_open + cancelled_unfilled with acquired
items == filled only, cash == spent + still_committed + refunded; sell:
quoted == sold + still_open + returned_unsold);
economy.ceiling_gp_per_hour is the TYPED economic ceiling and
gp_per_hour_low/high are documented realized scenario bounds. Pinned
worked example + committed validator fixtures prove the model.
accounts:
membership: hybrid # members: 8 slots/full set; F2P: 3 slots/F2P rows (see economy)
min_level: 3 # post-Tutorial Island baseline; no skill requirement
requires_quest: []
requires_other:
- "Post-Tutorial Island account with Grand Exchange access (no skill or quest requirement)"
- "Members account: 8 GE slots, full candidate set (the modeled path below). F2P account: 3 GE slots and F2P-tradeable rows only — run the F2P path in profit_drivers."
- "Minimum starting capital: per slot = buy edge x 4h limit (arrowtips 2.23M, iron bar 1.45M, cosmic 2.196M, silver ore 0.949M, swordfish 3.09M, lobster 1.59M, magic logs 9.264M, mud 1.728M at the 2026-08-27 snapshot) — full 8-slot members coverage ~22.5M; the 3-slot F2P path needs ~5.5M (iron 1.45M + cosmic 2.196M + silver 0.949M). Capital below one slot's requirement takes that slot off the table."
best_practice:
equipment:
- item: cash stack (flip capital)
why: "Gross margin per item is a few gp, so profit scales with quantity and the 4h
buy-limit window caps per-item volume — capital breadth (more candidates/slots
covered) matters more than any gear. Bulk flipping at scale needs ~20M+ cash."
source: "OSRS wiki — Merchanting (Bulk quantity flipping)"
techniques:
- name: flip the spread, not the trend
why: "Flipping profit is the buy/sell spread between pending offers (the impatience
premium), NOT price movement; it is safer when the item has high trade volume."
source: "OSRS wiki — Merchanting (Flipping)"
- name: prefer dynamic-price items, avoid static-price items
why: "Static-price items have no spread to harvest; only dynamic-price items carry
an exploitable margin."
source: "OSRS wiki — Merchanting (flipping guidance: 'strongly recommended to not
flip static-price items')"
- name: quote at the band edges, never instant-buy/sell
why: "Instant matching pays the standing offer's price (the market's impatience
price); a resting offer fills when a matching offer appears at or better than your
price. The 2% convenience fee makes sub-2%-spread staples unprofitable at band
edges outright, so the edges are where the margin lives."
source: "OSRS wiki — Merchanting (How the Grand Exchange works); OSRS wiki — Grand Exchange
(2% tax since 2025-05-29, capped 5M/item)"
- name: pace quotes inside the 4h buy-limit window, watch connected limits
why: "The buying rate is restricted to a set quantity every 4 hours per player, and
some items share a connected limit with related items — treat those as one bucket
when filling slots. The window starts when the FIRST unit of the window is bought
(first_fill_ts) and resets 4h later. The reset ONLY restores buying capacity —
it never cancels, expires, or otherwise affects a resting offer — and cancelling
an offer does NOT renew or restart the window."
source: "OSRS wiki — Grand Exchange (Buy limits; Grand Exchange/Buying limits)"
- name: run the path that matches the account's slot set
why: "Members get eight GE slots and may trade any row; F2P accounts get three slots
and may only purchase free-to-play rows. The modeled economics below assume the
members set — a hybrid account must swap to the 3-slot F2P path (Iron bar /
Cosmic rune / Silver ore) instead of chasing members-only rows it cannot buy."
source: "OSRS wiki — Grand Exchange (Members get eight Grand Exchange slots ... free players are limited to three. Additionally, free players may only purchase members' items)"
- name: treat each slot as its own asynchronous machine; the scheduler never touches resting slots
why: "A slot's offer fills on its own timeline (partial fills keep resting). The
global scheduler pass acts only on dispatchable slots (free -> quote,
*-awaiting-collect -> ge_collect, ready-to-sell -> sell, ready-to-requote ->
requote, closed -> free archival release) and leaves resting buy-open /
sell-open slots untouched — one slot's cancel or close never advances an
unrelated slot. A FULLY FILLED offer never sells or closes directly: it
first enters an explicit buy-/sell-filled-awaiting-collect state because
its assets sit in the GE collection box until ge_collect (finding 2,
governor 10390). A 'closed' slot is only ever transient: the completed
cycle's ledger is archived at close and the pass releases the physical
slot to free (closed -> free), so the same slot runs the next flip —
never one flip and done (governor 10471)."
source: "governor review comments 10323 + 10390 + 10471 (2026-08-28/29); OSRS wiki — Grand Exchange / Bank (collection box); docs/action-grammar.md"
- name: keep a per-slot offer ledger; ge_cancel -> collection box -> ge_collect -> reconcile, never strand units
why: "Every slot tracks its side-aware ledger: a BUY quote partitions units into
filled + still_open + cancelled_unfilled with acquired items = filled ONLY (the
unfilled/cancelled quantity is CASH: spent / still_committed / refunded), and a
SELL quote partitions sold + still_open + returned_unsold. ge_cancel moves
filled items, refunded CASH, unsold units and sale proceeds to the GE COLLECTION
BOX (never directly to inventory); a separate ge_collect withdraws them and the
ledger is reconciled before the slot is reused. The ONLY cancel triggers are a
stale scanned edge (margin no longer positive) or an explicit strategy
reallocation — never the 4h buy-limit reset, which merely restores capacity."
source: "OSRS wiki — Grand Exchange (offer mechanics; collection); Bank (collection interface); governor review comment 10390 findings 1-4; docs/action-grammar.md (ge_cancel / ge_collect)"
economy:
# Model (documented, reproducible from the snapshot):
# Members path (8 GE slots — the modeled economics below):
# per slot: buy `limit` units at the low edge, sell at the high edge.
# tax_pu = min(floor(0.02 * sell), 5_000_000) # 2% convenience fee,
# # rounded DOWN to whole coins per item,
# # capped 5M/item (owner decision
# # 2026-08-28; cap never hit here)
# net_pu = sell - tax_pu - buy # integer per-item net (floored tax)
# 4h/slot = net_pu * limit # buy-limit window caps per-4h volume
# F2P path (3 GE slots, F2P-tradeable rows only): same arithmetic, top-3
# F2P rows — see profit_drivers for the table and rate.
# Snapshots 2026-08-27: prices.runescape.wiki mapping + latest; limits and
# members flags cross-checked against the wiki item pages (all agree). Top 8
# by 4h/slot below in profit_drivers.
#
# Lifetime qualification (governor 10323 finding 5, 2026-08-28):
# ceiling_gp_per_hour (149,500) is the TYPED ECONOMIC CEILING: ideal full
# fills of every slot, zero overhead — never presented as an achieved
# runtime rate. gp_per_hour_low/high are DOCUMENTED REALIZED SCENARIO
# BOUNDS (not uncited multipliers), each with explicit arithmetic in
# profit_drivers:
# high 138,000 = 149,500 x 37/40 — 'resting-fill' scenario: all 8 slots
# full-fill at their quoted edges; each 40-min rotation is 37 min of
# resting waits + 3 min of scan/place/collect actions.
# low 74,750 = 149,500 x 0.50 — 'requote-heavy' scenario: every 4h
# window's volume completes only after one stale-edge ge_cancel +
# requote cycle (the no-fill remainder is refunded to the collection
# box; the slot re-quotes when the margin clears again or capacity
# allows), halving the window's filled volume.
# Bounds nest: 0 <= 74,750 <= 138,000 <= 149,500 (validator-enforced).
item: Rune arrowtips # anchor: top candidate by modeled 4h/slot (members)
item_id: 44
cost_per_cycle: 223 # buy @ low edge per unit (snapshot 2026-08-27)
income_per_cycle: 244 # sell @ high edge per unit, gross (snapshot 2026-08-27)
net_per_cycle: 17 # 244 - min(floor(244*0.02), 5M) - 223 = 244 - 4 - 223 (floored, capped tax)
ceiling_gp_per_hour: 149500 # TYPED economic ceiling: sum of the 8 slots' 4h/slot (= 598,000) / 4h — ideal full fills, zero overhead (see profit_drivers table)
gp_per_hour_low: 74750 # realized scenario bound 'requote-heavy' = 149,500 x 0.50 (documented in profit_drivers)
gp_per_hour_high: 138000 # realized scenario bound 'resting-fill' = 149,500 x 37/40 (documented in profit_drivers)
xp_per_hour: 0 # flipping awards no XP
supplies:
- item: Rune arrowtips # canonical wiki names per action-grammar conventions
qty_per_cycle: 1
source: ge
ge_limit: 10000 # mapping + Module:Exchange agree
- item: Iron bar
qty_per_cycle: 1
source: ge
ge_limit: 10000
- item: Cosmic rune
qty_per_cycle: 1
source: ge
ge_limit: 18000
profit_drivers:
- "Spread per unit x trade volume; volume is the safety factor (high-volume items fill faster and drift less)"
- "Slot breadth: cover as many net-positive candidates as capital allows (members 8 slots / F2P 3 slots)"
- "Members path — snapshot 2026-08-27 top 8 by modeled 4h/slot (floored+capped 2% tax): Rune arrowtips 170,000 (M, buy 223 / sell 244 / limit 10000); Iron bar 90,000 (F2P, 145/157/10000); Cosmic rune 72,000 (F2P, 122/128/18000); Magic logs 72,000 (M, 772/793/12000); Silver ore 65,000 (F2P, 73/79/13000); Mud rune 54,000 (M, 96/101/18000); Raw swordfish 45,000 (F2P, 206/213/15000); Raw lobster 30,000 (F2P, 106/110/15000) — total 598,000 per 4h = 149,500 gp/h CEILING on 8 slots"
- "F2P path — 3 slots, F2P-tradeable rows only: Iron bar 90,000 + Cosmic rune 72,000 + Silver ore 65,000 = 227,000 per 4h = 56,750 gp/h ceiling (alternates: Raw swordfish 45,000, Raw lobster 30,000)"
- "Post-2%-fee reality: 11 of 21 classic staples are net-positive at band edges for members; sub-2%-spread items (law/chaos/blood runes, dragon bones, adamantite/mithril bars) need sub-low buys or above-high sells"
- "Realized bounds are DOCUMENTED SCENARIOS with explicit arithmetic (governor 10323 finding 5 — no uncited fill/duty multipliers): HIGH 'resting-fill' 138,000 gp/h is the ceiling scaled by the rotation duty of 37 resting minutes per 40-minute rotation (149,500 x 37/40 = 138,287.5 -> 138,000): scan/place/collect actions cost ~3 min per rotation at the 600s quote-check cadence; LOW 'requote-heavy' 74,750 gp/h assumes every 4h window's volume completes only after one stale-edge ge_cancel + requote cycle, so the no-fill remainder (refunded to the collection box and re-quoted when the margin clears or capacity allows) halves the filled volume (149,500 x 0.50). The ceiling 149,500 itself is an economic bound, never an achieved rate. Cancelling never renews the buy limit and the limit reset never cancels an offer (governor 10390 finding 1)."
slot_state_machine:
sub_states: [free, buy-open, buy-filled-awaiting-collect, buy-cancelled-awaiting-collect, ready-to-sell, sell-open, sell-filled-awaiting-collect, sell-cancelled-awaiting-collect, ready-to-requote, closed]
slot_transitions:
free: [buy-open]
buy-open: [buy-filled-awaiting-collect, buy-cancelled-awaiting-collect]
buy-filled-awaiting-collect: [ready-to-sell, free]
buy-cancelled-awaiting-collect: [ready-to-sell, ready-to-requote, free]
ready-to-sell: [sell-open]
sell-open: [sell-filled-awaiting-collect, sell-cancelled-awaiting-collect]
sell-filled-awaiting-collect: [closed]
sell-cancelled-awaiting-collect: [ready-to-requote, closed]
ready-to-requote: [buy-open, sell-open, free]
closed: [free] # archival release — the physical slot is reusable for the next flip (governor 10471)
slot_transition_whens:
- "free -> buy-open: scheduler pass quotes a resting buy at the low edge (qty = min(remaining 4h limit, slot capital, capital available))"
- "buy-open -> buy-filled-awaiting-collect: offer FULLY filled (filled == quoted) — the units sit in the GE COLLECTION BOX until ge_collect; the slot may NOT sell them directly (governor 10390 finding 2)"
- "buy-open -> buy-cancelled-awaiting-collect: quote_age_policy abort — scanned edge stale (margin no longer positive) or explicit strategy reallocation; NEVER the 4h limit reset. ge_cancel moves filled items + refunded CASH (the unspent remainder) to the collection box; the buy limit is NOT renewed"
- "buy-filled-awaiting-collect -> ready-to-sell: ge_collect withdrawn the units; sell edge positive — dispatch sells"
- "buy-filled-awaiting-collect -> free: ge_collect withdrawn; margin gone or capital reallocated — bank the units, release the slot"
- "buy-cancelled-awaiting-collect -> ready-to-sell: ge_collect withdrawn the bought units; sell edge positive — sell the units in hand"
- "buy-cancelled-awaiting-collect -> ready-to-requote: ge_collect withdrawn; buy margin still positive AND remaining limit > 0 — requote at the fresh low edge"
- "buy-cancelled-awaiting-collect -> free: ge_collect withdrawn (full cash refund); slot released (margin gone or capital reallocated)"
- "ready-to-sell -> sell-open: scheduler pass quotes a resting sell at the high edge for the units in hand"
- "sell-open -> sell-filled-awaiting-collect: sell offer FULLY filled (sold == quoted) — the proceeds sit in the GE COLLECTION BOX until ge_collect; the slot may NOT close directly (governor 10390 finding 2)"
- "sell-open -> sell-cancelled-awaiting-collect: quote_age_policy abort — scanned edge stale or strategy reallocation; ge_cancel: unsold units + proceeds received so far move to the collection box (never to inventory)"
- "sell-filled-awaiting-collect -> closed: ge_collect withdrawn the proceeds; ledger reconciled and ARCHIVED — the archived cycle record is immutable; the physical slot is released at the next dispatch by the closed-to-free archival transition (governor 10471)"
- "sell-cancelled-awaiting-collect -> ready-to-requote: ge_collect withdrawn; requote the sell at the fresh high edge or the current band"
- "sell-cancelled-awaiting-collect -> closed: ge_collect withdrawn; ledger archived, deliberately closed (exit taken) — the physical slot releases to free exactly like a completed sell (governor 10471)"
- "closed -> free: ARCHIVAL RELEASE — the completed cycle's ledger stays archived (immutable, conserved), and the physical GE slot returns to free to place the next flip's buy (two-rotation reuse regression)"
- "ready-to-requote -> buy-open: requoted buy placed (buy_capacity_ok: limit_used + qty <= limit until limit_resets_at)"
- "ready-to-requote -> sell-open: requoted sell placed at the fresh high edge"
- "ready-to-requote -> free: slot abandoned and released"
scheduler_iteration: >-
One pass over every slot exactly once, at the 600 s quote-check cadence.
Only dispatchable slots act: free -> quote (ge_buy), buy-filled-
awaiting-collect / buy-cancelled-awaiting-collect /
sell-filled-awaiting-collect / sell-cancelled-awaiting-collect ->
ge_collect, ready-to-sell -> sell (ge_sell), ready-to-requote ->
requote, closed -> free (archival release: the completed cycle's
ledger was archived at close; the pass returns the physical slot to
free so the next flip can begin). Resting buy-open / sell-open slots
produce NO action and are never closed, advanced, re-quoted or touched
by the pass. A pass therefore advances only the slots whose sub-state
is dispatchable, a completed offer always passes through its
*-filled-awaiting-collect state (ge_collect) before its assets are
reused, and a closed slot is only ever transient — released to free
rather than left to strand after one flip (governor 10471).
quote_age_policy: >-
Offer lifetime is INDEPENDENT of the 4h buy-limit ledger. Resting
offers are re-checked at the 600 s cadence and kept resting while the
scanned margin stays positive — a profitable resting offer remains
open regardless of limit_resets_at (a limit-window reset only restores
buy capacity; it never cancels, expires, or implies cancelling an
offer). An offer is aborted ONLY on a stale band edge (scanned margin
no longer positive) or an explicit strategy reallocation (slot freed
for a better candidate / stop condition). No-fill alone never aborts an
offer whose edge still clears the margin.
persisted_state: >-
Per-slot record persisted between passes and across breaks: sub-state,
item, quoted qty, and SIDE-AWARE buckets — buy side: filled, still_open,
cancelled_unfilled, cash_quoted, spent, still_committed, refunded;
sell side: sold, still_open, returned_unsold, proceeds_in_box,
cash_collected; plus limit_used, first_fill_ts, limit_resets_at,
quote placed_at, last check_ts. The GE offers themselves persist
server-side; the record is the agent's copy of truth for the scheduler
and conservation checks.
limit_ledger: >-
Per item: limit_used (units bought so far in the current 4h window),
first_fill_ts (when the FIRST unit of the window was bought),
limit_resets_at = first_fill_ts + 4h, remaining = limit - limit_used.
The ledger controls ONLY the buying rate: it is DISTINCT from offer
expiry (offers rest independently of the window), cancelling never
renews or restarts it, and the reset never cancels an offer — a requote
before limit_resets_at may buy at most remaining (buy_capacity_ok:
limit_used + qty <= limit). A slot with limit_used == limit cannot
place a new or requoted buy until limit_resets_at; its resting offer
is unaffected.
conservation: >-
SIDE-AWARE per-slot invariants at every snapshot (governor 10390
finding 4). BUY side: quoted units == filled + still_open +
cancelled_unfilled per item, and ACQUIRED ITEMS == filled ONLY — the
unfilled/cancelled quantity is a CASH refund, never refunded units;
cash_quoted == spent + still_committed + refunded. SELL side: quoted
units == sold + still_open + returned_unsold per item (a partial sell
legitimately spans sold + still_open). No negative or duplicated
quantities; impossible pairs (still_open + cancelled_unfilled on a
buy; still_open + returned_unsold on a sell) are violations. Phase
gates: a buy-filled-awaiting-collect slot has NOTHING sold yet (a full
buy cannot sell before ge_collect) and a sell-filled-awaiting-collect
slot holds its proceeds in the box; a 'closed' slot has no uncollected
proceeds or cancelled/returned units — it holds ONLY the archived
(immutable) cycle record, and the physical slot is released to free
via the closed -> free archival transition so the SAME slot can run
the next flip (governor 10471; two-rotation reuse regression: the
archived rotation-1 ledger survives rotation 2 bit-for-bit).
Committed validator fixtures —
including the negative fixtures (phantom refunded buy units; a full
buy sold before collection; a full sell closed before proceeds
collection; a 'closed' sink that strands the slot after one flip) —
prove the invariants and the scheduler dispatch.
break_resume: >-
Breaks are taken only when no slot is dispatchable: every slot is
resting (buy-open / sell-open) or free — never mid-collect, mid-place,
or in an *-awaiting-collect state. A closed slot is only ever
transient (archived and released to free by the pass), so a break
never catches the activity between archive and release; full state
(offers server-side + the persisted per-slot record) survives the
pause; resume by re-opening the GE, re-checking offers, re-running the
scheduler pass and continuing each slot from its persisted sub-state.
Slots and slots only: the 8-slot members / 3-slot F2P activity is
never downgraded to single-slot work.
state_machine:
initial: idle
terminal: [complete, failed]
states:
idle:
goal: "be logged in at a bank with a GE booth, capital >= floor, every slot's ledger closed (sub-state free — a closed slot is transient and released to free by the pass — all live buckets zero)"
next: [scan, complete]
scan:
goal: "read the current high/low band for every candidate; compute net-positive edges with floored+capped tax (a3)"
next: [schedule, idle]
schedule:
goal: "run ONE scheduler pass over all 8 (members) or 3 (F2P) slots: dispatch free -> quote, *-awaiting-collect -> ge_collect, ready-to-sell -> sell, ready-to-requote -> requote; resting slots untouched (slot_state_machine.scheduler_iteration)"
next: [wait, collect]
wait:
goal: "hold resting offers; at the 600 s cadence re-check offers (a6) and re-run the scheduler pass (schedule); a slot leaves wait when it becomes dispatchable"
next: [schedule, collect]
collect:
goal: "ge_collect every awaiting-collect slot: withdraw filled items, refunds, unsold units and proceeds from the collection box; reconcile each slot's ledger (conservation invariants) and only then allow slot reuse (a9)"
next: [schedule, scan, complete, idle]
complete:
goal: "session GP target reached"
failed:
goal: "hard failure — stop, log ERROR sentinel, alert"
transitions:
- from: idle
when: "GE access available AND capital >= floor AND session target not reached"
to: scan
- from: idle
when: "capital below floor OR session target already reached"
to: complete
- from: scan
when: "at least one net-positive candidate at the scanned band edges (floored+capped tax)"
to: schedule
- from: scan
when: "no net-positive candidate on 3 consecutive scans (prices stale — stand down)"
to: idle
- from: schedule
when: "pass placed or requoted offers (quotes outstanding) — rest until the next cadence"
to: wait
- from: schedule
when: "pass marked slots awaiting collection (ge_cancel happened) — withdraw before further dispatch"
to: collect
- from: wait
when: "600 s cadence reached — re-check offers and re-run the scheduler pass"
to: schedule
- from: wait
when: "a slot filled and proceeds/results are ready to collect"
to: collect
- from: collect
when: "only resting slots remain (closed slots are released to free by the same pass); cadence continues"
to: wait
- from: collect
when: "session GP target reached"
to: complete
- from: collect
when: "rotation done and window open — start the next rotation"
to: scan
- from: collect
when: "capital fell below floor after reconciliation"
to: idle
actions:
- id: a1
step: reach-ge
verb: move_to
target: "bank with Grand Exchange booth"
params:
location: "Grand Exchange (Varrock) area bank"
method: [teleport, walk]
on_fail: retry
anti_ban:
delay: [400, 1600]
- id: a2
step: open-ge
verb: ge_open
target: grand-exchange
params:
booth: "GE counter or bank booth"
on_fail: retry
anti_ban:
delay: [800, 1800]
- id: a3
step: scan-bands
verb: ge_check_price
target: "current candidates"
params:
items: "top-N candidates from the entry table"
band: "high and low edge per item"
margin: "net-positive = sell - min(floor(0.02 x sell), 5,000,000) - buy > 0"
on_fail: skip
anti_ban:
delay: [1200, 2800]
- id: a4
step: place-buys
verb: ge_buy
target: grand-exchange
params:
item: "per dispatchable free slot (scheduler pass)"
qty: "min(remaining 4h limit, slot capacity, capital available) — buy_capacity_ok(qty + limit_used <= limit)"
price: "low edge (rounded down)"
max_price: "low edge — never instant"
ledger: "record quoted qty, cash_quoted, limit_used check; sub-state -> buy-open"
on_fail: skip
anti_ban:
delay: [900, 2700]
- id: a5
step: hold-fills
verb: sleep
params:
delay: [6000, 12000]
until: "ge_offers_done changes or a fill event or the 600s cadence (a resting offer keeps resting; the 4h limit reset plays no role in offer lifetime)"
on_fail: retry
anti_ban:
delay: [300, 900]
- id: a6
step: check-fills
verb: ge_offers_done
target: "all quoted slots"
params:
phase: "per-slot: buy-open (buy outstanding) | sell-open (sell outstanding)"
per_slot: "side-aware buckets per slot — buy: filled (units in the collection box) / still_open (resting offer) / cancelled_unfilled; sell: sold (proceeds in the box) / still_open / returned_unsold — partial fills keep the offer resting with filled units held in the box"
verdict: "full | partial | none | stale-edge # 'lapse' is NOT a verdict: the 4h window is a capacity clock, not an offer deadline"
on_fail: retry
- id: a7
step: place-sells
verb: ge_sell
target: grand-exchange
params:
item: "per ready-to-sell slot (units in hand AFTER ge_collect — a fully filled buy must first pass through buy-filled-awaiting-collect -> ge_collect)"
qty: "units in hand for that slot"
price: "high edge (rounded up)"
ledger: "record sold qty per slot; sub-state -> sell-open"
on_fail: skip
anti_ban:
delay: [900, 2700]
- id: a8
step: cancel-to-box
verb: ge_cancel
target: "slot offers aborted by quote_age_policy (stale edge / strategy reallocation)"
params:
offers: "slots whose scanned band edge no longer clears the margin (stale), or slots explicitly reallocated by the strategy — NEVER offers whose 4h limit-window merely reset (a reset restores buy capacity only; quote_age_policy)"
moved: "BUY cancel: filled items + refunded CASH (the unspent remainder) -> GE COLLECTION BOX; SELL cancel: unsold units + proceeds received so far -> GE COLLECTION BOX (never directly to inventory)"
limit: "the 4h buy limit is NOT renewed or restarted by the cancel (limit_used stays); the next reset does not cancel anything either"
sub_state: "buy-open -> buy-cancelled-awaiting-collect; sell-open -> sell-cancelled-awaiting-collect"
on_fail: retry
- id: a9
step: collect-and-reconcile
verb: ge_collect
target: grand-exchange
params:
withdraw: "from the GE collection box: bought (filled) items, refunded CASH, unsold units, sale proceeds — per slot"
reconcile: "SIDE-AWARE per-slot ledger conservation check BEFORE any slot reuse — buy: quoted == filled + still_open + cancelled_unfilled and cash_quoted == spent + still_committed + refunded; sell: quoted == sold + still_open + returned_unsold"
decide: "buy-filled/buy-cancelled -> ready-to-sell (sell edge positive) | ready-to-requote (buy margin positive and remaining limit > 0) | free (release); sell-filled/sell-cancelled -> closed | ready-to-requote"
archive: "at 'closed' the completed cycle's ledger is reconciled and ARCHIVED (immutable); the pass then releases the physical slot to free (closed -> free) so the SAME slot places the next flip's buy — the two-rotation reuse regression proves the archived record survives rotation 2 (governor 10471)"
log: "RESULT item, qty, net gp per slot (floored+capped tax)"
on_fail: alert
anti_ban:
delay: [600, 1400]
- id: a10
step: take-break
verb: take_break
target: "current location"
params:
duration: "[60, 240] seconds of idle AFK per break (matches run_control.break.for)"
safe: "only when every slot is resting (buy-open / sell-open) or free — resting offers persist server-side at their quoted edges and the per-slot record persists locally; a closed slot is transient (archived + released to free by the pass), so breaks never catch the activity between archive and release; never break mid-collect (coins in hand) or mid-place (offer half-configured)"
resume: "re-open GE (a2), re-check offer state (a6), re-run the scheduler pass (schedule), continue each slot from its persisted sub-state (a4/a7/a8/a9)"
on_fail: skip
run_control:
iteration: "one scheduler pass over every slot at the 600 s cadence — dispatch free -> quote, *-awaiting-collect -> ge_collect, ready-to-sell -> sell, ready-to-requote -> requote; resting slots untouched (slot_state_machine.scheduler_iteration)"
max_iterations: null
break:
every: [6, 10] # passes (resting offers persist; state = GE offers + per-slot record)
for: [60, 240] # seconds of idle AFK
stop_conditions:
- "session GP target reached"
- "capital under floor"
- "no net-positive candidates on 3 consecutive scans"
- "3 consecutive failed/aborted passes (client issue — do not loop)"
# ================= WORKED EXAMPLE (pinned, deterministic — owner addendum 2026-08-28; slot-machine walk per governor 10323, corrected per 10390) =================
# Members account, 8 GE slots, capital 22,500,000 gp. Snapshot prices 2026-08-27
# (buy/sell = low/high edge; tax = min(floor(0.02 x sell), 5,000,000)).
# The run below walks THREE slots in detail (arrowtips / iron / cosmic) exactly as
# the committed scheduler-dispatch + conservation fixtures; the other five slots
# behave identically to the iron slot and are omitted for brevity.
# Ledgers are SIDE-AWARE throughout: a BUY's acquired items are `filled` ONLY;
# unfilled/cancelled quantity is CASH (spent / still_committed / refunded).
#
# t0 idle -> scan -> schedule (pass 1): all slots free. Scheduler dispatch:
# free -> quote. Slot ledger after the pass (conservation holds):
# arrowtips: buy-open, quoted 10,000 @ 223; cash_quoted 2,230,000
# = spent 0 + still_committed 2,230,000 + refunded 0
# iron: buy-open, quoted 10,000 @ 145; cash_quoted 1,450,000
# cosmic: buy-open, quoted 18,000 @ 122; cash_quoted 2,196,000
# Unit buckets: quoted == still_open for every slot; filled/cancelled = 0.
#
# t1 wait, ~18 min: arrowtips PARTIAL fill 4,000/10,000 (a6 verdict PARTIAL).
# BUY ledger: quoted 10,000 == filled 4,000 + still_open 6,000 + cancelled 0.
# Acquired items = 4,000 ONLY; they sit in the GE collection box (collectible
# at any bank), while the offer keeps resting with 6,000 still open. Cash:
# cash_quoted 2,230,000 == spent 892,000 + still_committed 1,338,000.
# limit_ledger: first_fill_ts now, limit_used 4,000, limit_resets_at = +4h,
# remaining 6,000 (window CAPACITY — not an offer deadline).
# Iron: FULL fill 10,000/10,000 -> buy-filled-awaiting-collect (units sit in
# the box until ge_collect; the slot cannot sell them directly — finding 2).
# Iron cash: spent 1,450,000. Cosmic: no fill; offer keeps resting (edge
# positive, quote age within policy — finding 1: no-fill alone never aborts).
#
# t2 cadence -> schedule (pass 2): dispatch ONLY the dispatchable slot:
# iron buy-filled-awaiting-collect -> ge_collect (a9): withdraw 10,000 units;
# ledger reconciled (quoted 10,000 == filled 10,000; cash spent 1,450,000,
# no refund); sell edge positive -> ready-to-sell.
# arrowtips buy-open -> NO ACTION (resting, partial fill still open).
# cosmic buy-open -> NO ACTION (resting, no fill, edge positive).
# => the pass never closes or advances unrelated slots (regression fixture).
#
# t3 pass 3: iron ready-to-sell -> sell-open (a7): 10,000 @ 157 (high edge).
#
# t4 iron sell-open FULL fill -> sell-filled-awaiting-collect: proceeds
# 1,570,000 sit in the GE collection box until ge_collect. SELL ledger:
# quoted 10,000 == sold 10,000 + still_open 0 + returned_unsold 0.
#
# t5 pass 4: iron sell-filled-awaiting-collect -> ge_collect (a9): withdraw
# 1,570,000; tax floor(157 x 0.02) = 3/item -> net (157 - 3 - 145) x 10,000
# = 90,000. proceeds_in_box -> cash_collected; ledger reconciled and
# ARCHIVED (the immutable cycle record: quoted 10,000 == sold 10,000);
# sub-state -> closed, and the SAME pass's archival dispatch releases the
# physical slot: closed -> free (governor 10471). The slot can place the
# next flip's buy immediately — it is never stranded after one flip.
# Conservation: quoted 10,000 == sold 10,000, zero proceeds left uncollected.
#
# t6 LIMIT RESET, NOT AN OFFER EVENT (finding 1): arrowtips reaches
# limit_resets_at with 6,000 still open on the resting offer. The window
# reset restores buying capacity ONLY — the offer KEEPS RESTING at its edge;
# nothing is cancelled and the 4,000 filled units stay collectible in the
# box. Cosmic likewise unaffected.
#
# t7 arrowtips offer FULLY fills the remaining 6,000 -> buy-filled-awaiting-
# collect -> pass 5 ge_collect: withdraw 10,000 units (4,000 from the t1
# partial + 6,000 from the t7 fills — the units are what the offer actually
# bought; NO phantom "refunded units"). BUY ledger: quoted 10,000 == filled
# 10,000; cash 2,230,000 == spent 2,230,000 (still_committed 0 — a full fill
# spends its whole quote). Sell edge positive -> ready-to-sell.
# limit_ledger: window 1 used 4,000 (t1); window 2 (post-reset) used 6,000.
#
# t8 pass 6: arrowtips ready-to-sell -> sell-open 10,000 @ 244 -> full fill ->
# sell-filled-awaiting-collect -> pass 7 ge_collect: proceeds 2,440,000;
# tax floor(244 x 0.02) = 4/item -> net (244 - 4 - 223) x 10,000 = 170,000
# (matches the 170,000 4h/slot table row) -> closed (ledger archived) ->
# closed -> free: the arrowtips physical slot is released for the next flip.
#
# t8b SECOND ROTATION in the SAME physical slot (governor 10471 regression):
# the iron slot released at t5 and the arrowtips slot released at t8 are
# free and dispatchable again — pass 8 quotes a NEW arrowtips buy in the
# iron slot (free -> buy-open, a4; buy_capacity_ok re-checked against the
# CURRENT window's limit_ledger). Rotation 1's archived ledgers (iron
# closed record, arrowtips closed record) remain archived and bit-for-bit
# untouched — no loss, duplication, or mutation — while the live ledger is
# a fresh rotation-2 record (quoted 10,000, cash_quoted 2,230,000,
# still_open 10,000). This is the committed slot_reuse regression: a slot
# completes one flip, is released, and runs the next one.
#
# t9 STALE-EDGE recovery (cosmic, finding 1 cancel trigger): at a cadence check
# the cosmic LOW edge has risen 122 -> 126: net 128 - floor(128 x 0.02)=2 -
# 126 = 0 -> margin no longer positive -> quote_age_policy aborts the offer
# (ge_cancel, a8): ZERO units were filled, so the collection box receives
# refunded CASH 2,196,000 and NO items. BUY ledger: quoted 18,000 == filled 0
# + still_open 0 + cancelled_unfilled 18,000; cash 2,196,000 == spent 0 +
# still_committed 0 + refunded 2,196,000. Sub-state -> buy-cancelled-
# awaiting-collect. (The 18,000 never-purchased units are cash, never
# "refunded units" — the phantom-items fixture.)
#
# t10 pass 8: cosmic buy-cancelled-awaiting-collect -> ge_collect: withdraw
# refunded CASH 2,196,000; ledger reconciled (refunded -> collected). The
# fresh-edge margin is still 0, so the slot is NOT re-quoted: ready-to-
# requote requires a positive margin — decide free, capital reallocated to a
# candidate that still clears (e.g. magic logs); a requote in a later window
# is governed by buy_capacity_ok as usual (the cancel did NOT renew the
# window, and the next reset does not cancel anything).
#
# t11 FIRST SCHEDULED BREAK after 6-10 passes (midpoint 8): every slot is
# resting (buy-open / sell-open), free, or collected (closed) — nothing
# dispatchable; per-slot records persisted (slot_state_machine.break_resume);
# take-break 60-240 s (a10); RESUME at a2 (re-open GE) -> a6 (check-fills)
# -> schedule (next pass) -> continue per-slot from persisted sub-state.
# Breaks never strand an offer and never interrupt a collect/place.
#
# t12 STOP: session target (e.g. 900,000 net) -> collect -> complete; capital
# fell below floor -> idle -> complete; 3 consecutive failed passes -> failed.
#
# Rate qualification (typed ceiling vs realized scenarios — finding 5):
# ceiling 149,500 = 598,000 per 4h / 4h — ideal full fills of all 8 slots,
# zero overhead. Realized HIGH 138,000 = 149,500 x 37/40 (37 min resting waits
# + 3 min scan/place/collect per 40-min rotation at the 600 s cadence).
# Realized LOW 74,750 = 149,500 x 0.50 (requote-heavy: every 4h window's
# volume completes only after one ge_cancel + requote cycle — the no-fill
# remainder is refunded and re-quoted on the next window, halving volume).
# The ceiling is NEVER presented as an achieved runtime rate.
# =====================================================================================================
risk:
ban_level: low
notes:
- "Flipping is standard gameplay; the detectable signal is quote churn — keep per-offer pacing humanised, never instant-buy/sell loops."
- "The 2% fee (since 2025-05-29) and 5M/item cap make sub-2%-spread staples unprofitable at band edges; re-scan before every rotation."
- "Thin/volatile items: a resting offer can sit for hours — offer lifetime is governed by quote_age_policy (abort only on a stale scanned edge or strategy reallocation), NEVER by the 4h buy-limit window. A limit reset merely restores buy capacity; it cancels nothing."
- "Partial fills are normal: filled units of a partial buy sit in the GE collection box (collectible at any bank) while the offer keeps resting; never cancel-and-replace a partially-filled offer mid-window just to churn — the side-aware ledger keeps the filled qty attached until the offer fully fills or is aborted per quote_age_policy."
- "ge_cancel NEVER returns items/coins directly to inventory: everything lands in the GE collection box. BUY cancels return filled items + refunded CASH (the unfilled quantity is cash, never 'refunded units'); SELL cancels return unsold units + proceeds received. Reuse a slot only after ge_collect + side-aware ledger reconciliation (conservation invariants)."
- "The 4h buy limit is independent of offer life: cancelling does not renew it and the reset does not cancel offers. A slot at limit_used == limit cannot place a new/requoted BUY until limit_resets_at (first_fill_ts + 4h); its resting offer is unaffected."
- "Breaks are safe only when no slot is dispatchable (resting offers or post-collect points, see a10 and break_resume); state = GE offers + the persisted per-slot record, which survive the pause."
- "A completed cycle NEVER strands a slot: after ge_collect the ledger is reconciled + archived at 'closed' and the scheduler pass releases the physical slot to free (closed -> free archival release), so every slot keeps running flip after flip. Never model a slot as one-flip-then-done (governor 10471)."
sources:
- "OSRS wiki — Grand Exchange (2% convenience fee since 2025-05-29, capped 5M/item, sub-50gp effectively untaxed, tax-exempt low-value items; buying rate restricted per 4h window starting on the FIRST purchase of the window; connected limits; verified 2026-08-27)"
- "OSRS wiki — Grand Exchange/Buying limits (4h window mechanics; the limit controls how many units may be bought and resets 4h after the first unit is bought; outstanding offers remain active until filled or explicitly aborted — a limit reset is NOT offer expiry)"
- "OSRS wiki — Grand Exchange + Bank (collection interface): items/coins from full or partial trades — including aborted offers — remain in the collection box until collected; partially completed trades can be collected at any bank"
- "OSRS wiki — Merchanting (flipping = spread exploitation; high trade volume = safety; bulk flipping needs ~20M+ cash; avoid static-price items; offer matching at or better than your price)"
- "prices.runescape.wiki/api/v1/osrs/mapping (item ids, members flags, limits — limits cross-checked against Module:Exchange/<Item>, all agree)"
- "prices.runescape.wiki/api/v1/osrs/latest (high/low price band snapshot 2026-08-27)"
- "governor review comment 10323 on PR #7 (2026-08-28): per-slot asynchronous sub-state machine + scheduler pass (finding 3); ge_cancel -> collection box + ge_collect + reconciliation (finding 2); buy-limit ledger distinct from offer expiry, cancellation never renews (finding 1); conservation invariants (finding 4); typed ceiling vs documented realized scenarios (finding 5); explicit scheduler iteration + persisted break/resume, no slot downgrade (finding 6)"
- "governor review comment 10390 on PR #7 (2026-08-28): the 4h limit reset never cancels offers — resting offers remain open while the margin is positive (finding 1); filled offers must pass through explicit *-filled-awaiting-collect states and ge_collect before reuse/close (finding 2); side-aware ledgers kill the phantom-units error (finding 3); side-aware conservation with negative fixtures, including sale-before-collection and close-before-proceeds-collection (finding 4)"
- "governor review comment 10471 on PR #7 (2026-08-29): a completed flip must NOT permanently strand the GE slot — after proceeds are collected and the completed ledger is archived/reconciled the physical slot returns to free via the archival transition closed -> free (also for a deliberately closed cancelled sell); committed two-rotation regression (buy/collect/sell/collect -> archive -> second buy in the same slot) with the archived ledger preserved"
- "owner authorisations 2026-08-28/29 (guidance comments 10264, 10271, 10334, 10527/10528): capped/floored global tax formula; complete lifecycle contract; continued full-lifecycle repair on the same branch/PR (retry-budget superseded)"
- "docs/action-grammar.md (ge_cancel/ge_collect rows, 2026-08-28) + schemas/entry.schema.json (slot_state_machine, ceiling_gp_per_hour)"
verified: "2026-08-27" # keep quoted (YAML 1.1 date parsing); prices last re-fetched live 2026-08-27
related:
- low-alch-f2p
- f2p-alch-catalog
- mule-topup

View File

@ -3,83 +3,189 @@ title: Low-Level Alchemy (F2P)
domain: economy
tags: [alching, magic-training, f2p, loss-training]
status: verified
version: 1
updated: "2026-08-27" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date
version: 4 # 4: bounce-fix 6 2026-08-28 (governor comment 10394, PR #10) — real build-loadout action a8 (verified collected staff/candidate/natures, overflow deposit, bank-stock-shortfall-only withdrawal, carried counts PROVEN before the first cast a9; action ids renumbered a9-a13), capital-loss stop sign corrected (initial capital - total assets > 684,400), Magic target pinned at 55 (the LA -> HLA handoff; 54 is a continue), GP bounds tied to the two declared fill scenarios (max-fill net -126.6 -> low -123,700; live-low net -112 -> high -65,600). 3: bounce-fix 4 2026-08-28 (owner guidance 10331, PR #10) — explicit rate_model (session-realized rates incl. startup over a 2h horizon: xp 23,000, gp -109,500..-65,600), complete GE acquisition path (open -> fill -> collect) for the initial buy AND every restock (bank-stock fast path), fail-closed staff equip, monotonic break example (550/1,100/1,650 independent of 901-cast restocks), assets-based capital stops
updated: "2026-08-28" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date
summary: >
Magic training from level 21 by low-alching F2P items with the highest
lowalch-value among affordable, high-limit items. At current prices it is
a LOSS activity (money in exchange for Magic XP); run it only for XP.
Model pins (owner guidance 2026-08-28): one iteration = one cast; breaks
every 400-700 COMPLETED CASTS (cast-count based, independent of bank
visits — midpoint example 550, 1,100, 1,650, ...); restock when candidate
or nature drops below the numeric restock_threshold (100) against a
carried_batch of 1,000/1,000 (restock cadence 901 casts, monotonic and
independent of breaks). EVERY purchase follows the complete GE path open
-> place -> fill/wait -> collect, for the initial acquisition and for
every restock (a bank-stock fast path applies only when the bank actually
holds enough stock). Staff-of-fire equip failure is FAIL-CLOSED (bounded
retries, then log ERROR + alert + stop — no fire-rune fallback is
modeled). Session horizon 2 h; all rates are SESSION-REALIZED under the
declared rate_model (startup + restock + breaks included; see economy).
accounts:
membership: f2p
min_level: 21 # Magic 21 to cast Low Level Alchemy (wiki-verified)
requires_quest: []
requires_other:
- "Staff of Fire equipped (removes 3 fire runes from the cost)"
- "A bank with GE access within reach"
- "Staff of Fire equipped (removes 3 fire runes from the cost; NO level requirement documented on the OSRS wiki — verified 2026-08-28, see sources). Equip failure is FAIL-CLOSED: bounded retries (3), then log ERROR + alert + stop — there is no fire-rune fallback in this model"
- "A bank with GE access within reach (alch spot = the bank booth; restock trip [60, 120] s)"
- "Starting capital >= ~1.30M gp CASH (staff hard cap 5,000 + first batch 1,000/1,000 at MAX allowed fill 306,600 + session loss reserve 684,400 = 6 worst-case 901-cast batches at live-low+5% fills (covers Magic 21 -> 55) + resting-offer float 306,600). The capital stop checks TOTAL ASSETS (cash + supplies at current buy cost), never raw cash — cash legitimately converts into supplies mid-run"
best_practice:
equipment:
- item: staff of fire
why: "unlimited fire runes — LA rune cost drops from 3 fire + 1 nature to 1 nature per cast (~15gp/cycle saved)"
source: "OSRS wiki — Staff of Fire; Low Level Alchemy (Cost)"
why: "unlimited fire runes — LA rune cost drops from 3 fire + 1 nature to 1 nature per cast (~15gp/cycle saved); equip requirement: none documented on the OSRS wiki (verified 2026-08-28); if it cannot be equipped after bounded retries the run FAILS CLOSED (no fallback modeled)"
source: "OSRS wiki — Staff of fire (id 1387: no requirements row in the infobox, none in the prose — contrast Battlestaff 'requires 30 Attack and 30 Magic'); Low Level Alchemy (Cost)"
techniques:
- name: cast on noted (stacked) items
why: "one inventory slot, minimal mouse travel between spell icon and item"
why: "one inventory slot, minimal mouse travel between spell icon and item; means an inventory does NOT define a batch size — batch sizes are the explicit numeric carried_batch below"
source: "OSRS wiki — Low Level Alchemy (Usage: casting alchemy spells on noted items)"
- name: bank at a GE-adjacent bank
why: "supply restock without a second travel leg"
source: "researched 2026-08-27 (standard operator practice; wiki LA Cost section corroborates rune economy)"
why: "supply restock without a second travel leg — runes and alch items both come from the GE; restock trip 60-120s"
source: "OSRS wiki — Money making guide/High alching profitable free-to-play items (bulk alching via noted items; West Varrock Bank is the common F2P alch/trade hub); OSRS wiki — High Level Alchemy (buying GE items below alch value for break-even/profit; noted-item casting saves bank withdrawals)"
economy:
# Verified 2026-08-27 against prices.runescape.wiki (live 'latest')
# + OSRS wiki Low_Level_Alchemy infobox.
# Bounce-fix 6 (2026-08-28, governor comment 10394): the GP bounds are tied
# to the DECLARED actual-fill scenarios with the SAME costs as the capital
# model — live-low fills (net -112/cast: 148 + 144) and MAX allowed fills
# (live low + 5 %: 155.4 + 151.2 = 306.6 -> net -126.6/cast), because the
# executable purchase accepts live low + 5 % (a4/a10 max_price).
item: Gold necklace # example anchor; rank by cheapest candidate live
item_id: 1654
cost_per_cycle: 292 # necklace buy 148 + nature rune 144 (staff of fire)
cost_per_cycle: 292 # necklace buy 148 + nature rune 144 (staff of fire, live low)
cost_per_cycle_max_fill: 306.6 # live low + 5 %: 155.4 + 151.2 (same costs as the capital model)
income_per_cycle: 180 # lowalch of gold necklace (40% of value 450)
net_per_cycle: -112
gp_per_hour_low: -250000 # negative: this burns GP
gp_per_hour_high: -200000
xp_per_hour: 55800 # 31 xp/cast (wiki) @ ~1800 casts/hr (3-tick, wiki)
net_per_cycle: -112 # at live-low fills
net_per_cycle_max_fill: -126.6 # at MAX allowed fills (worst declared scenario)
gp_per_hour_low: -123700 # session-realized WORST declared scenario: 977.1 casts/hr x -126.6 (max-fill) = -123,700 (fast 3.4s cycle, 300s startup, 2h horizon)
gp_per_hour_high: -65600 # session-realized BEST declared scenario: 585.9 casts/hr x -112 (live-low) = -65,621 (slow 5.2s cycle, 600s startup, 2h horizon)
xp_per_hour: 23000 # session-realized midpoint: 741.8 casts/hr x 31 = 22,995 (pin 23,000)
# ---- rate model (derived; enforced by tools/validate.py rate_model_errors) ----
# Cast: 1.8s (3 ticks x 0.6s, rate_model.cast_ticks) + anti_ban.delay
# [1.6, 3.4]s ADDITIVE (action-grammar.md:135) = 3.4-5.2s, midpoint 4.3s.
# BATCH model: carried_batch 1,000 candidate + 1,000 natures; restock_threshold
# 100 -> 901-cast restock cadence. Overhead per 901-cast batch: restock trip
# [60, 120]s (mid 90) + breaks (every [400, 700] casts, mid 550, for
# [45, 120]s, mid 82.5s -> 82.5 x 901/550 = 135.2s).
# STEADY-STATE casts/hr = 901 x 3600 / (901 x cycle + trip + break_share x 901):
# fast 3.4s -> 1,019.7; midpoint 4.3s -> 791.3; slow 5.2s -> 639.2.
# SESSION-REALIZED (2 h horizon = 7,200 s; startup 300-600 s, mid 450 s ->
# 450/7200 = 6.25 % amortised): 977.1 / 741.8 / 585.9 casts/hr.
# xp_per_hour = 741.8 x 31 = 22,995 (pin 23,000);
# gp_per_hour = casts/hr x net-per-cast over the TWO declared fill
# scenarios (same costs as the capital model):
# worst = fast casts/hr x max-fill net: 977.1 x -126.6 = -123,700 (low)
# best = slow casts/hr x live-low net: 585.9 x -112 = -65,621 (high)
# (mid x mid-fill -119.3 = -88,497 is inside the band)
# BATCH ECONOMICS (901 casts; deposits 901 x 180 = 162,180 gp per batch):
# input cost at live low: 901 x 292 = 263,092 -> net -100,912/batch.
# input cost at MAX fill (live low + 5 %: 1,000 x 155.4 + 1,000 x 151.2 =
# 306,600 per batch): 901 x 306.6 = 276,247 -> net -114,067/batch (worst
# case). Reserve = 6 worst-case batches (Magic 21 -> 55) = 684,400 gp;
# the capital stops check TOTAL ASSETS, not raw cash (accounts + stop_conditions).
# Superseded figures (all fail the derived gate on purpose): loop-only 837
# casts/hr (25,953 xp/hr); the 8-14-cast-break regime (~305 casts/hr); the
# 1,800-cast regime; the pre-startup realized 24,500 (791.3 x 31) — startup
# amortisation is part of the model.
supplies:
- item: gold necklace
qty_per_cycle: 1
source: ge
ge_limit: 18000 # mapping 'limit', wiki-verified
ge_limit: 18000 # mapping 'limit', wiki-verified; one carried_batch of 1,000 fits the 4h window
- item: nature rune
qty_per_cycle: 1
source: ge
ge_limit: 12500
ge_limit: 18000 # mapping 'limit' + Module:Exchange/Nature_rune agree;
# matches magic-alch-handoff (consistency check enforces)
profit_drivers:
- "NONE at current prices — XP is the product, not GP"
- "Pick the candidate with the least-negative net each run (live rank)"
- "Batch-scale economics (worst case 1 nature/cast): carried_batch 1,000/1,000, restock every 901 casts — one batch = 901 x -112 = -100,912 gp at live-low fills (-114,067 at MAX allowed fills), +27,931 xp, deposits 162,180 gp"
# Explicit activity/rate discriminator (owner guidance 2026-08-28, bounce 4):
# this entry OPTS IN to the derived-rate gate; every rate derives from the
# entry's own declared lifecycle and the validator re-derives it.
rate_model:
anchor_tier: la # xp_per_hour pins the LA tier's realized rate
cast_ticks: 3 # LA is a 3-tick cast -> 1.8 s (0.6 s/tick)
restock_trip_s: [60, 120] # GE-adjacent booth trip per restock
session_horizon_s: 7200 # 2 h session; startup is amortised over it
startup_s: [300, 600] # one-time GE acquisition + loadout
alch_carry_batch: 1000 # stacked candidates + natures
alch_restock_threshold: 100 # -> 901-cast LA restock cadence
state_machine:
initial: idle
terminal: [complete, failed]
states:
idle:
goal: reach the bank + have supplies in inventory
next: [casting, bank_supplies]
goal: "session start at the GE bank booth. Open GE (a2), then place the initial offers: staff of fire (a3), candidate 1,000 (a4), nature runes 1,000 (a5) at live low + 5% (staff: +25% up to the 5,000 cap). Record initial capital in the cash ledger (total assets = cash + supplies at current buy cost). Nothing is bought or collected without an open GE interface"
next: [wait_offers, complete, failed]
wait_offers:
goal: "wait for the pending offers (ge_offers_done); when filled, collect every purchase + refund from the collection box (a6 — never leave purchases in the box). PARTIAL FILLS: collect the filled portion, re-place the remainder at the current edge (a4/a5); 3 consecutive no-fill rounds -> failed. An offer still unfilled at session end is left resting by design — its coins are tied up (float reserve) and return when it fills or the operator aborts it (the single operator-touch point)"
next: [ready, wait_offers, failed]
ready:
goal: "equip the staff of fire (a7 — bounded retries, then FAIL CLOSED) and build the VERIFIED loadout (a8 build-loadout): verify the collected staff + candidate + natures (from a6), deposit overflow, withdraw only the real bank-stock shortfall, and PROVE the carried counts equal carried_batch 1,000/1,000 BEFORE the first cast (a9)"
next: [casting, bank_supplies, failed]
casting:
goal: cast Low Level Alchemy on the current candidate item
next: [bank_supplies, idle, complete, failed]
goal: "cast Low Level Alchemy on the current candidate item (a9) — each cast consumes 1 candidate + 1 nature and pays 180 gp (lowalch), +31 xp, net -112 gp at live-low fills; take a break when the completed-cast counter hits run_control.break.every ([400, 700] casts, via a13); the counter is NEVER reset by restocking"
next: [bank_supplies, complete, failed]
bank_supplies:
goal: bank the produced coins, withdraw fresh candidate + runes
next: [casting, idle]
goal: "restock to carried_batch: deposit coins + products and withdraw ALL candidate/natures the bank holds (a10 — bank-stock fast path when the bank already holds enough); for the remaining deficit, open GE (a2), buy the deficit (a11, live low + 5%), collect it (a12) — never withdraw a batch that was never bought. Restocking never resets the cast counter or the break counter; the break cadence (550/1,100/1,650, ...) is independent of the restock cadence (901/1,802, ...)"
next: [casting, bank_supplies, failed]
complete:
goal: reached the run's XP/level target
goal: "reached the run's XP/level target (default 55 — the LA -> HLA handoff) or the 2 h session horizon"
failed:
goal: hard failure — stop, log ERROR sentinel, alert
goal: "hard failure — stop, log ERROR sentinel, alert"
transitions:
- from: casting
when: "candidate item count < buffer OR nature rune count < buffer"
to: bank_supplies
- from: idle
when: "magic level >= target (default 54, hand off to HLA at 55)"
when: "staff + candidate + nature offers all placed (a3/a4/a5)"
to: wait_offers
- from: idle
when: "magic level >= target (55 — the LA -> HLA handoff) at session start"
to: complete
- from: idle
when: "3 consecutive offer-place failures at session start (no fallback candidate)"
to: failed
- from: wait_offers
when: "ge_offers_done AND all purchases collected (a6)"
to: ready
- from: wait_offers
when: "partial fill: filled portion collected (a6), remainder re-placed at the current edge (a4/a5) — still waiting"
to: wait_offers
- from: wait_offers
when: "3 consecutive no-fill rounds (offer cannot fill at the accepted edge)"
to: failed
- from: ready
when: "staff equipped (a7) AND build-loadout verified the carried counts at carried_batch 1,000/1,000 (a8 — collected staff/candidate/natures present, overflow deposited, only a real bank-stock shortfall withdrawn)"
to: casting
- from: ready
when: "staff equip failed after bounded retries — FAIL CLOSED"
to: failed
- from: ready
when: "build-loadout verification shows a carried shortfall (candidate or nature < carried_batch) that the bank does not hold — restock first"
to: bank_supplies
- from: casting
when: "candidate count < restock_threshold (100) OR nature count < restock_threshold (100) — deterministic restock trigger, pinned numbers"
to: bank_supplies
- from: casting
when: "magic level >= target (55 — the LA -> HLA handoff; level 54 is a CONTINUE: LA keeps casting until the handoff boundary)"
to: complete
- from: casting
when: "3 consecutive failed casts (client issue — do not loop)"
to: failed
- from: bank_supplies
when: "restocked to carried_batch (1,000 candidate + 1,000 nature) — bank fast path or re-bought deficit collected"
to: casting
- from: bank_supplies
when: "restock partial: deficit re-bought but offers still filling (ge_offers_done false) — loop the restock phase"
to: bank_supplies
- from: bank_supplies
when: "3 consecutive no-fill restock rounds OR a capital stop fires (assets / loss-reserve checks in stop_conditions)"
to: failed
actions:
- id: a1
@ -87,25 +193,95 @@ actions:
verb: move_to
target: bank
params:
location: "nearest bank with GE"
location: "GE-adjacent bank (alch spot = the bank booth, so restock is a single short leg)"
method: [teleport, walk]
on_fail: retry
anti_ban:
delay: [400, 1600]
- id: a2
step: buy-supplies
step: open-ge
verb: ge_open
target: grand-exchange
params:
note: "open the GE interface at the booth. The entry NEVER buys (a3/a4/a5/a10) or collects (a6/a11) without an open GE interface — the action grammar defines ge_open, ge_buy, ge_collect as separate verbs, so the open step is explicit"
on_fail: retry
anti_ban:
delay: [600, 1400]
- id: a3
step: buy-staff
verb: ge_buy
target: grand-exchange
params:
item: "current candidate item"
qty: "fill to buffer"
max_price: "live low + 5%"
item: "staff of fire"
qty: 1
max_price: "live low + 25 % (wiki value 1,500; hard cap 5,000)"
note: "offer rests until filled or the run ends; on a partial/no fill, collect what filled (a6) and re-place the remainder at the current edge — bounded retries, then the run fails closed (no fire-rune fallback exists)"
on_fail: retry
anti_ban:
delay: [1200, 3000]
- id: a3
- id: a4
step: buy-supplies
verb: ge_buy
target: grand-exchange
params:
item: "current least-loss live candidate (default: gold necklace)"
qty: "carried_batch = 1,000 (fits the 4h GE limit window of 18,000)"
max_price: "live low + 5 %"
note: "offer rests until filled or the run ends; on a partial fill, collect the filled portion (a6) and re-place the remainder at the current edge"
on_fail: retry
anti_ban:
delay: [1200, 3000]
- id: a5
step: buy-natures
verb: ge_buy
target: grand-exchange
params:
item: "nature rune"
qty: "carried_batch = 1,000 (4h GE limit 18,000)"
max_price: "live low + 5 %"
note: "offer rests until filled or the run ends; partial fills handled like a4"
on_fail: retry
anti_ban:
delay: [1200, 3000]
- id: a6
step: collect-purchases
verb: ge_collect
target: grand-exchange
params:
note: "collect every filled purchase and refund from the GE collection box into the inventory — staff, candidate, natures (and the equivalent on every restock). Partial offers: the collected portion is added to the ledger; nothing is left in the box at session start"
on_fail: retry
anti_ban:
delay: [600, 1400]
- id: a7
step: equip-staff
verb: menu_option
target: "staff of fire in inventory"
params:
option: "Wield"
note: "Equip requirement is NONE documented on the OSRS wiki Staff of fire page (no requirements row in the infobox, none in the prose; compare Battlestaff 'requires 30 Attack and 30 Magic'; verified 2026-08-28). FAIL-CLOSED path: bounded retries (3), then log ERROR + alert + stop — no fire-rune fallback is modeled (never loop on equip attempts)"
on_fail: retry
anti_ban:
delay: [600, 1800]
- id: a8
step: build-loadout
verb: bank_inventory
target: bank
params:
deposit: [overflow products]
withdraw: "only the real bank-stock shortfall up to carried_batch (1,000 candidate + 1,000 nature) — the purchases collected by a6 are already in the inventory; a deficit the bank does not actually hold is NEVER withdrawn (it is bought at the GE via the bank_supplies phase, a11)"
verify: "staff of fire collected and present (a6), candidate count == carried_batch 1,000, nature count == carried_batch 1,000 — the carried counts are PROVEN before the first cast (a9); a verified shortfall sends the slot to bank_supplies instead of casting"
on_fail: retry
anti_ban:
delay: [500, 1100]
- id: a9
step: cast-alch
verb: use_item_on
target: "candidate item in inventory"
@ -113,43 +289,153 @@ actions:
spell: "Low Level Alchemy"
item: "cheapest live candidate in inventory"
on: "itself"
consumption: "1 candidate + 1 nature per cast (staff of fire default); pays 180 gp (lowalch) + 31 xp, net -112 gp at live-low fills"
on_fail: retry
anti_ban:
delay: [1600, 3400] # 3-tick cast = ~5.4s game; stay under it, humanised
delay: [1600, 3400] # MANDATORY post-action sleep, ADDITIVE to the 1.8s cast (3 ticks x 0.6s; action-grammar.md:135): cycle = 1.8s + 1.6-3.4s = 3.4-5.2s, midpoint 4.3s -> session-realized ~742 casts/hr incl. startup + restock + breaks (rate_model)
- id: a4
step: bank-loop
- id: a10
step: restock-deposit
verb: bank_inventory
target: bank
params:
deposit: [all coins, all products]
withdraw: "candidate item + nature runes up to buffer"
withdraw: "ALL candidate + nature runes currently in the bank, up to carried_batch (1,000/1,000) — the bank-stock FAST PATH applies only when the bank actually holds enough stock; the remaining deficit is bought at the GE (a11) and collected (a12)"
on_fail: alert
anti_ban:
delay: [500, 1100]
- id: a11
step: restock-buy
verb: ge_buy
target: grand-exchange
params:
item: "current least-loss live candidate + nature runes"
qty: "deficit to carried_batch = carried_batch - inventory count after the a10 fast path (e.g. 901 candidate + 901 nature after a 901-cast batch with an empty bank)"
max_price: "live low + 5 %"
note: "the deficit is RE-BOUGHT at the GE on every restock — the entry never withdraws a batch that was never purchased; partial/failed fills use the same collect + re-place recovery as the initial acquisition"
on_fail: retry
anti_ban:
delay: [1200, 3000]
- id: a12
step: restock-collect
verb: ge_collect
target: grand-exchange
params:
note: "collect the re-bought deficit into the inventory — inventory returns to carried_batch 1,000/1,000 before casting resumes"
on_fail: retry
anti_ban:
delay: [600, 1400]
- id: a13
step: take-break
verb: take_break
target: "current location"
params:
duration: "[45, 120] seconds of idle AFK per break (matches run_control.break.for)"
note: "executed by the translator when the completed-cast counter reaches run_control.break.every — this IS run_control.break's executor, fired on CAST COUNTS, not on bank visits; the cast counter is NOT reset by restocking (breaks land at 550/1,100/1,650 in the midpoint example, independent of the 901-cast restock cadence)"
on_fail: skip
run_control:
iteration: "one cast"
max_iterations: null
iteration: "one cast" # = one Low Level Alchemy cast (+31 xp, -112 gp at the anchor). Per docs/schema.md run_control, break.every counts ITERATIONS — so it counts CASTS here, NOT bank trips (owner decision 2026-08-28)
max_iterations: null # null = until complete condition
break:
every: [8, 14] # bank cycles (breaks land on bank visits)
for: [45, 120] # seconds of idle AFK
every: [400, 700] # COMPLETED CASTS between AFK breaks (midpoint 550 = ~39 min at the 4.3s midpoint cycle). Cast-count-based and independent of bank visits; a restock trip NEVER counts as a break and does not reset this counter
for: [45, 120] # seconds of idle AFK per break
stop_conditions:
- "magic level >= target"
- "gp under floor"
- "magic level >= target (55 — the LA -> HLA handoff)"
- "session elapsed >= 2 h (session horizon — startup is amortised over this window in the rate model)"
- "total assets (cash + supplies at current buy cost) < 311,600 — cannot fund the next batch at max fill (306,600) plus staff replacement (5,000); assets, NOT raw cash: cash that became supplies is not a loss"
- "session net loss = initial capital - total assets > 684,400 — the 6-batch worst-case loss reserve is exhausted (a loss is WHEN ASSETS FALL; total assets - initial capital goes NEGATIVE on losses and can never exceed the reserve — the reversed sign never fires)"
- "3 consecutive failed casts (client issue — do not loop)"
- "3 consecutive no-fill GE rounds (initial acquisition or restock) with no fallback candidate available"
- "staff of fire equip failed after bounded retries — FAIL CLOSED (log ERROR, alert, stop)"
# ================= WORKED EXAMPLE (pinned, deterministic — owner guidance 2026-08-28) =================
# Constants: carried_batch = 1,000 candidate + 1,000 nature; restock_threshold = 100;
# break.every = [400, 700] casts — the example pins the MIDPOINT 550;
# break.for = [45, 120] s; restock cadence = 901 casts (1,000 - 99); the
# staff-of-fire path consumes 1 candidate + 1 nature per cast (worst case 1:1).
# The two counters are MONOTONIC and INDEPENDENT: breaks at 550, 1,100, 1,650,
# 2,200... (multiples of 550) and restocks at 901, 1,802, 2,703... (multiples
# of 901) — gcd(550, 901) = 1, so they interleave forever and restocking NEVER
# resets or shifts the break counter.
#
# Step 0 — preparation (one-time startup, ~300-600 s): account holds >= ~1.30M
# gp cash (staff cap 5,000 + first batch max 306,600 + reserve 684,400 +
# float 306,600). At the GE bank: open GE (a2); place offers — staff (a3),
# 1,000 gold necklaces (a4), 1,000 nature runes (a5); wait for fills
# (ge_offers_done) and collect everything (a6); equip the staff (a7); loadout
# = carried_batch 1,000/1,000 (a8). Cast counter := 0; cash ledger opened.
# Step 1 — casts #1..#550: each cast consumes 1 candidate + 1 nature (-292 gp)
# and pays 180 gp (lowalch) + 31 xp -> net -112 gp. Cast counter 0 -> 550
# (~39 min at midpoint).
# Step 2 — FIRST SCHEDULED BREAK at cast 550 (the interval since cast 0):
# take-break 45-120 s (a13). Resume casting at cast 551. The break fired on
# the CAST COUNT, not on any bank visit.
# Step 3 — casts #551..#901: countdown candidate 1,000-901 = 99 < 100
# (restock_threshold) and nature 99 < 100 simultaneously (worst case 1:1) ->
# the transition fires at the end of cast #901: FIRST RESTOCK at cast 901.
# Deposit 901 x 180 = 162,180 gp (a10); the bank holds 0 supplies (all sales
# were GE buys), so the fast path yields nothing -> open GE (a2), RE-BUY the
# deficit 901 candidate + 901 nature (a11, live low + 5 %), collect (a12) ->
# inventory back to 1,000/1,000. Batch net: -100,912 gp (live-low fills) or
# -114,067 gp (max fills). The cast counter (901) and break counter continue.
# Step 4 — SECOND BREAK at cast 1,100: the interval since break #1 is exactly
# 550 casts (1,100 - 550); the cast-901 restock did NOT reset or shift it.
# Step 5 — THIRD BREAK at cast 1,650 (1,100 + 550); SECOND RESTOCK at cast
# 1,802 (901-cadence, 1,802 = 901 x 2). The sequences 550/1,100/1,650/2,200
# and 901/1,802/2,703 interleave independently.
# Step 6 — continue until a stop_condition fires: Magic 55 (the LA -> HLA
# handoff — level 54 CONTINUES casting), the 2 h session horizon, the
# capital stops (assets floor 311,600 or initial capital - total assets >
# 684,400), 3 failed casts, or 3 consecutive no-fill GE rounds.
# ============================================================================================
risk:
ban_level: low
notes:
- "High cast cadence is the main detection vector; keep anti_ban delays."
- "GE buy bursts: pace offers, respect the 4h limit window."
- "High cast cadence is the main detection vector; keep the mandatory anti_ban delays (cadence pin: additive post-cast sleep [1.6, 3.4]s; session-realized ~742 casts/hr midpoint incl. startup + restock + breaks over a 2 h session; steady-state ~791; loop-only 837 is a ceiling reference, never the output)."
- "Breaks are CAST-COUNT-based (every [400, 700] completed casts, 45-120s AFK) and independent of bank visits — never treat a restock trip as a break, and never reset the break counter on restock (gcd(550, 901) = 1)."
- "GE buy bursts: pace offers, respect the 4h limit window (18,000 necklaces / 18,000 natures); offers persist until filled or aborted and a 4h limit reset is NOT an offer expiry."
# --- recovery block (owner guidance 2026-08-28: every activity must cover its complete runnable lifecycle) ---
# - Missing materials / depleted supplies: the restock_threshold transition (casting ->
# bank_supplies at candidate OR nature < 100) catches both mid-run depletions; the
# restock phase (a9 -> a2 -> a10 -> a11) rebuilds to carried_batch and the loop
# resumes (worked example steps 3-4). The entry NEVER withdraws a batch that was
# never purchased — every restock re-buys the deficit at the GE.
# - GE offers: partial fills are collected (a6/a11) and the remainder is re-placed at
# the current edge (a3/a4/a5/a10); after 3 consecutive no-fill rounds (initial or
# restock) the run stops (failed) — never sit in an unfillable offer loop. Unfilled
# offers at session end are left resting by design (float reserve; coins return when
# they fill or the operator aborts them — the single operator-touch point).
# - Staff of fire cannot be equipped (a7): bounded retries (3), then FAIL CLOSED —
# log ERROR + alert + stop. No fire-rune fallback is modeled (it would need its own
# purchase/supply/rate path; the owner decision rejects a prose-only fallback).
# - Invalid loadout (wrong item in hand / spell unavailable / carried counts below
# carried_batch): re-run build-loadout (a8); a verified shortfall the bank does
# not hold goes to bank_supplies; 3 consecutive failed casts -> failed.
# - Capital: the stop checks TOTAL ASSETS (cash + supplies at current buy cost), and
# both capital stops are assets-based (see stop_conditions) — cash converted into
# supplies is not a loss and never fires a stop by itself.
# - Explicit completion/stop: stop_conditions above (Magic 55 LA -> HLA handoff,
# 2 h session horizon, assets floor 311,600, loss reserve 684,400, repeated
# failures, fail-closed staff equip).
sources:
- "OSRS wiki — Low Level Alchemy (level 21, 31 xp/cast, 3-tick, F2P; infobox 2026-08-27)"
- "prices.runescape.wiki mapping (id 1654 gold necklace: members=false, limit 18000, lowalch 180, highalch 270, value 450; id 561 nature rune; id 554 fire rune)"
- "prices.runescape.wiki latest 2026-08-27 (necklace buy 148, nature buy 144, fire buy 5)"
verified: "2026-08-27" # keep quoted (YAML 1.1 date parsing)
- "OSRS wiki — Grand Exchange (offers persist until filled or aborted; filled items and coin refunds are collected from the booth/collection box; partial fills are separately collectable) — verified 2026-08-28"
- "OSRS wiki — Grand Exchange/Buying limits (4-hour window; the limit resets 4 hours after the FIRST unit is bought; outstanding offers remain active and a limit reset is NOT an offer expiry) — verified 2026-08-28"
- "OSRS wiki — Staff of fire (page verified 2026-08-28 for the equip requirement: id 1387, F2P item, tradeable, options Wield — NO requirements row in the Infobox Item and NO requirement statement in the prose; contrast Battlestaff, whose prose states 'requires 30 Attack and 30 Magic to wield')"
- "docs/schema.md run_control (iteration unit; break.every counts iterations) + docs/action-grammar.md:135 (anti_ban.delay = additive post-action sleep) + verbs ge_open/ge_buy/ge_collect/ge_offers_done (separate GE primitives) — the cadence + break + GE + lifecycle model; owner guidance 2026-08-28 (rate_model, fail-closed staff, monotonic counters); governor comment 10394 (build-loadout verified action, sign-corrected loss stop, 55 handoff boundary, fill-scenario GP bounds)"
verified: "2026-08-27" # keep quoted (YAML 1.1 date parsing); prices last re-fetched live 2026-08-27
related:
- magic-alch-handoff
- high-alch-p2p
- f2p-alch-catalog
- mule-topup

View File

@ -0,0 +1,652 @@
id: magic-alch-handoff
title: Magic training via alchemy (LA → SII → HLA handoff)
domain: training
tags: [magic-training, alching, f2p, xp-training]
status: verified
version: 5 # 5: bounce-fix 7 2026-08-29 (governor comment 10477, PR #10) — EXECUTABLE tier boundary: every tier predicate is mutually exclusive — SII only while 43 <= Magic < 55, HLA only when Magic >= 55 AND the session target is above 55, LA below 43 or the explicit SII-skip path in 43-54, completion FIRST when the configured target is reached (no Magic level can match two tiers; the old "43-55" SII window overlapped HLA at exactly 55). 4: bounce-fix 6 2026-08-28 (governor comment 10394) — build-loadout a7 VERIFIES collected staff/supplies and PROVES carried counts before the first cast (ready/ready->cast strengthened), capital-loss stop sign corrected (initial capital - total assets > 690,075). 3: bounce-fix 4 2026-08-28 (owner guidance 10331) — explicit rate_model discriminator (derived per-entry lifecycle rates), per-recipe inventory capacity floor(27/qty) with derived min/mid/max rates pinned in recipes.rates, complete GE acquisition path (open -> fill -> collect) for initial buy AND every restock (bank-stock fast path), fail-closed staff equip (bounded retries, no fire-rune fallback), configurable session target (55 = LA/SII->HLA handoff completion; >55 = executable HLA tier to the target; LA stays the 43-54 fallback), capital model (cash vs supplies, max-fill costs, reserve, assets-based stops)
updated: "2026-08-28" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date
summary: >
The standard F2P alchemy training path: Low Level Alchemy from level 21
(31 xp/cast), an optional Superheat Item window from 43 (53 xp/cast, smelts
ore into bars), then High Level Alchemy from 55 (65 xp/cast, the wiki's
recommended end-state). All three spells are F2P and stay castable after
their unlock levels, so the handoffs are efficiency recommendations, not
hard caps. The SESSION TARGET is configurable: the default 55 completes at
the LA/SII -> HLA handoff; a target above 55 transitions into the
executable HLA tier and continues until the target — HLA is never
declared-but-unreachable, and Low Alchemy remains the executable fallback
throughout Magic 43-54 whenever SII is skipped. SII model (owner guidance
2026-08-28): a dynamic recipe table governed by the account's Smithing
level — the highest UNLOCKED recipe whose complete recipe cost (ALL inputs
at ALLOWED fill prices, live low + 5%) plus one nature rune stays under the
bar's live GE price is selected; its primary ore (target_input, never coal)
is the spell target; materials unobtainable -> next lower unlocked recipe,
else skip SII and stay on LA. SII inventory cycles are CAPACITY-DERIVED:
floor(27 / total input qty) casts per bank cycle (bronze 13, iron/silver/
gold 27, steel 9, mithril 5, adamantite 3, runite 3) — never a fixed pack.
Rates are SESSION-REALIZED under the declared rate_model: cast cycle +
mandatory additive delays + breaks + banking + startup over a 2 h horizon
(anchors: LA 23,000 / SII steel 12,400 / HLA 48,200 Magic XP/hr; per-recipe
min/mid/max rates pinned in the recipes table and enforced by the
validator). Every purchase follows the complete GE path open -> place ->
fill/wait -> collect, for the initial acquisition and every restock (bank
fast path only when the bank holds stock). Staff-of-fire equip failure is
FAIL-CLOSED (bounded retries, then log ERROR + alert + stop).
accounts:
membership: f2p # LA, SII and HLA are all members=No spells
min_level: 21 # Magic 21 — Low Level Alchemy unlock
requires_quest: []
requires_other:
- "Staff of Fire equipped (drops LA/SII/HLA rune cost to 1 nature rune per cast; NO level requirement documented on the OSRS wiki — verified 2026-08-28, see sources). Equip failure is FAIL-CLOSED: bounded retries (3), then log ERROR + alert + stop — there is no fire-rune fallback in this model"
- "SII tier requires the selected recipe's Smithing level (recipes table: bronze 1 / iron 15 / silver 20 / steel 30 / gold 40 / mithril 50 / adamantite 70 / runite 85) — the highest UNLOCKED recipe is selected and an unmet gate is NEVER selected (selection.mode)"
- "Starting capital >= ~1.31M gp CASH for the LA/HLA tier path (staff hard cap 5,000 + first batch 1,000/1,000 at MAX allowed fill 307,650 + session loss reserve 690,075 = 6 worst-case 901-cast batches at live-low+5% fills (covers Magic 21 -> 55) + resting-offer float 307,650). The SII window uses capacity-sized packs (steel example: 9 x (1 iron 105 + 2 coal 157.5 + 1 nature 152.25) = 5,150 gp per pack at max fill). The capital stop checks TOTAL ASSETS (cash + supplies at current buy cost), never raw cash — cash legitimately converts into supplies"
- "Session target Magic level configured at session start: default 55 (completes at the LA/SII -> HLA handoff); a target above 55 runs the HLA tier until the target; LA remains the executable fallback throughout 43-54 when SII is skipped"
- "A bank with GE access within reach (alch spot = the bank booth; restock trips [60, 120] s)"
best_practice:
equipment:
- item: staff of fire
why: "unlimited fire runes — the rune cost of every alchemy spell drops to a single nature rune per cast; equip requirement: none documented on the OSRS wiki (verified 2026-08-28); if it cannot be equipped after bounded retries the run FAILS CLOSED (no fallback modeled)"
source: "OSRS wiki — High Level Alchemy (rune cost table); OSRS wiki — Staff of fire (id 1387: no requirements row in the infobox, none in the prose — contrast Battlestaff 'requires 30 Attack and 30 Magic')"
techniques:
- name: hand off LA -> HLA at level 55 (configurable target)
why: "LA page: 'Players with level 55 Magic are generally recommended to use High Level Alchemy instead, as it provides more coins and experience per cast' — 65 vs 31 xp/cast, 60% vs 40% of item value. The default session target is 55; a target above 55 keeps casting HLA (65 xp/cast) until the configured level — the HLA tier is never declared but unreachable"
source: "OSRS wiki — Low Level Alchemy (Comparison with High Level Alchemy); High Level Alchemy (infobox: level 55, 65 xp)"
- name: optional Superheat Item window 43-54 (43 <= Magic < 55)
why: "SII unlocks at 43 (53 xp/cast) and smelts ore into bars without a furnace — a training window that can profit when ore/bar margins are positive; it cannot be cast on noted ore, so it needs the exact recipe inputs in inventory rather than a note stack. The inventory holds floor(27 / total input qty) casts per bank cycle (steel = 9) — never a fixed 24-cast pack"
source: "OSRS wiki — Superheat Item (43 Magic, 53 xp, F2P; 'smelt ore into bars without a furnace'; 'cannot be cast on noted ore')"
- name: select the highest UNLOCKED SII recipe, never an unmet gate
why: "at each cast the active recipe = the recipes-table entry with the highest smithing_level <= current Smithing whose complete input cost (ALL inputs at ALLOWED fill prices = live low + 5%) + one nature rune at allowed fill stays under the bar's live GE price; the spell targets that recipe's declared target_input (the primary ore — never coal); when the selected recipe's materials cannot be obtained, fall back to the next lower unlocked recipe; when none has a positive margin, skip SII and stay on LA (LA is executable throughout 43-54)"
source: "OSRS wiki — Superheat Item (requires the Smithing level and materials of the bar); Smithing (bar smelting table); prices.runescape.wiki (live GE prices); owner guidance 2026-08-28 (selection.mode, allowed-fill eligibility)"
- name: buy GE items below alch value, cast on noted items at a bank
why: "HLA page: 'Players who train this way can break even on the cost of runes, or even turn a profit, by buying items on the Grand Exchange for a lower cost than the item's High Alchemy value'; all alchemy spells work on noted items, saving bank withdrawals per cast"
source: "OSRS wiki — High Level Alchemy (GE buying for break-even/profit; noted-item casting)"
- name: treat the handoffs as soft efficiency choices
why: "all three spells remain castable past 21/43/55 and stay on the standard F2P spellbook, so the tier switches optimise xp/cast and cost rather than gate the path; LA is the executable fallback whenever the SII window is skipped"
source: "OSRS wiki — Low Level Alchemy; Superheat Item; High Level Alchemy (level + experience infoboxes, members=No)"
economy:
# Per-cast economics at 2026-08-27 snapshot (prices.runescape.wiki latest):
# staff of fire -> 1 nature rune per cast. Nature rune id 561 buy 145
# (mapping limit 18000, cross-checked against Module:Exchange/Nature_rune,
# members=false, lowalch 72 = 40% of value 180 — consistency assertion).
# Income/net per cast depend on the alched item or the recipe bar, so they
# live in the per-activity entries and in the recipes table (this entry
# prices the shared consumable + the rate model).
# xp_per_hour pins the rate_model ANCHOR tier (sii, steel-bar): the entry's
# own declared lifecycle — no hard-coded per-activity constants.
item: nature rune
item_id: 561
cost_per_cycle: 145 # one nature rune per cast (staff of fire), buy price 2026-08-27
xp_per_hour: 12400 # SII anchor, SESSION-REALIZED: steel mid 249.1 casts/hr x 53 x startup factor 0.9375 = 12,379 (pin 12,400)
# ---- rate model (derived from rate_model + actions + run_control + recipes;
# enforced by the validator's rate_model_errors gate) ----
# Cast: 1.8s (3 ticks x 0.6s, rate_model.cast_ticks) + anti_ban.delay
# [1.6, 3.4]s ADDITIVE (action-grammar.md:135) = 3.4-5.2s, midpoint 4.3s.
# Restock trip [60, 120]s (rate_model.restock_trip_s); breaks every
# [400, 700] casts for [45, 120]s (run_control.break) -> amortised break
# share 45/700 fast, 82.5/550 mid, 120/400 slow.
# ALCH tiers (LA/HLA): 901-cast restock batch (carried_batch 1,000 -
# restock_threshold 100), steady-state 1,019.7 / 791.3 / 639.2 casts/hr;
# SESSION-REALIZED over the 2 h horizon with startup 300-600s (factor
# 0.9583/0.9375/0.9167): 977.1 / 741.8 / 585.9 casts/hr ->
# LA 23,000 xp/hr = 31 x 741.8 (22,995)
# HLA 48,200 xp/hr = 65 x 741.8 (48,217)
# SII tiers: per-recipe capacity = floor(27 / total input qty) (steel 9,
# bronze 13, iron/silver/gold 27, mithril 5, adamantite/runite 3); full
# derived min/mid/max rates live in each recipe's rates block (validator-
# checked): steel steady mid 249.1 casts/hr = 13,204 Magic XP/hr; the
# xp_per_hour anchor adds the startup factor (0.9375 mid) -> 12,400.
# Superseded figures (all fail the derived gate on purpose): loop-only
# 837 casts/hr; the 8-14-cast-break regime (~305 casts/hr); the 1,400/
# 1,800-cast regimes; the impossible 24-cast SII pack (518.0 casts/hr) —
# capacity is floor(27/qty), never a fixed pack.
supplies:
- item: nature rune
qty_per_cycle: 1
source: ge
ge_limit: 18000 # mapping + Module:Exchange agree
profit_drivers:
- "XP per cast by tier (wiki infoboxes): LA 31 (level 21) / SII 53 (level 43) / HLA 65 (level 55); SII also grants the bar's Smithing XP (recipes table)"
- "HLA can break even or profit on GE-bought items priced below their High Alchemy value (HLA page); LA is a loss at current prices (see low-alch-f2p)"
- "SII turns a profit only when the complete recipe cost (ALL inputs at ALLOWED fill prices + one nature rune at allowed fill) stays under the resulting bar's GE price — re-check margins per window and select the highest UNLOCKED recipe with a positive margin; never a steel-only default, never an unmet Smithing gate"
# Selection model (owner guidance 2026-08-28): the Superheat recipe is chosen
# at runtime from the recipes table — never hardcoded to one bar, never above
# the account's Smithing level, with a deterministic fallback chain. The
# validator proves eligibility (smithing gate + complete-recipe margin),
# target_input (declared input, never coal), unique gates, and the fallback
# path via its fixtures.
selection:
mode: highest-unlocked
eligible_rule: "at each cast, the active recipe = the recipes-table entry with the highest smithing_level <= current Smithing level whose complete input cost at ALLOWED fill prices (every input at live GE low + 5%, the a4 max_price cap) + one nature rune at allowed fill < the bar's live GE price — the full actual/allowed recipe cost, not a headline ore price"
fallback: "when the selected recipe's materials cannot be obtained (3 consecutive failed GE purchases / unfillable offers), drop to the next lower unlocked recipe and re-check its allowed-fill margin; when no unlocked recipe has a positive margin, skip SII for this window and stay on LA throughout 43-54 — LA remains executable (a8 handles level < 43 AND the 43-54 SII-skip window); never select a recipe whose Smithing requirement is unmet, never cast on coal"
# Concrete F2P bar recipes for Superheat Item. SII requires the bar's
# Smithing level and its exact materials, so every selectable bar is modelled
# with ALL inputs and quantities (id 453 coal appears in steel/mithril/
# adamantite/runite). Levels, xp and item ids from the OSRS wiki (Smithing
# smelting table; Superheat Item grants the bar's Smithing XP per cast).
# target_input = the PRIMARY ORE the spell is cast on (a declared input,
# never coal — the validator enforces both).
# rates (validator-checked, derived from the entry's rate_model): capacity =
# floor(27 / total input qty) casts per bank cycle (28-slot inventory minus
# the nature stack); casts_per_hour and magic_xp_per_hour are the STEADY-
# STATE min/mid/max (cycle endpoints 3.4/4.3/5.2s, trip 60/90/120s,
# break share 45/700, 82.5/550, 120/400). The economy.xp_per_hour anchor
# adds the startup factor (see rate_model).
# econ 2026-08-27: bar margin = bar GE price - (all inputs at allowed fill +
# one nature rune at allowed fill); the client selects via selection.mode.
recipes:
bronze-bar:
bar: Bronze bar
bar_item_id: 2349
smithing_level: 1
smithing_xp: 6.2
target_input: Copper ore
inputs:
- item: Copper ore
item_id: 436
qty: 1
source: ge
- item: Tin ore
item_id: 438
qty: 1
source: ge
rates:
capacity: 13
casts_per_hour: {min: 445.6, mid: 316.5, max: 244.4}
magic_xp_per_hour: {min: 23617, mid: 16776, max: 12952}
iron-bar:
bar: Iron bar
bar_item_id: 2351
smithing_level: 15
smithing_xp: 12.5
target_input: Iron ore
inputs:
- item: Iron ore
item_id: 440
qty: 1
source: ge
rates:
capacity: 27
casts_per_hour: {min: 633.1, mid: 462.5, max: 362.0}
magic_xp_per_hour: {min: 33554, mid: 24514, max: 19187}
silver-bar:
bar: Silver bar
bar_item_id: 2355
smithing_level: 20
smithing_xp: 13.7
target_input: Silver ore
inputs:
- item: Silver ore
item_id: 442
qty: 1
source: ge
rates:
capacity: 27
casts_per_hour: {min: 633.1, mid: 462.5, max: 362.0}
magic_xp_per_hour: {min: 33554, mid: 24514, max: 19187}
steel-bar:
bar: Steel bar
bar_item_id: 2353
smithing_level: 30
smithing_xp: 17.5
target_input: Iron ore
inputs:
- item: Iron ore
item_id: 440
qty: 1
source: ge
- item: Coal
item_id: 453
qty: 2
source: ge
rates:
capacity: 9
casts_per_hour: {min: 355.3, mid: 249.1, max: 191.2}
magic_xp_per_hour: {min: 18833, mid: 13204, max: 10131}
gold-bar:
bar: Gold bar
bar_item_id: 2357
smithing_level: 40
smithing_xp: 22.5
target_input: Gold ore
inputs:
- item: Gold ore
item_id: 444
qty: 1
source: ge
rates:
capacity: 27
casts_per_hour: {min: 633.1, mid: 462.5, max: 362.0}
magic_xp_per_hour: {min: 33554, mid: 24514, max: 19187}
mithril-bar:
bar: Mithril bar
bar_item_id: 2359
smithing_level: 50
smithing_xp: 30
target_input: Mithril ore
inputs:
- item: Mithril ore
item_id: 447
qty: 1
source: ge
- item: Coal
item_id: 453
qty: 4
source: ge
rates:
capacity: 5
casts_per_hour: {min: 232.8, mid: 160.4, max: 122.0}
magic_xp_per_hour: {min: 12338, mid: 8499, max: 6468}
adamantite-bar:
bar: Adamantite bar
bar_item_id: 2361
smithing_level: 70
smithing_xp: 37.5
target_input: Adamantite ore
inputs:
- item: Adamantite ore
item_id: 449
qty: 1
source: ge
- item: Coal
item_id: 453
qty: 6
source: ge
rates:
capacity: 3
casts_per_hour: {min: 153.4, mid: 104.5, max: 79.1}
magic_xp_per_hour: {min: 8131, mid: 5538, max: 4194}
runite-bar:
bar: Runite bar
bar_item_id: 2363
smithing_level: 85
smithing_xp: 50
target_input: Runite ore
inputs:
- item: Runite ore
item_id: 451
qty: 1
source: ge
- item: Coal
item_id: 453
qty: 8
source: ge
rates:
capacity: 3
casts_per_hour: {min: 153.4, mid: 104.5, max: 79.1}
magic_xp_per_hour: {min: 8131, mid: 5538, max: 4194}
# Explicit activity/rate discriminator (owner guidance 2026-08-28, bounce 4):
# this entry OPTS IN to the derived-rate gate; every rate below derives from
# the entry's own declared lifecycle, and the validator re-derives it.
rate_model:
anchor_tier: sii # xp_per_hour pins the SII tier's realized rate
anchor_recipe: steel-bar # the anchor recipe (must be a declared recipe)
cast_ticks: 3 # LA/SII/HLA are 3-tick casts -> 1.8 s (0.6 s/tick)
restock_trip_s: [60, 120] # GE-adjacent booth trip per restock
session_horizon_s: 7200 # 2 h session; startup is amortised over it
startup_s: [300, 600] # one-time GE acquisition + loadout
alch_carry_batch: 1000 # LA/HLA tier: stacked candidates + natures
alch_restock_threshold: 100 # -> 901-cast LA/HLA restock cadence
sii_capacity_slots: 27 # 28-slot inventory minus the nature stack
state_machine:
initial: idle
terminal: [complete, failed]
states:
idle:
goal: "session start at the GE bank booth; choose the session Magic target (default 55 — completes at the LA/SII -> HLA handoff; above 55 the HLA tier runs to the target). Open GE (a2), place the initial offers (staff a3, tier supplies a4), record initial capital in the cash ledger (total assets = cash + supplies at current buy cost). Nothing is bought or collected without an open GE interface"
next: [wait_offers, complete, failed]
wait_offers:
goal: "wait for the pending offers (ge_offers_done); when filled, collect every purchase + refund from the collection box (a5 — never leave purchases in the box). PARTIAL FILLS: collect the filled portion, re-place the remainder at the current edge (a3/a4); 3 consecutive no-fill rounds -> failed. An offer still unfilled at session end is left resting by design — its coins are tied up (float reserve) and return when it fills or the operator aborts it (the single operator-touch point)"
next: [ready, wait_offers, failed]
ready:
goal: "equip the staff of fire (a6 — bounded retries, then FAIL CLOSED) and build the VERIFIED loadout (a7 build-loadout): verify the collected staff + tier supplies (from a5), deposit overflow, withdraw only the real bank-stock shortfall, and PROVE the carried counts equal the tier's carried amounts BEFORE the first cast (a8/a9)"
next: [cast, bank_supplies, failed]
cast:
goal: "cast the active tier spell with the tier-correct target — LA (< 43, or 43-54 when SII is skipped) or HLA (>= 55, only when the session target is above 55) on a noted alch candidate (a8); SII (43 <= Magic < 55, selection.mode) on the selected recipe's declared target_input (primary ore, never coal) with its exact allowed-fill inputs withdrawn (a9). The tier predicates are MUTUALLY EXCLUSIVE: at any Magic level exactly one tier is selectable — SII only while 43 <= Magic < 55, HLA only when Magic >= 55 and the target is higher, completion FIRST when the target is reached (no level can match two tiers; the old '43-55' SII window overlapped HLA at exactly 55). A break fires on the completed-cast counter (a13), NEVER on bank visits, and restocking never resets it"
next: [cast, bank_supplies, complete, failed]
bank_supplies:
goal: "restock to the tier's carried amounts: deposit coins + smelted bars and withdraw ALL tier supplies the bank holds (a10 — bank-stock fast path only when the bank actually holds enough); for the remaining deficit, open GE (a2), buy the deficit (a11, live low + 5%), collect it (a12) — never withdraw a batch that was never purchased. Restocking never resets the cast or break counters"
next: [cast, bank_supplies, failed]
complete:
goal: "magic level >= the configured session target (default 55 — completes at the LA/SII -> HLA handoff; a target above 55 finishes only after the executable HLA tier reaches it)"
failed:
goal: "hard failure — stop, log ERROR sentinel, alert"
transitions:
- from: idle
when: "staff + tier supply offers all placed (a3/a4)"
to: wait_offers
- from: idle
when: "magic level >= session target at session start"
to: complete
- from: idle
when: "3 consecutive offer-place failures at session start (no fallback candidate/recipe)"
to: failed
- from: wait_offers
when: "ge_offers_done AND all purchases collected (a5)"
to: ready
- from: wait_offers
when: "partial fill: filled portion collected (a5), remainder re-placed at the current edge (a3/a4) — still waiting"
to: wait_offers
- from: wait_offers
when: "3 consecutive no-fill rounds (offer cannot fill at the accepted edge)"
to: failed
- from: ready
when: "staff equipped (a6) AND build-loadout verified the carried counts at the tier's carried amounts (a7 — collected staff/supplies present, overflow deposited, only a real bank-stock shortfall withdrawn; carried counts PROVEN)"
to: cast
- from: ready
when: "staff equip failed after bounded retries — FAIL CLOSED"
to: failed
- from: ready
when: "build-loadout verification shows a carried shortfall the bank does not hold — restock first"
to: bank_supplies
- from: cast
when: "level < 43, or (43 <= level < 55 with SII skipped) — LA tier: cast on the cheapest alch candidate (LA is executable below 43 and throughout the 43-54 window only when a recipe-margin issue explicitly skips SII; at Magic 55+ the LA predicate is never true — no level matches two tiers)"
to: cast
- from: cast
when: "43 <= level < 55 and the SII window is chosen (MUTUALLY EXCLUSIVE with HLA — SII is selectable ONLY below 55, never at 55+) — selection.mode picks the recipes-table entry with the highest smithing_level <= current Smithing and a positive allowed-fill complete-cost margin; cast on its target_input"
to: cast
- from: cast
when: "43 <= level < 55 — SII fallback: the selected recipe's materials cannot be obtained, so drop to the next lower unlocked recipe (never an unmet Smithing gate) and continue SII if its margin is positive"
to: cast
- from: cast
when: "43 <= level < 55 and no unlocked recipe has a positive margin this window — skip SII and stay on LA"
to: cast
- from: cast
when: "level >= 55 and session target > 55 — the HLA tier executes (a8) until magic reaches the target (MUTUALLY EXCLUSIVE: HLA is selectable only when the target is ABOVE 55, so an account at 55 with target 55 completes first rather than matching SII/HLA)"
to: cast
- from: cast
when: "LA/HLA tier: candidate count < restock_threshold (100) OR nature count < restock_threshold (100); SII tier: bars_in_inventory >= the selected recipe's capacity (floor(27 / total input qty): steel 9, bronze 13, mithril 5, ...) OR any selected-recipe input < 1 — deterministic restock triggers, pinned numbers"
to: bank_supplies
- from: cast
when: "magic level >= session target (default 55 — completes at the LA/SII -> HLA handoff; targets above 55 require the executable HLA tier FIRST — completion is evaluated before tier selection, so the configured target always wins over any remaining tier predicate)"
to: complete
- from: cast
when: "3 consecutive failed casts (client issue — do not loop)"
to: failed
- from: bank_supplies
when: "restocked to the tier's carried amounts (bank fast path or re-bought deficit collected)"
to: cast
- from: bank_supplies
when: "restock partial: deficit re-bought but offers still filling (ge_offers_done false) — loop the restock phase"
to: bank_supplies
- from: bank_supplies
when: "3 consecutive no-fill restock rounds OR a capital stop fires (assets / loss-reserve checks in stop_conditions)"
to: failed
actions:
- id: a1
step: reach-bank
verb: move_to
target: bank
params:
location: "GE-adjacent bank (alch spot = the bank booth, so restock is a single short leg, 60-120s)"
method: [teleport, walk]
on_fail: retry
anti_ban:
delay: [400, 1600]
- id: a2
step: open-ge
verb: ge_open
target: grand-exchange
params:
note: "open the GE interface at the booth. The entry NEVER buys (a3/a4/a11) or collects (a5/a12) without an open GE interface — the action grammar defines ge_open, ge_buy, ge_collect as separate verbs, so the open step is explicit"
on_fail: retry
anti_ban:
delay: [600, 1400]
- id: a3
step: buy-staff
verb: ge_buy
target: grand-exchange
params:
item: "staff of fire"
qty: 1
max_price: "live low + 25 % (wiki value 1,500; hard cap 5,000)"
note: "offer rests until filled or the run ends; on a partial/no fill, collect what filled (a5) and re-place the remainder at the current edge — bounded retries, then the run fails closed (no fire-rune fallback exists)"
on_fail: retry
anti_ban:
delay: [1200, 3000]
- id: a4
step: buy-supplies
verb: ge_buy
target: grand-exchange
params:
item: "nature runes + the tier's supplies — LA/HLA: the current least-loss alch candidate; SII: the exact unnoted inputs of the SELECTED recipe (every input and quantity from the recipes table, e.g. steel-bar = 1 iron ore + 2 coal), only while the allowed-fill complete-cost margin is positive"
qty: "LA/HLA: carried_batch 1,000 (4h GE limits: natures 18,000, candidates per mapping); SII: one capacity-sized pack of the selected recipe — capacity = floor(27 / total input qty) casts (steel = 9, so 9 x each input quantity + 9 natures per pack)"
max_price: "live low + 5 %"
note: "offer rests until filled or the run ends; partial fills are collected (a5) and the remainder re-placed at the current edge; 3 consecutive no-fills -> SII recipe fallback, else failed"
on_fail: retry
anti_ban:
delay: [1200, 3000]
- id: a5
step: collect-purchases
verb: ge_collect
target: grand-exchange
params:
note: "collect every filled purchase and refund from the GE collection box into the inventory — staff, candidates/natures, or the recipe's exact inputs (and the equivalent on every restock). Partial offers: the collected portion is added to the ledger; nothing is left in the box at session start"
on_fail: retry
anti_ban:
delay: [600, 1400]
- id: a6
step: equip-staff
verb: menu_option
target: "staff of fire in inventory"
params:
option: "Wield"
note: "Equip requirement is NONE documented on the OSRS wiki Staff of fire page (no requirements row in the infobox, none in the prose; compare Battlestaff 'requires 30 Attack and 30 Magic'; verified 2026-08-28). FAIL-CLOSED path: bounded retries (3), then log ERROR + alert + stop — no fire-rune fallback is modeled (never loop on equip attempts)"
on_fail: retry
anti_ban:
delay: [600, 1800]
- id: a7
step: build-loadout
verb: bank_inventory
target: bank
params:
deposit: [overflow products]
withdraw: "to the tier's carried amounts (only the real bank-stock shortfall — collected purchases from a5 are already in the inventory): LA/HLA 1,000 candidate + 1,000 nature; SII one capacity-sized pack of the selected recipe (steel: 9 iron + 18 coal + 9 natures)"
verify: "the collected staff of fire + tier supplies (a5) are present, and the carried counts EQUAL the tier's carried amounts — the loadout is PROVEN before the first cast (a8/a9); a verified shortfall the bank does not hold sends the slot to bank_supplies instead"
on_fail: retry
anti_ban:
delay: [500, 1100]
- id: a8
step: cast-alch
verb: use_item_on
target: "noted alch candidate in inventory (LA and HLA tiers)"
params:
spell: "Low Level Alchemy (level < 43, or 43-54 when SII is skipped) | High Level Alchemy (level >= 55 — only when the session target is above 55; HLA is never declared-but-unreachable; tier predicates are MUTUALLY EXCLUSIVE — SII only 43 <= Magic < 55, HLA only >= 55 with the target higher, completion first at the target)"
item: "cheapest live alch candidate in inventory — LA/HLA are alchemy tiers, so the selection rule is the alch-candidate rule"
on: "itself"
on_fail: retry
anti_ban:
delay: [1600, 3400] # MANDATORY post-action sleep, ADDITIVE to the 1.8s cast (3 ticks x 0.6s; action-grammar.md:135): cycle = 3.4-5.2s, mid 4.3s -> LA/HLA session-realized ~742 casts/hr incl. startup + restock + breaks (rate_model)
- id: a9
step: cast-superheat
verb: use_item_on
target: "the selected recipe's declared target_input in inventory (the primary ore — never coal; SII tier only, 43 <= Magic < 55)"
params:
spell: "Superheat Item"
selected_recipe: "runtime selection per selection.mode: the recipes-table entry with the highest smithing_level <= current Smithing whose complete input cost at ALLOWED fill prices + one nature rune at allowed fill < the bar's live GE price; fall back to the next lower unlocked recipe when its materials cannot be obtained"
target_input: "the selected recipe's declared target_input field (primary ore; coal can never be the spell target)"
on: "itself"
consumption: "1 x each recipe input (e.g. steel = 1 iron ore + 2 coal) + 1 nature per cast -> 1 bar, +53 Magic xp + the bar's Smithing xp; the pack holds capacity = floor(27 / total input qty) casts (steel = 9)"
on_fail: skip # no unlocked positive-margin recipe -> skip SII, stay on LA (selection.fallback)
anti_ban:
delay: [1600, 3400] # post-action sleep (additive): same 3.4-5.2s/cast as a8; per-recipe realized rates in recipes.rates (steel mid 249.1 casts/hr steady, session anchor 12,400 xp/hr)
- id: a10
step: restock-deposit
verb: bank_inventory
target: bank
params:
deposit: [all coins, all products] # products incl. smelted bars during SII
withdraw: "ALL tier supplies currently in the bank, up to the carried amounts (LA/HLA 1,000/1,000; SII one capacity-sized pack of the selected recipe) — the bank-stock FAST PATH applies only when the bank actually holds enough; the remaining deficit is bought at the GE (a11) and collected (a12)"
on_fail: alert
anti_ban:
delay: [500, 1100]
- id: a11
step: restock-buy
verb: ge_buy
target: grand-exchange
params:
item: "the tier's supplies to top up to the carried amounts"
qty: "deficit = carried amount - inventory count after the a10 fast path (LA/HLA: 901 candidate + 901 natures after a 901-cast batch with an empty bank; SII: the selected recipe's capacity-sized pack minus whatever the bank held)"
max_price: "live low + 5 %"
note: "the deficit is RE-BOUGHT at the GE on every restock — the entry never withdraws a batch that was never purchased; partial/failed fills use the same collect + re-place recovery as the initial acquisition"
on_fail: retry
anti_ban:
delay: [1200, 3000]
- id: a12
step: restock-collect
verb: ge_collect
target: grand-exchange
params:
note: "collect the re-bought deficit into the inventory — carried amounts are restored before casting resumes"
on_fail: retry
anti_ban:
delay: [600, 1400]
- id: a13
step: take-break
verb: take_break
target: "current location"
params:
duration: "[45, 120] seconds of idle AFK per break (matches run_control.break.for)"
note: "executed by the translator when the completed-cast counter reaches run_control.break.every [400, 700] casts — this IS run_control.break's executor, fired on CAST COUNTS, not on bank visits; the cast counter is NOT reset by restocking (breaks land at 550/1,100/1,650 in the midpoint example, independent of the 901-cast alch / capacity-sized SII restock cadences)"
on_fail: skip
run_control:
iteration: "one cast" # schema.md: break.every counts iterations, so every
# value below is in completed casts, NOT bank trips
max_iterations: null
break:
every: [400, 700] # COMPLETED CASTS between AFK breaks (midpoint 550 = ~39 min
# at the 4.3s midpoint cycle). Cast-count-based, independent of
# banking; a restock trip NEVER counts as a break and does not
# reset this counter (owner decision 2026-08-28)
for: [45, 120] # seconds of idle AFK
stop_conditions:
- "magic level >= session target (default 55: completes at the LA/SII -> HLA handoff; when the configured target is above 55, the HLA tier executes from 55 until the target — HLA is never declared-but-unreachable)"
- "session elapsed >= 2 h (session horizon — the rate model amortises startup over it)"
- "total assets (cash + supplies at current buy cost) < the next tier batch at max fill plus staff replacement (LA/HLA: 312,650 = 307,650 + 5,000; SII steel: 10,150 = 5,150 + 5,000) — assets, NOT raw cash: cash that became supplies is not a loss"
- "session net loss = initial capital - total assets > 690,075 — the 6-batch worst-case LA reserve (Magic 21 -> 55 at max fills) is exhausted (a loss is WHEN ASSETS FALL; total assets - initial capital goes NEGATIVE on losses and can never exceed the reserve — the reversed sign never fires)"
- "3 consecutive failed casts (client issue — do not loop)"
- "3 consecutive no-fill GE rounds (initial acquisition or restock) with no fallback recipe/candidate available"
- "staff of fire equip failed after bounded retries — FAIL CLOSED (log ERROR, alert, stop)"
# ================= WORKED EXAMPLE (pinned, deterministic — owner guidance 2026-08-28) =================
# SII tier, steel-bar path (Magic 43-54, Smithing 30, session target 55).
# Constants: break.every [400, 700] casts (the example pins the midpoint
# 550), break.for [45, 120] s; steel capacity = floor(27 / (1 iron + 2 coal))
# = 9 casts per bank cycle — restocks every 9 casts; the counter sequences
# are MONOTONIC and INDEPENDENT (restocking never resets the break counter).
#
# Step 0 — prep (one-time startup ~300-600 s): account has 30 Smithing +
# 43-54 Magic and the LA/HLA-tier capital (>= ~1.31M gp). At the GE bank:
# choose the session target (55); open GE (a2); place offers — staff (a3),
# 1,000 nature runes + LA candidate OR - for the SII window - the selected
# recipe's first pack (steel: 9 iron ore + 18 coal + 9 natures, a4); wait
# for fills (ge_offers_done) and collect everything (a5); equip the staff
# (a6); loadout = 9 iron + 18 coal + 9 natures (a7). Cast counter := 0.
# Steel is the highest UNLOCKED recipe (Smithing 30); bronze (1), iron (15)
# and silver (20) are unlocked but LOWER, so steel is selected. Smithing 20
# accounts select silver-bar (capacity 27) and steel stays locked (never an
# unmet gate).
# Step 1 — casts #1..#9: each cast consumes 1 iron ore + 2 coal + 1 nature
# and produces 1 steel bar (+53 Magic xp, +17.5 Smithing xp). After cast #9
# the inventory holds 9 bars (capacity reached) -> FIRST RESTOCK at cast 9:
# deposit the 9 bars (a10); the bank holds 0 steel inputs (all sales were
# GE buys), so the fast path yields nothing -> open GE (a2), RE-BUY the
# pack deficit 9 iron + 18 coal + 9 natures (a11, live low + 5 %), collect
# (a12) -> inventory rebuilt. Restocks repeat every 9 casts (9, 18, 27, ...,
# and at 558 after the first break below) — capacity-derived, deterministic.
# Step 2 — FIRST SCHEDULED BREAK at cast 550 (midpoint of break.every
# [400, 700]): the 61st cycle is mid-flight — take-break 45-120 s (a13),
# resume at cast 551, finish the cycle (restock at 558). The break fired on
# the CAST COUNTER while a bank cycle was still running: breaks and banking
# are independent by construction, and restocking did NOT shift the break.
# Step 3 — break #2 at cast 1,100 (exactly 550 after #1 — monotonic,
# unaffected by the 9-cast restock cadence); break #3 at 1,650; restocks
# continue at 1,107, 1,116, ... (every 9). On the alch tiers (LA and the
# post-55 HLA tier) the restock cadence is the 901-cast batch: restock #1
# at cast 901, restock #2 at cast 1,802 — monotonic, independent pins just
# like the breaks (restocking never resets or shifts the break counter).
# The SII window ends at Magic 55:
# with the default target 55 the session completes at the LA/SII -> HLA
# handoff; with a target above 55 the HLA tier (a8, 65 xp/cast, 901-cast
# batch model) executes until the target.
# Fallback chain pinned: steel inputs unfillable at the GE (3 failed offers)
# -> drop to silver-bar (20, next lower unlocked) -> if silver is also
# unfillable or margin-negative at allowed fills -> skip SII this window and
# stay on LA throughout 43-54 (a8 handles the SII-skip window explicitly).
# =====================================================================================================
risk:
ban_level: low
notes:
- "3-tick cast cadence is the main detection vector; keep anti_ban delays (cadence pin: additive post-cast sleep [1.6, 3.4]s; session-realized ~742 casts/hr on the alch tiers and ~234 casts/hr on the steel SII anchor, incl. startup + restock + breaks over a 2 h session — loop-only 837 is a ceiling reference, never the output)."
- "Breaks are CAST-COUNT-based (every [400, 700] completed casts, 45-120s AFK) and independent of bank visits — never treat a restock trip as a break, and never reset the break counter on restock (break interval 550 and restock cadences 901/9 are coprime by construction)."
- "The valuable-item warning fires on item GE value (not alch value) — the script must handle the warning widget when alching high-value items (HLA page)."
- "SII: withdraw the EXACT recipe inputs (unnoted, per the recipes table) sized to the derived capacity (floor(27 / input qty)) and cast on the declared target_input (primary ore) — never on coal, never on a noted stack; bank the produced bars when bars_in_inventory >= capacity."
- "SII ore-buy bursts are pace-sensitive like any GE buy; respect the 4h limit window; re-check the allowed-fill complete-cost margin per window because ore/bar prices move independently."
# --- recovery block (owner guidance 2026-08-28: every activity must cover its complete runnable lifecycle) ---
# - Missing materials / depleted supplies: LA/HLA restock at candidate OR nature
# < 100 (carried_batch 1,000/1,000); SII restocks at bars >= the selected
# recipe's capacity (floor(27 / input qty)) or any selected-recipe input < 1 —
# deterministic threshold transitions to bank_supplies; the restock phase
# (a10 -> a2 -> a11 -> a12) RE-BUYS the deficit at the GE — never withdraw a
# batch that was never purchased.
# - GE offers: partial fills are collected (a5/a12) and the remainder re-placed
# at the current edge (a3/a4/a11); after 3 consecutive no-fill rounds (initial
# or restock) the SII recipe falls back to the next lower unlocked recipe;
# with no fallback recipe left, SII is skipped and LA takes over; with no
# fallback candidate, the run stops (failed). Unfilled offers at session end
# are left resting by design (float reserve; coins return when they fill or
# the operator aborts them — the single operator-touch point).
# - Staff of fire cannot be equipped (a6): bounded retries (3), then FAIL
# CLOSED — log ERROR + alert + stop. No fire-rune fallback is modeled (it
# would need its own purchase/supply/rate path; the owner decision rejects a
# prose-only fallback).
# - Invalid loadout (wrong item in hand / spell unavailable): re-run
# build-loadout (a7); 3 consecutive failed casts -> failed (stop, log ERROR
# sentinel, alert).
# - Capital: both capital stops are assets-based (total assets = cash +
# supplies at current buy cost) — cash converted into supplies is not a loss
# and never fires a stop by itself (see stop_conditions).
# - Explicit completion/stop: stop_conditions above (session target 55 or the
# >55 HLA tier, 2 h session horizon, assets floor 312,650 / 10,150, loss
# reserve 690,075, repeated failures, fail-closed staff equip).
sources:
- "OSRS wiki — Low Level Alchemy (21 Magic, 31 xp/cast, members=No, 3-tick, 40% of item value; 'recommended to use High Level Alchemy instead' at 55 Magic)"
- "OSRS wiki — Superheat Item (43 Magic, 53 xp/cast, members=No; smelts ore into bars without a furnace; cannot be cast on noted ore; grants the bar's Smithing XP; requires the bar's Smithing level and materials)"
- "OSRS wiki — High Level Alchemy (55 Magic, 65 xp/cast, members=No, 60% of item value; GE buying for break-even/profit; noted-item casting; valuable-item warning)"
- "OSRS wiki — Smithing (bar smelting table: smithing levels, per-bar XP and ore/coal quantities for bronze/iron/silver/steel/gold/mithril/adamantite/runite)"
- "OSRS wiki — Grand Exchange (offers persist until filled or aborted; filled items and coin refunds are collected from the booth/collection box; partial fills are separately collectable) — verified 2026-08-28"
- "OSRS wiki — Grand Exchange/Buying limits (4-hour window; the limit resets 4 hours after the FIRST unit is bought; outstanding offers remain active and a limit reset is NOT an offer expiry) — verified 2026-08-28"
- "prices.runescape.wiki/api/v1/osrs/mapping (id 561 nature rune: members=false, limit 18000, value 180, lowalch 72; limit cross-checked against Module:Exchange/Nature_rune — agree)"
- "prices.runescape.wiki/api/v1/osrs/latest (nature rune buy 145, snapshot 2026-08-27; steel-bar fixture prices: iron ore 100, coal 150, steel bar 600)"
- "OSRS wiki — Staff of fire (page verified 2026-08-28 for the equip requirement: id 1387, F2P item, tradeable, options Wield — NO requirements row in the Infobox Item and NO requirement statement in the prose; contrast Battlestaff, whose prose states 'requires 30 Attack and 30 Magic to wield')"
- "docs/schema.md run_control (iteration unit; break.every counts iterations) + docs/action-grammar.md:135 (anti_ban.delay = additive post-action sleep) + verbs ge_open/ge_buy/ge_collect/ge_offers_done (separate GE primitives) — the cadence + break + GE + lifecycle model; owner guidance 2026-08-28 (capacity-derived SII, rate_model, configurable target, fail-closed staff); governor comment 10394 (verified build-loadout a7, sign-corrected loss stop)"
verified: "2026-08-27" # keep quoted (YAML 1.1 date parsing); prices last re-fetched live 2026-08-27
related:
- low-alch-f2p
- f2p-alch-catalog
- high-alch-p2p

View File

@ -0,0 +1,775 @@
id: mining-f2p
title: Mining (F2P)
domain: training
tags: [mining, gathering, f2p, skill-training]
status: verified
version: 7 # 7: bounce-fix 6 2026-08-29 (owner guidance for governor 10476) — explicit tier-rank tie-break (rank 1..6; equal Mining gates resolve to the higher rank): Mining 1 now selects IRON (7-tick roll), Attack 1 EQUIPS it (attack gate 1) -> 28 ore slots; level-1 worked lifecycle/rates recomputed (iron cadence midpoint 5.1 s -> 398 ores/hr -> pin 6,900 XP/hr); pickaxe_examples gain expected_slots (28 equipped / 27 carried) + the recomputed 1-Attack/1-Mining fixture (iron, equipped, 28) — 1-Attack/41-Mining rune-carry fixture preserved; validator + schema enforce rank and slots. # 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-29" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date
summary: >
F2P mining with TWO explicit run modes, chosen at session start and
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 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
until the GP target. Targets: iron @ SE Varrock (safe) and adamantite @
Al Kharid south (respawn-bound — modeled WITH world hopping per the
wiki money guide's 120+ ores/hr; without hopping the ceiling is ~30
ores/hr). Runite is NOT selectable: F2P runite sits in the Wilderness
(PK risk, ~12-min respawn) and is out of the modelled set.
Pickaxe policy (two SEPARATE gates, provable): choose the best tier by
MINING level (usability — rune usable at 41 Mining), equip ONLY when the
ATTACK level permits (rune equip = 40 Attack), otherwise CARRY the same
selected tool (a 1-Attack/41-Mining account carries and uses a rune
pickaxe — pinned in pickaxe_examples and recomputed by the validator).
TIE-BREAK: tiers with equal Mining gates resolve by explicit rank
(pickaxe_tiers rank 1..6) — bronze and iron share the Mining-1 gate, so
Mining 1 selects IRON (7-tick roll), and Attack 1 equips it (attack
gate 1): a 1-Attack/1-Mining account mines copper/tin with an EQUIPPED
iron pickaxe and 28 ore slots (fixture in pickaxe_examples).
accounts:
membership: f2p # whole ore chain verified F2P via the wiki guide
min_level: 1 # clay/copper/tin mineable at level 1
requires_quest: []
requires_other:
- "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. Equal Mining gates resolve by explicit tier rank (rank 1..6): at Mining 1 the tie-break selects IRON over bronze, and Attack 1 EQUIPS it (attack gate 1) — pickaxe_examples pins 1-Attack/1-Mining (iron, equipped, 28 ore slots)"
- "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"
best_practice:
equipment:
- item: "pickaxe: highest tier usable at the account's MINING level (pickaxe_tiers: bronze 1, iron 1, steel 6, mithril 21, adamant 31, rune 41)"
why: "each tier is faster — the roll interval drops from 8 ticks (bronze) to 3 ticks (rune), and the guide recommends always using the highest tier available. Two SEPARATE gates: the Mining level decides WHICH pickaxe can be USED, the Attack level decides ONLY whether it can be EQUIPPED (steel 5, mithril 20, adamant 30, rune 40). Equip only when the Attack requirement is met, otherwise carry the same selected tool — a 1-Attack/41-Mining account selects, carries, and uses a rune pickaxe. TIE-BREAK on equal Mining gates: explicit rank (pickaxe_tiers rank 1..6) — bronze and iron both open at Mining 1, so Mining 1 selects IRON (rank 2) and Attack 1 EQUIPS it (attack gate 1, 28 ore slots), never bronze (rank 1)"
source: "OSRS wiki — Free-to-play Mining training (Pickaxes: tier table with tick intervals); OSRS wiki — Rune pickaxe (41 Mining to use, 40 Attack to equip) and Steel/Mithril/Adamant pickaxe pages (use vs equip gates)"
techniques:
- name: "mode contract: fast-xp vs banked-income (chosen at session start, validated against mine_sites, never silently highest-available)"
why: "fast-xp powermines iron 15+ (copper/tin below 15) and DROPS every full inventory; banked-income fixes ONE target mine_sites row (validated: Mining level >= ore level, access/safety gates met, inventory policy bank) and BANKS every load. The two modes pick different rocks, use different inventory handling, and stop at different targets — the entry never selects a rock for the operator"
source: "owner decision 2026-08-28 (governor guidance channel, PR #11): expose separate fast-xp and banked-income modes; encoded in mining_modes + state_machine + a2 params"
- name: "start with Doric's Quest for 1,300 xp (level 1 to 10)"
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. 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)"
source: "OSRS wiki — Free-to-play Mining training (Levels 1/10-15 and 15-99: banking locations)"
- name: "banked-income example: adamantite at Al Kharid south with world hopping (70 Mining)"
why: "deterministic target row: adamantite (70 Mining, 95 xp, 584 gp low edge) at the Al Kharid south rocks, banked at the adjacent bank. The site is RESPAWN-BOUND (2 rocks, ~4-min respawn): the wiki F2P adamantite money guide reports about 120+ ores/hour ONLY by hopping worlds — modeled 70,080 gp/hr with the hop_world policy; without hopping the ceiling is ~30 ores/hr = 17,520 gp/hr. The entry models the hop, never the idealized iron cadence for a high-tier ore"
source: "OSRS wiki — Money making guide/Mining adamantite ore (F2P) (4-min respawn; ~120 ores/hr with world hopping); OSRS wiki — Free-to-play Mining training (Levels 30-99 and money-making); prices.runescape.wiki snapshot 2026-08-27"
- 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 (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 (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 (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 with the rank TIE-BREAK: bronze and iron both meet the Mining-1 gate, and rank 2 (iron, 7-tick roll) beats rank 1 (bronze, 8-tick) -> IRON is selected; equip gate Attack 1 >= iron's attack gate 1 -> EQUIP the iron pickaxe (28 ore slots); 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, iron cadence [4200, 6000] ms) -> wait_rock: ore gained after ~1.6 rolls (success model) -> mine; both ores are 17.5 xp each. (4) first full-inventory boundary: 28 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: ~398 ores/hr = ~6,960 XP/hr at the iron cadence midpoint 5.1 s (pin 6,900; economy)."
source: "derived from this entry's policy model (rank tie-break in pickaxe_tiers + pickaxe_examples 1-Attack/1-Mining + mining_selections levels 1/14 + mine_sites se-varrock-copper / se-varrock-tin + state_machine + actions a9/a2/a6/a16/a17), synthesized 2026-08-29"
pickaxe_tiers:
# rank = explicit tier order (1..6) used as the SELECTION tie-break:
# among tiers whose mining_level gate the account meets, the highest
# rank wins. Bronze and iron share the Mining 1 gate, so at Mining 1 the
# rule deterministically selects IRON (rank 2, 7-tick roll) — never
# bronze. (An owned-only bronze run is an operator choice, not the
# entry's selection rule.) attack_level remains the equip-ONLY gate:
# iron's attack gate is 1, so a 1-Attack/1-Mining account EQUIPS it.
bronze-pickaxe:
mining_level: 1 # usability gate: Mining level needed to USE this pickaxe
attack_level: 1 # equip gate: Attack level needed only to EQUIP it
roll_ticks: 8 # ore roll interval (ticks x 0.6 s = 4.8 s)
rank: 1
iron-pickaxe:
mining_level: 1
attack_level: 1
roll_ticks: 7
rank: 2
steel-pickaxe:
mining_level: 6
attack_level: 5
roll_ticks: 6
rank: 3
mithril-pickaxe:
mining_level: 21
attack_level: 20
roll_ticks: 5
rank: 4
adamant-pickaxe:
mining_level: 31
attack_level: 30
roll_ticks: 4
rank: 5
rune-pickaxe:
mining_level: 41
attack_level: 40
roll_ticks: 3
rank: 6
# Structured proof of the two-gate rule (validator recomputes each row from
# pickaxe_tiers and compares): select by MINING (tie-break: highest rank),
# equip only when ATTACK permits, carry otherwise. expected_slots = 28
# (equipped pickaxe leaves 28 ore slots) or 27 (carried pickaxe occupies
# one slot). The 1-Attack/41-Mining account is owner-mandated; the
# 1-Attack/1-Mining row proves the level-1 tie-break (iron, equipped, 28).
pickaxe_examples:
- account: "1-Attack/1-Mining"
attack: 1
mining: 1
expected_tier: iron-pickaxe
expected_equipped: true # 1 Mining: bronze/iron both eligible -> rank tie-break -> IRON; Attack 1 >= 1 -> EQUIP (28 ore slots)
expected_slots: 28
- account: "1-Attack/41-Mining"
attack: 1
mining: 41
expected_tier: rune-pickaxe
expected_equipped: false # 41 Mining selects rune; Attack 1 < 40 -> CARRY (27 ore slots)
expected_slots: 27
- account: "40-Attack/70-Mining"
attack: 40
mining: 70
expected_tier: rune-pickaxe
expected_equipped: true # 70 Mining selects rune; Attack 40 >= 40 -> EQUIP (28 ore slots)
expected_slots: 28
- account: "5-Attack/30-Mining"
attack: 5
mining: 30
expected_tier: mithril-pickaxe
expected_equipped: false # 30 Mining selects mithril (21); Attack 5 < 20 -> CARRY (27 ore slots)
expected_slots: 27
# 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: "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]
stop: "banked GP target reached (e.g. 500,000 gp) or session cap"
# Structured per-site model (owner guidance 2026-08-28): every target row
# carries ore, gates, rock count, respawn, mining-success input, modelled
# rate, hop policy, inventory policy, access/safety and route — the selected
# target in activity state references one row id, so "location contains ore"
# and profitability are validated data, not prose.
mine_sites:
al-kharid-powermine:
ore: Iron ore
ore_id: 440
mining_level: 15
xp: 35
rock_count: 3 # the only F2P site with 3 iron rocks around one tile
respawn_seconds: 5.4 # wiki Iron rocks respawn
rolls_per_ore: 1.33 # success model, 61+ (derived from the wiki 52,500 bench: 1,500 ores/hr / 2,000 3-tick rolls/hr); 41-60 = 1.40
modeled_ores_per_hour: 803 # fast-xp drop model (economy derivation)
world_hop: false
selectable: true
inventory_policy: drop
access: "combat level >= 29 — the level-14 scorpion's aggro ceiling is combat 28 (wiki Aggressiveness)"
safety: "hard gate + survive-state retreat fallback; no food modeled"
route: "drop in place — no bank leg"
se-varrock-drop:
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: 803 # same drop model; 2 rocks x 3.59 s mining = 7.2 s > 5.4 s respawn -> no idle
world_hop: false
selectable: true
inventory_policy: drop
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: 398 # iron cadence midpoint 5.1 s (tie-break select: iron at Mining 1): 1.6 x 5.1 + 0.5 + 0.39 = 9.05 s/ore -> 398 (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: 398 # iron cadence midpoint 5.1 s (see se-varrock-copper)
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
mining_level: 15
xp: 35
rock_count: 2
respawn_seconds: 5.4
rolls_per_ore: 1.33
modeled_ores_per_hour: 713 # bank model: 3.59 + 0 + 1.07 + 0.39 = 5.05 s/ore (economy derivation)
world_hop: false
selectable: true
inventory_policy: bank
access: "no aggressive NPCs"
safety: "none required"
route: "east Varrock bank leg ~30 s per 28-ore load"
al-kharid-south-adamantite:
ore: Adamantite ore
ore_id: 449
mining_level: 70
xp: 95
rock_count: 2
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 (a15); the modelled rate assumes it
selectable: true
inventory_policy: bank
access: "Al Kharid south rocks (no aggressive NPCs at this cluster)"
safety: "banked at the Al Kharid bank after every load"
route: "bank leg ~25 s; world-hop (hop_world) when both rocks are depleted — fresh worlds have fresh rocks"
runite-wilderness:
ore: Runite ore
ore_id: 451
mining_level: 85
xp: 125
rock_count: 2
respawn_seconds: 720 # wiki Runite ore: ~12-min respawn
rolls_per_ore: 3.0
modeled_ores_per_hour: 0 # not modelled
world_hop: false
selectable: false # F2P runite is in the Wilderness (PK risk) — NOT a target
inventory_policy: bank
access: "Wilderness (PK risk) — NOT modeled"
safety: "out of scope: would require Wilderness travel + PK survival + heavy world hopping"
route: "not selectable in this entry; the hazard is refused, not hand-waved"
economy:
# Anchor: iron ore (the fast-xp training ore). Sell prices are the
# 2026-08-27 snapshot (prices.runescape.wiki latest, low edge).
#
# MODELLED POLICY RATES (midpoint model, derived 2026-08-28) — the
# entry's OWN parameters per site (mine_sites), not the wiki's
# zero-overhead ideals:
# success model: a roll is NOT one ore. rolls_per_ore is derived from
# the wiki benchmark for iron at 61+ (52,500 xp/hr = 1,500 ores/hr at a
# perfect 3-tick 2,000 rolls/hr -> 1.33 rolls/ore ~ 75% success; 41-60:
# 50,000 -> 1.40). Lower tiers conservatively more rolls; the
# high-tier sites are respawn-bound, not success-bound.
# per-ore time = rolls_per_ore x 2.7 s (a2 delay midpoint [1800, 3600])
# + respawn idle + drop-or-bank + break amortised.
# Respawn idle (pinned, NOT a flat 0.3 s): 3 rocks x 3.59 s mining =
# 10.8 s > 5.4 s iron respawn -> ZERO idle (cluster self-sustains);
# 2 rocks x 3.59 s = 7.2 s > 5.4 s -> ZERO idle. Adamantite is the
# opposite: 2 rocks x 240 s respawn -> respawn-BOUND, modelled with
# world hopping (120 ores/hr sourced) — 30 ores/hr without hopping.
# fast-xp iron (drop): 3.59 + 0 + 0.5 (28 x 0.5 s drops / 28) + 0.39
# (120 s break per 11 rotations / 28) = 4.48 s/ore -> 803 ores/hr ->
# 28,105 XP/hr -> pin 28,000 (xp_per_hour below).
# banked iron @ SE Varrock: 3.59 + 0 + 1.07 (30 s bank leg / 28) + 0.39
# = 5.05 s/ore -> 713 ores/hr -> 49,910 gp/hr -> pin 49,900.
# 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.
# copper/tin path (Mining 1-14, 17.5 xp): the rank tie-break at Mining 1
# selects IRON (bronze and iron share the Mining-1 gate; rank 2 beats
# rank 1) and Attack 1 EQUIPS it (attack gate 1) -> 28 ore slots, iron
# cadence midpoint 5.1 s (iron [4200, 6000]) -> 1.6 rolls x 5.1 + 0.5
# drop + 0.39 break = 9.05 s/ore -> 398 ores/hr -> ~6,960 XP/hr -> pin
# 6,900 (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
# midpoint cadence alone caps that ideal at ~46,667 XP/hr, so 52,500 is
# cited ONLY as a labeled wiki benchmark and never as this entry's
# expected output.
item: Iron ore # fast-xp anchor; banked-income targets in mine_sites
item_id: 440
cost_per_cycle: 0 # mining consumes nothing (pickaxe has no fuel; no supply costs)
income_per_cycle: 70 # iron ore sell price (low edge), snapshot 2026-08-27
net_per_cycle: 70 # no per-cycle costs; fast-xp drops ores -> realized income 0
gp_per_hour_low: 0 # fast-xp mode drops every load — XP is the product
gp_per_hour_high: 70080 # top modeled banked-income rate: adamantite WITH world hopping (120 ores/hr x 584); iron @ SE Varrock 49,900; no-hop adamantite 17,520 documented in profit_drivers
xp_per_hour: 28000 # MODELLED fast-xp policy rate (rune 61+ drop, success model + drop loop + breaks) — NOT the 52,500 wiki benchmark
supplies:
- item: "pickaxe — best tier selectable by MINING level (pickaxe_tiers: bronze 1, iron 1, steel 6, mithril 21, adamant 31, rune 41; equal Mining gates tie-break by rank, so Mining 1 selects iron); equip ONLY when the ATTACK level permits (steel 5, mithril 20, adamant 30, rune 40), otherwise carry the same selected tool (a 1-Attack/41-Mining account carries and uses a rune pickaxe; a 1-Attack/1-Mining account equips its iron pickaxe — attack gate 1)"
qty_per_cycle: 1
source: bank
profit_drivers:
- "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 ~398 ores/hr -> ~6,960 XP/hr at the iron cadence midpoint (rank tie-break at Mining 1 selects iron, equipped at Attack 1; pin 6,900) — 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"
state_machine:
initial: prepare
terminal: [complete, failed]
states:
prepare:
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/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); equal Mining
gates resolve by explicit tier rank (rank 1..6) — bronze and iron
both open at Mining 1, so Mining 1 selects IRON. 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; a 1-Attack/1-Mining account selects and EQUIPS an
iron pickaxe — attack gate 1 — with 28 ore slots). 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"
next: [mine, recover]
mine:
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, 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]
wait_rock:
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 (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"
next: [drop_run, mine, break_state, complete]
bank_run:
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, 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-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"
next: [mine, failed]
complete:
goal: "fast-xp: XP/level target reached. banked-income: banked GP target reached (or session cap)."
failed:
goal: "hard failure — stop, log ERROR sentinel, alert"
transitions:
- from: prepare
when: "mode + target row set and validated (mine_sites), tool ready, inventory built"
to: travel
- from: prepare
when: "target already reached at session start (fast-xp level target / banked GP target)"
to: complete
- from: prepare
when: "3 consecutive failed tool acquisitions or an unvalidatable target (invalid banked ore/location combination, or tool purchase/equip failure)"
to: failed
- from: travel
when: "is_at the selected mine_sites location and positioned at the cluster"
to: mine
- from: travel
when: "path failure (move_to retries exhausted)"
to: recover
- from: mine
when: "a2 rock click issued (the tier roll is now in progress)"
to: wait_rock
- from: mine
when: "inventory full (fast-xp mode)"
to: drop_run
- from: mine
when: "inventory full (banked-income mode)"
to: bank_run
- from: mine
when: "both target rocks depleted AND the site's world_hop == true"
to: hop
- from: mine
when: "no fresh target-tier rock available for 2 respawn cycles, or has_item pickaxe == false (tool lost/griefed)"
to: recover
- from: mine
when: "HP < 40% (hp_is — defensive only; unexpected aggro despite the gate)"
to: survive
- from: mine
when: "fast-xp: XP/level target reached; banked-income: banked GP target reached (or session cap)"
to: complete
- from: wait_rock
when: "ore gained (count_item ore > count at click) or the clicked rock depleted"
to: mine
- from: wait_rock
when: "roll timeout (no outcome within 2x the equipped tier's roll interval)"
to: mine
- from: wait_rock
when: "both target rocks depleted AND the site's world_hop == true"
to: hop
- from: wait_rock
when: "inventory full (fast-xp mode)"
to: drop_run
- from: wait_rock
when: "inventory full (banked-income mode)"
to: bank_run
- from: wait_rock
when: "cluster fully depleted and the respawn wait exceeded (no rock of the target tier in range)"
to: recover
- from: hop
when: "landed on a fresh F2P world with target rocks available"
to: mine
- from: hop
when: "8 consecutive hops with no ore gained (possible world lockout)"
to: recover
- from: drop_run
when: "inventory still holds ore (count_item ore > 0)"
to: drop_run
- from: drop_run
when: "inventory empty of ore (count_item ore == 0) and break counter < break.every"
to: mine
- from: drop_run
when: "inventory empty of ore (count_item ore == 0) and break counter >= break.every"
to: break_state
- from: drop_run
when: "fast-xp XP target reached (target < inventory capacity)"
to: complete
- from: bank_run
when: "ores deposited and back at the mine, break counter < break.every"
to: mine
- from: bank_run
when: "ores deposited and back at the mine, break counter >= break.every"
to: break_state
- from: bank_run
when: "banked GP target reached on this deposit"
to: complete
- from: break_state
when: "take_break finished (60-180 s)"
to: mine
- from: break_state
when: "session cap reached during the break"
to: complete
- from: recover
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"
to: travel
- from: recover
when: "rocks unavailable -> respawned or rotated to a fresh cluster of the target tier (or hopped, for world_hop rows)"
to: mine
- from: recover
when: "recovery looped 3 times (persistent tool/location/path failure)"
to: failed
- from: survive
when: "out of combat (HP stable, no attacker in range)"
to: mine
- from: survive
when: "is_dead (gate bypassed/griefed)"
to: failed
actions:
- id: a1
step: travel-to-mine
verb: move_to
target: "the mode's mine_sites location"
params:
location: "fast-xp: Al Kharid Mine powermine (combat 29+ only) or South-east Varrock mine (sub-29 default — SAME drop mode); banked-income: the session's validated target row (example: Al Kharid south adamantite rocks)"
method: [walk, teleport]
note: "arrive with the pickaxe equipped ONLY if level_is Attack >= the tier's attack_level (rune = 40 Attack); otherwise carry the same selected tool in the inventory"
route_gate: "Al Kharid powermine ONLY at combat level >= 29 (level_is Combat >= 29 — the level-14 scorpion's aggro ceiling is combat 28, wiki Aggressiveness); below combat 29 run fast-xp at the South-east Varrock DROP site (no aggressive NPCs, same drop behaviour — the mode does not change); the Al Kharid south adamantite spot sits outside the scorpion cluster"
on_fail: retry
anti_ban:
delay: [400, 1600]
- id: a2
step: mine-rock
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 -> 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)"
roll_table: "pickaxe roll interval (ticks x 0.6 s, wiki table verified 2026-08-27): bronze 8 = 4.8 s, iron 7 = 4.2 s, steel 6 = 3.6 s, mithril 5 = 3.0 s, adamant 4 = 2.4 s, rune 3 = 1.8 s"
cadence: "mandatory pause per click = the equipped tier's full roll interval from roll_table + [0, 1800] ms humanisation: rune [1800, 3600], adamant [2400, 4200], mithril [3000, 4800], steel [3600, 5400], iron [4200, 6000], bronze [4800, 6600]. The pause must never be shorter than the equipped tier's FULL roll, the wait_rock success model runs rolls_per_ore rolls per ore, and the outcome wait must elapse before any re-click — clicking before the outcome resolves cannot speed the ore and is the bot tell"
wait_for: "mining outcome — modeled as the wait_rock state: re-click a fresh rock only when ore was gained, the clicked rock depleted, or the roll timed out (2x roll)"
on_fail: retry
anti_ban:
delay: [1800, 3600] # rune-pickaxe anchor (61+): full 1.8 s roll + up to 1.8 s humanisation per roll attempt; per-tier floor in roll_table/cadence
- id: a3
step: bank-approach
verb: move_to
target: bank
params:
location: "nearest bank to the mine (Al Kharid bank for the powermine/adamantite spot, east Varrock bank for SE Varrock mine, Lumbridge Castle for the Swamp mine)"
method: [walk, teleport]
on_fail: retry
anti_ban:
delay: [400, 1200]
- id: a4
step: deposit-ores
verb: bank_inventory
target: bank
params:
deposit: [all ores]
withdraw: "nothing (banked-income mode; the pickaxe stays equipped or carried — inventory rebuilt as empty ore space for the next load)"
note: "log the load to activity state: target row id, ore, qty, banked value"
on_fail: alert
anti_ban:
delay: [500, 1100]
- id: a5
step: return-to-mine
verb: move_to
target: "the mine"
params:
method: [walk, teleport]
on_fail: retry
anti_ban:
delay: [400, 1200]
- id: a6
step: drop-ore-slot
verb: right_click
target: "ore slot in inventory"
params:
option: "Drop"
iterate: "one occupied ore slot per execution — non-stackable ores need one Drop per slot; drop_run -> drop_run re-executes a6 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"
mode: "fast-xp only — banked-income never drops ore"
on_fail: skip
anti_ban:
delay: [200, 800] # drop churn on a full inventory is human-normal
- id: a7
step: retreat-aggro
verb: retreat
target: "self"
params:
from: "aggro radius at the mine (defensive fallback — the Al Kharid 29+ gate normally prevents all aggro)"
method: "walk out of attack range, then re-enter the cluster once out of combat"
on_fail: retry
anti_ban:
delay: [600, 1800]
- id: a8
step: check-tool
verb: has_item
target: "the selected pickaxe tier (Mining-gated: pickaxe_tiers)"
params:
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:
delay: [300, 900]
- id: a9
step: withdraw-tool
verb: bank_inventory
target: bank
params:
withdraw: "1x selected pickaxe tier (owned-tool branch)"
note: "the owned tool comes from the bank — never bought twice"
on_fail: retry
anti_ban:
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
params:
item: "the selected pickaxe tier (Mining-gated)"
qty: 1
max_price: "GE mid + 10% cap"
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: a12
step: wait-ge-fill
verb: ge_offers_done
target: "the pickaxe purchase offer"
params:
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: a13
step: collect-tool
verb: ge_collect
target: grand-exchange
params:
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: a14
step: equip-tool
verb: menu_option
target: "the selected pickaxe tier in the inventory"
params:
option: "Wield"
gate: "RUN ONLY IF level_is Attack >= the tier's attack_level (steel 5, mithril 20, adamant 30, rune 40) — otherwise SKIP and carry the same selected tool in the inventory; mining from the inventory works at any level (a 1-Attack/41-Mining account mines with a carried rune pickaxe; a 1-Attack/1-Mining account DOES equip its iron pickaxe — attack gate 1 — giving 28 ore slots)"
on_fail: skip
anti_ban:
delay: [600, 1400]
- id: a15
step: hop-world
verb: hop_world
target: "a fresh F2P world"
params:
site: "world_hop rows ONLY (al-kharid-south-adamantite) — fired when both target rocks are depleted (hop state); each landed world has fresh rocks"
count: "hop count tracked; after 8 consecutive hops with no ore, go to recover"
on_fail: retry
anti_ban:
delay: [900, 2200]
- id: a16
step: take-break
verb: take_break
target: "self"
params:
duration: "run_control.break.for [60, 180] s of idle AFK"
note: "breaks are independent of incidental banking: the counter runs on rotations (run_control.break.every [8, 14]), not on bank visits or hops"
on_fail: retry
anti_ban:
delay: [300, 900]
- id: a17
step: reposition-cluster
verb: walk_to
target: "cluster center / fresh rock of the target tier"
params:
location: "the session's mine_sites location, cluster of the target tier"
note: "recovery + post-break reposition: after take_break, respawn waits, or a recover diagnosis, walk to the cluster's centre tile and re-select a fresh rock; never click a rock while walking"
on_fail: retry
anti_ban:
delay: [400, 1200]
run_control:
iteration: "one full mine -> drop (fast-xp) or mine -> bank (banked-income) rotation (world_hop rows include the hop legs)"
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, 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:
ban_level: low
notes:
- "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). At Mining 1 the rank tie-break selects the iron pickaxe (equal Mining gate, higher rank) and Attack 1 equips it."
- "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 iron 15+) and 6,900 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"
- "OSRS wiki — Mining (skill overview, members=No)"
- "OSRS wiki — Rune pickaxe (41 Mining to use, 40 Attack to equip) / Adamant pickaxe (31 Mining, 30 Attack) / Mithril pickaxe (21 Mining, 20 Attack) / Steel pickaxe (6 Mining, 5 Attack) — use gate vs equip gate per tier — verified 2026-08-28"
- "prices.runescape.wiki/api/v1/osrs/mapping (ore ids: iron 440, coal 453, silver 442, gold 444, mithril 447, adamantite 449, runite 451 — all members=false; buy limits)"
- "prices.runescape.wiki/api/v1/osrs/latest (ore sell prices, snapshot 2026-08-27)"
verified: "2026-08-28" # keep quoted (YAML 1.1 date parsing)
related:
- magic-alch-handoff
- f2p-alch-catalog
- mule-topup

View File

@ -0,0 +1,692 @@
id: woodcutting-f2p
title: Woodcutting (F2P)
domain: training
tags: [woodcutting, gathering, f2p, skill-training]
status: verified
version: 5 # 5: bounce-fix 4 2026-08-29 — governor 10398 + owner decision 10499: executable axe preparation (a2-a9 check/withdraw/GE-buy/wait/collect/equip-or-carry/loadout), unobtainable-axe -> failed (never complete), drop transition gated to afk-xp, per-SELECTED-row dynamic rates with 27/28 capacity from axe_equipped (woodcutting_fixtures recomputed by the validator), drop-mode realized GP zero
updated: "2026-08-29" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date
summary: >-
F2P woodcutting on the verified F2P tree ladder — Tree (1/25xp), Oak
(15/37.5xp), Willow (30/67.5xp), Maple (45/100xp, optional), Yew
(60/175xp) — with THREE explicit, mutually exclusive run modes (owner
decision 10288, 2026-08-28): `fast-xp` (XP-oriented progression, willow
as the intended fast-XP target), `afk-xp` (quest/location-gated
low-interaction progression, maple only when Dragon Slayer I access is
satisfied), and `banked-income` (banked logs sold at the GE, yew from 60
when eligible). Each mode has a deterministic first-match ladder (no
parenthesized "or"), a level-1 fallback, a pinned activity state
(mode + tree + spot), a complete start-to-finish lifecycle, and a worked
example covering preparation, the first full-inventory boundary, the
first scheduled break and the resume. Axe model (corrected per governor
10323 / owner 10288): the account uses the best F2P axe whose
WOODCUTTING level it meets (rune/gilded = 41); it equips it only when
its separate ATTACK level is met (rune/gilded = 40), otherwise it
carries that same axe (costing one inventory slot — 27 log slots vs 28
when equipped). RATES are realized, not idealized: every mode's
logs/hour equals its documented model chain (chop cadence x capacity /
cycle incl. walking/depositing or dropping, x respawn/switch duty x
break duty), and xp/hour and gp/hour derive from it — committed as
structured fixtures in tools/validate.py. Verified 2026-08-28 (wiki
axe/tree pages + prices.runescape.wiki latest).
accounts:
membership: f2p
min_level: 1 # Woodcutting 1 — normal trees from the start
requires_quest: [] # EMPTY for the base entry — Tree/Oak/Willow/Yew need no quest. Dragon Slayer I is NOT an entry-level prerequisite: it gates ONLY the afk-xp maple row (Corsair Cove Resource Area), expressed in woodcutting_modes and the a1 ladder — never in accounts.requires_quest.
requires_other:
- "An axe. The account USES the best F2P axe whose Woodcutting level it meets (bronze 1 / iron 1 / steel 6 / black 11 / mithril 21 / adamant 31 / rune 41 / gilded 41 — verified wiki table 2026-08-28); it EQUIPS it only when its separate Attack level is met (steel 5 / black 10 / mithril 20 / adamant 30 / rune 40 / gilded 40), otherwise it CARRIES that same axe and chops from the inventory (legal at every level). A carried axe costs ONE log slot (27 log slots), an equipped axe costs none (28 log slots); inventory-full logic derives the count dynamically, never hardcoded."
- "Combat 15+ to use the Draynor Village willow spot (two aggressive level-7 Dark wizards attack players under combat 15); accounts below that use the Edgeville willow instead (fast-xp and banked-income ladders both carry the combat split as two deterministic rows)"
- "For the afk-xp maple row only: Dragon Slayer I completed (Corsair Cove Resource Area entrance gate); without it the mode's ladder falls back to willow"
- "Bank within a short walk of the tier spot (Draynor, Edgeville, Lumbridge, or Corsair Cove bank); rune axe one-time cost ~7.1-7.3k gp (GE live 2026-08-28) if not already owned"
axe_tiers:
# Canonical table (OSRS wiki axe pages, raw wikitext verified 2026-08-28):
# woodcutting_level = tool-USE requirement; attack_level = EQUIP requirement.
# tools/validate.py enforces this table exactly and recomputes axe_examples.
- {axe: bronze, woodcutting_level: 1, attack_level: 1, members: false}
- {axe: iron, woodcutting_level: 1, attack_level: 1, members: false}
- {axe: steel, woodcutting_level: 6, attack_level: 5, members: false}
- {axe: black, woodcutting_level: 11, attack_level: 10, members: false}
- {axe: mithril, woodcutting_level: 21, attack_level: 20, members: false}
- {axe: adamant, woodcutting_level: 31, attack_level: 30, members: false}
- {axe: rune, woodcutting_level: 41, attack_level: 40, members: false}
- {axe: gilded, woodcutting_level: 41, attack_level: 40, members: false}
- {axe: dragon, woodcutting_level: 61, attack_level: 60, members: true}
axe_examples:
# Committed regression for the equip-vs-carry rule (owner coverage 10288:
# "a 1-Attack/41-Woodcutting account carries and uses a rune axe rather
# than selecting a lower axe"). tools/validate.py recomputes each row.
- {account: "1-Attack / 41-Woodcutting (owner coverage)", woodcutting_level: 41, attack_level: 1, expected_axe: rune, expected_equipped: false}
- {account: "40-Attack / 41-Woodcutting", woodcutting_level: 41, attack_level: 40, expected_axe: rune, expected_equipped: true}
- {account: "40-Attack / 30-Woodcutting (below adamant 31)", woodcutting_level: 30, attack_level: 40, expected_axe: mithril, expected_equipped: true}
- {account: "5-Attack / 31-Woodcutting (adamant usable, not equippable)", woodcutting_level: 31, attack_level: 5, expected_axe: adamant, expected_equipped: false}
- {account: "1-Attack / 1-Woodcutting", woodcutting_level: 1, attack_level: 1, expected_axe: iron, expected_equipped: true}
- {account: "99-Attack / 99-Woodcutting (F2P top; dragon is members-only)", woodcutting_level: 99, attack_level: 99, expected_axe: rune, expected_equipped: true}
woodcutting_modes:
# Deterministic first-match ladders, ordered min_level DESC. The selected
# mode AND tree are pinned in activity state by a1 (never decided ad hoc).
# MODEL SPLIT (governor 10398, bounce-fix 4): mode-level `model` holds the
# mode-wide constants (drop/deposit/break); each LADDER ROW carries its own
# chop_seconds_per_log / walk_seconds_each_way / respawn_duty / log_price
# so rates derive from the SELECTED row, not a fixed top tree. The mode
# `rates` block is the canonical TOP-ROW reference (ladder[0], axe
# equipped = 28 slots, labeled as such); dynamic per-selected-row rates
# with 27/28 capacity from axe_equipped are committed in
# `woodcutting_fixtures` and recomputed by the validator.
- mode: fast-xp
intent: >-
Maximum Woodcutting XP per hour on the F2P ladder. Willow is the
intended fast-XP target from level 30 (67.5 xp/log, close bank loop,
high chop cadence); oak covers 15-29 and normal trees below 15. The
ladder intentionally does NOT switch to yew at 60 — the F2P guide's
XP recommendation is willow (fast-xp stays on willow; yew is the
banked-income target).
inventory_policy: bank
ladder:
- {tree: willow, min_level: 30, gate: combat-15-plus, spot: draynor-willows, bank: draynor, chop_seconds_per_log: 4.0, walk_seconds_each_way: 12, respawn_duty: 0.92, log_price: 21}
- {tree: willow, min_level: 30, gate: combat-under-15, spot: edgeville-willow, bank: edgeville, chop_seconds_per_log: 4.0, walk_seconds_each_way: 6, respawn_duty: 0.90, log_price: 21}
- {tree: oak, min_level: 15, gate: none, spot: draynor-oaks, bank: draynor, chop_seconds_per_log: 3.5, walk_seconds_each_way: 8, respawn_duty: 0.93, log_price: 40}
- {tree: tree, min_level: 1, gate: none, spot: lumbridge-west, bank: lumbridge, chop_seconds_per_log: 3.0, walk_seconds_each_way: 10, respawn_duty: 0.95, log_price: 23}
model:
drop_seconds_per_log: 0 # bank mode — no dropping
deposit_seconds: 4
break_duty: 0.94 # scheduled breaks land at the bank
rates: # CANONICAL TOP-ROW reference ONLY:
logs_per_hour: 622 # willow row, axe EQUIPPED (28 slots): 28/(28x4.0+2x12+4=140s)x3600x0.92x0.94 = 622.7; carried-axe/other-row profiles -> woodcutting_fixtures
xp_per_hour: 41985 # 622 x 67.5 (willow xp/log, wiki infobox)
gp_per_hour: 13062 # 622 x 21 (willow log sell, prices.runescape.wiki latest 2026-08-28)
log_price: 21
price_verified: "2026-08-28"
fallback: "below level 30 -> oak (15+) then normal trees (1+); below combat 15 the Draynor willow row is replaced by the Edgeville willow row (same min_level, different gate)"
- mode: afk-xp
intent: >-
Low-interaction, quest/location-gated progression: maple at Corsair
Cove Resource Area (100 xp/log, 4 trees, Dragon Slayer I gate) with
dropping instead of banking — the fewest interactions per hour of the
three modes. Maple is selected ONLY when level 45+ AND the quest gate
is satisfied; otherwise the ladder falls back through willow/oak/tree.
DROP mode: logs are dropped at the spot, so REALIZED GP is 0; the log
sell value is carried as reference/opportunity cost only.
inventory_policy: drop
ladder:
- {tree: maple, min_level: 45, gate: dragon-slayer-i, spot: corsair-cove-resource-area, bank: none-drop, chop_seconds_per_log: 6.0, walk_seconds_each_way: 0, respawn_duty: 0.80, log_price: 12}
- {tree: willow, min_level: 30, gate: combat-15-plus, spot: draynor-willows, bank: none-drop, chop_seconds_per_log: 6.0, walk_seconds_each_way: 0, respawn_duty: 0.92, log_price: 21}
- {tree: willow, min_level: 30, gate: combat-under-15, spot: edgeville-willow, bank: none-drop, chop_seconds_per_log: 6.0, walk_seconds_each_way: 0, respawn_duty: 0.90, log_price: 21}
- {tree: oak, min_level: 15, gate: none, spot: draynor-oaks, bank: none-drop, chop_seconds_per_log: 5.5, walk_seconds_each_way: 0, respawn_duty: 0.93, log_price: 40}
- {tree: tree, min_level: 1, gate: none, spot: lumbridge-west, bank: none-drop, chop_seconds_per_log: 5.0, walk_seconds_each_way: 0, respawn_duty: 0.95, log_price: 23}
model:
drop_seconds_per_log: 1.0 # dropping the whole load at the spot; NO bank legs (far-from-bank spot, per the guide's AFK trade-off)
deposit_seconds: 0
break_duty: 0.94 # scheduled breaks at the spot (drop mode has no bank)
rates: # CANONICAL TOP-ROW reference ONLY:
logs_per_hour: 386 # maple row, axe EQUIPPED (28 slots): 28/(28x(6.0+1.0)=196s)x3600x0.80x0.94 = 386.8; carried-axe (27-slot) profile -> woodcutting_fixtures
xp_per_hour: 38600 # 386 x 100 (maple xp/log) — deliberately below fast-xp per the guide (maple is AFK, not top XP)
gp_per_hour: 0 # DROP MODE: logs are dropped — REALIZED GP IS 0 (validator-enforced); log_price 12 is reference/opportunity cost only, never realized income
log_price: 12
price_verified: "2026-08-28"
fallback: "below 45 or without Dragon Slayer I -> willow (30) then oak (15) then tree (1); never select maple without the quest gate"
- mode: banked-income
intent: >-
Banked logs sold at the GE: yew from level 60 (175 xp/log, 111 gp/log
live 2026-08-28, Edgeville south spot ~8s from the bank) when
eligible, with deterministic lower fallbacks (willow/oak/tree banked)
below 60. This is the income mode — the mode split implements the F2P
guide's "cut willow/maple for experience, yew for profit".
inventory_policy: bank
ladder:
- {tree: yew, min_level: 60, gate: none, spot: edgeville-south-yews, bank: edgeville, chop_seconds_per_log: 7.5, walk_seconds_each_way: 8, respawn_duty: 0.52, log_price: 111}
- {tree: willow, min_level: 30, gate: combat-15-plus, spot: draynor-willows, bank: draynor, chop_seconds_per_log: 4.0, walk_seconds_each_way: 12, respawn_duty: 0.92, log_price: 21}
- {tree: willow, min_level: 30, gate: combat-under-15, spot: edgeville-willow, bank: edgeville, chop_seconds_per_log: 4.0, walk_seconds_each_way: 6, respawn_duty: 0.90, log_price: 21}
- {tree: oak, min_level: 15, gate: none, spot: draynor-oaks, bank: draynor, chop_seconds_per_log: 3.5, walk_seconds_each_way: 8, respawn_duty: 0.93, log_price: 40}
- {tree: tree, min_level: 1, gate: none, spot: lumbridge-west, bank: lumbridge, chop_seconds_per_log: 3.0, walk_seconds_each_way: 10, respawn_duty: 0.95, log_price: 23}
model:
drop_seconds_per_log: 0
deposit_seconds: 4
break_duty: 0.94 # scheduled breaks at the bank
rates: # CANONICAL TOP-ROW reference ONLY:
logs_per_hour: 214 # yew row, axe EQUIPPED (28 slots): 28/(28x7.5+2x8+4=230s)x3600x0.52x0.94 = 214.2; carried-axe/other-row profiles -> woodcutting_fixtures
xp_per_hour: 37450 # 214 x 175 (yew xp/log)
gp_per_hour: 23754 # 214 x 111 (yew log sell, prices.runescape.wiki latest 2026-08-28)
log_price: 111
price_verified: "2026-08-28"
fallback: "below 60 -> willow (30) then oak (15) then tree (1), all banked; yew never selected below 60"
# Dynamic per-selected-row rate proof (governor 10398, bounce-fix 4): the
# mode `rates` blocks above are the canonical TOP-ROW references only;
# these fixtures prove the entry derives logs/xp/gp from the SELECTED
# ladder row AND the actual 27/28-slot capacity from axe_equipped. Each
# row is recomputed by the validator (select_ladder_row + best_axe +
# capacity = 28 - (1 if carried else 0)) and must match within tolerance.
woodcutting_fixtures:
- account: "level-1 fast-xp"
mode: fast-xp
woodcutting_level: 1
attack_level: 1
quest_gate: false
combat_level: 3
expected_tree: tree
expected_axe: iron
expected_equipped: true # 1 WC / 1 Attack: iron axe (tie rank) equipped
expected_logs_per_hour: 833
expected_xp_per_hour: 20836
expected_gp_per_hour: 19170
- account: "level-50 fast-xp"
mode: fast-xp
woodcutting_level: 50
attack_level: 40
quest_gate: false
combat_level: 50
expected_tree: willow
expected_axe: rune
expected_equipped: true # 28 log slots — matches the canonical top-row reference
expected_logs_per_hour: 622
expected_xp_per_hour: 41985
expected_gp_per_hour: 13062
- account: "level-50 afk-xp with Dragon Slayer I (maple)"
mode: afk-xp
woodcutting_level: 50
attack_level: 1
quest_gate: true
combat_level: 50
expected_tree: maple
expected_axe: rune
expected_equipped: false # owner coverage: rune axe CARRIED -> 27 log slots
expected_logs_per_hour: 387
expected_xp_per_hour: 38674
expected_gp_per_hour: 0 # drop mode — realized GP is 0
- account: "level-50 afk-xp WITHOUT Dragon Slayer I (willow fallback)"
mode: afk-xp
woodcutting_level: 50
attack_level: 1
quest_gate: false
combat_level: 50
expected_tree: willow
expected_axe: rune
expected_equipped: false # carried rune axe -> 27 log slots
expected_logs_per_hour: 445 # willow drop row, rune CARRIED (27 slots): 27/(27x(6.0+1.0)=189s)x3600x0.92x0.94 = 444.8
expected_xp_per_hour: 30021 # 444.8 x 67.5 (willow xp/log)
expected_gp_per_hour: 0 # drop mode — realized GP is 0
- account: "level-70 banked-income (yew)"
mode: banked-income
woodcutting_level: 70
attack_level: 40
quest_gate: false
combat_level: 60
expected_tree: yew
expected_axe: rune
expected_equipped: true # 28 log slots — matches the canonical top-row reference
expected_logs_per_hour: 214
expected_xp_per_hour: 37450
expected_gp_per_hour: 23754
- account: "level-30 fast-xp under combat 15 (Edgeville willow)"
mode: fast-xp
woodcutting_level: 30
attack_level: 1
quest_gate: false
combat_level: 3
expected_tree: willow
expected_axe: mithril
expected_equipped: false # 30 WC selects mithril (21); Attack 1 < 20 -> CARRY -> 27 log slots
expected_logs_per_hour: 663
expected_xp_per_hour: 44766
expected_gp_per_hour: 13927
best_practice:
equipment:
- item: "rune axe (best F2P axe: 41 Woodcutting to USE, 40 Attack to EQUIP)"
why: "fastest F2P chop rate; per the wiki axe table Rune axe (41) and Gilded axe (41) are 'the best axes available in Free-to-play' (dragon is members-only). F2P ladder: Bronze(1)/Iron(1)/Steel(6)/Black(11)/Mithril(21)/Adamant(31)/Rune(41)/Gilded(41) — the number is the WOODCUTTING level to use the axe for chopping, NOT an Attack gate. EQUIP is a separate Attack requirement (rune/gilded 40): equip only when the account meets it, otherwise carry that same axe and chop from the inventory — legal at every level, costs one log slot. Never select a lower axe because the Attack level is low."
source: "OSRS wiki — Woodcutting (axe table incl. F2P/members flags) + Rune axe / Gilded axe / Black axe item pages raw wikitext 2026-08-28 ('Level 41 Woodcutting is required to use this axe' / 'Attack level of 40 to wield this axe'; gilded identical; black: 11 to use, 10 to equip; dragon: 61/60 members); governor review comment 10248 + owner decision 10288 (carry-and-use rule)"
techniques:
- name: pick the mode first, then the deterministic ladder
why: "Owner decision 10288: run exactly ONE of fast-xp / afk-xp / banked-income; the ladder inside the mode decides the tree by level + quest/combat gates (first match, min_level DESC — no parenthesized 'or'), and the chosen mode + tree are pinned in activity state. fast-xp never silently becomes income; afk-xp never runs maple without the quest gate; banked-income never claims yew below 60."
source: "owner-guidance comment 10288 (2026-08-28); governor 10248 (deterministic tier field)"
- name: per-tier route table (spots and banks, wiki 2026-08-27/28)
why: "Tree (1) — west of Lumbridge Castle, bank Lumbridge; Oak (15) — two east of Draynor Village bank (alt: south-west of Varrock west bank), bank Draynor; Willow (30) — south-west of Draynor Village bank GATED to combat 15+ (two aggressive level-7 Dark wizards attack under combat 15), sub-15 accounts use the single willow East of Edgeville, bank Edgeville; Maple (45) — Corsair Cove Resource Area (4 trees, only F2P maples, requires Dragon Slayer I), far from a bank; Yew (60) — two south of Edgeville bank, bank Edgeville."
source: "OSRS wiki — Free-to-play Woodcutting training + Draynor Village + tree location lists (2026-08-27, re-verified 2026-08-28)"
- name: drop versus bank is a MODE property, not an ad-hoc choice
why: "fast-xp banks (Draynor loop keeps the bank close and income attached); afk-xp DROPS (Corsair Cove is far from a bank — the guide's AFK trade-off; no bank legs at all); banked-income banks and sells (yew 111 gp/log live). Each mode's rate chain includes its own dropping or banking route (model fields + validator chain)."
source: "owner-guidance comment 10288 (complete dropping or banking route per mode); OSRS wiki — Free-to-play Woodcutting training (drop-vs-bank trade-off)"
- name: bank logs, keep the axe
why: "Logs are F2P-tradeable income: live sell prices 2026-08-28 — willow 21, oak 40, maple 12, yew 111 per log (GE limits 15k/15k/15k/12k per 4h — none binding at modeled rates). deposit_logs deposits logs ONLY — the axe is never deposited (equipped or carried)."
source: "prices.runescape.wiki latest 2026-08-28"
- name: rates are realized, not idealized
why: "Every mode's rates come from its documented model chain: capacity / cycle (chop seconds x capacity + walking + depositing, or + dropping) x 3600 x respawn/switch duty x break duty — including travel, tree respawn, dropping/banking and mandatory breaks. The validator recomputes the chain within 1% and derives xp/gp from it; no hidden 'chop loop only' numbers."
source: "owner-guidance comment 10288 (validators and rate claims must include travel, respawn, dropping or banking, and mandatory breaks)"
economy:
# Verified 2026-08-28 against prices.runescape.wiki latest + OSRS wiki
# tree-page infoboxes. Logs are pure income (no per-cycle cost; axe is
# one-time equipment). Anchor = fast-xp mode (Draynor willows, combat 15+).
item: Willow logs
item_id: 1519
cost_per_cycle: 0 # no supplies consumed; axe cost amortised
income_per_cycle: 21 # willow log sell price (latest.high 2026-08-28)
net_per_cycle: 21
gp_per_hour_low: 13100 # fast-xp realized banked income: 622 logs/hr x 21 (model chain, 2026-08-28)
gp_per_hour_high: 23800 # banked-income realized: 214 yew logs/hr x 111 (model chain, 2026-08-28)
xp_per_hour: 42000 # fast-xp realized: 41,985 (622 willow logs/hr x 67.5; see woodcutting_modes)
supplies: []
profit_drivers:
- "Logs are the income: sell at the GE (willow 21, oak 40, maple 12, yew 111 per log — live 2026-08-28; all F2P-tradeable with 12k-15k/4h buy limits, none binding at modeled rates)"
- "THREE explicit modes (owner decision 10288), each with a deterministic ladder and realized rates — fast-xp 622 logs/hr -> 41,985 xp/hr (willow, banked); afk-xp 386 logs/hr -> 38,600 xp/hr (maple @45 + Dragon Slayer I, dropped); banked-income 214 logs/hr -> 37,450 xp/hr and 23,754 gp/hr (yew @60, banked)"
- "Mode split implements the F2P guide: willow/maple for experience, yew for profit — fast-xp stays on willow past 60; banked-income takes yew at 60+"
- "Maple (45, Dragon Slayer I, Corsair Cove Resource Area) is an optional AFK F2P tier: 100 xp/log, 4 trees, far from a bank — more AFK, lower xp/hr than willows (modeled accordingly)"
- "Axe economics: best F2P axe by Woodcutting level (rune from 41); one-time GE cost ~7.1-7.3k gp (2026-08-28); equip only when Attack permits, else carry (one log slot)"
state_machine:
initial: idle
terminal: [complete, failed]
states:
idle:
goal: "logged in at a bank with an axe available (bank or inventory); choose run mode (explicit run parameter) and confirm prerequisites"
next: [arm_run, complete]
arm_run:
goal: "PIN the run in activity state: mode (fast-xp | afk-xp | banked-income), tree, spot, bank, axe, axe_equipped (true/false) — decided once by the a1 ladder, never changed mid-run; re-arm only on fallback events"
next: [prepare, idle]
prepare:
goal: >-
EXECUTABLE axe preparation (governor 10398, bounce-fix 4), run
BEFORE walking to the trees: (1) check inventory and bank for the
pinned best axe (a2); (2) owned -> withdraw it (a3); missing ->
GE purchase chain a4 open-ge -> a5 buy-axe (ONE offer) -> a6
wait-ge-fill (BOUNDED fill window; on no-fill/partial-fill TIMEOUT
cancel the resting offer and collect the item or refund) -> a7
collect-axe (ge_collect + has_item verification); (3) equip it
ONLY when level_is Attack >= the tier's equip level (a8, menu_option
Wield) or carry the same axe; (4) build the initial inventory (a9:
axe + empty log slots — nothing else is needed). An axe that cannot
be obtained after the bounded recovery (3 acquisition loops) is a
FAILED run: log ERROR + alert, never a silent lower-axe continue
and never a 'complete'.
next: [walk_to_trees, prepare, failed]
walk_to_trees:
goal: "walk the pinned tier route from the bank to the pinned tree spot"
next: [chopping, idle]
chopping:
goal: "chop the pinned tree until the inventory is full or the tree falls; one chop attempt = click + observe (log gained / tree fell / full / fail) — see a11"
next: [drop_logs, walk_to_bank, walk_to_trees, complete, failed]
drop_logs:
goal: "AFK-XP DROP MODE ONLY: drop the whole load at the spot (a13, menu_option per log group); inventory now empty; no bank legs"
next: [chopping, walk_to_trees]
walk_to_bank:
goal: "walk the pinned route from the tree spot to the pinned bank (bank modes only: fast-xp | banked-income)"
next: [deposit_logs]
deposit_logs:
goal: "deposit ALL logs (a15 — logs only; the axe stays equipped or carried, never deposited); short bank trip lands the scheduled break"
next: [walk_back]
walk_back:
goal: "walk back from the bank to the pinned tree spot"
next: [chopping, idle]
complete:
goal: "XP/level target or GP target reached — collect and stop"
failed:
goal: "hard failure — stop, log ERROR sentinel, alert"
transitions:
- from: idle
when: "mode + prerequisites confirmed and session target not reached"
to: arm_run
- from: idle
when: "session target already reached at session start"
to: complete
- from: arm_run
when: "ladder picked a row (mode, tree, spot, bank pinned in activity state) — axe decided: highest usable by Woodcutting level, equip only when Attack permits else carry"
to: prepare
- from: arm_run
when: "maple row selected but Dragon Slayer I access denied at the gate — re-arm on the next ladder row (fallback) and log the denial"
to: idle
- from: prepare
when: "axe acquired (owned withdraw or GE open/buy/wait/collect verified) AND equipped-or-carried (a8) AND loadout built (a9)"
to: walk_to_trees
- from: prepare
when: "GE buy offer still filling (a6 bounded window polling) or a partial fill re-placed — loop the acquisition chain"
to: prepare
- from: prepare
when: "3 consecutive no-fill GE rounds or the bank also lacks any usable F2P axe — axe unobtainable: log ERROR + alert"
to: failed
- from: walk_to_trees
when: "at the pinned tree spot"
to: chopping
- from: chopping
when: "mode is afk-xp (inventory_policy drop) AND inventory full (28 log slots with the axe equipped, 27 with the axe carried — derived dynamically, never hardcoded)"
to: drop_logs
- from: chopping
when: "mode is fast-xp OR banked-income (inventory_policy bank) AND inventory full — the two full-inventory routes are mutually exclusive by mode"
to: walk_to_bank
- from: chopping
when: "tree fell and no interactable tree at the spot (> 30s empty) — move to the next tree or hop_world (a17)"
to: walk_to_trees
- from: drop_logs
when: "load dropped — inventory empty"
to: chopping
- from: walk_to_bank
when: "at the pinned bank"
to: deposit_logs
- from: deposit_logs
when: "logs deposited (axe still present)"
to: walk_back
- from: walk_back
when: "at the pinned tree spot"
to: chopping
- from: walk_back
when: "spot unavailable on return (crowded > 60s or invalid access) — re-arm on the next ladder row (fallback)"
to: idle
- from: idle
when: "woodcutting level >= target (fast-xp / afk-xp) or GP target banked (banked-income)"
to: complete
actions:
- id: a1
step: arm-run
verb: level_is
target: "woodcutting level (mode ladder)"
params:
mode: "fast-xp | afk-xp | banked-income — explicit run parameter, pinned with the chosen tree/spot/bank in activity state (owner 10288)"
ladder: "deterministic first match, min_level DESC (see woodcutting_modes): fast-xp -> willow@30 (combat-15-plus: draynor / combat-under-15: edgeville) -> oak@15 -> tree@1; afk-xp -> maple@45 (dragon-slayer-i gate) -> willow@30 -> oak@15 -> tree@1; banked-income -> yew@60 -> willow@30 -> oak@15 -> tree@1"
spot: "per the chosen row (route table in a10)"
pin: "{mode, tree, spot, bank, axe, axe_equipped} written to activity state — never re-decided mid-run except by explicit fallback events"
on_fail: retry
anti_ban:
delay: [200, 900]
- id: a2
step: check-axe
verb: has_item
target: "the pinned best axe tier (by Woodcutting level — see axe_tiers)"
params:
check: "inventory AND bank (bank_open / item_count); branch: owned -> a3 withdraw-axe; missing -> the GE purchase chain a4 -> a5 -> a6 -> a7"
decide: "never a lower axe because the Attack level is low — the WOODCUTTING level selects the tier, Attack only decides equip-or-carry (axe_examples)"
on_fail: retry
anti_ban:
delay: [300, 1000]
- id: a3
step: withdraw-axe
verb: bank_inventory
target: bank
params:
withdraw: "1x the pinned best axe tier (owned-tool branch — the axe comes from the bank, never bought twice)"
verify: "has_item axe == true after withdrawal"
on_fail: retry
anti_ban:
delay: [400, 1200]
- id: a4
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 (a2 missing-tool); owned axes go straight to a3 withdraw-axe"
on_fail: retry
anti_ban:
delay: [600, 1600]
- id: a5
step: buy-axe
verb: ge_buy
target: grand-exchange
params:
item: "the pinned best axe tier (Woodcutting-gated)"
qty: 1
max_price: "live high + 10% cap (rune axe ~7.1-7.3k gp live 2026-08-28; GE limit 40/4h — one axe is trivial)"
note: "place exactly ONE buy offer; the fill waits at most the BOUNDED a6 window — a resting offer is never retried indefinitely"
pair_with: "a4 open-ge (before) then a6 wait-ge-fill (bounded) then a7 collect-axe (collect + verify)"
on_fail: retry
anti_ban:
delay: [1200, 3000]
- id: a6
step: wait-ge-fill
verb: ge_offers_done
target: "the axe purchase offer"
params:
until: "the axe offer FILLS (has_item axe) — 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 the gp refund (a7), then verify — fail CLOSED via prepare recovery (3 loops -> failed); never re-place the same resting offer"
on_fail: alert
anti_ban:
delay: [900, 2400]
- id: a7
step: collect-axe
verb: ge_collect
target: grand-exchange
params:
collect: "finished GE transactions — the bought axe if the offer filled, and the gp refund if it was cancelled on timeout; collection closes the offer slot"
verify: "has_item axe == true after collection; if still missing (no-fill / cancelled), loop the acquisition chain via prepare (a2 path, 3 loops -> failed) — a failed purchase is diagnosed, never silently re-queued"
on_fail: retry
anti_ban:
delay: [600, 1400]
- id: a8
step: equip-or-carry-axe
verb: menu_option
target: "best axe in inventory (decided by Woodcutting level)"
params:
rule: "equip (menu_option 'Wield <axe>') ONLY when Attack >= the axe's equip level (rune/gilded 40, adamant 30, mithril 20, black 10, steel 5); otherwise carry it and chop from the inventory — the same axe, never a lower one because Attack is low (owner coverage: 1-Attack/41-Woodcutting carries and uses rune)"
action: "menu_option axe -> 'Wield <axe>'"
capacity: "equipped: 28 log slots; carried: 27 (one slot for the axe)"
on_fail: retry
anti_ban:
delay: [300, 1100]
- id: a9
step: build-loadout
verb: has_item
target: "the pinned axe tier in inventory (equipped or carried)"
params:
verify: "inventory holds exactly ONE axe of the pinned tier and the rest is empty log space — the initial inventory needs nothing else (no supplies); the axe is never deposited mid-run"
on_fail: retry
anti_ban:
delay: [300, 900]
- id: a10
step: walk-to-trees
verb: walk_to
target: "pinned tier tree spot"
params:
route: "tree -> west of Lumbridge Castle (bank lumbridge); oak -> two east of Draynor Village bank (bank draynor); willow -> south-west of Draynor Village bank, combat 15+ only (bank draynor) OR the single willow East of Edgeville, combat under 15 (bank edgeville); maple -> Corsair Cove Resource Area, Dragon Slayer I gate (no bank — drop mode); yew -> two south of Edgeville bank (bank edgeville)"
method: [walk, teleport]
on_fail: retry
anti_ban:
delay: [400, 1600]
- id: a11
step: chop-tree
verb: click_object
target: "tree of the pinned tier"
params:
object: "tree/oak/willow/maple/yew per the pinned ladder row"
attempt: "one chop = click + observe: (a) log gained -> continue; (b) tree fell -> switch to the next interactable tree at the spot; (c) inventory full -> route to drop (afk-xp) or bank (fast-xp / banked-income) by the mode-gated transitions; (d) no interactable tree for > 30s -> walk_to_trees move / hop_world (a17); (e) click failed -> retry up to 3, then alert"
rhythm: "humanised cadence; the SELECTED ladder row's chop_seconds_per_log (woodcutting_modes) — never a fixed top-tree cadence"
on_fail: retry
anti_ban:
delay: [900, 2400]
- id: a12
step: check-inventory
verb: item_count
target: "logs in inventory"
params:
item: "logs of the pinned tier"
op: "inventory full -> drop (a13, afk-xp ONLY) or walk_to_bank (a14, fast-xp / banked-income ONLY); available log slots = 28 with the axe equipped, 27 with the axe carried — never hardcode 28"
on_fail: skip
anti_ban:
delay: [150, 700]
- id: a13
step: drop-load
verb: menu_option
target: "logs (afk-xp drop mode only)"
params:
action: "drop all logs of the pinned tier at the spot (grouped menu_option drops)"
note: "no bank legs in drop mode — the rate chain uses the drop_seconds_per_log model constant; realized gp from dropped logs is 0 (log value is reference/opportunity cost only)"
on_fail: retry
anti_ban:
delay: [200, 800]
- id: a14
step: walk-to-bank
verb: walk_to
target: "pinned bank from the tree spot"
params:
bank: "lumbridge (tree) | draynor (oak / combat-15+ willow) | edgeville (combat-under-15 willow / yew)"
method: [walk]
on_fail: retry
anti_ban:
delay: [400, 1600]
- id: a15
step: deposit-logs
verb: bank_inventory
target: bank
params:
deposit: [all logs] # logs ONLY — the axe stays (equipped or carried); never deposit the axe
withdraw: []
break: "scheduled breaks land here (bank modes) — see a18"
on_fail: alert
- id: a16
step: walk-back-to-trees
verb: walk_to
target: "pinned tree spot from the bank"
params:
route: "reverse of a14 — back to the same spot"
spot_check: "if the spot is unavailable on return (crowded > 60s -> hop_world; invalid access -> re-arm on the next ladder row)"
method: [walk]
on_fail: retry
anti_ban:
delay: [400, 1600]
- id: a17
step: move-or-hop-empty
verb: hop_world
target: "current spot"
params:
when: "no interactable tree for > 30s at the pinned spot, or the spot is crowded for > 60s (shared spots: Draynor willows, Edgeville yews)"
then: "move to the next tree at the same spot (walk_to), then hop_world if the spot is still empty for > 60s"
on_fail: retry
anti_ban:
delay: [800, 2200]
- id: a18
step: take-break
verb: take_break
target: "current location"
params:
duration: "[45, 150] seconds of idle AFK per break (run_control.break.for)"
safe: "bank modes: break lands at the bank after deposit_logs (inventory empty, axe safe); drop mode: break at the spot with an empty inventory — logs dropped, axe in hand; NEVER mid-chop (axe in tree) or mid-walk"
resume: "continue from the pinned activity state (mode/tree/spot unchanged): bank modes -> walk_back (a16) -> chopping; drop mode -> chopping"
on_fail: skip
run_control:
iteration: "one full inventory cycle: chop load -> (drop at the spot) or (bank + walk back) -> resume chopping the pinned tree; cycle length = 28 x chop_seconds_per_log + drop or bank legs per woodcutting_modes.model"
max_iterations: null
break:
every: [8, 14] # inventory cycles (breaks land at the bank in bank modes, at the spot in drop mode — INDEPENDENT of incidental banking)
for: [45, 150] # seconds of idle AFK
stop_conditions:
- "woodcutting level >= target (fast-xp / afk-xp) or GP target banked (banked-income)"
- "axe lost and unobtainable at bank + GE (recovery exhausted)"
- "3 consecutive empty-tree waits after hop_world (world dead or spot gone — stop and report)"
- "3 consecutive failed clicks (client issue — do not loop)"
# ================= WORKED EXAMPLES (owner 10288: each mode = preparation -> first
# full-inventory boundary -> first scheduled break -> resume) =================
# Rates below are the model-chain values from woodcutting_modes
# (validator-pinned; the SELECTED ladder row's chop/walk/respawn + the
# 27/28-slot capacity from axe_equipped — see woodcutting_fixtures).
#
# --- fast-xp @ level 50 (pinned per governor 10248: level-50 example) ---
# Account: 50 Woodcutting, 40+ Attack, combat 15+. Mode fast-xp, ladder
# first match = willow@30 combat-15-plus -> Draynor willows, bank Draynor.
# a1 pins {mode fast-xp, tree willow, spot draynor-willows, bank draynor,
# axe rune, axe_equipped true}. Preparation (prepare state): a2 check-axe
# -> owned -> a3 withdraw-axe; a8 rune axe equip gate — 40 Attack >= 40 ->
# EQUIPPED (28 log slots); a9 loadout verified.
# a11 chop: 28 logs at ~4s each (112s) -> a12 inventory full -> a14 walk to
# Draynor bank (~12s) -> a15 deposit (4s) -> a16 walk back (~12s).
# FIRST FULL-INVENTORY BOUNDARY crossed at 140s: 28 willow logs banked.
# Cycle x respawn 0.92 x break 0.94 = 622 logs/hr model; xp 41,985/hr.
# Every 8-14 cycles (mid ~11 = ~25.7 min), a18 take-break 45-150s LANDS AT
# THE BANK (post-deposit, inventory empty, axe safe) — a scheduled break,
# independent of the incidental per-load banking. Resume: a16 walk back,
# chopping continues from the pinned state.
# Session target 250k xp: ~6h of cycles + 6-7 breaks -> complete.
#
# --- afk-xp @ level 50 with Dragon Slayer I (pinned level-50 example) ---
# Account: 50 Woodcutting, 1 Attack, Dragon Slayer I done. Mode afk-xp,
# ladder first match = maple@45 dragon-slayer-i -> Corsair Cove Resource
# Area. a1 pins {mode afk-xp, tree maple, spot corsair-cove-resource-area,
# bank none-drop, axe rune, axe_equipped FALSE}. a2/a3: rune axe owned ->
# withdrawn; a8: rune axe usable at 41 WC -> selected; 1 Attack < 40 ->
# CARRIED (27 log slots) — the owner coverage case: rune axe used from the
# inventory, never a lower axe.
# a11 chop until 27 logs (~162s at 6s each incl. post-fall reaction); tree
# falls ~60s so the spot's 4 trees rotate; a12 full -> a13 DROP the load at
# the spot (~27s) — no bank legs at all. FIRST FULL-INVENTORY BOUNDARY at
# ~189s (dropped, not banked). Cycle x respawn 0.80 x break 0.94 = 387
# logs/hr model; xp ~38,670/hr; REALIZED GP 0 (drop mode — log value 12
# gp/log is reference/opportunity cost only, never realized income);
# ~387 clicks/hr — the lowest-interaction mode. a18 break 45-150s AT THE
# SPOT (inventory empty, axe in hand) every 8-14 cycles, INDEPENDENT of the
# per-load dropping. Resume: a11 continues.
# WITHOUT Dragon Slayer I the same account falls back to willow@30
# (combat-15-plus) — deterministic, logged, never a silent maple
# (woodcutting_fixtures level-50 no-quest row).
#
# --- banked-income @ level 70 (pinned level-70 example) ---
# Account: 70 Woodcutting, 40+ Attack, combat 15+. Mode banked-income,
# ladder first match = yew@60 -> Edgeville south yews, bank Edgeville.
# a1 pins {mode banked-income, tree yew, spot edgeville-south-yews, bank
# edgeville, axe rune, axe_equipped true}; a2/a3 withdraw owned rune axe;
# a8 equipped (40 Attack). a11 chop: 28 yew logs at ~7.5s each (210s; yew
# falls ~114s, respawns ~59.4s — the 2-tree spot rotates, occasionally
# both down -> respawn_duty 0.52). a12 full -> a14 walk to Edgeville bank
# (~8s) -> a15 deposit (4s) -> a16 walk back (~8s). FIRST FULL-INVENTORY
# BOUNDARY at 230s: 28 yew logs banked. Cycle x respawn 0.52 x break 0.94
# = 214 logs/hr model; xp 37,450/hr; gp 23,754/hr (111 gp/log). a18 break
# 45-150s AT THE BANK every 8-14 cycles (mid ~11 = ~47 min). Session
# target: 1M gp banked = ~42h of cycles + breaks.
# Below 60 the same mode banks willow@30 (fallback) — yew never below 60.
#
# All three examples: preparation (a1-a9: arm-run -> check-axe ->
# withdraw-or-GE-buy -> collect/verify -> equip-or-carry -> build-loadout)
# -> first full-inventory boundary -> first scheduled break -> correct
# resume from the pinned activity state; tree depletion -> move/hop (a17);
# access denial / missing axe -> explicit recovery (a2-a7 bounded GE loop,
# a16 spot_check, re-arm fallback — an unobtainable axe FAILS the run with
# ERROR + alert, it never completes).
# ======================================================================
risk:
ban_level: low
notes:
- "Woodcutting is one of the most botted skills; keep chop cadence humanised and bank rhythm varied (anti_ban delays + per-mode chop_seconds_per_log)."
- "Draynor willow spot is HOSTILE for accounts under combat 15: two aggressive level-7 Dark wizards attack players below that threshold — the ladder routes sub-15 accounts to the Edgeville willow via a separate deterministic row (combat-under-15). The no-combat claim applies ONLY to the gated routes; treat Draynor as a combat zone for low-level accounts."
- "Shared tree spots (Draynor willows, Edgeville yews): crowded > 60s -> hop_world (a11); do not camp a single world for hours."
- "Maple route (Corsair Cove Resource Area): quest-gated (Dragon Slayer I) and far from a bank — afk-xp only, drop mode; never select it without the quest gate (ladder + state machine enforce)."
- "The axe always stays with the account: deposit_logs deposits logs ONLY; an axe is never deposited, alched, or traded away mid-run. Losing the axe is a stop-and-report event, not a silent downgrade."
- "Mode discipline is the anti-detection edge: one pinned mode per run, deterministic ladder, explicit fallbacks — no mid-run mode flips."
sources:
- "OSRS wiki — Free-to-play Woodcutting training (full tier ladder, 2026-08-27 re-verified 2026-08-28): maple = Corsair Cove Resource Area ONLY for F2P, requires Dragon Slayer I; yew 'earn a profit' but 'recommended to cut willow or maple trees for experience'; 'best place to chop yew trees is south of Edgeville bank'; willow spots 'south-west of the bank in Draynor Village, but beware of aggressive dark wizards' + 'one tree East of Edgeville that is very close to a bank'; oak spots incl. south-west of Varrock west bank; drop-vs-bank trade-off"
- "OSRS wiki — Draynor Village (two aggressive level-7 Dark Wizards near the willows 'will attack any players under combat level 15')"
- "OSRS wiki — Woodcutting (axe table: F2P axes Bronze 1, Iron 1, Steel 6, Black 11, Mithril 21, Adamant 31, Rune 41, Gilded 41; 'best axes available in Free-to-play'; Dragon 61+ members)"
- "OSRS wiki axe pages raw wikitext, verified 2026-08-28: Rune axe 'Level 41 Woodcutting is required to use this axe' + 'Attack level of 40 to wield this axe'; Gilded axe 'Attack level of 40 to wield ... Woodcutting level of 41 to use'; Black axe 'requires level 11 Woodcutting. Level 10 Attack is required to equip'; Dragon axe 'requiring 60 Attack to wield' and '61 Woodcutting to use' (members) — the Woodcutting number is the USE requirement, Attack is the EQUIP requirement"
- "OSRS wiki — Tree (members=No, level 1, 25 xp), Oak tree (15, 37.5 xp), Willow tree (30, 67.5 xp; falls 50 ticks/30s), Maple tree (45, 100 xp; members1=No Corsair Cove Resource Area — only F2P maples; falls 100 ticks/60s), Yew tree (60, 175 xp; falls 190 ticks/114s, respawn 99 ticks/59.4s) — infoboxes 2026-08-27/28"
- "OSRS wiki — Corsair Cove Resource Area ('requires completion of Dragon Slayer I to enter'; 4 maple trees + 3 yew trees; only F2P maple location)"
- "OSRS wiki — Pine tree (scenery in Shayzien/Varlamore, CANNOT be chopped — disproves any '45 Pine' tier)"
- "prices.runescape.wiki mapping + latest 2026-08-28 (live buy/sell + 4h limits: logs 1511 22/23 limit 15000; oak 1521 39/40 limit 15000; willow 1519 20/21 limit 15000; maple 1517 11/12 limit 15000; yew 1515 107/111 limit 12000; rune axe 1359 7140/7293 limit 40; black 1001/1009; adamant 573/600; mithril 350/350)"
- "Governor review comments on PR #12: 10178 (bounce 1 — maple F2P ladder, deterministic tier selection), 10216 (bounce 2 — DS I scoping to the maple tier), 10248 (bounce 3 — axe USE-vs-EQUIP conflation, nondeterministic tier 'or'), 10263 (governor guidance request — mode options A/B/C), 10398 (bounce 4 — executable axe preparation chain, missing-tools -> failed, drop transition gated to afk-xp, per-selected-row dynamic 27/28-slot rates with fixtures, drop-mode realized GP zero)"
- "Owner decision comment 10288 (2026-08-28): three modes fast-xp / afk-xp / banked-income; deterministic lower fallbacks + pinned activity state; level-50 and level-70 worked examples; full per-mode lifecycle (prereqs, axe acquisition, initial inventory, travel, depletion/respawn, one chop attempt, inventory-full, complete dropping or banking route, inventory reconstruction, breaks independent of incidental banking, recovery from unavailable trees / invalid access / missing tools / path failure, completion); realized rates incl. travel/respawn/dropping-or-banking/breaks; axe model 41 WC to use / 40 Attack to equip with the 1-Attack/41-WC rune-axe carry coverage; repair the SAME issue-3 branch, no replacement PR"
- "Owner decision comment 10499 (2026-08-29): authorize one more repair iteration implementing governor 10398 exactly — executable prepare path (check -> withdraw or GE open/buy/bounded-wait/cancel-refund/collect -> verify -> equip-or-carry -> build inventory -> travel), unobtainable axe -> failed with error/alert (never complete), inventory-full drop transition exclusive to the drop mode, per-selected-profile 27/28-capacity rates with level-1 / level-50 quest+no-quest / level-70 fixtures, realized GP zero for drop modes with log value as reference-only; repair this PR, no replacement"
verified: "2026-08-28" # keep quoted (YAML 1.1 date parsing); prices + axe pages re-verified live 2026-08-28
related:
- low-alch-f2p
- f2p-alch-catalog
- flip-candidates

View File

@ -68,8 +68,9 @@ Legend for the mapping table:
| `ge_open` | open GE at a bank | — | — | — |
| `ge_buy` | buy `qty` of `item` at offer price, paced | — | — | — |
| `ge_sell` | sell `qty` of `item`, confirm | — | — | — |
| `ge_cancel` | abort an offer (quote_age_policy only — stale edge or strategy reallocation, never a 4h limit-window reset): BUY — filled items + refunded CASH (unspent remainder) enter the GE COLLECTION BOX; SELL — unsold units + proceeds received so far enter the box; NEVER directly to inventory; the 4h buy limit is NOT renewed | — | — | — |
| `ge_offers_done` | predicate: no pending buy/sell offers | — | — | — |
| `ge_collect` | collect finished GE transactions | — | — | — |
| `ge_collect` | withdraw from the GE collection box: bought (filled) items, refunded CASH, unsold units, sale proceeds; reconcile the SIDE-AWARE per-slot ledger (buy: quoted = filled + still_open + cancelled_unfilled; sell: quoted = sold + still_open + returned_unsold) before any slot reuse; a completed cycle closes with the ledger reconciled + ARCHIVED and the same pass releases the physical slot to free (`closed -> free` archival transition), so the slot immediately runs the next flip (governor 10471) | — | — | — |
| `ge_check_price` | read current buy/sell price of an item | — | — | — |
## F. Combat

View File

@ -39,10 +39,13 @@
"item": { "type": "string" },
"item_id": { "type": "integer" },
"cost_per_cycle": { "type": "number" },
"cost_per_cycle_max_fill": { "type": "number" },
"income_per_cycle": { "type": "number" },
"net_per_cycle": { "type": "number" },
"net_per_cycle_max_fill": { "type": "number" },
"gp_per_hour_low": { "type": "number" },
"gp_per_hour_high": { "type": "number" },
"ceiling_gp_per_hour": { "type": "number", "exclusiveMinimum": 0 },
"xp_per_hour": { "type": "number" },
"supplies": {
"type": "array",
@ -62,6 +65,251 @@
}
},
"selection": {
"type": "object",
"required": ["mode", "eligible_rule", "fallback"],
"additionalProperties": false,
"properties": {
"mode": { "enum": ["highest-unlocked"] },
"eligible_rule": { "type": "string", "minLength": 10 },
"fallback": { "type": "string", "minLength": 10 }
}
},
"recipes": {
"type": "object",
"minProperties": 1,
"propertyNames": { "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" },
"additionalProperties": {
"type": "object",
"required": ["bar", "smithing_level", "target_input", "inputs"],
"additionalProperties": false,
"properties": {
"bar": { "type": "string", "minLength": 2 },
"bar_item_id": { "type": "integer", "minimum": 1 },
"smithing_level": { "type": "integer", "minimum": 1, "maximum": 126 },
"smithing_xp": { "type": "number", "exclusiveMinimum": 0 },
"target_input": { "type": "string", "minLength": 2 },
"inputs": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["item", "qty", "source"],
"additionalProperties": false,
"properties": {
"item": { "type": "string", "minLength": 2 },
"item_id": { "type": "integer", "minimum": 1 },
"qty": { "type": "integer", "minimum": 1 },
"source": { "enum": ["ge", "p2p", "drops", "craft", "bank", "npc-shop"] }
}
}
},
"rates": {
"type": "object",
"required": ["capacity", "casts_per_hour", "magic_xp_per_hour"],
"additionalProperties": false,
"properties": {
"capacity": { "type": "integer", "minimum": 1 },
"casts_per_hour": {
"type": "object",
"required": ["min", "mid", "max"],
"additionalProperties": false,
"properties": {
"min": { "type": "number", "minimum": 0 },
"mid": { "type": "number", "minimum": 0 },
"max": { "type": "number", "minimum": 0 }
}
},
"magic_xp_per_hour": {
"type": "object",
"required": ["min", "mid", "max"],
"additionalProperties": false,
"properties": {
"min": { "type": "number", "minimum": 0 },
"mid": { "type": "number", "minimum": 0 },
"max": { "type": "number", "minimum": 0 }
}
}
}
}
}
}
},
"rate_model": {
"type": "object",
"required": ["anchor_tier", "cast_ticks", "restock_trip_s", "session_horizon_s"],
"additionalProperties": false,
"properties": {
"anchor_tier": { "enum": ["la", "sii", "hla"] },
"anchor_recipe": { "type": "string" },
"cast_ticks": { "type": "integer", "minimum": 1 },
"restock_trip_s": {
"type": "array",
"items": { "type": "number", "minimum": 0 },
"minItems": 2,
"maxItems": 2
},
"session_horizon_s": { "type": "integer", "minimum": 1 },
"startup_s": {
"type": "array",
"items": { "type": "number", "minimum": 0 },
"minItems": 2,
"maxItems": 2
},
"alch_carry_batch": { "type": "integer", "minimum": 1 },
"alch_restock_threshold": { "type": "integer", "minimum": 0 },
"sii_capacity_slots": { "type": "integer", "minimum": 1 }
}
},
"pickaxe_tiers": {
"type": "object",
"minProperties": 1,
"propertyNames": { "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" },
"additionalProperties": {
"type": "object",
"required": ["mining_level", "attack_level", "roll_ticks", "rank"],
"additionalProperties": false,
"properties": {
"mining_level": { "type": "integer", "minimum": 1, "maximum": 126 },
"attack_level": { "type": "integer", "minimum": 1, "maximum": 126 },
"roll_ticks": { "type": "integer", "minimum": 1 },
"rank": { "type": "integer", "minimum": 1, "maximum": 6 }
}
}
},
"pickaxe_examples": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["account", "attack", "mining", "expected_tier", "expected_equipped", "expected_slots"],
"additionalProperties": false,
"properties": {
"account": { "type": "string", "minLength": 2 },
"attack": { "type": "integer", "minimum": 1, "maximum": 126 },
"mining": { "type": "integer", "minimum": 1, "maximum": 126 },
"expected_tier": { "type": "string", "minLength": 2 },
"expected_equipped": { "type": "boolean" },
"expected_slots": { "type": "integer", "minimum": 1, "maximum": 28 }
}
}
},
"mining_modes": {
"type": "object",
"required": ["fast-xp", "banked-income"],
"additionalProperties": false,
"properties": {
"fast-xp": {
"type": "object",
"required": ["inventory_policy", "ore_policy", "sites"],
"additionalProperties": false,
"properties": {
"inventory_policy": { "const": "drop" },
"ore_policy": { "type": "string", "minLength": 5 },
"sites": { "type": "array", "minItems": 1, "items": { "type": "string" } }
}
},
"banked-income": {
"type": "object",
"required": ["inventory_policy", "targets", "stop"],
"additionalProperties": false,
"properties": {
"inventory_policy": { "const": "bank" },
"targets": { "type": "array", "minItems": 1, "items": { "type": "string" } },
"stop": { "type": "string", "minLength": 5 }
}
}
}
},
"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,
"propertyNames": { "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" },
"additionalProperties": {
"type": "object",
"required": ["ore", "ore_id", "mining_level", "xp", "rock_count",
"respawn_seconds", "rolls_per_ore", "modeled_ores_per_hour",
"world_hop", "selectable", "inventory_policy", "access",
"safety", "route"],
"additionalProperties": false,
"properties": {
"ore": { "type": "string", "minLength": 2 },
"ore_id": { "type": "integer", "minimum": 1 },
"mining_level": { "type": "integer", "minimum": 1, "maximum": 126 },
"xp": { "type": "number", "exclusiveMinimum": 0 },
"rock_count": { "type": "integer", "minimum": 1 },
"respawn_seconds": { "type": "number", "exclusiveMinimum": 0 },
"rolls_per_ore": { "type": "number", "exclusiveMinimum": 0 },
"modeled_ores_per_hour": { "type": "number", "minimum": 0 },
"world_hop": { "type": "boolean" },
"selectable": { "type": "boolean" },
"inventory_policy": { "enum": ["drop", "bank"] },
"access": { "type": "string", "minLength": 3 },
"safety": { "type": "string", "minLength": 3 },
"route": { "type": "string", "minLength": 5 }
}
}
},
"slot_state_machine": {
"type": "object",
"required": ["sub_states", "slot_transitions", "slot_transition_whens",
"scheduler_iteration", "quote_age_policy", "persisted_state",
"limit_ledger", "conservation", "break_resume"],
"additionalProperties": false,
"properties": {
"sub_states": {
"type": "array",
"minItems": 10,
"items": { "type": "string" }
},
"slot_transitions": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": { "type": "string" }
}
},
"slot_transition_whens": {
"type": "array",
"minItems": 8,
"items": { "type": "string", "minLength": 8 }
},
"scheduler_iteration": { "type": "string", "minLength": 10 },
"quote_age_policy": { "type": "string", "minLength": 10 },
"persisted_state": { "type": "string", "minLength": 10 },
"limit_ledger": { "type": "string", "minLength": 10 },
"conservation": { "type": "string", "minLength": 10 },
"break_resume": { "type": "string", "minLength": 10 }
}
},
"best_practice": {
"type": "object",
"required": ["equipment"],
@ -96,6 +344,123 @@
}
},
"axe_tiers": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["axe", "woodcutting_level", "attack_level", "members"],
"additionalProperties": false,
"properties": {
"axe": { "type": "string" },
"woodcutting_level": { "type": "integer", "minimum": 1, "maximum": 99 },
"attack_level": { "type": "integer", "minimum": 1, "maximum": 99 },
"members": { "type": "boolean" }
}
}
},
"axe_examples": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["account", "woodcutting_level", "attack_level", "expected_axe", "expected_equipped"],
"additionalProperties": false,
"properties": {
"account": { "type": "string" },
"woodcutting_level": { "type": "integer", "minimum": 1, "maximum": 99 },
"attack_level": { "type": "integer", "minimum": 1, "maximum": 99 },
"expected_axe": { "type": "string" },
"expected_equipped": { "type": "boolean" }
}
}
},
"woodcutting_modes": {
"type": "array",
"minItems": 3,
"maxItems": 3,
"items": {
"type": "object",
"required": ["mode", "intent", "inventory_policy", "ladder", "fallback", "model", "rates"],
"additionalProperties": false,
"properties": {
"mode": { "enum": ["fast-xp", "afk-xp", "banked-income"] },
"intent": { "type": "string", "minLength": 10 },
"inventory_policy": { "enum": ["bank", "drop"] },
"fallback": { "type": "string", "minLength": 3 },
"ladder": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["tree", "min_level", "gate", "spot", "bank",
"chop_seconds_per_log", "walk_seconds_each_way",
"respawn_duty", "log_price"],
"additionalProperties": false,
"properties": {
"tree": { "enum": ["tree", "oak", "willow", "maple", "yew"] },
"min_level": { "type": "integer", "minimum": 1, "maximum": 99 },
"gate": { "enum": ["none", "combat-15-plus", "combat-under-15", "dragon-slayer-i"] },
"spot": { "type": "string", "minLength": 3 },
"bank": { "type": "string", "minLength": 3 },
"chop_seconds_per_log": { "type": "number", "exclusiveMinimum": 0 },
"walk_seconds_each_way": { "type": "number", "minimum": 0 },
"respawn_duty": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 },
"log_price": { "type": "number", "exclusiveMinimum": 0 }
}
}
},
"model": {
"type": "object",
"required": ["drop_seconds_per_log", "deposit_seconds", "break_duty"],
"additionalProperties": false,
"properties": {
"drop_seconds_per_log": { "type": "number", "minimum": 0 },
"deposit_seconds": { "type": "number", "minimum": 0 },
"break_duty": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 }
}
},
"rates": {
"type": "object",
"required": ["logs_per_hour", "xp_per_hour", "gp_per_hour", "log_price", "price_verified"],
"additionalProperties": false,
"properties": {
"logs_per_hour": { "type": "integer", "exclusiveMinimum": 0 },
"xp_per_hour": { "type": "integer", "minimum": 0 },
"gp_per_hour": { "type": "integer", "minimum": 0 },
"log_price": { "type": "number", "exclusiveMinimum": 0 },
"price_verified": { "type": "string" }
}
}
}
}
},
"woodcutting_fixtures": {
"type": "array",
"items": {
"type": "object",
"required": ["account", "mode", "woodcutting_level", "attack_level",
"quest_gate", "combat_level", "expected_tree", "expected_axe",
"expected_equipped", "expected_logs_per_hour",
"expected_xp_per_hour", "expected_gp_per_hour"],
"additionalProperties": false,
"properties": {
"account": { "type": "string", "minLength": 3 },
"mode": { "enum": ["fast-xp", "afk-xp", "banked-income"] },
"woodcutting_level": { "type": "integer", "minimum": 1, "maximum": 99 },
"attack_level": { "type": "integer", "minimum": 1, "maximum": 99 },
"quest_gate": { "type": "boolean" },
"combat_level": { "type": "integer", "minimum": 3 },
"expected_tree": { "enum": ["tree", "oak", "willow", "maple", "yew"] },
"expected_axe": { "type": "string" },
"expected_equipped": { "type": "boolean" },
"expected_logs_per_hour": { "type": "integer", "exclusiveMinimum": 0 },
"expected_xp_per_hour": { "type": "integer", "minimum": 0 },
"expected_gp_per_hour": { "type": "integer", "minimum": 0 }
}
}
},
"state_machine": {
"type": "object",
"required": ["initial", "terminal", "states"],
@ -155,7 +520,7 @@
"click_button", "menu_option",
"bank_inventory", "deposit_all", "withdraw", "has_item",
"count_item", "move_to_inv", "sort_inventory",
"ge_open", "ge_buy", "ge_sell", "ge_offers_done", "ge_collect",
"ge_open", "ge_buy", "ge_sell", "ge_cancel", "ge_offers_done", "ge_collect",
"ge_check_price",
"attack", "prayer", "buff", "eat", "retreat", "is_dead", "respawn",
"is_at", "item_count", "level_is", "xp_is", "hp_is",

File diff suppressed because it is too large Load Diff