Compare commits

...

21 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
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
8d85c65801 fix(low-alch/handoff)... 2026-08-28 15:15:23 -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
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
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
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
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
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
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
5 changed files with 3436 additions and 69 deletions

View File

@ -3,83 +3,189 @@ title: Low-Level Alchemy (F2P)
domain: economy domain: economy
tags: [alching, magic-training, f2p, loss-training] tags: [alching, magic-training, f2p, loss-training]
status: verified status: verified
version: 1 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-27" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date updated: "2026-08-28" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date
summary: > summary: >
Magic training from level 21 by low-alching F2P items with the highest 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 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. 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: accounts:
membership: f2p membership: f2p
min_level: 21 # Magic 21 to cast Low Level Alchemy (wiki-verified) min_level: 21 # Magic 21 to cast Low Level Alchemy (wiki-verified)
requires_quest: [] requires_quest: []
requires_other: requires_other:
- "Staff of Fire equipped (removes 3 fire runes from the cost)" - "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" - "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: best_practice:
equipment: equipment:
- item: staff of fire - 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)" 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; Low Level Alchemy (Cost)" 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: techniques:
- name: cast on noted (stacked) items - 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)" source: "OSRS wiki — Low Level Alchemy (Usage: casting alchemy spells on noted items)"
- name: bank at a GE-adjacent bank - name: bank at a GE-adjacent bank
why: "supply restock without a second travel leg" why: "supply restock without a second travel leg — runes and alch items both come from the GE; restock trip 60-120s"
source: "researched 2026-08-27 (standard operator practice; wiki LA Cost section corroborates rune economy)" 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: economy:
# Verified 2026-08-27 against prices.runescape.wiki (live 'latest') # Verified 2026-08-27 against prices.runescape.wiki (live 'latest')
# + OSRS wiki Low_Level_Alchemy infobox. # + 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: Gold necklace # example anchor; rank by cheapest candidate live
item_id: 1654 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) income_per_cycle: 180 # lowalch of gold necklace (40% of value 450)
net_per_cycle: -112 net_per_cycle: -112 # at live-low fills
gp_per_hour_low: -250000 # negative: this burns GP net_per_cycle_max_fill: -126.6 # at MAX allowed fills (worst declared scenario)
gp_per_hour_high: -200000 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)
xp_per_hour: 55800 # 31 xp/cast (wiki) @ ~1800 casts/hr (3-tick, wiki) 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: supplies:
- item: gold necklace - item: gold necklace
qty_per_cycle: 1 qty_per_cycle: 1
source: ge 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 - item: nature rune
qty_per_cycle: 1 qty_per_cycle: 1
source: ge source: ge
ge_limit: 12500 ge_limit: 18000 # mapping 'limit' + Module:Exchange/Nature_rune agree;
# matches magic-alch-handoff (consistency check enforces)
profit_drivers: profit_drivers:
- "NONE at current prices — XP is the product, not GP" - "NONE at current prices — XP is the product, not GP"
- "Pick the candidate with the least-negative net each run (live rank)" - "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: state_machine:
initial: idle initial: idle
terminal: [complete, failed] terminal: [complete, failed]
states: states:
idle: idle:
goal: reach the bank + have supplies in inventory 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: [casting, bank_supplies] 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: casting:
goal: cast Low Level Alchemy on the current candidate item 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, idle, complete, failed] next: [bank_supplies, complete, failed]
bank_supplies: bank_supplies:
goal: bank the produced coins, withdraw fresh candidate + runes 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, idle] next: [casting, bank_supplies, failed]
complete: 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: failed:
goal: hard failure — stop, log ERROR sentinel, alert goal: "hard failure — stop, log ERROR sentinel, alert"
transitions: transitions:
- from: casting
when: "candidate item count < buffer OR nature rune count < buffer"
to: bank_supplies
- from: idle - 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 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: actions:
- id: a1 - id: a1
@ -87,25 +193,95 @@ actions:
verb: move_to verb: move_to
target: bank target: bank
params: 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] method: [teleport, walk]
on_fail: retry on_fail: retry
anti_ban: anti_ban:
delay: [400, 1600] delay: [400, 1600]
- id: a2 - 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 verb: ge_buy
target: grand-exchange target: grand-exchange
params: params:
item: "current candidate item" item: "staff of fire"
qty: "fill to buffer" qty: 1
max_price: "live low + 5%" 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 on_fail: retry
anti_ban: anti_ban:
delay: [1200, 3000] 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 step: cast-alch
verb: use_item_on verb: use_item_on
target: "candidate item in inventory" target: "candidate item in inventory"
@ -113,43 +289,153 @@ actions:
spell: "Low Level Alchemy" spell: "Low Level Alchemy"
item: "cheapest live candidate in inventory" item: "cheapest live candidate in inventory"
on: "itself" 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 on_fail: retry
anti_ban: 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 - id: a10
step: bank-loop step: restock-deposit
verb: bank_inventory verb: bank_inventory
target: bank target: bank
params: params:
deposit: [all coins, all products] 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 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: run_control:
iteration: "one cast" 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 max_iterations: null # null = until complete condition
break: break:
every: [8, 14] # bank cycles (breaks land on bank visits) 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 for: [45, 120] # seconds of idle AFK per break
stop_conditions: stop_conditions:
- "magic level >= target" - "magic level >= target (55 — the LA -> HLA handoff)"
- "gp under floor" - "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 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: risk:
ban_level: low ban_level: low
notes: notes:
- "High cast cadence is the main detection vector; keep anti_ban delays." - "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)."
- "GE buy bursts: pace offers, respect the 4h limit window." - "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: sources:
- "OSRS wiki — Low Level Alchemy (level 21, 31 xp/cast, 3-tick, F2P; infobox 2026-08-27)" - "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 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)" - "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: related:
- magic-alch-handoff
- high-alch-p2p - high-alch-p2p
- f2p-alch-catalog - f2p-alch-catalog
- mule-topup - 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,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

@ -39,8 +39,10 @@
"item": { "type": "string" }, "item": { "type": "string" },
"item_id": { "type": "integer" }, "item_id": { "type": "integer" },
"cost_per_cycle": { "type": "number" }, "cost_per_cycle": { "type": "number" },
"cost_per_cycle_max_fill": { "type": "number" },
"income_per_cycle": { "type": "number" }, "income_per_cycle": { "type": "number" },
"net_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_low": { "type": "number" },
"gp_per_hour_high": { "type": "number" }, "gp_per_hour_high": { "type": "number" },
"ceiling_gp_per_hour": { "type": "number", "exclusiveMinimum": 0 }, "ceiling_gp_per_hour": { "type": "number", "exclusiveMinimum": 0 },
@ -63,6 +65,105 @@
} }
}, },
"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": { "pickaxe_tiers": {
"type": "object", "type": "object",
"minProperties": 1, "minProperties": 1,
@ -243,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": { "state_machine": {
"type": "object", "type": "object",
"required": ["initial", "terminal", "states"], "required": ["initial", "terminal", "states"],

File diff suppressed because it is too large Load Diff