From 0ba4646296144cdd82e5f4bbd78945433421c905 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 03:31:01 -0500 Subject: [PATCH 1/5] codex: woodcutting (F2P) entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified against OSRS wiki 2026-08-27 + live prices: - F2P tree ladder: Tree 1/25xp, Oak 15/37.5xp, Willow 30/67.5xp, Yew 60/175xp (wiki tree-page infoboxes + F2P location lists) - Issue draft corrected: '45 Pine' disproven (OSRS pines are scenery only, Pine tree page); level 45 is Maple (members) — noted in handoff - F2P axes verified from wiki axe table + item pages: Bronze(1)..Rune(41) + Gilded(41) all F2P; Rune axe = best F2P; Dragon = members - economy anchored on Draynor Village willows: live log sell prices (willow 21, yew 112), xp/hr grounded in wiki per-log XP (hourly rate explicitly flagged as estimate) - bankside spot chosen per Oak/Willow/Yew page location lists; tree fall timers cited (willow 30s, yew 114s) - N/A relog/respawn noted (no combat/death vector) validate.py exit 0 (0 errors). Closes #3 --- codex/training/woodcutting-f2p.yaml | 206 ++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 codex/training/woodcutting-f2p.yaml diff --git a/codex/training/woodcutting-f2p.yaml b/codex/training/woodcutting-f2p.yaml new file mode 100644 index 0000000..415fcc9 --- /dev/null +++ b/codex/training/woodcutting-f2p.yaml @@ -0,0 +1,206 @@ +id: woodcutting-f2p +title: Woodcutting (F2P) +domain: training +tags: [woodcutting, gathering, f2p, skill-training] +status: verified +version: 1 +updated: "2026-08-27" # 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), Oak (15), + Willow (30), Yew (60) — anchored at bankside Draynor Village willows + for sustained XP with logs banked and sold at the GE (yew logs are the + top F2P income per log). Maple (45) and Magic (75) trees are members + content; there is no choppable pine tree in OSRS (pines are scenery + only) — both verified from the wiki, see the handoff note. + +accounts: + membership: f2p + min_level: 1 # Woodcutting 1 — normal trees from the start + requires_quest: [] + requires_other: + - "An axe (bronze/iron are free; iron axe ~66 gp at the GE)" + - "Bank with GE reach within a short walk (Draynor Village bank or Varrock GE)" + +best_practice: + equipment: + - item: rune axe (level 41 — best F2P axe) + why: "fastest F2P chop rate; per the wiki axe table Rune axe (41) and Gilded axe (41) are 'the best axes available in Free-to-Play'; Dragon axe is members-only. F2P ladder: Bronze(1)/Iron(1)/Steel(6)/Black(11)/Mithril(21)/Adamant(31) — all F2P per the same table" + source: "OSRS wiki — Woodcutting (axe table incl. F2P/members flags) + Rune axe, Black axe, Gilded axe item pages (members = No) + Dragon axe (members = Yes)" + techniques: + - name: chop bankside trees (Draynor Village) + why: "bank + trees adjacent — the inventory-full -> bank loop costs seconds; Oak tree page lists Draynor Village oaks 'two directly east of the bank'; Willow tree page lists Draynor Village among members=No locations; yews also nearby (Draynor Manor)" + source: "OSRS wiki — Oak tree (location list), Willow tree (location list incl. Draynor Village members=No), Yew tree (members=No locations incl. Edgeville/Varrock Palace/Rimmington)" + - name: bank logs, don't drop + why: "logs are F2P-tradeable income: live sell prices 2026-08-27 — willow 21, yew 112 per log (GE limit 15k / 12k per 4h); a 28-log inventory of willows banks ~590 gp" + source: "prices.runescape.wiki latest 2026-08-27 (willow 1519 sell 21 / buy 20; yew 1515 sell 112 / buy 111; limits 15000/12000)" + - name: tier progression for XP + why: "per-log XP (wiki infoboxes): Tree 25, Oak 37.5, Willow 67.5, Yew 175 — stay at the highest unlocked F2P tier; Willow (30) is the practical XP/hr tier, Yew (60) the income tier" + source: "OSRS wiki — Tree/Oak tree/Willow tree/Yew tree pages (level + xp infoboxes, 2026-08-27)" + +economy: + # Verified 2026-08-27 against prices.runescape.wiki latest + + # OSRS wiki tree-page infoboxes. Logs are pure income (no per-cycle cost; + # axe is one-time equipment). Anchor = Draynor Village willows. + item: Willow logs # example anchor; highest practical F2P XP/hr tier + item_id: 1519 + cost_per_cycle: 0 # no supplies consumed; axe cost amortised + income_per_cycle: 21 # willow log sell price (latest.high) + net_per_cycle: 21 + gp_per_hour_low: 14000 # est. ~700 logs/hr x 21 (see hourly-rate note) + gp_per_hour_high: 21000 # est. ~1000 logs/hr x 21 + xp_per_hour: 60000 # est. ~900 willow logs/hr x 67.5 xp (see note) + supplies: [] + profit_drivers: + - "Logs are the income: sell at the GE (willow 21, oak 42, yew 112 per log — live 2026-08-27; all F2P-tradeable with 12k-15k/4h buy limits)" + - "Yew (level 60) is the income tier: 175 xp/log and ~112gp/log sell; willow (30) is the XP-per-hour tier for the 30-60 band" + - "Hourly figures are engineering estimates (wiki per-log XP x assumed chop rate) — re-verify against live runs; the wiki-cited facts are per-log XP and live log prices" + +state_machine: + initial: idle + terminal: [complete, failed] + states: + idle: + goal: pick the F2P tier by level and arm the run + next: [walk_to_trees] + walk_to_trees: + goal: reach the Draynor bankside tree spot for the selected tier + next: [chopping, idle] + chopping: + goal: chop the tier tree until inventory full or the tree falls + next: [bank_logs, walk_to_trees, complete, failed] + bank_logs: + goal: deposit logs at the bank, return to the trees + next: [chopping, idle] + complete: + goal: reached the run's XP/level target + failed: + goal: hard failure — stop, log ERROR sentinel, alert + transitions: + - from: idle + when: "arm tier by level_is gates: >= 60 yew, >= 30 willow, >= 15 oak, else tree" + to: walk_to_trees + - from: walk_to_trees + when: "at the tree spot" + to: chopping + - from: chopping + when: "inventory full (item_count logs == 28)" + to: bank_logs + - from: chopping + when: "tree fell and none interactable at the spot (> 30s of empty-tree state)" + to: walk_to_trees + - from: bank_logs + when: "logs deposited" + to: chopping + - from: idle + when: "woodcutting level >= target (default 60 -> yew tier; 99 for max)" + to: complete + +# --- handoff note (wiki-verified 2026-08-27, do not copy from other entries) --- +# F2P tree ladder (level / xp per log, wiki tree-page infoboxes): +# Tree 1 / 25, Oak 15 / 37.5, Willow 30 / 67.5, Yew 60 / 175. +# NOT on the F2P ladder: +# Maple (45 / 100) — members: virtually all maple spots are members-only +# (Seers' Village, Woodcutting Guild, etc.); the single members=No flag +# (Corsair Cove Resource Area) sits inside a members resource area. +# Magic (75 / 250) — members. +# Pine — the issue draft's "45 Pine" is DISPROVEN: OSRS pine trees +# (Shayzien/Varlamore, added 2021) are scenery only and cannot be +# chopped ("Pine tree" wiki page). Level 45 is Maple, not Pine. + +actions: + - id: a1 + step: gate-tier + verb: level_is + target: "current woodcutting level" + params: + tiers: ">=60 yew | >=30 willow | >=15 oak | else tree" + spot: "Draynor Village bankside (willows; oaks east of bank; yews at Draynor Manor)" + on_fail: retry + anti_ban: + delay: [200, 900] + + - id: a2 + step: walk-to-trees + verb: walk_to + target: "selected tier tree spot" + params: + location: "Draynor Village" + method: [walk] + on_fail: retry + anti_ban: + delay: [400, 1600] + + - id: a3 + step: chop-tree + verb: click_object + target: "tree of the selected tier" + params: + object: "tree/oak/willow/yew per tier" + until: "inventory full OR tree fell" + on_fail: retry + anti_ban: + delay: [900, 2400] # human chop rhythm; yew fall = 114s, willow = 30s + + - id: a4 + step: check-inventory + verb: item_count + target: "logs in inventory" + params: + item: "logs of selected tier" + op: ">= 28 -> bank" + on_fail: skip + anti_ban: + delay: [150, 700] + + - id: a5 + step: bank-loop + verb: bank_inventory + target: bank + params: + deposit: [all logs] + withdraw: [] + on_fail: alert + + - id: a6 + step: move-on-empty-tree + verb: walk_to + target: "next tree / adjacent spot" + params: + location: "same tier spot or neighbouring tree" + when: "tree fell (no interactable tree at spot)" + on_fail: retry + anti_ban: + delay: [800, 2200] + +run_control: + iteration: "one inventory of logs (28)" + max_iterations: null + break: + every: [8, 14] # inventories (breaks land at the bank) + for: [45, 150] # seconds of idle AFK + stop_conditions: + - "woodcutting level >= target" + - "3 consecutive empty-tree waits at the spot (world dead? hop or re-route)" + - "3 consecutive failed clicks (client issue — do not loop)" + +risk: + ban_level: low + notes: + - "Woodcutting is one of the most botted skills; keep chop cadence humanised and bank rhythm varied (anti_ban delays above)." + - "Shared tree spots (Draynor willows): if the spot is crowded for > 60s, hop_world; do not camp a single world for hours." + - "No combat/death vector: tree-fall damage does not exist — relog/respawn-at-0hp handling is N/A for this entry." + +sources: + - "OSRS wiki — Woodcutting (axe table: F2P axes Bronze 1, Iron 1, Steel 6, Black 11, Mithril 21, Adamant 31, Rune 41, Gilded 41; 'best axes available in Free-to-Play'; Dragon 61+ members)" + - "OSRS wiki — Tree (members=No, level 1, 25 xp), Oak tree (15, 37.5 xp; F2P locations Draynor/Varrock/Lumbridge), Willow tree (30, 67.5 xp; members=No locations Rimmington/Edgeville/Lumbridge/Draynor Village; falls 50 ticks/30s), Yew tree (60, 175 xp; members=No locations incl. Varrock Palace/Edgeville/Rimmington; falls 190 ticks/114s, respawn 99 ticks/59.4s), Maple tree (45, 100 xp; members), Magic tree (75, 250 xp; members) — infoboxes 2026-08-27" + - "OSRS wiki — Pine tree (scenery in Shayzien/Varlamore, CANNOT be chopped — disproves any '45 Pine' tier)" + - "OSRS wiki — Rune axe / Black axe / Gilded axe item pages (members = No, verified 2026-08-27)" + - "prices.runescape.wiki mapping 2026-08-27 (item ids + GE limits: logs 1511/1521/1519 limit 15000, yew 1515 limit 12000)" + - "prices.runescape.wiki latest 2026-08-27 (live buy/sell: willow 20/21, oak 40/42, yew 111/112, rune axe buy 7470)" + - "Per-log XP and hourly estimate: hourly = wiki per-log XP x assumed chop rate (700-1000 logs/hr; flagged as estimate — re-verify against live runs)" +verified: "2026-08-27" # keep quoted (YAML 1.1 date parsing) + +related: + - low-alch-f2p + - f2p-alch-catalog From 8a8cc0d6b3c5ee5e72e009db5736af68c44bfc35 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 12:55:50 -0500 Subject: [PATCH 2/5] fix(codex): F2P maple path, XP/income mode, executable bank loop, combat gate (issue #3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Governor-review bounce (PR #12) fixes on issue-3: 1. Maple IS F2P — restored as an optional level-45 tier. The only F2P maples are the 4 trees in the Corsair Cove Resource Area (requires Dragon Slayer I to enter), per the Free-to-play Woodcutting training guide and Maple tree page (members1=No @ Corsair Cove). Entry no longer labels Maple itself members-only; the quest gate is in accounts.requires_quest and the tier route table. 2. XP-versus-income mode is now explicit: state machine and a1 gate-tier take a mode parameter — XP mode stays on Willow (or Maple with Dragon Slayer I) after 60; income mode selects Yew at Edgeville. The guide: yew logs 'earn a profit' but 'it is recommended to cut willow or maple trees for experience'. Default is XP mode; income must be an explicit run parameter. 3. Bank loop is now executable and tier-specific: actions split into walk-to-bank (a5) + deposit-logs (a6) + walk-back-to-trees (a7), and the state machine pins chop -> walk_to_bank -> deposit_logs -> walk_back -> chop with dedicated states. Per-tier tree+bank routes (Lumbridge/Draynor/Edgeville/Corsair Cove) replace the hardcoded Draynor-for-everything; Yew is routed to Edgeville (guide: 'best place to chop yew trees is south of Edgeville bank'). 4. Combat gate instead of a false no-combat claim: the Draynor willow spot sits between two aggressive level-7 Dark Wizards that attack players under combat 15 (Draynor Village wiki page). Accounts below combat 15 are routed to the single Edgeville willow (bank adjacent); relog/respawn N/A now scoped to the gated routes only, and the risk notes call out Draynor as a combat zone for low-level accounts. Tests: python3 tools/validate.py -> 2 entries, 0 errors, 1 pre-existing warning (researched flag on low-alch-f2p, owned by issue #5's PR); merged-tree check (main 79cab78 + head) keeps the issue-6 catalog, gate clean; git diff --check clean. --- codex/training/woodcutting-f2p.yaml | 159 +++++++++++++++++++--------- 1 file changed, 108 insertions(+), 51 deletions(-) diff --git a/codex/training/woodcutting-f2p.yaml b/codex/training/woodcutting-f2p.yaml index 415fcc9..bc5b436 100644 --- a/codex/training/woodcutting-f2p.yaml +++ b/codex/training/woodcutting-f2p.yaml @@ -3,45 +3,56 @@ title: Woodcutting (F2P) domain: training tags: [woodcutting, gathering, f2p, skill-training] status: verified -version: 1 +version: 2 updated: "2026-08-27" # 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), Oak (15), - Willow (30), Yew (60) — anchored at bankside Draynor Village willows - for sustained XP with logs banked and sold at the GE (yew logs are the - top F2P income per log). Maple (45) and Magic (75) trees are members - content; there is no choppable pine tree in OSRS (pines are scenery - only) — both verified from the wiki, see the handoff note. + Willow (30), Maple (45, optional), Yew (60) — with a per-tier tree and + bank route and an explicit XP-versus-income mode: after level 60 the + guide recommends staying on Willow (or Maple if Dragon Slayer I is done) + for experience and cutting Yew only for profit. Maple IS F2P content at + the Corsair Cove Resource Area (4 trees; requires Dragon Slayer I to + enter); Magic (75) is members-only; there is no choppable pine tree in + OSRS (pines are scenery only) — all verified from the wiki, see the + handoff note. The Draynor willow spot is flanked by two aggressive + level-7 Dark wizards that attack players under combat 15 — this entry + gates that spot to combat 15+ and routes lower-combat accounts to the + Edgeville willow. accounts: membership: f2p min_level: 1 # Woodcutting 1 — normal trees from the start - requires_quest: [] + requires_quest: + - "Dragon Slayer I — ONLY for the optional Maple path (the Corsair Cove Resource Area is quest-gated; the rest of the ladder needs no quest)" requires_other: - "An axe (bronze/iron are free; iron axe ~66 gp at the GE)" - - "Bank with GE reach within a short walk (Draynor Village bank or Varrock GE)" + - "Combat 15+ to use the Draynor Village willow spot (two aggressive level-7 Dark wizards attack players under combat 15); accounts below that use the Edgeville willow instead" + - "Bank within a short walk of the tier spot (Draynor, Edgeville, Lumbridge, Varrock, or Corsair Cove bank)" best_practice: equipment: - item: rune axe (level 41 — best F2P axe) - why: "fastest F2P chop rate; per the wiki axe table Rune axe (41) and Gilded axe (41) are 'the best axes available in Free-to-Play'; Dragon axe is members-only. F2P ladder: Bronze(1)/Iron(1)/Steel(6)/Black(11)/Mithril(21)/Adamant(31) — all F2P per the same table" + why: "fastest F2P chop rate; per the wiki axe table Rune axe (41) and Gilded axe (41) are 'the best axes available in Free-to-play'; Dragon axe is members-only. F2P ladder: Bronze(1)/Iron(1)/Steel(6)/Black(11)/Mithril(21)/Adamant(31) — all F2P per the same table" source: "OSRS wiki — Woodcutting (axe table incl. F2P/members flags) + Rune axe, Black axe, Gilded axe item pages (members = No) + Dragon axe (members = Yes)" techniques: - - name: chop bankside trees (Draynor Village) - why: "bank + trees adjacent — the inventory-full -> bank loop costs seconds; Oak tree page lists Draynor Village oaks 'two directly east of the bank'; Willow tree page lists Draynor Village among members=No locations; yews also nearby (Draynor Manor)" - source: "OSRS wiki — Oak tree (location list), Willow tree (location list incl. Draynor Village members=No), Yew tree (members=No locations incl. Edgeville/Varrock Palace/Rimmington)" + - name: pick the tier route by level, quests, and combat level + why: "per-tier spots and banks (2026-08-27): Tree (1) — west of Lumbridge Castle, bank Lumbridge; Oak (15) — two east of Draynor Village bank, bank Draynor (oaks also south-west of Varrock west bank); Willow (30) — south-west of Draynor Village bank, bank Draynor, GATED to combat 15+ (two aggressive level-7 Dark wizards attack players under combat 15); accounts below that use the single willow East of Edgeville, bank Edgeville; Maple (45) — Corsair Cove Resource Area (4 trees, the only F2P maples; requires Dragon Slayer I; far from a bank, bank Corsair Cove); Yew (60) — south of Edgeville bank (two trees), bank Edgeville" + source: "OSRS wiki — Free-to-play Woodcutting training (level-45 Maple path + Corsair Cove Resource Area; willow spots incl. 'beware of aggressive dark wizards'; yew recommendation 'south of Edgeville bank'); Draynor Village page (two level-7 dark wizards attack players under combat 15); Oak tree / Willow tree / Maple tree / Yew tree location lists — all 2026-08-27" + - name: XP mode vs income mode after level 60 + why: "the F2P guide: yew logs 'earn a profit' but it 'is recommended to cut willow or maple trees for experience and use the time saved to earn' — so after 60 the entry stays on Willow (or Maple with Dragon Slayer I) for XP and switches to Yew only in income mode" + source: "OSRS wiki — Free-to-play Woodcutting training (Levels 60-99: Yew trees section, 2026-08-27)" - name: bank logs, don't drop - why: "logs are F2P-tradeable income: live sell prices 2026-08-27 — willow 21, yew 112 per log (GE limit 15k / 12k per 4h); a 28-log inventory of willows banks ~590 gp" - source: "prices.runescape.wiki latest 2026-08-27 (willow 1519 sell 21 / buy 20; yew 1515 sell 112 / buy 111; limits 15000/12000)" + why: "logs are F2P-tradeable income: live sell prices 2026-08-27 — willow 21, yew 112 per log (GE limit 15k / 12k per 4h); a 28-log inventory of willows banks ~590 gp. (Dropping is faster XP per the guide, but this entry models the banked/income variant; the XP/income mode switch covers the trade-off.)" + source: "prices.runescape.wiki latest 2026-08-27 (willow 1519 sell 21 / buy 20; yew 1515 sell 112 / buy 111; limits 15000/12000); OSRS wiki — Free-to-play Woodcutting training (drop-vs-bank trade-off)" - name: tier progression for XP - why: "per-log XP (wiki infoboxes): Tree 25, Oak 37.5, Willow 67.5, Yew 175 — stay at the highest unlocked F2P tier; Willow (30) is the practical XP/hr tier, Yew (60) the income tier" - source: "OSRS wiki — Tree/Oak tree/Willow tree/Yew tree pages (level + xp infoboxes, 2026-08-27)" + why: "per-log XP (wiki infoboxes): Tree 25, Oak 37.5, Willow 67.5, Maple 100, Yew 175 — climb to the highest unlocked F2P tier; Willow (30) is the practical XP/hr tier through 60+, Maple (45) is an optional 100-xp AFK-ish tier when Dragon Slayer I is done, Yew (60) is the income tier" + source: "OSRS wiki — Tree/Oak tree/Willow tree/Maple tree/Yew tree pages (level + xp infoboxes, 2026-08-27)" economy: # Verified 2026-08-27 against prices.runescape.wiki latest + # OSRS wiki tree-page infoboxes. Logs are pure income (no per-cycle cost; - # axe is one-time equipment). Anchor = Draynor Village willows. + # axe is one-time equipment). Anchor = Draynor Village willows (combat 15+). item: Willow logs # example anchor; highest practical F2P XP/hr tier item_id: 1519 cost_per_cycle: 0 # no supplies consumed; axe cost amortised @@ -53,7 +64,8 @@ economy: supplies: [] profit_drivers: - "Logs are the income: sell at the GE (willow 21, oak 42, yew 112 per log — live 2026-08-27; all F2P-tradeable with 12k-15k/4h buy limits)" - - "Yew (level 60) is the income tier: 175 xp/log and ~112gp/log sell; willow (30) is the XP-per-hour tier for the 30-60 band" + - "EXPLICIT MODE SWITCH (state machine + a1): XP mode = Willow (30) through 60+ (Maple 45 with Dragon Slayer I); income mode = Yew (60) at Edgeville. Default is XP mode; income mode must be an explicit run parameter, never the unstated default" + - "Maple (45, Dragon Slayer I, Corsair Cove Resource Area) is an optional F2P tier: 100 xp/log, 4 trees, far from a bank — the guide notes it is more AFK but lower XP/hr than willows" - "Hourly figures are engineering estimates (wiki per-log XP x assumed chop rate) — re-verify against live runs; the wiki-cited facts are per-log XP and live log prices" state_machine: @@ -61,61 +73,80 @@ state_machine: terminal: [complete, failed] states: idle: - goal: pick the F2P tier by level and arm the run - next: [walk_to_trees] + goal: pick the F2P tier by mode + level + quest/combat gates and arm the run + next: [walk_to_trees, complete] walk_to_trees: - goal: reach the Draynor bankside tree spot for the selected tier + goal: walk the tier route from the bank to the tier's tree spot next: [chopping, idle] chopping: goal: chop the tier tree until inventory full or the tree falls - next: [bank_logs, walk_to_trees, complete, failed] - bank_logs: - goal: deposit logs at the bank, return to the trees + next: [walk_to_bank, walk_to_trees, complete, failed] + walk_to_bank: + goal: walk the tier route from the tree spot to the tier's bank + next: [deposit_logs] + deposit_logs: + goal: deposit all logs at the bank + next: [walk_back] + walk_back: + goal: walk back from the bank to the tier's tree spot next: [chopping, idle] complete: - goal: reached the run's XP/level target + goal: reached the run's XP/level or GP target failed: goal: hard failure — stop, log ERROR sentinel, alert transitions: - from: idle - when: "arm tier by level_is gates: >= 60 yew, >= 30 willow, >= 15 oak, else tree" + when: "arm tier: XP mode -> tree (<15) / oak (<30) / willow (30+, combat 15+ at Draynor or Edgeville below) / maple (45+ AND Dragon Slayer I done) / willow again after 60; income mode -> yew (60+) — see a1" to: walk_to_trees - from: walk_to_trees - when: "at the tree spot" + when: "at the tier tree spot" to: chopping - from: chopping when: "inventory full (item_count logs == 28)" - to: bank_logs + to: walk_to_bank - from: chopping when: "tree fell and none interactable at the spot (> 30s of empty-tree state)" to: walk_to_trees - - from: bank_logs + - from: walk_to_bank + when: "at the tier bank" + to: deposit_logs + - from: deposit_logs when: "logs deposited" + to: walk_back + - from: walk_back + when: "at the tier tree spot" to: chopping - from: idle - when: "woodcutting level >= target (default 60 -> yew tier; 99 for max)" + when: "woodcutting level >= target (XP mode: 99 or target; income mode: target GP banked)" to: complete # --- handoff note (wiki-verified 2026-08-27, do not copy from other entries) --- # F2P tree ladder (level / xp per log, wiki tree-page infoboxes): -# Tree 1 / 25, Oak 15 / 37.5, Willow 30 / 67.5, Yew 60 / 175. +# Tree 1 / 25, Oak 15 / 37.5, Willow 30 / 67.5, Maple 45 / 100 (F2P at +# Corsair Cove Resource Area ONLY — Dragon Slayer I required to enter), +# Yew 60 / 175. # NOT on the F2P ladder: -# Maple (45 / 100) — members: virtually all maple spots are members-only -# (Seers' Village, Woodcutting Guild, etc.); the single members=No flag -# (Corsair Cove Resource Area) sits inside a members resource area. # Magic (75 / 250) — members. # Pine — the issue draft's "45 Pine" is DISPROVEN: OSRS pine trees # (Shayzien/Varlamore, added 2021) are scenery only and cannot be -# chopped ("Pine tree" wiki page). Level 45 is Maple, not Pine. +# chopped ("Pine tree" wiki page). Level 45 is Maple, not Pine — and +# Maple IS F2P via Corsair Cove, so the tier is optional, not members. +# Willow spots and combat: Draynor Village willows sit between two +# aggressive level-7 Dark wizards that attack players under combat 15 — +# gate the spot to combat 15+; the single willow East of Edgeville (bank +# adjacent) is the lower-combat alternative. actions: - id: a1 step: gate-tier verb: level_is - target: "current woodcutting level" + target: "current woodcutting level (mode + gates)" params: - tiers: ">=60 yew | >=30 willow | >=15 oak | else tree" - spot: "Draynor Village bankside (willows; oaks east of bank; yews at Draynor Manor)" + mode: "xp | income (explicit run parameter; xp is the default)" + tiers: + xp: ">= 45 with Dragon Slayer I done -> maple (or willow) | >= 30 -> willow (combat 15+ : Draynor; else Edgeville) | >= 15 -> oak | else tree" + income: ">= 60 -> yew (Edgeville) | >= 45 with Dragon Slayer I done -> maple | >= 30 -> willow | >= 15 -> oak | else tree" + spot: "per tier — see a2 route table" on_fail: retry anti_ban: delay: [200, 900] @@ -125,8 +156,8 @@ actions: verb: walk_to target: "selected tier tree spot" params: - location: "Draynor Village" - method: [walk] + route: "Tree -> west of Lumbridge Castle (bank: Lumbridge); Oak -> two east of Draynor Village bank (bank: Draynor; alt: south-west of Varrock west bank, bank: Varrock west); Willow -> south-west of Draynor Village bank, combat 15+ only (bank: Draynor) OR the single willow East of Edgeville, combat < 15 (bank: Edgeville); Maple -> Corsair Cove Resource Area, Dragon Slayer I only (bank: Corsair Cove); Yew -> two south of Edgeville bank (bank: Edgeville)" + method: [walk, teleport] on_fail: retry anti_ban: delay: [400, 1600] @@ -136,7 +167,7 @@ actions: verb: click_object target: "tree of the selected tier" params: - object: "tree/oak/willow/yew per tier" + object: "tree/oak/willow/maple/yew per tier" until: "inventory full OR tree fell" on_fail: retry anti_ban: @@ -148,13 +179,24 @@ actions: target: "logs in inventory" params: item: "logs of selected tier" - op: ">= 28 -> bank" + op: ">= 28 -> walk_to_bank" on_fail: skip anti_ban: delay: [150, 700] - id: a5 - step: bank-loop + step: walk-to-bank + verb: walk_to + target: "tier bank from the tree spot" + params: + bank: "Lumbridge (tree) | Draynor (oak / combat-15+ willow) | Edgeville (combat-<15 willow / yew) | Corsair Cove (maple)" + method: [walk] + on_fail: retry + anti_ban: + delay: [400, 1600] + + - id: a6 + step: deposit-logs verb: bank_inventory target: bank params: @@ -162,25 +204,36 @@ actions: withdraw: [] on_fail: alert - - id: a6 + - id: a7 + step: walk-back-to-trees + verb: walk_to + target: "tier tree spot from the bank" + params: + route: "reverse of a5 — back to the same tier spot" + method: [walk] + on_fail: retry + anti_ban: + delay: [400, 1600] + + - id: a8 step: move-on-empty-tree verb: walk_to target: "next tree / adjacent spot" params: - location: "same tier spot or neighbouring tree" + location: "same tier spot or neighbouring tree (per the tier route)" when: "tree fell (no interactable tree at spot)" on_fail: retry anti_ban: delay: [800, 2200] run_control: - iteration: "one inventory of logs (28)" + iteration: "one full chop -> bank -> walk-back cycle (28 logs)" max_iterations: null break: every: [8, 14] # inventories (breaks land at the bank) for: [45, 150] # seconds of idle AFK stop_conditions: - - "woodcutting level >= target" + - "woodcutting level >= target (XP mode) or GP target banked (income mode)" - "3 consecutive empty-tree waits at the spot (world dead? hop or re-route)" - "3 consecutive failed clicks (client issue — do not loop)" @@ -188,12 +241,16 @@ risk: ban_level: low notes: - "Woodcutting is one of the most botted skills; keep chop cadence humanised and bank rhythm varied (anti_ban delays above)." - - "Shared tree spots (Draynor willows): if the spot is crowded for > 60s, hop_world; do not camp a single world for hours." - - "No combat/death vector: tree-fall damage does not exist — relog/respawn-at-0hp handling is N/A for this entry." + - "Draynor willow spot is HOSTILE for accounts under combat 15: two aggressive level-7 Dark wizards attack players below that threshold — the state machine routes sub-15 accounts to the Edgeville willow instead of the Draynor spot. The 'no combat/death vector' claim applies ONLY to the gated routes (Edgeville/Corsair Cove/Lumbridge/Varrock); treat Draynor as a combat zone for low-level accounts." + - "Shared tree spots (Draynor willows, Edgeville yews): if the spot is crowded for > 60s, hop_world; do not camp a single world for hours." + - "Maple route (Corsair Cove Resource Area): quest-gated (Dragon Slayer I) and far from a bank — only select it when the account has completed the quest and the bank trip is acceptable (AFK trade-off per the guide)." sources: - - "OSRS wiki — Woodcutting (axe table: F2P axes Bronze 1, Iron 1, Steel 6, Black 11, Mithril 21, Adamant 31, Rune 41, Gilded 41; 'best axes available in Free-to-Play'; Dragon 61+ members)" - - "OSRS wiki — Tree (members=No, level 1, 25 xp), Oak tree (15, 37.5 xp; F2P locations Draynor/Varrock/Lumbridge), Willow tree (30, 67.5 xp; members=No locations Rimmington/Edgeville/Lumbridge/Draynor Village; falls 50 ticks/30s), Yew tree (60, 175 xp; members=No locations incl. Varrock Palace/Edgeville/Rimmington; falls 190 ticks/114s, respawn 99 ticks/59.4s), Maple tree (45, 100 xp; members), Magic tree (75, 250 xp; members) — infoboxes 2026-08-27" + - "OSRS wiki — Free-to-play Woodcutting training (full tier ladder, 2026-08-27): maple = Corsair Cove Resource Area ONLY for F2P, requires Dragon Slayer I; yew 'earn a profit' but 'recommended to cut willow or maple trees for experience'; 'best place to chop yew trees is south of Edgeville bank'; willow spots 'south-west of the bank in Draynor Village, but beware of aggressive dark wizards' + 'one tree East of Edgeville that is very close to a bank'; oak spots incl. south-west of Varrock west bank / behind Lumbridge General Store / near Port Sarim; drop-vs-bank trade-off" + - "OSRS wiki — Draynor Village (two aggressive level-7 Dark Wizards near the willows 'will attack any players under combat level 15')" + - "OSRS wiki — Woodcutting (axe table: F2P axes Bronze 1, Iron 1, Steel 6, Black 11, Mithril 21, Adamant 31, Rune 41, Gilded 41; 'best axes available in Free-to-play'; Dragon 61+ members)" + - "OSRS wiki — Tree (members=No, level 1, 25 xp), Oak tree (15, 37.5 xp; F2P locations Draynor/Varrock/Lumbridge), Willow tree (30, 67.5 xp; members=No locations Rimmington/Edgeville/Lumbridge/Draynor Village; falls 50 ticks/30s), Maple tree (45, 100 xp; members1=No Corsair Cove Resource Area — only F2P maples; falls 100 ticks/60s), Yew tree (60, 175 xp; members=No locations incl. Varrock Palace/Edgeville; falls 190 ticks/114s, respawn 99 ticks/59.4s), Magic tree (75, 250 xp; members) — infoboxes 2026-08-27" + - "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)" - "OSRS wiki — Rune axe / Black axe / Gilded axe item pages (members = No, verified 2026-08-27)" - "prices.runescape.wiki mapping 2026-08-27 (item ids + GE limits: logs 1511/1521/1519 limit 15000, yew 1515 limit 12000)" From 368734da654e929d1524f7c912963e26d22e3a3b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 17:06:15 -0500 Subject: [PATCH 3/5] codex: fix woodcutting-f2p entry per governor review (bounce fix attempt 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dragon Slayer I removed from accounts.requires_quest (empty for the base entry); it now gates ONLY the optional Maple tier (a1 tier gate, a2 maple route, idle transition) - Dynamic inventory: no hardcoded 28-log assumption — equip the axe only when the Attack requirement is met (rune/gilded = 41), otherwise chop with the axe carried (27 log slots); inventory-full derived dynamically; banking deposits logs ONLY, axe preserved - version 2 -> 3 --- codex/training/woodcutting-f2p.yaml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/codex/training/woodcutting-f2p.yaml b/codex/training/woodcutting-f2p.yaml index bc5b436..d9f8c38 100644 --- a/codex/training/woodcutting-f2p.yaml +++ b/codex/training/woodcutting-f2p.yaml @@ -3,7 +3,7 @@ title: Woodcutting (F2P) domain: training tags: [woodcutting, gathering, f2p, skill-training] status: verified -version: 2 +version: 3 # 3: bounce-fix 2 2026-08-27 — DS I is a Maple-tier gate only (requires_quest empty), dynamic inventory (conditional axe equip, no hardcoded 28) updated: "2026-08-27" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date summary: > @@ -23,18 +23,17 @@ summary: > accounts: membership: f2p min_level: 1 # Woodcutting 1 — normal trees from the start - requires_quest: - - "Dragon Slayer I — ONLY for the optional Maple path (the Corsair Cove Resource Area is quest-gated; the rest of the ladder needs no quest)" + requires_quest: [] # EMPTY for the base entry — Tree/Oak/Willow/Yew need no quest. Dragon Slayer I is NOT an entry-level prerequisite: it gates ONLY the optional level-45 Maple tier (Corsair Cove Resource Area) and is expressed in the a1 tier gate, the a2 maple route, and the idle transition — never in accounts.requires_quest. requires_other: - - "An axe (bronze/iron are free; iron axe ~66 gp at the GE)" + - "An axe. Equip the best-tier F2P axe the account can wield (rune/gilded = 41 Attack to wield); below that, carry the axe in the inventory and chop from the inventory — this works at any level. A carried axe costs ONE log slot (27 log slots), an equipped axe costs none (28 log slots); all inventory-full logic derives the count dynamically, never hardcoded" - "Combat 15+ to use the Draynor Village willow spot (two aggressive level-7 Dark wizards attack players under combat 15); accounts below that use the Edgeville willow instead" - "Bank within a short walk of the tier spot (Draynor, Edgeville, Lumbridge, Varrock, or Corsair Cove bank)" best_practice: equipment: - - item: rune axe (level 41 — best F2P axe) - why: "fastest F2P chop rate; per the wiki axe table Rune axe (41) and Gilded axe (41) are 'the best axes available in Free-to-play'; Dragon axe is members-only. F2P ladder: Bronze(1)/Iron(1)/Steel(6)/Black(11)/Mithril(21)/Adamant(31) — all F2P per the same table" - source: "OSRS wiki — Woodcutting (axe table incl. F2P/members flags) + Rune axe, Black axe, Gilded axe item pages (members = No) + Dragon axe (members = Yes)" + - item: rune axe (41 Attack to wield — best F2P axe) + why: "fastest F2P chop rate; per the wiki axe table Rune axe (41) and Gilded axe (41) are 'the best axes available in Free-to-play'; Dragon axe is members-only. F2P ladder: Bronze(1)/Iron(1)/Steel(6)/Black(11)/Mithril(21)/Adamant(31) — all F2P per the same table. EQUIP GATE: rune/gilded need 41 Attack to wield — equip only when the account meets it, otherwise chop with the axe in the inventory (legal at every level; costs one log slot)" + source: "OSRS wiki — Woodcutting (axe table incl. F2P/members flags) + Rune axe, Black axe, Gilded axe item pages (members = No, 41 Attack requirement) + Dragon axe (members = Yes)" techniques: - name: pick the tier route by level, quests, and combat level why: "per-tier spots and banks (2026-08-27): Tree (1) — west of Lumbridge Castle, bank Lumbridge; Oak (15) — two east of Draynor Village bank, bank Draynor (oaks also south-west of Varrock west bank); Willow (30) — south-west of Draynor Village bank, bank Draynor, GATED to combat 15+ (two aggressive level-7 Dark wizards attack players under combat 15); accounts below that use the single willow East of Edgeville, bank Edgeville; Maple (45) — Corsair Cove Resource Area (4 trees, the only F2P maples; requires Dragon Slayer I; far from a bank, bank Corsair Cove); Yew (60) — south of Edgeville bank (two trees), bank Edgeville" @@ -102,7 +101,7 @@ state_machine: when: "at the tier tree spot" to: chopping - from: chopping - when: "inventory full (item_count logs == 28)" + when: "inventory full (all slots used: 28 logs with the axe equipped, 27 with the axe carried — derive dynamically, never hardcode 28)" to: walk_to_bank - from: chopping when: "tree fell and none interactable at the spot (> 30s of empty-tree state)" @@ -179,7 +178,7 @@ actions: target: "logs in inventory" params: item: "logs of selected tier" - op: ">= 28 -> walk_to_bank" + op: "inventory full -> walk_to_bank — available log slots = inventory capacity minus non-log items (axe equipped = 28, axe carried = 27); never hardcode 28" on_fail: skip anti_ban: delay: [150, 700] @@ -200,7 +199,7 @@ actions: verb: bank_inventory target: bank params: - deposit: [all logs] + deposit: [all logs] # logs ONLY — the axe must stay (equipped or carried); never deposit the axe withdraw: [] on_fail: alert @@ -227,7 +226,7 @@ actions: delay: [800, 2200] run_control: - iteration: "one full chop -> bank -> walk-back cycle (28 logs)" + iteration: "one full chop -> bank -> walk-back cycle (inventory load: 28 logs with the axe equipped, 27 with the axe carried)" max_iterations: null break: every: [8, 14] # inventories (breaks land at the bank) From 86c0c896d848864c177c03011ef20d205e4b41da Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 04:21:31 -0500 Subject: [PATCH 4/5] woodcutting-f2p: three explicit modes + corrected axe model (governor 10323 bounce-fix 3, owner decision 10288) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fast-xp / afk-xp / banked-income modes with deterministic first-match ladders (no 'or'), level-1 fallbacks, pinned activity state - axe model corrected: 41 Woodcutting to USE / 40 Attack to EQUIP; equip only when Attack permits, else carry the same axe — committed axe_examples incl. the 1-Attack/41-WC rune-axe carry case (owner) - full per-mode lifecycle: prereqs, axe acquisition, initial inventory, travel, depletion/respawn, one chop attempt, inventory-full, complete dropping or banking route, breaks independent of incidental banking, recovery branches, completion - per-mode worked examples: prep -> first full-inventory boundary -> first scheduled break -> resume (level-50 and level-70 pinned) - realized rates: logs/hr = model chain (chop x capacity / cycle incl. walk/deposit or drop, x respawn duty x break duty); xp/gp derived and validator-enforced (axe_tiers / axe_examples / woodcutting_modes structured schema blocks + 16 committed selftest fixtures) - wiki-verified axe table 2026-08-28; prices refreshed 2026-08-28 --- codex/training/woodcutting-f2p.yaml | 499 +++++++++++++++++++++------- schemas/entry.schema.json | 89 +++++ tools/validate.py | 380 +++++++++++++++++++-- 3 files changed, 821 insertions(+), 147 deletions(-) diff --git a/codex/training/woodcutting-f2p.yaml b/codex/training/woodcutting-f2p.yaml index d9f8c38..479feb5 100644 --- a/codex/training/woodcutting-f2p.yaml +++ b/codex/training/woodcutting-f2p.yaml @@ -3,149 +3,286 @@ title: Woodcutting (F2P) domain: training tags: [woodcutting, gathering, f2p, skill-training] status: verified -version: 3 # 3: bounce-fix 2 2026-08-27 — DS I is a Maple-tier gate only (requires_quest empty), dynamic inventory (conditional axe equip, no hardcoded 28) -updated: "2026-08-27" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date +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 -summary: > - F2P woodcutting on the verified F2P tree ladder — Tree (1), Oak (15), - Willow (30), Maple (45, optional), Yew (60) — with a per-tier tree and - bank route and an explicit XP-versus-income mode: after level 60 the - guide recommends staying on Willow (or Maple if Dragon Slayer I is done) - for experience and cutting Yew only for profit. Maple IS F2P content at - the Corsair Cove Resource Area (4 trees; requires Dragon Slayer I to - enter); Magic (75) is members-only; there is no choppable pine tree in - OSRS (pines are scenery only) — all verified from the wiki, see the - handoff note. The Draynor willow spot is flanked by two aggressive - level-7 Dark wizards that attack players under combat 15 — this entry - gates that spot to combat 15+ and routes lower-combat accounts to the - Edgeville willow. +summary: >- + F2P woodcutting on the verified F2P tree ladder — Tree (1/25xp), Oak + (15/37.5xp), Willow (30/67.5xp), Maple (45/100xp, optional), Yew + (60/175xp) — with THREE explicit, mutually exclusive run modes (owner + decision 10288, 2026-08-28): `fast-xp` (XP-oriented progression, willow + as the intended fast-XP target), `afk-xp` (quest/location-gated + low-interaction progression, maple only when Dragon Slayer I access is + satisfied), and `banked-income` (banked logs sold at the GE, yew from 60 + when eligible). Each mode has a deterministic first-match ladder (no + parenthesized "or"), a level-1 fallback, a pinned activity state + (mode + tree + spot), a complete start-to-finish lifecycle, and a worked + example covering preparation, the first full-inventory boundary, the + first scheduled break and the resume. Axe model (corrected per governor + 10323 / owner 10288): the account uses the best F2P axe whose + WOODCUTTING level it meets (rune/gilded = 41); it equips it only when + its separate ATTACK level is met (rune/gilded = 40), otherwise it + carries that same axe (costing one inventory slot — 27 log slots vs 28 + when equipped). RATES are realized, not idealized: every mode's + logs/hour equals its documented model chain (chop cadence x capacity / + cycle incl. walking/depositing or dropping, x respawn/switch duty x + break duty), and xp/hour and gp/hour derive from it — committed as + structured fixtures in tools/validate.py. Verified 2026-08-28 (wiki + axe/tree pages + prices.runescape.wiki latest). accounts: membership: f2p min_level: 1 # Woodcutting 1 — normal trees from the start - requires_quest: [] # EMPTY for the base entry — Tree/Oak/Willow/Yew need no quest. Dragon Slayer I is NOT an entry-level prerequisite: it gates ONLY the optional level-45 Maple tier (Corsair Cove Resource Area) and is expressed in the a1 tier gate, the a2 maple route, and the idle transition — never in accounts.requires_quest. + requires_quest: [] # EMPTY for the base entry — Tree/Oak/Willow/Yew need no quest. Dragon Slayer I is NOT an entry-level prerequisite: it gates ONLY the afk-xp maple row (Corsair Cove Resource Area), expressed in woodcutting_modes and the a1 ladder — never in accounts.requires_quest. requires_other: - - "An axe. Equip the best-tier F2P axe the account can wield (rune/gilded = 41 Attack to wield); below that, carry the axe in the inventory and chop from the inventory — this works at any level. A carried axe costs ONE log slot (27 log slots), an equipped axe costs none (28 log slots); all inventory-full logic derives the count dynamically, never hardcoded" - - "Combat 15+ to use the Draynor Village willow spot (two aggressive level-7 Dark wizards attack players under combat 15); accounts below that use the Edgeville willow instead" - - "Bank within a short walk of the tier spot (Draynor, Edgeville, Lumbridge, Varrock, or Corsair Cove bank)" + - "An axe. The account USES the best F2P axe whose Woodcutting level it meets (bronze 1 / iron 1 / steel 6 / black 11 / mithril 21 / adamant 31 / rune 41 / gilded 41 — verified wiki table 2026-08-28); it EQUIPS it only when its separate Attack level is met (steel 5 / black 10 / mithril 20 / adamant 30 / rune 40 / gilded 40), otherwise it CARRIES that same axe and chops from the inventory (legal at every level). A carried axe costs ONE log slot (27 log slots), an equipped axe costs none (28 log slots); inventory-full logic derives the count dynamically, never hardcoded." + - "Combat 15+ to use the Draynor Village willow spot (two aggressive level-7 Dark wizards attack players under combat 15); accounts below that use the Edgeville willow instead (fast-xp and banked-income ladders both carry the combat split as two deterministic rows)" + - "For the afk-xp maple row only: Dragon Slayer I completed (Corsair Cove Resource Area entrance gate); without it the mode's ladder falls back to willow" + - "Bank within a short walk of the tier spot (Draynor, Edgeville, Lumbridge, or Corsair Cove bank); rune axe one-time cost ~7.1-7.3k gp (GE live 2026-08-28) if not already owned" + +axe_tiers: + # Canonical table (OSRS wiki axe pages, raw wikitext verified 2026-08-28): + # woodcutting_level = tool-USE requirement; attack_level = EQUIP requirement. + # tools/validate.py enforces this table exactly and recomputes axe_examples. + - {axe: bronze, woodcutting_level: 1, attack_level: 1, members: false} + - {axe: iron, woodcutting_level: 1, attack_level: 1, members: false} + - {axe: steel, woodcutting_level: 6, attack_level: 5, members: false} + - {axe: black, woodcutting_level: 11, attack_level: 10, members: false} + - {axe: mithril, woodcutting_level: 21, attack_level: 20, members: false} + - {axe: adamant, woodcutting_level: 31, attack_level: 30, members: false} + - {axe: rune, woodcutting_level: 41, attack_level: 40, members: false} + - {axe: gilded, woodcutting_level: 41, attack_level: 40, members: false} + - {axe: dragon, woodcutting_level: 61, attack_level: 60, members: true} + +axe_examples: + # Committed regression for the equip-vs-carry rule (owner coverage 10288: + # "a 1-Attack/41-Woodcutting account carries and uses a rune axe rather + # than selecting a lower axe"). tools/validate.py recomputes each row. + - {account: "1-Attack / 41-Woodcutting (owner coverage)", woodcutting_level: 41, attack_level: 1, expected_axe: rune, expected_equipped: false} + - {account: "40-Attack / 41-Woodcutting", woodcutting_level: 41, attack_level: 40, expected_axe: rune, expected_equipped: true} + - {account: "40-Attack / 30-Woodcutting (below adamant 31)", woodcutting_level: 30, attack_level: 40, expected_axe: mithril, expected_equipped: true} + - {account: "5-Attack / 31-Woodcutting (adamant usable, not equippable)", woodcutting_level: 31, attack_level: 5, expected_axe: adamant, expected_equipped: false} + - {account: "1-Attack / 1-Woodcutting", woodcutting_level: 1, attack_level: 1, expected_axe: iron, expected_equipped: true} + - {account: "99-Attack / 99-Woodcutting (F2P top; dragon is members-only)", woodcutting_level: 99, attack_level: 99, expected_axe: rune, expected_equipped: true} + +woodcutting_modes: + # Deterministic first-match ladders, ordered min_level DESC. The selected + # mode AND tree are pinned in activity state by a1 (never decided ad hoc). + - mode: fast-xp + intent: >- + Maximum Woodcutting XP per hour on the F2P ladder. Willow is the + intended fast-XP target from level 30 (67.5 xp/log, close bank loop, + high chop cadence); oak covers 15-29 and normal trees below 15. The + ladder intentionally does NOT switch to yew at 60 — the F2P guide's + XP recommendation is willow (fast-xp stays on willow; yew is the + banked-income target). + inventory_policy: bank + ladder: + - {tree: willow, min_level: 30, gate: combat-15-plus, spot: draynor-willows, bank: draynor} + - {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} + 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 + 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 + xp_per_hour: 41985 # 622 x 67.5 (willow xp/log, wiki infobox) + gp_per_hour: 13062 # 622 x 21 (willow log sell, prices.runescape.wiki latest 2026-08-28) + log_price: 21 + price_verified: "2026-08-28" + fallback: "below level 30 -> oak (15+) then normal trees (1+); below combat 15 the Draynor willow row is replaced by the Edgeville willow row (same min_level, different gate)" + + - mode: afk-xp + intent: >- + Low-interaction, quest/location-gated progression: maple at Corsair + Cove Resource Area (100 xp/log, 4 trees, Dragon Slayer I gate) with + dropping instead of banking — the fewest interactions per hour of the + three modes. Maple is selected ONLY when level 45+ AND the quest gate + is satisfied; otherwise the ladder falls back through willow/oak/tree. + 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} + 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 + 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) + log_price: 12 + price_verified: "2026-08-28" + fallback: "below 45 or without Dragon Slayer I -> willow (30) then oak (15) then tree (1); never select maple without the quest gate" + + - mode: banked-income + intent: >- + Banked logs sold at the GE: yew from level 60 (175 xp/log, 111 gp/log + live 2026-08-28, Edgeville south spot ~8s from the bank) when + eligible, with deterministic lower fallbacks (willow/oak/tree banked) + below 60. This is the income mode — the mode split implements the F2P + guide's "cut willow/maple for experience, yew for profit". + inventory_policy: bank + ladder: + - {tree: yew, min_level: 60, gate: none, spot: edgeville-south-yews, bank: edgeville} + - {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} + 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 + 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" best_practice: equipment: - - item: rune axe (41 Attack to wield — best F2P axe) - why: "fastest F2P chop rate; per the wiki axe table Rune axe (41) and Gilded axe (41) are 'the best axes available in Free-to-play'; Dragon axe is members-only. F2P ladder: Bronze(1)/Iron(1)/Steel(6)/Black(11)/Mithril(21)/Adamant(31) — all F2P per the same table. EQUIP GATE: rune/gilded need 41 Attack to wield — equip only when the account meets it, otherwise chop with the axe in the inventory (legal at every level; costs one log slot)" - source: "OSRS wiki — Woodcutting (axe table incl. F2P/members flags) + Rune axe, Black axe, Gilded axe item pages (members = No, 41 Attack requirement) + Dragon axe (members = Yes)" + - item: "rune axe (best F2P axe: 41 Woodcutting to USE, 40 Attack to EQUIP)" + why: "fastest F2P chop rate; per the wiki axe table Rune axe (41) and Gilded axe (41) are 'the best axes available in Free-to-play' (dragon is members-only). F2P ladder: Bronze(1)/Iron(1)/Steel(6)/Black(11)/Mithril(21)/Adamant(31)/Rune(41)/Gilded(41) — the number is the WOODCUTTING level to use the axe for chopping, NOT an Attack gate. EQUIP is a separate Attack requirement (rune/gilded 40): equip only when the account meets it, otherwise carry that same axe and chop from the inventory — legal at every level, costs one log slot. Never select a lower axe because the Attack level is low." + source: "OSRS wiki — Woodcutting (axe table incl. F2P/members flags) + Rune axe / Gilded axe / Black axe item pages raw wikitext 2026-08-28 ('Level 41 Woodcutting is required to use this axe' / 'Attack level of 40 to wield this axe'; gilded identical; black: 11 to use, 10 to equip; dragon: 61/60 members); governor review comment 10248 + owner decision 10288 (carry-and-use rule)" techniques: - - name: pick the tier route by level, quests, and combat level - why: "per-tier spots and banks (2026-08-27): Tree (1) — west of Lumbridge Castle, bank Lumbridge; Oak (15) — two east of Draynor Village bank, bank Draynor (oaks also south-west of Varrock west bank); Willow (30) — south-west of Draynor Village bank, bank Draynor, GATED to combat 15+ (two aggressive level-7 Dark wizards attack players under combat 15); accounts below that use the single willow East of Edgeville, bank Edgeville; Maple (45) — Corsair Cove Resource Area (4 trees, the only F2P maples; requires Dragon Slayer I; far from a bank, bank Corsair Cove); Yew (60) — south of Edgeville bank (two trees), bank Edgeville" - source: "OSRS wiki — Free-to-play Woodcutting training (level-45 Maple path + Corsair Cove Resource Area; willow spots incl. 'beware of aggressive dark wizards'; yew recommendation 'south of Edgeville bank'); Draynor Village page (two level-7 dark wizards attack players under combat 15); Oak tree / Willow tree / Maple tree / Yew tree location lists — all 2026-08-27" - - name: XP mode vs income mode after level 60 - why: "the F2P guide: yew logs 'earn a profit' but it 'is recommended to cut willow or maple trees for experience and use the time saved to earn' — so after 60 the entry stays on Willow (or Maple with Dragon Slayer I) for XP and switches to Yew only in income mode" - source: "OSRS wiki — Free-to-play Woodcutting training (Levels 60-99: Yew trees section, 2026-08-27)" - - name: bank logs, don't drop - why: "logs are F2P-tradeable income: live sell prices 2026-08-27 — willow 21, yew 112 per log (GE limit 15k / 12k per 4h); a 28-log inventory of willows banks ~590 gp. (Dropping is faster XP per the guide, but this entry models the banked/income variant; the XP/income mode switch covers the trade-off.)" - source: "prices.runescape.wiki latest 2026-08-27 (willow 1519 sell 21 / buy 20; yew 1515 sell 112 / buy 111; limits 15000/12000); OSRS wiki — Free-to-play Woodcutting training (drop-vs-bank trade-off)" - - name: tier progression for XP - why: "per-log XP (wiki infoboxes): Tree 25, Oak 37.5, Willow 67.5, Maple 100, Yew 175 — climb to the highest unlocked F2P tier; Willow (30) is the practical XP/hr tier through 60+, Maple (45) is an optional 100-xp AFK-ish tier when Dragon Slayer I is done, Yew (60) is the income tier" - source: "OSRS wiki — Tree/Oak tree/Willow tree/Maple tree/Yew tree pages (level + xp infoboxes, 2026-08-27)" + - name: pick the mode first, then the deterministic ladder + why: "Owner decision 10288: run exactly ONE of fast-xp / afk-xp / banked-income; the ladder inside the mode decides the tree by level + quest/combat gates (first match, min_level DESC — no parenthesized 'or'), and the chosen mode + tree are pinned in activity state. fast-xp never silently becomes income; afk-xp never runs maple without the quest gate; banked-income never claims yew below 60." + source: "owner-guidance comment 10288 (2026-08-28); governor 10248 (deterministic tier field)" + - name: per-tier route table (spots and banks, wiki 2026-08-27/28) + why: "Tree (1) — west of Lumbridge Castle, bank Lumbridge; Oak (15) — two east of Draynor Village bank (alt: south-west of Varrock west bank), bank Draynor; Willow (30) — south-west of Draynor Village bank GATED to combat 15+ (two aggressive level-7 Dark wizards attack under combat 15), sub-15 accounts use the single willow East of Edgeville, bank Edgeville; Maple (45) — Corsair Cove Resource Area (4 trees, only F2P maples, requires Dragon Slayer I), far from a bank; Yew (60) — two south of Edgeville bank, bank Edgeville." + source: "OSRS wiki — Free-to-play Woodcutting training + Draynor Village + tree location lists (2026-08-27, re-verified 2026-08-28)" + - name: drop versus bank is a MODE property, not an ad-hoc choice + why: "fast-xp banks (Draynor loop keeps the bank close and income attached); afk-xp DROPS (Corsair Cove is far from a bank — the guide's AFK trade-off; no bank legs at all); banked-income banks and sells (yew 111 gp/log live). Each mode's rate chain includes its own dropping or banking route (model fields + validator chain)." + source: "owner-guidance comment 10288 (complete dropping or banking route per mode); OSRS wiki — Free-to-play Woodcutting training (drop-vs-bank trade-off)" + - name: bank logs, keep the axe + why: "Logs are F2P-tradeable income: live sell prices 2026-08-28 — willow 21, oak 40, maple 12, yew 111 per log (GE limits 15k/15k/15k/12k per 4h — none binding at modeled rates). deposit_logs deposits logs ONLY — the axe is never deposited (equipped or carried)." + source: "prices.runescape.wiki latest 2026-08-28" + - name: rates are realized, not idealized + why: "Every mode's rates come from its documented model chain: capacity / cycle (chop seconds x capacity + walking + depositing, or + dropping) x 3600 x respawn/switch duty x break duty — including travel, tree respawn, dropping/banking and mandatory breaks. The validator recomputes the chain within 1% and derives xp/gp from it; no hidden 'chop loop only' numbers." + source: "owner-guidance comment 10288 (validators and rate claims must include travel, respawn, dropping or banking, and mandatory breaks)" economy: - # Verified 2026-08-27 against prices.runescape.wiki latest + - # OSRS wiki tree-page infoboxes. Logs are pure income (no per-cycle cost; - # axe is one-time equipment). Anchor = Draynor Village willows (combat 15+). - item: Willow logs # example anchor; highest practical F2P XP/hr tier + # Verified 2026-08-28 against prices.runescape.wiki latest + OSRS wiki + # tree-page infoboxes. Logs are pure income (no per-cycle cost; axe is + # one-time equipment). Anchor = fast-xp mode (Draynor willows, combat 15+). + item: Willow logs item_id: 1519 cost_per_cycle: 0 # no supplies consumed; axe cost amortised - income_per_cycle: 21 # willow log sell price (latest.high) + income_per_cycle: 21 # willow log sell price (latest.high 2026-08-28) net_per_cycle: 21 - gp_per_hour_low: 14000 # est. ~700 logs/hr x 21 (see hourly-rate note) - gp_per_hour_high: 21000 # est. ~1000 logs/hr x 21 - xp_per_hour: 60000 # est. ~900 willow logs/hr x 67.5 xp (see note) + gp_per_hour_low: 13100 # fast-xp realized banked income: 622 logs/hr x 21 (model chain, 2026-08-28) + gp_per_hour_high: 23800 # banked-income realized: 214 yew logs/hr x 111 (model chain, 2026-08-28) + xp_per_hour: 42000 # fast-xp realized: 41,985 (622 willow logs/hr x 67.5; see woodcutting_modes) supplies: [] profit_drivers: - - "Logs are the income: sell at the GE (willow 21, oak 42, yew 112 per log — live 2026-08-27; all F2P-tradeable with 12k-15k/4h buy limits)" - - "EXPLICIT MODE SWITCH (state machine + a1): XP mode = Willow (30) through 60+ (Maple 45 with Dragon Slayer I); income mode = Yew (60) at Edgeville. Default is XP mode; income mode must be an explicit run parameter, never the unstated default" - - "Maple (45, Dragon Slayer I, Corsair Cove Resource Area) is an optional F2P tier: 100 xp/log, 4 trees, far from a bank — the guide notes it is more AFK but lower XP/hr than willows" - - "Hourly figures are engineering estimates (wiki per-log XP x assumed chop rate) — re-verify against live runs; the wiki-cited facts are per-log XP and live log prices" + - "Logs are the income: sell at the GE (willow 21, oak 40, maple 12, yew 111 per log — live 2026-08-28; all F2P-tradeable with 12k-15k/4h buy limits, none binding at modeled rates)" + - "THREE explicit modes (owner decision 10288), each with a deterministic ladder and realized rates — fast-xp 622 logs/hr -> 41,985 xp/hr (willow, banked); afk-xp 386 logs/hr -> 38,600 xp/hr (maple @45 + Dragon Slayer I, dropped); banked-income 214 logs/hr -> 37,450 xp/hr and 23,754 gp/hr (yew @60, banked)" + - "Mode split implements the F2P guide: willow/maple for experience, yew for profit — fast-xp stays on willow past 60; banked-income takes yew at 60+" + - "Maple (45, Dragon Slayer I, Corsair Cove Resource Area) is an optional AFK F2P tier: 100 xp/log, 4 trees, far from a bank — more AFK, lower xp/hr than willows (modeled accordingly)" + - "Axe economics: best F2P axe by Woodcutting level (rune from 41); one-time GE cost ~7.1-7.3k gp (2026-08-28); equip only when Attack permits, else carry (one log slot)" state_machine: initial: idle terminal: [complete, failed] states: idle: - goal: pick the F2P tier by mode + level + quest/combat gates and arm the run - next: [walk_to_trees, complete] + goal: "logged in at a bank with an axe available (bank or inventory); choose run mode (explicit run parameter) and confirm prerequisites" + next: [arm_run, complete] + arm_run: + goal: "PIN the run in activity state: mode (fast-xp | afk-xp | banked-income), tree, spot, bank, axe, axe_equipped (true/false) — decided once by the a1 ladder, never changed mid-run; re-arm only on fallback events" + next: [walk_to_trees, idle] walk_to_trees: - goal: walk the tier route from the bank to the tier's tree spot + goal: "walk the pinned tier route from the bank to the pinned tree spot" next: [chopping, idle] chopping: - goal: chop the tier tree until inventory full or the tree falls - next: [walk_to_bank, walk_to_trees, complete, failed] + 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" + 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" + next: [chopping, walk_to_trees] walk_to_bank: - goal: walk the tier route from the tree spot to the tier's bank + goal: "walk the pinned route from the tree spot to the pinned bank (bank modes only)" next: [deposit_logs] deposit_logs: - goal: deposit all logs at the bank + goal: "deposit ALL logs (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 tier's tree spot + goal: "walk back from the bank to the pinned tree spot" next: [chopping, idle] complete: - goal: reached the run's XP/level or GP target + goal: "XP/level target or GP target reached — collect and stop" failed: - goal: hard failure — stop, log ERROR sentinel, alert + goal: "hard failure — stop, log ERROR sentinel, alert" transitions: - from: idle - when: "arm tier: XP mode -> tree (<15) / oak (<30) / willow (30+, combat 15+ at Draynor or Edgeville below) / maple (45+ AND Dragon Slayer I done) / willow again after 60; income mode -> yew (60+) — see a1" + 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)" + 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 + - 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: walk_to_trees - when: "at the tier tree spot" + when: "at the pinned tree spot" to: chopping - from: chopping - when: "inventory full (all slots used: 28 logs with the axe equipped, 27 with the axe carried — derive dynamically, never hardcode 28)" + when: "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" to: walk_to_bank - from: chopping - when: "tree fell and none interactable at the spot (> 30s of empty-tree state)" + when: "tree fell and no interactable tree at the spot (> 30s empty) — move to the next tree or hop_world" to: walk_to_trees + - from: drop_logs + when: "load dropped — inventory empty" + to: chopping - from: walk_to_bank - when: "at the tier bank" + when: "at the pinned bank" to: deposit_logs - from: deposit_logs - when: "logs deposited" + when: "logs deposited (axe still present)" to: walk_back - from: walk_back - when: "at the tier tree spot" + when: "at the pinned tree spot" to: chopping + - from: walk_back + when: "spot unavailable on return (crowded > 60s or invalid access) — re-arm on the next ladder row (fallback)" + to: idle - from: idle - when: "woodcutting level >= target (XP mode: 99 or target; income mode: target GP banked)" + when: "woodcutting level >= target (fast-xp / afk-xp) or GP target banked (banked-income)" to: complete -# --- handoff note (wiki-verified 2026-08-27, do not copy from other entries) --- -# F2P tree ladder (level / xp per log, wiki tree-page infoboxes): -# Tree 1 / 25, Oak 15 / 37.5, Willow 30 / 67.5, Maple 45 / 100 (F2P at -# Corsair Cove Resource Area ONLY — Dragon Slayer I required to enter), -# Yew 60 / 175. -# NOT on the F2P ladder: -# Magic (75 / 250) — members. -# Pine — the issue draft's "45 Pine" is DISPROVEN: OSRS pine trees -# (Shayzien/Varlamore, added 2021) are scenery only and cannot be -# chopped ("Pine tree" wiki page). Level 45 is Maple, not Pine — and -# Maple IS F2P via Corsair Cove, so the tier is optional, not members. -# Willow spots and combat: Draynor Village willows sit between two -# aggressive level-7 Dark wizards that attack players under combat 15 — -# gate the spot to combat 15+; the single willow East of Edgeville (bank -# adjacent) is the lower-combat alternative. - actions: - id: a1 - step: gate-tier + step: arm-run verb: level_is - target: "current woodcutting level (mode + gates)" + target: "woodcutting level (mode ladder)" params: - mode: "xp | income (explicit run parameter; xp is the default)" - tiers: - xp: ">= 45 with Dragon Slayer I done -> maple (or willow) | >= 30 -> willow (combat 15+ : Draynor; else Edgeville) | >= 15 -> oak | else tree" - income: ">= 60 -> yew (Edgeville) | >= 45 with Dragon Slayer I done -> maple | >= 30 -> willow | >= 15 -> oak | else tree" - spot: "per tier — see a2 route table" + 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)" + 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] @@ -153,110 +290,218 @@ actions: - id: a2 step: walk-to-trees verb: walk_to - target: "selected tier tree spot" + target: "pinned tier tree spot" params: - route: "Tree -> west of Lumbridge Castle (bank: Lumbridge); Oak -> two east of Draynor Village bank (bank: Draynor; alt: south-west of Varrock west bank, bank: Varrock west); Willow -> south-west of Draynor Village bank, combat 15+ only (bank: Draynor) OR the single willow East of Edgeville, combat < 15 (bank: Edgeville); Maple -> Corsair Cove Resource Area, Dragon Slayer I only (bank: Corsair Cove); Yew -> two south of Edgeville bank (bank: Edgeville)" + route: "tree -> west of Lumbridge Castle (bank lumbridge); oak -> two east of Draynor Village bank (bank draynor); willow -> south-west of Draynor Village bank, combat 15+ only (bank draynor) OR the single willow East of Edgeville, combat under 15 (bank edgeville); maple -> Corsair Cove Resource Area, Dragon Slayer I gate (no bank — drop mode); yew -> two south of Edgeville bank (bank edgeville)" method: [walk, teleport] on_fail: retry anti_ban: delay: [400, 1600] - id: a3 - step: chop-tree - verb: click_object - target: "tree of the selected tier" + step: equip-or-carry-axe + verb: right_click + target: "best axe in inventory (decided by Woodcutting level)" params: - object: "tree/oak/willow/maple/yew per tier" - until: "inventory full OR tree fell" + rule: "equip (menu_option 'Wield ') 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 '" + capacity: "equipped: 28 log slots; carried: 27 (one slot for the axe)" on_fail: retry anti_ban: - delay: [900, 2400] # human chop rhythm; yew fall = 114s, willow = 30s + 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 + 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" + on_fail: retry + anti_ban: + delay: [900, 2400] + + - id: a6 step: check-inventory verb: item_count target: "logs in inventory" params: - item: "logs of selected tier" - op: "inventory full -> walk_to_bank — available log slots = inventory capacity minus non-log items (axe equipped = 28, axe carried = 27); never hardcode 28" + 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" on_fail: skip anti_ban: delay: [150, 700] - - id: a5 + - id: a7 + 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" + on_fail: retry + anti_ban: + delay: [200, 800] + + - id: a8 step: walk-to-bank verb: walk_to - target: "tier bank from the tree spot" + target: "pinned bank from the tree spot" params: - bank: "Lumbridge (tree) | Draynor (oak / combat-15+ willow) | Edgeville (combat-<15 willow / yew) | Corsair Cove (maple)" + bank: "lumbridge (tree) | draynor (oak / combat-15+ willow) | edgeville (combat-under-15 willow / yew)" method: [walk] on_fail: retry anti_ban: delay: [400, 1600] - - id: a6 + - id: a9 step: deposit-logs verb: bank_inventory target: bank params: - deposit: [all logs] # logs ONLY — the axe must stay (equipped or carried); never deposit the axe + 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" on_fail: alert - - id: a7 + - id: a10 step: walk-back-to-trees verb: walk_to - target: "tier tree spot from the bank" + target: "pinned tree spot from the bank" params: - route: "reverse of a5 — back to the same tier spot" + route: "reverse of a8 — 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: a8 - step: move-on-empty-tree - verb: walk_to - target: "next tree / adjacent spot" + - id: a11 + step: move-or-hop-empty + verb: hop_world + target: "current spot" params: - location: "same tier spot or neighbouring tree (per the tier route)" - when: "tree fell (no interactable tree at spot)" + when: "no interactable tree for > 30s at the pinned spot, or the spot is crowded for > 60s (shared spots: Draynor willows, Edgeville yews)" + then: "move to the next tree at the same spot (walk_to), then hop_world if the spot is still empty for > 60s" on_fail: retry anti_ban: delay: [800, 2200] + - id: a12 + 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" + on_fail: skip + run_control: - iteration: "one full chop -> bank -> walk-back cycle (inventory load: 28 logs with the axe equipped, 27 with the axe carried)" + iteration: "one full inventory cycle: chop load -> (drop at the spot) or (bank + walk back) -> resume chopping the pinned tree; cycle length = 28 x chop_seconds_per_log + drop or bank legs per woodcutting_modes.model" max_iterations: null break: - every: [8, 14] # inventories (breaks land at the bank) - for: [45, 150] # seconds of idle AFK + every: [8, 14] # inventory cycles (breaks land at the bank in bank modes, at the spot in drop mode — INDEPENDENT of incidental banking) + for: [45, 150] # seconds of idle AFK stop_conditions: - - "woodcutting level >= target (XP mode) or GP target banked (income mode)" - - "3 consecutive empty-tree waits at the spot (world dead? hop or re-route)" + - "woodcutting level >= target (fast-xp / afk-xp) or GP target banked (banked-income)" + - "axe lost and unobtainable at bank + GE (recovery exhausted)" + - "3 consecutive empty-tree waits after hop_world (world dead or spot gone — stop and report)" - "3 consecutive failed clicks (client issue — do not loop)" + # ================= WORKED EXAMPLES (owner 10288: each mode = preparation -> first + # full-inventory boundary -> first scheduled break -> resume) ================= + # Rates below are the model-chain values from woodcutting_modes (validator-pinned). + # + # --- 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). + # 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 + # THE BANK (post-deposit, inventory empty, axe safe) — a scheduled break, + # independent of the incidental per-load banking. Resume: a10 walk back, + # chopping continues from the pinned state. + # Session target 250k xp: ~6h of cycles + 6-7 breaks -> complete. + # + # --- afk-xp @ level 50 with Dragon Slayer I (pinned level-50 example) --- + # Account: 50 Woodcutting, 1 Attack, Dragon Slayer I done. Mode afk-xp, + # ladder first match = maple@45 dragon-slayer-i -> Corsair Cove Resource + # Area. a1 pins {mode afk-xp, tree maple, spot corsair-cove-resource-area, + # bank none-drop, axe rune, axe_equipped FALSE}. 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 + # 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. + # WITHOUT Dragon Slayer I the same account falls back to willow@30 + # (combat-15-plus) — deterministic, logged, never a silent maple. + # + # --- 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. + # 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). + # ====================================================================== + risk: ban_level: low notes: - - "Woodcutting is one of the most botted skills; keep chop cadence humanised and bank rhythm varied (anti_ban delays above)." - - "Draynor willow spot is HOSTILE for accounts under combat 15: two aggressive level-7 Dark wizards attack players below that threshold — the state machine routes sub-15 accounts to the Edgeville willow instead of the Draynor spot. The 'no combat/death vector' claim applies ONLY to the gated routes (Edgeville/Corsair Cove/Lumbridge/Varrock); treat Draynor as a combat zone for low-level accounts." - - "Shared tree spots (Draynor willows, Edgeville yews): if the spot is crowded for > 60s, hop_world; do not camp a single world for hours." - - "Maple route (Corsair Cove Resource Area): quest-gated (Dragon Slayer I) and far from a bank — only select it when the account has completed the quest and the bank trip is acceptable (AFK trade-off per the guide)." + - "Woodcutting is one of the most botted skills; keep chop cadence humanised and bank rhythm varied (anti_ban delays + per-mode chop_seconds_per_log)." + - "Draynor willow spot is HOSTILE for accounts under combat 15: two aggressive level-7 Dark wizards attack players below that threshold — the ladder routes sub-15 accounts to the Edgeville willow via a separate deterministic row (combat-under-15). The no-combat claim applies ONLY to the gated routes; treat Draynor as a combat zone for low-level accounts." + - "Shared tree spots (Draynor willows, Edgeville yews): crowded > 60s -> hop_world (a11); do not camp a single world for hours." + - "Maple route (Corsair Cove Resource Area): quest-gated (Dragon Slayer I) and far from a bank — afk-xp only, drop mode; never select it without the quest gate (ladder + state machine enforce)." + - "The axe always stays with the account: deposit_logs deposits logs ONLY; an axe is never deposited, alched, or traded away mid-run. Losing the axe is a stop-and-report event, not a silent downgrade." + - "Mode discipline is the anti-detection edge: one pinned mode per run, deterministic ladder, explicit fallbacks — no mid-run mode flips." sources: - - "OSRS wiki — Free-to-play Woodcutting training (full tier ladder, 2026-08-27): maple = Corsair Cove Resource Area ONLY for F2P, requires Dragon Slayer I; yew 'earn a profit' but 'recommended to cut willow or maple trees for experience'; 'best place to chop yew trees is south of Edgeville bank'; willow spots 'south-west of the bank in Draynor Village, but beware of aggressive dark wizards' + 'one tree East of Edgeville that is very close to a bank'; oak spots incl. south-west of Varrock west bank / behind Lumbridge General Store / near Port Sarim; drop-vs-bank trade-off" + - "OSRS wiki — Free-to-play Woodcutting training (full tier ladder, 2026-08-27 re-verified 2026-08-28): maple = Corsair Cove Resource Area ONLY for F2P, requires Dragon Slayer I; yew 'earn a profit' but 'recommended to cut willow or maple trees for experience'; 'best place to chop yew trees is south of Edgeville bank'; willow spots 'south-west of the bank in Draynor Village, but beware of aggressive dark wizards' + 'one tree East of Edgeville that is very close to a bank'; oak spots incl. south-west of Varrock west bank; drop-vs-bank trade-off" - "OSRS wiki — Draynor Village (two aggressive level-7 Dark Wizards near the willows 'will attack any players under combat level 15')" - "OSRS wiki — Woodcutting (axe table: F2P axes Bronze 1, Iron 1, Steel 6, Black 11, Mithril 21, Adamant 31, Rune 41, Gilded 41; 'best axes available in Free-to-play'; Dragon 61+ members)" - - "OSRS wiki — Tree (members=No, level 1, 25 xp), Oak tree (15, 37.5 xp; F2P locations Draynor/Varrock/Lumbridge), Willow tree (30, 67.5 xp; members=No locations Rimmington/Edgeville/Lumbridge/Draynor Village; falls 50 ticks/30s), Maple tree (45, 100 xp; members1=No Corsair Cove Resource Area — only F2P maples; falls 100 ticks/60s), Yew tree (60, 175 xp; members=No locations incl. Varrock Palace/Edgeville; falls 190 ticks/114s, respawn 99 ticks/59.4s), Magic tree (75, 250 xp; members) — infoboxes 2026-08-27" + - "OSRS wiki axe pages raw wikitext, verified 2026-08-28: Rune axe 'Level 41 Woodcutting is required to use this axe' + 'Attack level of 40 to wield this axe'; Gilded axe 'Attack level of 40 to wield ... Woodcutting level of 41 to use'; Black axe 'requires level 11 Woodcutting. Level 10 Attack is required to equip'; Dragon axe 'requiring 60 Attack to wield' and '61 Woodcutting to use' (members) — the Woodcutting number is the USE requirement, Attack is the EQUIP requirement" + - "OSRS wiki — Tree (members=No, level 1, 25 xp), Oak tree (15, 37.5 xp), Willow tree (30, 67.5 xp; falls 50 ticks/30s), Maple tree (45, 100 xp; members1=No Corsair Cove Resource Area — only F2P maples; falls 100 ticks/60s), Yew tree (60, 175 xp; falls 190 ticks/114s, respawn 99 ticks/59.4s) — infoboxes 2026-08-27/28" - "OSRS wiki — Corsair Cove Resource Area ('requires completion of Dragon Slayer I to enter'; 4 maple trees + 3 yew trees; only F2P maple location)" - "OSRS wiki — Pine tree (scenery in Shayzien/Varlamore, CANNOT be chopped — disproves any '45 Pine' tier)" - - "OSRS wiki — Rune axe / Black axe / Gilded axe item pages (members = No, verified 2026-08-27)" - - "prices.runescape.wiki mapping 2026-08-27 (item ids + GE limits: logs 1511/1521/1519 limit 15000, yew 1515 limit 12000)" - - "prices.runescape.wiki latest 2026-08-27 (live buy/sell: willow 20/21, oak 40/42, yew 111/112, rune axe buy 7470)" - - "Per-log XP and hourly estimate: hourly = wiki per-log XP x assumed chop rate (700-1000 logs/hr; flagged as estimate — re-verify against live runs)" -verified: "2026-08-27" # keep quoted (YAML 1.1 date parsing) + - "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)" + - "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" +verified: "2026-08-28" # keep quoted (YAML 1.1 date parsing); prices + axe pages re-verified live 2026-08-28 related: - low-alch-f2p - f2p-alch-catalog + - flip-candidates diff --git a/schemas/entry.schema.json b/schemas/entry.schema.json index c50ce9b..b889189 100644 --- a/schemas/entry.schema.json +++ b/schemas/entry.schema.json @@ -96,6 +96,95 @@ } }, + "axe_tiers": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["axe", "woodcutting_level", "attack_level", "members"], + "additionalProperties": false, + "properties": { + "axe": { "type": "string" }, + "woodcutting_level": { "type": "integer", "minimum": 1, "maximum": 99 }, + "attack_level": { "type": "integer", "minimum": 1, "maximum": 99 }, + "members": { "type": "boolean" } + } + } + }, + "axe_examples": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["account", "woodcutting_level", "attack_level", "expected_axe", "expected_equipped"], + "additionalProperties": false, + "properties": { + "account": { "type": "string" }, + "woodcutting_level": { "type": "integer", "minimum": 1, "maximum": 99 }, + "attack_level": { "type": "integer", "minimum": 1, "maximum": 99 }, + "expected_axe": { "type": "string" }, + "expected_equipped": { "type": "boolean" } + } + } + }, + "woodcutting_modes": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { + "type": "object", + "required": ["mode", "intent", "inventory_policy", "ladder", "fallback", "model", "rates"], + "additionalProperties": false, + "properties": { + "mode": { "enum": ["fast-xp", "afk-xp", "banked-income"] }, + "intent": { "type": "string", "minLength": 10 }, + "inventory_policy": { "enum": ["bank", "drop"] }, + "fallback": { "type": "string", "minLength": 3 }, + "ladder": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["tree", "min_level", "gate", "spot", "bank"], + "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 } + } + } + }, + "model": { + "type": "object", + "required": ["chop_seconds_per_log", "drop_seconds_per_log", "walk_seconds_each_way", "deposit_seconds", "respawn_duty", "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 } + } + }, + "rates": { + "type": "object", + "required": ["logs_per_hour", "xp_per_hour", "gp_per_hour", "log_price", "price_verified"], + "additionalProperties": false, + "properties": { + "logs_per_hour": { "type": "integer", "exclusiveMinimum": 0 }, + "xp_per_hour": { "type": "integer", "minimum": 0 }, + "gp_per_hour": { "type": "integer", "minimum": 0 }, + "log_price": { "type": "number", "exclusiveMinimum": 0 }, + "price_verified": { "type": "string" } + } + } + } + } + }, + "state_machine": { "type": "object", "required": ["initial", "terminal", "states"], diff --git a/tools/validate.py b/tools/validate.py index 7667c3d..09bb309 100644 --- a/tools/validate.py +++ b/tools/validate.py @@ -1,21 +1,27 @@ #!/usr/bin/env python3 -"""OSRS Codex validator — the gate for every codex/**.yaml entry. +"""Validate OSRS Codex skill/economy entries. -Runs: - 1. JSON-Schema validation (schemas/entry.schema.json) - 2. Cross-field consistency checks the schema can't express: - - state_machine `next`/transition targets must be declared states - - `initial` must be a declared state - - duplicate action ids / step names - - status:verified entries with `verified:` older than --stale-days - => WARNING (entry should be re-verified, not dropped) - - `best_practice` lines whose source starts with "researched" - => WARNING (citation TODO — file a follow-up issue) - 3. Global id uniqueness across the whole codex tree. +Checks per entry: + 1. JSON-Schema validation (schemas/entry.schema.json). + 2. State-machine consistency: initial/terminal declared, every non-terminal + state has a `next`, every transition from/to is declared. + 3. Action id/step uniqueness. + 4. Staleness / research TODOs: status:verified entries with `verified:` + older than --stale-days => WARNING; best_practice lines whose source + starts with "researched" => WARNING (citation TODO). + 5. GE-margin entries (flipping/merchanting tags): floored+capped 2% tax + arithmetic (see ge_floor_tax_error). + 6. Woodcutting entries (woodcutting tag): structured axe + mode gates + (canonical axe tier table, deterministic per-mode ladders, modeled + realized rates) — see woodcutting_errors. Regression fixtures + (--selftest) commit the canonical table, the equip-vs-carry rule + (a 1-Attack/41-Woodcutting account carries and uses a rune axe), the + per-mode constraints, and the rate chain. Exit codes: 0 = clean (warnings ok), 1 = validation errors. -Usage: python3 tools/validate.py [--stale-days 30] +Usage: python3 tools/validate.py [--stale-days 30] [--selftest] + --selftest: run the committed regression fixtures only (no tree scan) """ import datetime import json @@ -28,23 +34,351 @@ import yaml ROOT = Path(__file__).resolve().parent.parent SCHEMA = json.loads((ROOT / "schemas" / "entry.schema.json").read_text()) -CODEX = ROOT / "codex" STALE_DAYS = 30 +XP_PER_LOG = {"tree": 25, "oak": 37.5, "willow": 67.5, "maple": 100, "yew": 175} +TREE_MIN_LEVEL = {"tree": 1, "oak": 15, "willow": 30, "maple": 45, "yew": 60} +# Canonical F2P axe table (OSRS wiki, verified 2026-08-28): the Woodcutting +# level is the TOOL-USE requirement (chopping), the Attack level is the +# EQUIP requirement. An account uses the highest axe whose Woodcutting level +# it meets, and equips it only when its Attack level is also met — otherwise +# it CARRIES that same axe (costing one inventory slot). Order is the tier +# tiebreak (iron beats bronze at equal requirements; gilded ~ rune). +AXE_TIERS = [ + # (axe, woodcutting_level_to_use, attack_level_to_equip, members_only) + ("bronze", 1, 1, False), + ("iron", 1, 1, False), + ("steel", 6, 5, False), + ("black", 11, 10, False), + ("mithril", 21, 20, False), + ("adamant", 31, 30, False), + ("rune", 41, 40, False), + ("gilded", 41, 40, False), + ("dragon", 61, 60, True), +] +AXE_NAMES = {row[0] for row in AXE_TIERS} +# Preference rank: at equal Woodcutting requirements the highest-rank axe wins +# (iron beats bronze; rune and gilded are stat-identical, rune wins the tie). +TIER_RANK = {"bronze": 0, "iron": 1, "steel": 2, "black": 3, "mithril": 4, + "adamant": 5, "rune": 6, "gilded": 6, "dragon": 7} + + +def best_axe(wc_level, attack_level): + """Deterministic axe selection (owner guidance 2026-08-28 / governor 10323): + pick the highest tier usable at wc_level (tool-use requirement); equip it + only when attack_level meets that tier's equip requirement, else carry it. + Returns (axe, equipped_bool). Ties at the same Woodcutting requirement are + broken by TIER_RANK (iron beats bronze; rune beats the cosmetic gilded).""" + usable = [row for row in AXE_TIERS if row[3] is False and wc_level >= row[1]] + if not usable: + return None, False + best = max(usable, key=lambda row: (row[1], TIER_RANK[row[0]])) + return best[0], attack_level >= best[2] + + +def ge_floor_tax_error(rel, data): + """GE flip-tax gate (2% convenience fee, floored per item, capped 5M): + net == income - min(floor(0.02*income), 5_000_000) - cost.""" + tags = {str(t).lower() for t in data.get("tags", [])} + if not tags & {"flipping", "merchanting"}: + return None + eco = data.get("economy") or {} + income = eco.get("income_per_cycle") + cost = eco.get("cost_per_cycle") + net = eco.get("net_per_cycle") + if not (isinstance(income, (int, float)) + and isinstance(cost, (int, float)) + and isinstance(net, (int, float))): + return f"{rel}: flipping entry needs numeric income/cost/net_per_cycle" + tax = min(int(income * 0.02), 5_000_000) + if net != income - tax - cost: + return (f"{rel}: GE flip net mismatch: net {net} != income {income}" + f" - min(floor(0.02*income), 5,000,000) = {tax} - cost {cost}" + f" (= {income - tax - cost})") + return None + + +def woodcutting_errors(rel, data): + """Structured gates for woodcutting entries (owner guidance 10288, + governor 10323 pattern: no phrase-search, exact computed checks). + + - axe_tiers must match the canonical table exactly; + - axe_examples are recomputed and must match (incl. the 1-Attack / + 41-Woodcutting rune-axe carry case); + - woodcutting_modes must be exactly [fast-xp, afk-xp, banked-income], + each ladder deterministic (rows ordered by min_level desc, first + match), with canonical per-tree level/gate constraints, and each + rate must equal the documented model chain (chop + walking/banking + or dropping + respawn duty + break duty) within tolerance, with + xp/gp derived from the canonical per-log XP and the declared price. + """ + errors = [] + tags = {str(t).lower() for t in data.get("tags", [])} + if "woodcutting" not in tags: + return errors + + # --- canonical axe table --- + tiers = data.get("axe_tiers") or [] + canon = {(row[0], row[1], row[2], bool(row[3])) for row in AXE_TIERS} + declared = {(str(t.get("axe")), t.get("woodcutting_level"), t.get("attack_level"), + bool(t.get("members"))) for t in tiers} + if declared != canon: + errors.append( + f"{rel}: axe_tiers must match the canonical F2P table exactly " + f"(bronze 1/1, iron 1/1, steel 6/5, black 11/10, mithril 21/20, " + f"adamant 31/30, rune 41/40, gilded 41/40, dragon 61/60 members); " + f"got {sorted(declared)}") + return errors + + # --- recompute the committed examples --- + for ex in data.get("axe_examples") or []: + name = ex.get("account", "?") + wc, atk = ex.get("woodcutting_level"), ex.get("attack_level") + axe, eq = best_axe(wc, atk) + if axe != ex.get("expected_axe") or eq != ex.get("expected_equipped"): + errors.append( + f"{rel}: axe_examples '{name}' (WC {wc}, Attack {atk}) — " + f"recomputed {axe}/equipped={eq}, entry declares " + f"{ex.get('expected_axe')}/equipped={ex.get('expected_equipped')}") + + # --- modes --- + modes = data.get("woodcutting_modes") or [] + if sorted(str(m.get("mode")) for m in modes) != ["afk-xp", "banked-income", "fast-xp"]: + errors.append(f"{rel}: woodcutting_modes must declare exactly " + f"fast-xp, afk-xp and banked-income") + return errors + for m in modes: + mode = m["mode"] + ladder = m.get("ladder") or [] + if not ladder: + errors.append(f"{rel}: {mode} ladder is empty") + continue + levels = [int(r["min_level"]) for r in ladder] + if levels != sorted(levels, reverse=True): + errors.append(f"{rel}: {mode} ladder must be ordered by " + f"min_level DESC (deterministic first-match); got {levels}") + if any(re.search(r"\bor\b", str(r.get("spot", "")).lower()) + or re.search(r"\bor\b", str(r.get("bank", "")).lower()) + for r in ladder): + errors.append(f"{rel}: {mode} ladder rows must be unambiguous " + f"(no 'or' inside spot/bank fields)") + for r in ladder: + tree, min_level, gate = r["tree"], int(r["min_level"]), r.get("gate") + if min_level != TREE_MIN_LEVEL.get(tree): + errors.append(f"{rel}: {mode} {tree} must have min_level " + f"{TREE_MIN_LEVEL.get(tree)} (got {min_level})") + if tree == "maple" and gate != "dragon-slayer-i": + errors.append(f"{rel}: {mode} maple requires gate " + f"dragon-slayer-i (Corsair Cove Resource Area)") + if tree != "maple" and gate == "dragon-slayer-i": + errors.append(f"{rel}: {mode} gate dragon-slayer-i may only " + f"apply to the maple row (got {tree})") + if mode == "fast-xp" and not any(r["tree"] == "willow" and int(r["min_level"]) == 30 + for r in ladder): + errors.append(f"{rel}: fast-xp must include a willow row at " + f"min_level 30 (willow is the intended fast-XP target)") + if mode == "banked-income" and not any(r["tree"] == "yew" for r in ladder): + errors.append(f"{rel}: banked-income must include a yew row " + f"(yew from 60 when eligible)") + if mode == "afk-xp" and not any(r["tree"] == "maple" for r in ladder): + errors.append(f"{rel}: afk-xp must include a maple row (quest-gated " + f"AFK target); include quest-gated maple only when " + f"access is satisfied — with lower fallbacks below") + if not any(int(r["min_level"]) == 1 for r in ladder): + errors.append(f"{rel}: {mode} ladder must bottom out at a level-1 " + f"fallback (tree) — deterministic lower-level fallback") + + # --- rate chain: logs/hr = capacity / cycle_seconds * 3600 * duties --- + cap = 28 # equipped axe; carried-axe accounts model 27-log loads (entry text) + model = m.get("model") or {} + try: + chop = float(model["chop_seconds_per_log"]) + drop = float(model.get("drop_seconds_per_log") or 0) + walk = float(model.get("walk_seconds_each_way") or 0) + deposit = float(model.get("deposit_seconds") or 0) + respawn = float(model["respawn_duty"]) + brk = float(model["break_duty"]) + except (KeyError, TypeError, ValueError): + errors.append(f"{rel}: {mode} model fields must be numeric " + f"(chop/drop/walk/deposit seconds, respawn/break duty)") + continue + if m.get("inventory_policy") == "drop": + cycle = cap * (chop + drop) + 0 * walk + deposit + else: + cycle = cap * chop + 2 * walk + deposit + if cycle <= 0: + errors.append(f"{rel}: {mode} cycle time must be positive") + continue + computed = (cap / cycle) * 3600.0 * respawn * brk + rates = m.get("rates") or {} + logs = rates.get("logs_per_hour") + if not isinstance(logs, (int, float)) or abs(logs - computed) / computed > 0.01: + errors.append( + f"{rel}: {mode} logs_per_hour {logs} must equal the model chain " + f"(capacity {cap} / {cycle:.0f}s cycle x 3600 x respawn " + f"{respawn} x break {brk} = {computed:.1f}) within 1%") + tree0 = ladder[0]["tree"] + xp_expect = logs * XP_PER_LOG[tree0] if isinstance(logs, (int, float)) else None + if xp_expect is not None and abs(rates.get("xp_per_hour", -1) - xp_expect) / xp_expect > 0.02: + errors.append(f"{rel}: {mode} xp_per_hour {rates.get('xp_per_hour')} " + f"must equal logs_per_hour x {XP_PER_LOG[tree0]} " + f"({xp_expect:.0f}) within 2%") + price = rates.get("log_price") + if isinstance(logs, (int, float)) and isinstance(price, (int, float)): + gp_expect = logs * price + if abs(rates.get("gp_per_hour", -1) - gp_expect) / gp_expect > 0.03: + errors.append(f"{rel}: {mode} gp_per_hour {rates.get('gp_per_hour')} " + f"must equal logs_per_hour x declared log_price " + f"({gp_expect:.0f}) within 3%") + return errors + + +def run_selftest(verbose=False): + """Committed regression fixtures. Returns error strings.""" + errors = [] + + def check(name, passed): + if verbose: + print(f"SELFTEST {'PASS' if passed else 'FAIL'} {name}") + if not passed: + errors.append(f"selftest: {name}") + + # --- owner-mandated axe coverage (10288): equip vs carry --- + check("axe: 41-Woodcutting/1-Attack account carries and USES a rune axe (does not pick a lower axe)", + best_axe(41, 1) == ("rune", False)) + check("axe: 41-Woodcutting/40-Attack account equips the rune axe", + best_axe(41, 40) == ("rune", True)) + check("axe: 30-Woodcutting account is below adamant (31) and uses mithril (equippable at 20 Attack)", + best_axe(30, 40) == ("mithril", True)) + check("axe: 31-Woodcutting/5-Attack account uses adamant but cannot equip it (carries)", + best_axe(31, 5) == ("adamant", False)) + check("axe: level-1 account uses an iron axe equipped from the start", + best_axe(1, 1) == ("iron", True)) + check("axe: dragon (61/60, members) is never selected for an F2P woodcutting entry; maxed account still picks the top F2P axe (rune)", + best_axe(99, 99) == ("rune", True)) + + # --- canonical table integrity --- + canon = {(row[0], row[1], row[2]) for row in AXE_TIERS} - {("dragon", 61, 60)} + want = {("bronze", 1, 1), ("iron", 1, 1), ("steel", 6, 5), ("black", 11, 10), + ("mithril", 21, 20), ("adamant", 31, 30), ("rune", 41, 40), ("gilded", 41, 40)} + check("axe: canonical F2P axe table matches the wiki (WC to use / Attack to equip)", + canon == want) + + def fake_entry(**over): + base = {"tags": ["woodcutting"], + "axe_tiers": [{"axe": a, "woodcutting_level": w, "attack_level": k, "members": m} + for a, w, k, m in AXE_TIERS], + "axe_examples": [{"account": "41WC/1Atk", "woodcutting_level": 41, "attack_level": 1, + "expected_axe": "rune", "expected_equipped": False}], + "woodcutting_modes": [ + {"mode": "fast-xp", "intent": "max xp per hour on the F2P ladder", + "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"}], + "model": {"chop_seconds_per_log": 4.0, "drop_seconds_per_log": 0, + "walk_seconds_each_way": 12, "deposit_seconds": 4, + "respawn_duty": 0.92, "break_duty": 0.94}, + "rates": {"logs_per_hour": 622, "xp_per_hour": 41985, + "gp_per_hour": 13062, "log_price": 21, + "price_verified": "2026-08-28"}}, + {"mode": "afk-xp", "intent": "low-interaction quest-gated progress", + "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": "oak", "min_level": 15, "gate": "none", + "spot": "draynor oaks", "bank": "draynor"}, + {"tree": "tree", "min_level": 1, "gate": "none", + "spot": "lumbridge west", "bank": "lumbridge"}], + "model": {"chop_seconds_per_log": 6.0, "drop_seconds_per_log": 1.0, + "walk_seconds_each_way": 0, "deposit_seconds": 0, + "respawn_duty": 0.80, "break_duty": 0.94}, + "rates": {"logs_per_hour": 386, "xp_per_hour": 38600, + "gp_per_hour": 4632, "log_price": 12, + "price_verified": "2026-08-28"}}, + {"mode": "banked-income", "intent": "banked logs sold at the GE", + "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": "oak", "min_level": 15, "gate": "none", + "spot": "draynor oaks", "bank": "draynor"}, + {"tree": "tree", "min_level": 1, "gate": "none", + "spot": "lumbridge west", "bank": "lumbridge"}], + "model": {"chop_seconds_per_log": 7.5, "drop_seconds_per_log": 0, + "walk_seconds_each_way": 8, "deposit_seconds": 4, + "respawn_duty": 0.52, "break_duty": 0.94}, + "rates": {"logs_per_hour": 214, "xp_per_hour": 37450, + "gp_per_hour": 23754, "log_price": 111, + "price_verified": "2026-08-28"}}]} + base.update(over) + return base + + check("modes: a compliant 3-mode entry passes every structured gate", + woodcutting_errors("selftest/good", fake_entry()) == []) + bad = fake_entry(woodcutting_modes=fake_entry()["woodcutting_modes"][1:]) + check("modes: missing a mode (only 2 declared) is rejected", + any("exactly" in e for e in woodcutting_errors("selftest/missing", bad))) + bad = fake_entry(axe_examples=[{"account": "41WC/1Atk", "woodcutting_level": 41, "attack_level": 1, + "expected_axe": "adamant", "expected_equipped": True}]) + check("modes: a 41-WC/1-Attack example declaring a lower/equipped axe is rejected", + any("recomputed rune" in e for e in woodcutting_errors("selftest/badex", bad))) + bad = fake_entry(woodcutting_modes=[dict(m, ladder=[dict(r, min_level=30 if r["tree"] == "maple" else r["min_level"]) for r in m["ladder"]]) + for m in fake_entry()["woodcutting_modes"]]) + check("modes: maple below level 45 is rejected", + any("maple must have min_level 45" in e for e in woodcutting_errors("selftest/maple45", bad))) + bad = fake_entry(woodcutting_modes=[dict(m, ladder=[dict(r, gate="none") for r in m["ladder"]]) + for m in fake_entry()["woodcutting_modes"]]) + check("modes: maple without the dragon-slayer-i gate is rejected", + any("gate dragon-slayer-i" in e for e in woodcutting_errors("selftest/maplegate", bad))) + bad = fake_entry(woodcutting_modes=[dict(m, ladder=[dict(r) for r in m["ladder"][::-1]]) + for m in fake_entry()["woodcutting_modes"]]) + check("modes: a ladder not ordered by min_level DESC is rejected (nondeterministic)", + any("ordered by min_level DESC" in e for e in woodcutting_errors("selftest/order", bad))) + # rates: 9999 logs/hr cannot match the model chain (min cycle ~140s -> <=720/hr) + badm = [dict(m, rates=dict(m["rates"], logs_per_hour=9999)) + for m in fake_entry()["woodcutting_modes"]] + bad = fake_entry(woodcutting_modes=badm) + check("modes: a rate far above the model chain (9999/hr) is rejected", + any("model chain" in e for e in woodcutting_errors("selftest/rate", bad))) + bad = fake_entry(axe_tiers=[{"axe": a, "woodcutting_level": w, "attack_level": k, "members": m} + for a, w, k, m in AXE_TIERS if a != "rune"]) + check("modes: a non-canonical axe table (rune missing) is rejected", + any("canonical F2P table" in e for e in woodcutting_errors("selftest/axes", bad))) + # xp mismatch: 622 logs of willow = 41,985; declaring 40,000 must fail + badm = fake_entry()["woodcutting_modes"] + badm[0] = dict(badm[0], rates=dict(badm[0]["rates"], xp_per_hour=40000)) + bad = fake_entry(woodcutting_modes=badm) + check("modes: xp_per_hour not equal to logs x per-log xp is rejected", + any("xp_per_hour" in e for e in woodcutting_errors("selftest/xp", bad))) + return errors + def load_entries(): - out = [] - for p in CODEX.rglob("*.yaml"): - if p.name.startswith("_"): # dataset files (e.g. _f2p-alch-catalog.yaml) - continue - out.append(p) - return sorted(out) + return sorted((ROOT / "codex").rglob("*.yaml")) def main(): global STALE_DAYS if "--stale-days" in sys.argv: STALE_DAYS = int(sys.argv[sys.argv.index("--stale-days") + 1]) + if "--selftest" in sys.argv: + errs = run_selftest(verbose=True) + for e in errs: + print(f"SELFTEST FAIL {e}") + print(f"RESULT: selftest, {len(errs)} errors, 0 warnings") + return 1 if errs else 0 errors, warnings = [], [] entries = load_entries() @@ -107,6 +441,12 @@ def main(): if len(a_steps) != len(set(a_steps)): errors.append(f"{rel}: duplicate action step names") + # 2b. GE flip-tax and woodcutting structured gates + err = ge_floor_tax_error(rel, data) + if err: + errors.append(err) + errors += woodcutting_errors(rel, data) + # 3. staleness / research TODOs if data.get("status") == "verified": try: From 889c3076b61534b97205ee8bb7c97d28d2aa47e6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 04:22:01 -0500 Subject: [PATCH 5/5] validate: keep skipping underscore-prefixed dataset files (regression fix) --- tools/validate.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/validate.py b/tools/validate.py index 09bb309..c48e09e 100644 --- a/tools/validate.py +++ b/tools/validate.py @@ -366,7 +366,12 @@ def run_selftest(verbose=False): def load_entries(): - return sorted((ROOT / "codex").rglob("*.yaml")) + out = [] + for p in sorted((ROOT / "codex").rglob("*.yaml")): + if p.name.startswith("_"): + continue # dataset files (e.g. _f2p-alch-catalog.yaml) are not entries + out.append(p) + return out def main():