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:
commit
dfa7e00bd0
2435
codex/economy/_f2p-alch-catalog.yaml
Normal file
2435
codex/economy/_f2p-alch-catalog.yaml
Normal file
File diff suppressed because it is too large
Load Diff
626
codex/economy/flip-candidates.yaml
Normal file
626
codex/economy/flip-candidates.yaml
Normal 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
|
||||
490
codex/economy/low-alch-members.yaml
Normal file
490
codex/economy/low-alch-members.yaml
Normal 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
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
1389
tools/validate.py
1389
tools/validate.py
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user