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
This commit is contained in:
Claude 2026-08-29 04:53:03 -05:00
commit dfa7e00bd0
7 changed files with 5156 additions and 276 deletions

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,490 @@
id: low-alch-members
title: Low-Level Alchemy (Members)
domain: economy
tags: [alching, magic-training, members, loss-training]
status: verified
version: 6 # 6: bounce-fix 5 2026-08-29 (owner guidance for governor 10361) — loss-reserve stop fixed to 'initial capital - total assets > 162,990' everywhere + validator boundary fixtures (below/at/above/profit) + realized GP/hr bounds derived from declared fill-price scenarios (A live-low -3.00/cast, B max allowed +5% -30.15/cast): gp low -29,463 / high -1,758. # 5: bounce-fix 4 2026-08-28 (owner guidance 10325) — complete GE acquisition path (open -> place -> fill -> collect) for initial buy AND every restock (bank-stock fast path only when the bank holds stock); fail-closed staff equip (bounded retries, no fire-rune fallback); capital model (cash vs supplies, staff cost, max-fill worst case, 6-batch loss reserve, float); monotonic break/restock counters (breaks 550/1,100/1,650 independent of restocks 901/1,802) + validator regression; startup-vs-steady-state rates over a 2 h session horizon
updated: "2026-08-29" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date
summary: >
Magic training from level 21 by low-alching members items with the lowest
loss per cast (high-limit, high-volume candidates first). Same state
machine and actions as low-alch-f2p; the catalog is the members item pool
(mapping lowalch != null, members=true, live GE prices 2026-08-27). At
current prices the method burns a small amount of GP per cast for 31 XP —
run it for XP, not GP. Least-loss liquid anchor at last check: rune
javelin tips (live-low net -3/cast; realized loss band -1,758 to -29,463
gp/hr across the declared live-low and max-allowed +5% fill scenarios —
XP is fill-independent at 23,000/hr session-realized midpoint). 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; rates are
session-realized (startup + restock + breaks included, see economy).
Handoff: switch to High Level Alchemy at level 55 (hard level gate +
strictly more coins AND XP per cast; see sources).
accounts:
membership: members
min_level: 21 # Magic 21 to cast Low Level Alchemy (wiki-verified)
requires_quest: []
requires_other:
- "Staff of Fire equipped (removes 3 fire runes from the cost; NO level requirement documented on the OSRS wiki — verified 2026-08-28, see sources — this is the default path). Equip failure is FAIL-CLOSED: flow performs bounded retries (3), then logs ERROR + alerts + stops — there is no fire-rune fallback in this model"
- "A bank with GE access within reach (alch spot = GE bank booth; restock trip [60, 120] s)"
- "Starting capital >= ~1.31M gp CASH (model breakdown: staff buy hard cap 5,000 + first batch at MAX allowed fill 570,150 + session loss reserve 162,990 = 6 worst-case batches at live-low+5% fills (covers Magic 21 -> 55) + resting-offer float 570,150). The capital stop check uses TOTAL ASSETS (cash + supplies at current buy cost), never raw cash — cash legitimately converts into supplies mid-run"
- "30 Attack + 50 Magic ONLY if adopting the optional Bryophyta's staff + Tome of Fire technique — below that the Staff of Fire path applies"
best_practice:
equipment:
- item: staff of fire
why: "unlimited fire runes — LA rune cost drops from 3 fire + 1 nature to 1 nature per cast (~15gp/cycle saved); equip requirement: none documented on the OSRS wiki (verified 2026-08-28); if it cannot be equipped after bounded retries the run FAILS CLOSED (no fallback modeled)"
source: "OSRS wiki — Staff of Fire (id 1387, F2P item, tradeable, no requirements row in the infobox and none in the prose — compare Battlestaff which states 'requires 30 Attack and 30 Magic'); Low Level Alchemy (Cost)"
techniques:
- name: cast on noted (stacked) items
why: "one inventory slot, minimal mouse travel between spell icon and item, no item un-noting; means an inventory does NOT define a batch size — batch sizes are the explicit numeric carried_batch below"
source: "OSRS wiki — Low Level Alchemy (Usage: casting alchemy spells on noted items)"
- name: Bryophyta's staff + Tome of Fire (members-only, 50 Magic, 30 Attack)
why: "charged Bryophyta's staff holds up to 1,000 nature runes (0 rune inventory slots) with a 1/15 chance per cast to not consume a nature rune; charged Tome of Fire supplies infinite fire runes in the shield slot and non-combat spells (alchemy) do not consume charges — the wiki documents this exact pair for alchemy profit; payback takes thousands of casts, so adopt at 50+ Magic when nature prices are high. EQUIP GATE: the staff requires 30 Attack + 30 Magic and the tome requires 50 Magic — accounts below 30 Attack (e.g. 1-Attack mules) cannot equip it and keep the ordinary Staff of Fire path (equipment section above) instead"
source: "OSRS wiki — Bryophyta's staff (passive effect; 'can be used along with a tome of fire to increase profits with High Level Alchemy'); OSRS wiki — Tome of Fire (level 50 Magic, non-combat spells do not consume charges)"
- name: self-craft nature runes as a cost cap
why: "members with 44 Runecraft can craft natures from pure essence, capping the per-cast rune cost at essence+time when GE nature prices spike; use the Abyss (no talisman needed) from the Mage of Zamorak"
source: "OSRS wiki — Nature rune (level 44 Runecraft, pure essence, 9 xp); OSRS wiki — Abyss (runic altar access)"
economy:
# Verified 2026-08-27 against prices.runescape.wiki (live 'latest') +
# OSRS wiki (spell infoboxes, Module:Exchange limits, item pages).
# Re-verified at bounce fix 2026-08-27 with a fresh live fetch; the
# modeled anchor is the ACTUAL least-loss liquid candidate.
# Bounce-fix 4 (2026-08-28, owner guidance 10325): all rate fields are
# SESSION-REALIZED figures — startup + restock + mandatory breaks
# included; loop-only and steady-state figures retained as labelled
# references; the 2 h session horizon is explicit (stop_conditions).
item: Rune javelin tips # least-loss liquid anchor 2026-08-27 (see profit_drivers)
item_id: 19580
cost_per_cycle: 543 # javelin tips buy 403 + nature rune 140 (staff of fire, live low 2026-08-27)
income_per_cycle: 540 # lowalch of rune javelin tips (40% of value 1350)
net_per_cycle: -3
gp_per_hour_low: -29463 # WORST-CASE realized: max-allowed fill scenario B (-30.15/cast) x session-fast 977.2 casts/hr = -29,462.6 (3.4 s cycle, 300 s startup, 2 h horizon) — see FILL-PRICE SCENARIOS below
gp_per_hour_high: -1758 # BEST-CASE realized: live-low fill scenario A (-3.00/cast) x session-slow 585.9 casts/hr = -1,757.7 (5.2 s cycle, 600 s startup, 2 h horizon) — see FILL-PRICE SCENARIOS below
xp_per_hour: 23000 # session-realized midpoint: 741.8 casts/hr x 31 = 22,995 (pin 23,000)
# ---- rate model (pinned; owner guidance 2026-08-28) ----
# LOOP (idealised, zero overhead — ceiling reference ONLY, never the
# entry's output): cycle = 1.8 s cast (3 ticks x 0.6 s) +
# anti_ban.delay [1.6, 3.4] s ADDITIVE (docs/action-grammar.md:135)
# = 3.4-5.2 s, midpoint 4.3 s -> loop-only casts/hr = 3600/4.3 = 837.2
# (range 692.3..1,058.8); loop-only xp mid = 25,953; gp mid = -2,512.
# STEADY-STATE (mandatory breaks + restock trips, no startup): per
# 901-cast batch: 901 x cycle + restock trip [60, 120] s (mid 90) +
# break share (break.for mid 82.5 s per break.every mid 550 casts:
# 45 s/700 at the fast end, 120 s/400 at the slow end):
# fast 3.4 s: 901 x 3.4 + 60 + 45 x 901/700 = 3,181.3 s -> 1,019.7 casts/hr
# mid 4.3 s: 901 x 4.3 + 90 + 82.5 x 901/550 = 4,099.5 s -> 791.3 casts/hr
# slow 5.2 s: 901 x 5.2 + 120 + 120 x 901/400 = 5,075.5 s -> 639.2 casts/hr
# SESSION-REALIZED (2 h horizon = 7,200 s; one-time startup 300-600 s,
# mid 450 s -> 450/7200 = 6.25 %): realized = steady-state x (1 -
# startup/session): fast 977.2, mid 741.8, slow 585.9 casts/hr.
# xp mid = 741.8 x 31 = 22,995 -> pin 23,000. REALIZED GP/hr derives from
# the FILL-PRICE SCENARIOS below (the SAME ledger as the batch economics):
# worst = -29,463 = scenario B x session-fast 977.2 (-30.15 x 977.2 = -29,462.6)
# best = -1,758 = scenario A x session-slow 585.9 (-3.00 x 585.9 = -1,757.7)
# midpoints: scenario A x 741.8 = -2,225; scenario B x 741.8 = -22,365.
# BATCH ECONOMICS (901 casts; deposits 901 x 540 = 486,540 gp per batch):
# input cost at live low: 901 x 543 = 489,243 -> net -2,703/batch
# input cost at MAX fill (live low + 5 %): 901 x 570.15 = 513,705 -> net
# -27,165/batch (worst case). Reserve = 6 worst-case batches (Magic
# 21 -> 55 = ~5,300 casts = ~6 batches) = 162,990 gp; the capital stop
# checks TOTAL ASSETS, not raw cash (see accounts + stop_conditions).
# FILL-PRICE SCENARIOS (one ledger for capital, batch economics, AND the
# realized GP bounds — every number below comes from the same scenario):
# A live-low fill: candidate 403 + nature 140 = 543/cast vs
# 540 lowalch -> net -3.00/cast (the anchor)
# B max allowed fill (+5%): candidate 403x1.05 = 423.15 + nature
# 140x1.05 = 147 -> 570.15/cast vs 540 lowalch
# -> net -30.15/cast (the executable buy policy
# permits live low + 5%; this scenario, not the
# -3 live-low margin, bounds the downside)
# gp_per_hour_low = -29,463 | gp_per_hour_high = -1,758 derive from these
# scenarios at the session rates above; the batch ledger uses the same
# 543 / 570.15 per-cast costs (901-cast batch: -2,703 live-low, -27,165
# max fill), so capital accounting and GP bounds can never diverge.
supplies:
- item: rune javelin tips
qty_per_cycle: 1
source: ge
ge_limit: 10000 # mapping + Module:Exchange/Rune javelin tips agree (re-verified 2026-08-27); one carried_batch of 1,000 fits the 4h window
- item: nature rune
qty_per_cycle: 1
source: ge
ge_limit: 18000
profit_drivers:
- "No guaranteed profit to chase — this is loss-minimizing training: XP is the product. Pick the least-loss (least-negative net) candidate live each run."
- "Least-loss liquid anchors 2026-08-27 (net = lowalch - low buy - nature 140): rune javelin tips (19580, -3/cast, limit 10,000) is the modeled anchor; rune arrow (892, -25/cast, limit 11,000) is the runner-up."
- "Jade ring (21084) is currently MARGINAL-PROFIT (+67/cast, low/high 303/326) — flips sign as prices move, so it is not the loss anchor. Opal amulet (u) (21099) shows a 20x low/high spread (17/365) = stale-low artifact, disqualified per data-sources.md thin-fill trap."
- "Alch-margin candidates by (lowalch - buy) 2026-08-27 (thin limits cannot sustain a run; 'low' fills on thin items can be stale):"
- "1. Trailblazer top (t1) (id 25031, lowalch 6000, buy 555, margin +5445, GE limit 5)"
- "2. Twisted trousers (t2) (id 24401, lowalch 4000, buy 100, margin +3900, GE limit 5)"
- "3. Adamant hasta(p) (id 11407, lowalch 832, buy 1, margin +831, GE limit 125)"
- "4. Adamant spear(p++) (id 5726, lowalch 832, buy 10, margin +822, GE limit 125)"
- "5. Bagged flower (id 8451, lowalch 2000, buy 1250, margin +750, GE limit 10000 — deep limit but thin market: a 10k fill at 1,250 is unverifiable, do not model a run on it)"
- "6. Dual sai (id 23206, lowalch 2400, buy 1750, margin +650, GE limit 8)"
- "7. Frying pan (id 7441, lowalch 664, buy 101, margin +563, GE limit 50)"
- "8. Maple blackjack(d) (id 6420, lowalch 640, buy 100, margin +540, GE limit 125)"
- "9. Adamant spear(p) (id 1259, lowalch 832, buy 450, margin +382, GE limit 125)"
- "10. Mystic mist staff (id 20733, lowalch 17000, buy 75727, margin -58727, GE limit 8) — buy spiked ABOVE alch value 2026-08-27; a true loss trap, do not buy"
- "Caveat: thin items (limits 5-125) cannot sustain a run and their 'low' fills can be stale — a bot cannot reliably buy at those prices. The dependable loss-minimizers are the liquid high-limit pair modeled above: rune javelin tips (-3/cast) and rune arrow (-25/cast)."
- "Batch-scale economics (default staff-of-fire path, worst case 1 nature/cast): carried_batch 1,000/1,000, restock every 901 casts — one batch = -2,703 gp at live-low fills (-27,165 at MAX allowed fills), +27,931 xp, deposits 486,540 gp; unfilled-offer float (up to 570,150 gp) is separate from loss (see accounts)."
state_machine:
initial: idle
terminal: [complete, failed]
states:
idle:
goal: "session start at the GE bank booth. Open GE (a2), then place the initial offers: staff of fire (a3), candidate 1,000 (a4), nature runes 1,000 (a5) at live low + 5% (staff: +25% up to the 5,000 cap). Record initial capital in the cash ledger (total assets = cash + supplies at current buy cost). Nothing is bought or collected without an open GE interface"
next: [wait_offers, complete, failed]
wait_offers:
goal: "wait for the pending offers (ge_offers_done); when filled, collect every purchase + refund from the collection box (a6 — never leave purchases in the box). PARTIAL FILLS: collect the filled portion, re-place the remainder at the current edge (a3/a4/a5); 3 consecutive no-fill rounds (offer cannot fill at the accepted edge) -> failed. An offer that is still unfilled at session end is left resting by design — its coins are tied up (float reserve) and return when it fills or is aborted by the operator (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 loadout to carried_batch 1,000 candidate + 1,000 nature (a8; purchases from a6 are already in the inventory; a8 deposits overflow and confirms the counts)"
next: [casting, failed]
casting:
goal: "cast Low Level Alchemy on the candidate (a9) — each cast consumes 1 candidate + 1 nature and pays 540 gp (lowalch), +31 xp, net -3 gp; take a break when the completed-cast counter hits run_control.break.every ([400, 700] casts, via a13); the counter is NEVER reset by restocking"
next: [bank_supplies, complete, failed]
bank_supplies:
goal: "restock to carried_batch: deposit coins + products and withdraw ALL candidate/natures the bank holds (a10 — bank-stock fast path when the bank already holds enough); for the remaining deficit, open GE (a2), buy the deficit (a11, live low + 5%), collect it (a12) — never withdraw a batch that was never bought. Restocking never resets the cast counter or the break counter; the break cadence (550/1,100/1,650, ...) is independent of the restock cadence (901/1,802, ...)"
next: [casting, bank_supplies, failed]
complete:
goal: "reached the run's XP/level target (default 55 -> hand off to HLA) or the 2 h session horizon"
failed:
goal: "hard failure — stop, log ERROR sentinel, alert"
transitions:
- from: idle
when: "staff + candidate + nature offers all placed (a3/a4/a5)"
to: wait_offers
- from: idle
when: "magic level >= target (55) at session start"
to: complete
- from: idle
when: "3 consecutive offer-place failures at session start (no fallback candidate)"
to: failed
- from: wait_offers
when: "ge_offers_done AND all purchases collected (a6)"
to: ready
- from: wait_offers
when: "partial fill: filled portion collected (a6), remainder re-placed at the current edge (a3/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 loadout at carried_batch 1,000/1,000 (a8)"
to: casting
- from: ready
when: "staff equip failed after bounded retries — FAIL CLOSED"
to: failed
- 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, hand off to HLA)"
to: complete
- from: casting
when: "3 consecutive failed casts (client issue — do not loop)"
to: failed
- from: bank_supplies
when: "restock complete: candidate == carried_batch (1,000) AND nature == carried_batch (1,000) — 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
# --- handoff note (wiki-verified 2026-08-27, do not copy from other entries) ---
# Low Level Alchemy: level 21, 31 xp/cast, 1 nature + 3 fire (spellbook: Normal).
# Superheat Item: level 43, 53 xp/cast, 1 nature + 4 fire — a SMITHING
# production spell (ore -> bar), NOT the magic-XP handoff; as pure Magic
# training it is only a soft side option (e.g. training Smithing alongside).
# High Level Alchemy: level 55, 65 xp/cast, 1 nature + 5 fire.
# Handoff: the LA page itself states "Players with level 55 Magic are
# generally recommended to use High Level Alchemy instead, as it provides
# more coins and experience per cast than Low Level Alchemy". So the switch
# is level 55: a HARD level gate (HLA uncastable below 55) plus strict
# dominance afterwards (with a fire staff both cost 1 nature/cast, and HLA
# pays 60% of value + 65 xp vs LA's 40% + 31 xp) — after 55, LA is strictly
# worse for Magic XP and is never the right call except when deliberately
# burning GP at minimum XP (never recommended).
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)"
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/a5/a11) or collects (a6/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 (a6) and re-place the remainder at the current edge — bounded retries, then the run fails closed (no fire-rune fallback exists)"
on_fail: retry
anti_ban:
delay: [1200, 3000]
- id: a4
step: buy-supplies
verb: ge_buy
target: grand-exchange
params:
item: "current least-loss live candidate (default: rune javelin tips)"
qty: "carried_batch = 1,000 (fits the 4h GE limit window of 10,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 which states '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: "to carried_batch: candidate 1,000 + nature runes 1,000 (only the shortfall — collected purchases from a6 are already in the inventory; both stackable, two inventory slots)"
on_fail: retry
anti_ban:
delay: [500, 1100]
- id: a9
step: cast-alch
verb: use_item_on
target: "candidate item in inventory"
params:
spell: "Low Level Alchemy"
item: "least-loss live candidate in inventory"
on: "itself"
consumption: "1 candidate + 1 nature per cast (staff of fire default); pays 540 gp (lowalch) + 31 xp, net -3 gp"
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 = 1.8s + 1.6-3.4s = 3.4-5.2s, midpoint 4.3s -> session-realized ~742 casts/hr incl. startup + restock + breaks (pinned in economy)
- id: a10
step: restock-deposit
verb: bank_inventory
target: bank
params:
deposit: [all coins, all products]
withdraw: "ALL candidate + nature runes currently in the bank, up to carried_batch (1,000/1,000) — the bank-stock FAST PATH applies only when the bank actually holds enough stock; the remaining deficit is bought at the GE (a11) and collected (a12)"
on_fail: alert
anti_ban:
delay: [500, 1100]
- id: a11
step: restock-buy
verb: ge_buy
target: grand-exchange
params:
item: "current least-loss live candidate + nature runes"
qty: "deficit to carried_batch = carried_batch - inventory count after the a10 fast path (e.g. 901 candidate + 901 nature after a 901-cast batch with an empty bank)"
max_price: "live low + 5 %"
note: "the deficit is RE-BOUGHT at the GE on every restock — the entry never withdraws a batch that was never purchased; partial/failed fills use the same collect + re-place recovery as the initial acquisition"
on_fail: retry
anti_ban:
delay: [1200, 3000]
- id: a12
step: restock-collect
verb: ge_collect
target: grand-exchange
params:
note: "collect the re-bought deficit into the inventory — inventory returns to carried_batch 1,000/1,000 before casting resumes"
on_fail: retry
anti_ban:
delay: [600, 1400]
- id: a13
step: take-break
verb: take_break
target: "current location"
params:
duration: "[45, 120] seconds of idle AFK per break (matches run_control.break.for)"
note: "executed by the translator when the completed-cast counter reaches run_control.break.every — this IS run_control.break's executor, fired on CAST COUNTS, not on bank visits; the cast counter is NOT reset by restocking (breaks land at 550/1,100/1,650 in the midpoint example, independent of the 901-cast restock cadence)"
on_fail: skip
run_control:
iteration: "one cast" # = one Low Level Alchemy cast (+31 xp, -3 gp at the anchor). Per docs/schema.md run_control, break.every counts ITERATIONS — so it counts CASTS here, NOT bank trips (owner decision 2026-08-28)
max_iterations: null # null = until complete condition
break:
every: [400, 700] # COMPLETED CASTS between AFK breaks (midpoint 550 = ~39 min at the 4.3s midpoint cycle). Cast-count-based and independent of bank visits; a restock trip NEVER counts as a break and does not reset this counter
for: [45, 120] # seconds of idle AFK per break
stop_conditions:
- "magic level >= target (55 -> hand off to HLA)"
- "session elapsed >= 2 h (session horizon — startup is amortised over this window in the rate model)"
- "total assets (cash + supplies at current buy cost) < 575,150 — cannot fund the next batch at max fill (570,150) plus staff replacement (5,000); assets, NOT raw cash: cash that became supplies is not a loss"
- "session net loss: initial capital - total assets > 162,990 — a loss makes 'initial capital - total assets' POSITIVE, so this strict-greater comparison fires on losses and never on profits; 162,990 = the 6-batch worst-case reserve (exactly AT the boundary it does NOT fire — strict >)"
- "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 —
# with Bryophyta's staff the nature side drains slower, so the candidate stays binding).
# 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 (validator regression: the old buggy
# 'restart the interval at the restock' example is rejected by the gate).
#
# Step 0 — preparation (one-time startup, ~300-600 s): account holds >= ~1.31M gp
# cash (staff cap 5,000 + first batch max 570,150 + reserve 162,990 + float
# 570,150). At the GE bank: open GE (a2); place offers — staff (a3), 1,000
# rune javelin tips (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 (-543 gp)
# and pays 540 gp (lowalch) + 31 xp -> net -3 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 540 = 486,540 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: -2,703 gp (live-low fills) or
# -27,165 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 (HLA handoff),
# the 2 h session horizon, the capital stops (assets floor 575,150, or
# loss = initial capital - total assets > 162,990), 3 failed casts, or 3
# consecutive no-fill GE rounds.
# ============================================================================================
risk:
ban_level: low
notes:
- "High cast cadence is the main detection vector; keep the mandatory anti_ban delays (cadence pin: additive post-cast sleep [1.6, 3.4] s; session-realized ~742 casts/hr midpoint incl. startup + restock + breaks over a 2 h session; steady-state ~791; loop-only 837 is a ceiling reference, never the output)."
- "Breaks are CAST-COUNT-based (every [400, 700] completed casts, 45-120 s AFK) and independent of bank visits — never treat a restock trip as a break, and never reset the break counter on restock (break/restock gcd(550, 901) = 1)."
- "GE buy bursts: pace offers, respect the 4h limit window (10,000 javelin tips / 18,000 natures); thin items (limit < 100) cannot sustain a long run; offers persist until filled or aborted and a 4h limit reset is NOT an offer expiry."
- "Thin-item 'latest low' fills can be stale — confirm fill price with a live GE offer before committing a run; the liquid high-limit pair (rune javelin tips / rune arrow) is the dependable loss-minimizer."
# --- 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 (a10 -> a2 -> a11 -> a12) 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/a12) and the remainder is re-placed at
# the current edge (a3/a4/a5/a11); 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): re-run build-loadout
# (a8); 3 consecutive failed casts -> failed (stop, log ERROR sentinel, alert).
# - 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. Loss direction is pinned
# everywhere as 'initial capital - total assets > 162,990': a loss makes that
# difference positive, so the stop fires on losses and never on profits (the
# reversed sign form can never exceed the threshold on a loss and is rejected by
# the validator regression; boundary fixtures: below/at/above + profit side).
# - Explicit completion/stop: stop_conditions above (Magic 55 HLA handoff, 2 h session
# horizon, assets floor 575,150, loss reserve 162,990, repeated failures, fail-closed
# staff equip).
sources:
- "OSRS wiki — Low Level Alchemy (level 21, 31 xp/cast, 3-tick, 1 nature + 3 fire; spellbook Normal; infobox 2026-08-27)"
- "OSRS wiki — High Level Alchemy (level 55, 65 xp/cast, 1 nature + 5 fire; 60% of item value; infobox 2026-08-27)"
- "OSRS wiki — Superheat Item (level 43, 53 xp/cast, 1 nature + 4 fire; smithing production spell; infobox 2026-08-27)"
- "OSRS wiki — Low Level Alchemy (handoff quote: 'Players with level 55 Magic are generally recommended to use High Level Alchemy instead, as it provides more coins and experience per cast than Low Level Alchemy')"
- "prices.runescape.wiki mapping 2026-08-27 (members/lowalch/highalch/limit catalog; rune javelin tips id 19580: lowalch 540, highalch 810, limit 10000, members=true; lowalch == highalch*2//3 asserted for every cited item)"
- "prices.runescape.wiki latest 2026-08-27, re-fetched at bounce fix (low = per-repo buy basis: rune javelin tips 403, rune arrow 45, nature rune 140, jade ring 303, opal amulet (u) 17)"
- "OSRS wiki — Module:Exchange/Rune javelin tips (limit 10000, lowalch 540, highalch 810, members=true — matches mapping; re-verified 2026-08-27) + Module:Exchange/Nature rune, Staff of fire and all cited candidates (GE limits agree with mapping; verified 2026-08-27)"
- "OSRS wiki — Rune javelin tips / Rune arrow / Bagged flower / Mystic mist staff etc. item pages (members = Yes for every cited catalog item; verified 2026-08-27)"
- "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 — Nature rune (members craft at level 44 Runecraft from pure essence, 9 xp; Nature Altar south-east of Tai Bwo Wannai)"
- "OSRS wiki — Abyss (access to runic altars without talismans via the Mage of Zamorak)"
- "OSRS wiki — Bryophyta's staff (level 30 Attack + Magic; up to 1,000 nature charges; 1/15 chance to not consume a charge on any nature-rune spell; alchemy combo with Tome of Fire)"
- "OSRS wiki — Tome of Fire (members, level 50 Magic to wield; charged tome is an infinite fire-rune source and non-combat spells do not consume charges)"
- "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, never assumed) — the cadence + break + GE + recovery model"
verified: "2026-08-27" # keep quoted (YAML 1.1 date parsing); prices last re-fetched live 2026-08-27
related:
- low-alch-f2p
- high-alch-p2p
- mule-topup

View File

@ -3,8 +3,8 @@ title: Woodcutting (F2P)
domain: training
tags: [woodcutting, gathering, f2p, skill-training]
status: verified
version: 4 # 4: bounce-fix 3 2026-08-28 — owner decision 10288 implemented: three explicit modes (fast-xp / afk-xp / banked-income) with deterministic ladders, full lifecycle per mode, per-mode worked examples, realized rates incl. travel/respawn/banking-or-dropping/breaks; axe model corrected (41 Woodcutting to USE, 40 Attack to EQUIP — equip only when Attack permits, otherwise carry the same axe); structured axe_tiers / axe_examples / woodcutting_modes blocks + committed validator fixtures (incl. the 1-Attack/41-Woodcutting rune-axe carry case)
updated: "2026-08-28" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date
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
@ -68,6 +68,14 @@ axe_examples:
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
@ -78,19 +86,16 @@ woodcutting_modes:
banked-income target).
inventory_policy: bank
ladder:
- {tree: willow, min_level: 30, gate: combat-15-plus, spot: draynor-willows, bank: draynor}
- {tree: willow, min_level: 30, gate: combat-under-15, spot: edgeville-willow, bank: edgeville}
- {tree: oak, min_level: 15, gate: none, spot: draynor-oaks, bank: draynor}
- {tree: tree, min_level: 1, gate: none, spot: lumbridge-west, bank: lumbridge}
- {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:
chop_seconds_per_log: 4.0 # humanised chop cadence incl. anti_ban delays; willow falls ~30s/tree, switch to the next interactable tree
drop_seconds_per_log: 0
walk_seconds_each_way: 12 # Draynor bank <-> willow spot
drop_seconds_per_log: 0 # bank mode — no dropping
deposit_seconds: 4
respawn_duty: 0.92 # tree-switch + occasional shared-spot respawn pause
break_duty: 0.94 # scheduled breaks (run_control.break) land at the bank
rates:
logs_per_hour: 622 # 28 logs / (28x4.0 + 2x12 + 4 = 140s) x 3600 = 720; x 0.92 x 0.94 = 622.7
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
@ -104,24 +109,23 @@ woodcutting_modes:
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}
- {tree: willow, min_level: 30, gate: combat-15-plus, spot: draynor-willows, bank: draynor}
- {tree: willow, min_level: 30, gate: combat-under-15, spot: edgeville-willow, bank: edgeville}
- {tree: oak, min_level: 15, gate: none, spot: draynor-oaks, bank: draynor}
- {tree: tree, min_level: 1, gate: none, spot: lumbridge-west, bank: lumbridge}
- {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:
chop_seconds_per_log: 6.0 # AFK idle-pace: slower reaction after each tree falls (maple falls ~60s)
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)
walk_seconds_each_way: 0
deposit_seconds: 0
respawn_duty: 0.80 # 4 trees, 60s falls, distant spot; occasional all-down pause
break_duty: 0.94 # scheduled breaks at the spot (drop mode has no bank)
rates:
logs_per_hour: 386 # 28 logs / (28x(6.0+1.0) = 196s) x 3600 = 514.3; x 0.80 x 0.94 = 386.8
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: 4632 # 386 x 12 (maple log sell value 2026-08-28; drop mode forgoes this unless the run optionally banks)
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"
@ -135,26 +139,103 @@ woodcutting_modes:
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}
- {tree: willow, min_level: 30, gate: combat-15-plus, spot: draynor-willows, bank: draynor}
- {tree: willow, min_level: 30, gate: combat-under-15, spot: edgeville-willow, bank: edgeville}
- {tree: oak, min_level: 15, gate: none, spot: draynor-oaks, bank: draynor}
- {tree: tree, min_level: 1, gate: none, spot: lumbridge-west, bank: lumbridge}
- {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:
chop_seconds_per_log: 7.5 # yew chops slower per log (higher-level tree); conservative cadence
drop_seconds_per_log: 0
walk_seconds_each_way: 8 # Edgeville south yews <-> Edgeville bank
deposit_seconds: 4
respawn_duty: 0.52 # TWO-tree spot: yew falls ~114s, respawns ~59.4s; double-down waits + world contention — deliberately conservative (the guide's own caveat on yew spots)
break_duty: 0.94 # scheduled breaks at the bank
rates:
logs_per_hour: 214 # 28 logs / (28x7.5 + 2x8 + 4 = 230s) x 3600 = 438.3; x 0.52 x 0.94 = 214.2
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)"
@ -206,21 +287,37 @@ state_machine:
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: [walk_to_trees, idle]
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 a4"
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 only: drop the whole load at the spot (menu_option per log group); inventory now empty; no bank legs"
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)"
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 (logs only — the axe stays equipped or carried, never deposited); short bank trip lands the scheduled break"
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"
@ -234,25 +331,34 @@ state_machine:
when: "mode + prerequisites confirmed and session target not reached"
to: arm_run
- from: idle
when: "session target already reached OR no axe obtainable (bank and GE both lack any F2P axe)"
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: equip when Attack permits, else carry"
to: walk_to_trees
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: "inventory full (28 log slots with the axe equipped, 27 with the axe carried — derived dynamically, never hardcoded)"
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 banked (fast-xp | banked-income) and inventory full"
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"
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"
@ -281,13 +387,103 @@ actions:
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 a2)"
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"
@ -298,64 +494,41 @@ actions:
anti_ban:
delay: [400, 1600]
- id: a3
step: equip-or-carry-axe
verb: right_click
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: "right_click axe -> menu_option 'Wield <axe>'"
capacity: "equipped: 28 log slots; carried: 27 (one slot for the axe)"
on_fail: retry
anti_ban:
delay: [300, 1100]
- id: a4
step: acquire-axe
verb: bank_inventory
target: "bank (then GE)"
params:
check: "withdraw the best axe the account can use (host has_item / item_count); if the bank lacks it -> ge_buy (rune axe ~7,293 high / 7,140 low live 2026-08-28, GE limit 40/4h — trivial)"
never: "deposit the axe at any point; a missing axe is a recovery event (stop -> alert) not a continue-with-lower-axe event"
on_fail: alert
anti_ban:
delay: [400, 1200]
- id: a5
- 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/bank; (d) no interactable tree for > 30s -> walk_to_trees move / hop_world; (e) click failed -> retry up to 3, then alert"
rhythm: "humanised cadence; per-mode chop_seconds_per_log in woodcutting_modes.model"
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: a6
- id: a12
step: check-inventory
verb: item_count
target: "logs in inventory"
params:
item: "logs of the pinned tier"
op: "inventory full -> drop (afk-xp) or walk_to_bank (fast-xp / banked-income); available log slots = 28 with the axe equipped, 27 with the axe carried — never hardcode 28"
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: a7
- 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 drop_seconds_per_log"
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: a8
- id: a14
step: walk-to-bank
verb: walk_to
target: "pinned bank from the tree spot"
@ -366,29 +539,29 @@ actions:
anti_ban:
delay: [400, 1600]
- id: a9
- 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 a12"
break: "scheduled breaks land here (bank modes) — see a18"
on_fail: alert
- id: a10
- id: a16
step: walk-back-to-trees
verb: walk_to
target: "pinned tree spot from the bank"
params:
route: "reverse of a8 — back to the same spot"
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: a11
- id: a17
step: move-or-hop-empty
verb: hop_world
target: "current spot"
@ -399,14 +572,14 @@ actions:
anti_ban:
delay: [800, 2200]
- id: a12
- 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 -> chopping; drop mode -> chopping"
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:
@ -423,21 +596,24 @@ run_control:
# ================= 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).
# 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}. a3: rune axe (41 WC <= 50 -> usable),
# 40 Attack -> equipped (28 log slots). a4: axe already owned.
# a5 chop: 28 logs at ~4s each (112s) -> a6 inventory full -> a8 walk to
# Draynor bank (~12s) -> a9 deposit (4s) -> a10 walk back (~12s).
# 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), a12 take-break 45-150s LANDS AT
# 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: a10 walk back,
# 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.
#
@ -445,37 +621,45 @@ run_control:
# 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}. a3: 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.
# a5 chop until 27 logs (~162s at 6s each incl. post-fall reaction); tree
# falls ~60s so the spot's 4 trees rotate; a6 full -> a7 DROP the load at
# 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 = 386
# logs/hr model; xp 38,600/hr; ~386 clicks/hr — the lowest-interaction
# mode. a12 break 45-150s AT THE SPOT (inventory empty, axe in hand) every
# 8-14 cycles, INDEPENDENT of the per-load dropping. Resume: a5 continues.
# ~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.
# (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}. a5 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). a6 full -> a8 walk to
# Edgeville bank (~8s) -> a9 deposit (4s) -> a10 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). a12 break 45-150s AT THE BANK every 8-14 cycles (mid ~11 =
# ~47 min). Session target: 1M gp banked = ~42h of cycles + breaks.
# 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-a4) -> first full-inventory boundary
# -> first scheduled break -> correct resume from the pinned activity
# state; tree depletion -> move/hop (a11); access denial / missing axe ->
# explicit recovery (a4, a10 spot_check, re-arm fallback).
# 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:
@ -497,8 +681,9 @@ sources:
- "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)"
- "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:

View File

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

View File

@ -43,6 +43,7 @@
"net_per_cycle": { "type": "number" },
"gp_per_hour_low": { "type": "number" },
"gp_per_hour_high": { "type": "number" },
"ceiling_gp_per_hour": { "type": "number", "exclusiveMinimum": 0 },
"xp_per_hour": { "type": "number" },
"supplies": {
"type": "array",
@ -62,6 +63,39 @@
}
},
"slot_state_machine": {
"type": "object",
"required": ["sub_states", "slot_transitions", "slot_transition_whens",
"scheduler_iteration", "quote_age_policy", "persisted_state",
"limit_ledger", "conservation", "break_resume"],
"additionalProperties": false,
"properties": {
"sub_states": {
"type": "array",
"minItems": 10,
"items": { "type": "string" }
},
"slot_transitions": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": { "type": "string" }
}
},
"slot_transition_whens": {
"type": "array",
"minItems": 8,
"items": { "type": "string", "minLength": 8 }
},
"scheduler_iteration": { "type": "string", "minLength": 10 },
"quote_age_policy": { "type": "string", "minLength": 10 },
"persisted_state": { "type": "string", "minLength": 10 },
"limit_ledger": { "type": "string", "minLength": 10 },
"conservation": { "type": "string", "minLength": 10 },
"break_resume": { "type": "string", "minLength": 10 }
}
},
"best_practice": {
"type": "object",
"required": ["equipment"],
@ -145,27 +179,30 @@
"minItems": 1,
"items": {
"type": "object",
"required": ["tree", "min_level", "gate", "spot", "bank"],
"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 }
"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": ["chop_seconds_per_log", "drop_seconds_per_log", "walk_seconds_each_way", "deposit_seconds", "respawn_duty", "break_duty"],
"required": ["drop_seconds_per_log", "deposit_seconds", "break_duty"],
"additionalProperties": false,
"properties": {
"chop_seconds_per_log": { "type": "number", "exclusiveMinimum": 0 },
"drop_seconds_per_log": { "type": "number", "minimum": 0 },
"walk_seconds_each_way": { "type": "number", "minimum": 0 },
"deposit_seconds": { "type": "number", "minimum": 0 },
"respawn_duty": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 },
"break_duty": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 }
}
},
@ -184,6 +221,31 @@
}
}
},
"woodcutting_fixtures": {
"type": "array",
"items": {
"type": "object",
"required": ["account", "mode", "woodcutting_level", "attack_level",
"quest_gate", "combat_level", "expected_tree", "expected_axe",
"expected_equipped", "expected_logs_per_hour",
"expected_xp_per_hour", "expected_gp_per_hour"],
"additionalProperties": false,
"properties": {
"account": { "type": "string", "minLength": 3 },
"mode": { "enum": ["fast-xp", "afk-xp", "banked-income"] },
"woodcutting_level": { "type": "integer", "minimum": 1, "maximum": 99 },
"attack_level": { "type": "integer", "minimum": 1, "maximum": 99 },
"quest_gate": { "type": "boolean" },
"combat_level": { "type": "integer", "minimum": 3 },
"expected_tree": { "enum": ["tree", "oak", "willow", "maple", "yew"] },
"expected_axe": { "type": "string" },
"expected_equipped": { "type": "boolean" },
"expected_logs_per_hour": { "type": "integer", "exclusiveMinimum": 0 },
"expected_xp_per_hour": { "type": "integer", "minimum": 0 },
"expected_gp_per_hour": { "type": "integer", "minimum": 0 }
}
}
},
"state_machine": {
"type": "object",
@ -244,7 +306,7 @@
"click_button", "menu_option",
"bank_inventory", "deposit_all", "withdraw", "has_item",
"count_item", "move_to_inv", "sort_inventory",
"ge_open", "ge_buy", "ge_sell", "ge_offers_done", "ge_collect",
"ge_open", "ge_buy", "ge_sell", "ge_cancel", "ge_offers_done", "ge_collect",
"ge_check_price",
"attack", "prayer", "buff", "eat", "retreat", "is_dead", "respawn",
"is_at", "item_count", "level_is", "xp_is", "hp_is",

File diff suppressed because it is too large Load Diff