Merge remote-tracking branch 'origin/main' into issue-1
# Conflicts: # tools/validate.py
This commit is contained in:
commit
d19a4d9de0
2435
codex/economy/_f2p-alch-catalog.yaml
Normal file
2435
codex/economy/_f2p-alch-catalog.yaml
Normal file
File diff suppressed because it is too large
Load Diff
490
codex/economy/low-alch-members.yaml
Normal file
490
codex/economy/low-alch-members.yaml
Normal file
@ -0,0 +1,490 @@
|
||||
id: low-alch-members
|
||||
title: Low-Level Alchemy (Members)
|
||||
domain: economy
|
||||
tags: [alching, magic-training, members, loss-training]
|
||||
status: verified
|
||||
version: 6 # 6: bounce-fix 5 2026-08-29 (owner guidance for governor 10361) — loss-reserve stop fixed to 'initial capital - total assets > 162,990' everywhere + validator boundary fixtures (below/at/above/profit) + realized GP/hr bounds derived from declared fill-price scenarios (A live-low -3.00/cast, B max allowed +5% -30.15/cast): gp low -29,463 / high -1,758. # 5: bounce-fix 4 2026-08-28 (owner guidance 10325) — complete GE acquisition path (open -> place -> fill -> collect) for initial buy AND every restock (bank-stock fast path only when the bank holds stock); fail-closed staff equip (bounded retries, no fire-rune fallback); capital model (cash vs supplies, staff cost, max-fill worst case, 6-batch loss reserve, float); monotonic break/restock counters (breaks 550/1,100/1,650 independent of restocks 901/1,802) + validator regression; startup-vs-steady-state rates over a 2 h session horizon
|
||||
updated: "2026-08-29" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date
|
||||
|
||||
summary: >
|
||||
Magic training from level 21 by low-alching members items with the lowest
|
||||
loss per cast (high-limit, high-volume candidates first). Same state
|
||||
machine and actions as low-alch-f2p; the catalog is the members item pool
|
||||
(mapping lowalch != null, members=true, live GE prices 2026-08-27). At
|
||||
current prices the method burns a small amount of GP per cast for 31 XP —
|
||||
run it for XP, not GP. Least-loss liquid anchor at last check: rune
|
||||
javelin tips (live-low net -3/cast; realized loss band -1,758 to -29,463
|
||||
gp/hr across the declared live-low and max-allowed +5% fill scenarios —
|
||||
XP is fill-independent at 23,000/hr session-realized midpoint). Model
|
||||
pins (owner guidance 2026-08-28): one
|
||||
iteration = one cast; breaks every 400-700 COMPLETED CASTS (cast-count
|
||||
based, independent of bank visits — midpoint example: 550, 1,100, 1,650,
|
||||
...); restock when candidate or nature drops below the numeric
|
||||
restock_threshold (100) against a carried_batch of 1,000/1,000 (restock
|
||||
cadence 901 casts, monotonic and independent of breaks). EVERY purchase
|
||||
follows the complete GE path open -> place -> fill/wait -> collect, for
|
||||
the initial acquisition and for every restock (a bank-stock fast path
|
||||
applies only when the bank actually holds enough stock). Staff-of-fire
|
||||
equip failure is FAIL-CLOSED (bounded retries, then log ERROR + alert +
|
||||
stop — no fire-rune fallback is modeled). Session horizon 2 h; rates are
|
||||
session-realized (startup + restock + breaks included, see economy).
|
||||
Handoff: switch to High Level Alchemy at level 55 (hard level gate +
|
||||
strictly more coins AND XP per cast; see sources).
|
||||
|
||||
accounts:
|
||||
membership: members
|
||||
min_level: 21 # Magic 21 to cast Low Level Alchemy (wiki-verified)
|
||||
requires_quest: []
|
||||
requires_other:
|
||||
- "Staff of Fire equipped (removes 3 fire runes from the cost; NO level requirement documented on the OSRS wiki — verified 2026-08-28, see sources — this is the default path). Equip failure is FAIL-CLOSED: flow performs bounded retries (3), then logs ERROR + alerts + stops — there is no fire-rune fallback in this model"
|
||||
- "A bank with GE access within reach (alch spot = GE bank booth; restock trip [60, 120] s)"
|
||||
- "Starting capital >= ~1.31M gp CASH (model breakdown: staff buy hard cap 5,000 + first batch at MAX allowed fill 570,150 + session loss reserve 162,990 = 6 worst-case batches at live-low+5% fills (covers Magic 21 -> 55) + resting-offer float 570,150). The capital stop check uses TOTAL ASSETS (cash + supplies at current buy cost), never raw cash — cash legitimately converts into supplies mid-run"
|
||||
- "30 Attack + 50 Magic ONLY if adopting the optional Bryophyta's staff + Tome of Fire technique — below that the Staff of Fire path applies"
|
||||
|
||||
best_practice:
|
||||
equipment:
|
||||
- item: staff of fire
|
||||
why: "unlimited fire runes — LA rune cost drops from 3 fire + 1 nature to 1 nature per cast (~15gp/cycle saved); equip requirement: none documented on the OSRS wiki (verified 2026-08-28); if it cannot be equipped after bounded retries the run FAILS CLOSED (no fallback modeled)"
|
||||
source: "OSRS wiki — Staff of Fire (id 1387, F2P item, tradeable, no requirements row in the infobox and none in the prose — compare Battlestaff which states 'requires 30 Attack and 30 Magic'); Low Level Alchemy (Cost)"
|
||||
techniques:
|
||||
- name: cast on noted (stacked) items
|
||||
why: "one inventory slot, minimal mouse travel between spell icon and item, no item un-noting; means an inventory does NOT define a batch size — batch sizes are the explicit numeric carried_batch below"
|
||||
source: "OSRS wiki — Low Level Alchemy (Usage: casting alchemy spells on noted items)"
|
||||
- name: Bryophyta's staff + Tome of Fire (members-only, 50 Magic, 30 Attack)
|
||||
why: "charged Bryophyta's staff holds up to 1,000 nature runes (0 rune inventory slots) with a 1/15 chance per cast to not consume a nature rune; charged Tome of Fire supplies infinite fire runes in the shield slot and non-combat spells (alchemy) do not consume charges — the wiki documents this exact pair for alchemy profit; payback takes thousands of casts, so adopt at 50+ Magic when nature prices are high. EQUIP GATE: the staff requires 30 Attack + 30 Magic and the tome requires 50 Magic — accounts below 30 Attack (e.g. 1-Attack mules) cannot equip it and keep the ordinary Staff of Fire path (equipment section above) instead"
|
||||
source: "OSRS wiki — Bryophyta's staff (passive effect; 'can be used along with a tome of fire to increase profits with High Level Alchemy'); OSRS wiki — Tome of Fire (level 50 Magic, non-combat spells do not consume charges)"
|
||||
- name: self-craft nature runes as a cost cap
|
||||
why: "members with 44 Runecraft can craft natures from pure essence, capping the per-cast rune cost at essence+time when GE nature prices spike; use the Abyss (no talisman needed) from the Mage of Zamorak"
|
||||
source: "OSRS wiki — Nature rune (level 44 Runecraft, pure essence, 9 xp); OSRS wiki — Abyss (runic altar access)"
|
||||
|
||||
economy:
|
||||
# Verified 2026-08-27 against prices.runescape.wiki (live 'latest') +
|
||||
# OSRS wiki (spell infoboxes, Module:Exchange limits, item pages).
|
||||
# Re-verified at bounce fix 2026-08-27 with a fresh live fetch; the
|
||||
# modeled anchor is the ACTUAL least-loss liquid candidate.
|
||||
# Bounce-fix 4 (2026-08-28, owner guidance 10325): all rate fields are
|
||||
# SESSION-REALIZED figures — startup + restock + mandatory breaks
|
||||
# included; loop-only and steady-state figures retained as labelled
|
||||
# references; the 2 h session horizon is explicit (stop_conditions).
|
||||
item: Rune javelin tips # least-loss liquid anchor 2026-08-27 (see profit_drivers)
|
||||
item_id: 19580
|
||||
cost_per_cycle: 543 # javelin tips buy 403 + nature rune 140 (staff of fire, live low 2026-08-27)
|
||||
income_per_cycle: 540 # lowalch of rune javelin tips (40% of value 1350)
|
||||
net_per_cycle: -3
|
||||
gp_per_hour_low: -29463 # WORST-CASE realized: max-allowed fill scenario B (-30.15/cast) x session-fast 977.2 casts/hr = -29,462.6 (3.4 s cycle, 300 s startup, 2 h horizon) — see FILL-PRICE SCENARIOS below
|
||||
gp_per_hour_high: -1758 # BEST-CASE realized: live-low fill scenario A (-3.00/cast) x session-slow 585.9 casts/hr = -1,757.7 (5.2 s cycle, 600 s startup, 2 h horizon) — see FILL-PRICE SCENARIOS below
|
||||
xp_per_hour: 23000 # session-realized midpoint: 741.8 casts/hr x 31 = 22,995 (pin 23,000)
|
||||
# ---- rate model (pinned; owner guidance 2026-08-28) ----
|
||||
# LOOP (idealised, zero overhead — ceiling reference ONLY, never the
|
||||
# entry's output): cycle = 1.8 s cast (3 ticks x 0.6 s) +
|
||||
# anti_ban.delay [1.6, 3.4] s ADDITIVE (docs/action-grammar.md:135)
|
||||
# = 3.4-5.2 s, midpoint 4.3 s -> loop-only casts/hr = 3600/4.3 = 837.2
|
||||
# (range 692.3..1,058.8); loop-only xp mid = 25,953; gp mid = -2,512.
|
||||
# STEADY-STATE (mandatory breaks + restock trips, no startup): per
|
||||
# 901-cast batch: 901 x cycle + restock trip [60, 120] s (mid 90) +
|
||||
# break share (break.for mid 82.5 s per break.every mid 550 casts:
|
||||
# 45 s/700 at the fast end, 120 s/400 at the slow end):
|
||||
# fast 3.4 s: 901 x 3.4 + 60 + 45 x 901/700 = 3,181.3 s -> 1,019.7 casts/hr
|
||||
# mid 4.3 s: 901 x 4.3 + 90 + 82.5 x 901/550 = 4,099.5 s -> 791.3 casts/hr
|
||||
# slow 5.2 s: 901 x 5.2 + 120 + 120 x 901/400 = 5,075.5 s -> 639.2 casts/hr
|
||||
# SESSION-REALIZED (2 h horizon = 7,200 s; one-time startup 300-600 s,
|
||||
# mid 450 s -> 450/7200 = 6.25 %): realized = steady-state x (1 -
|
||||
# startup/session): fast 977.2, mid 741.8, slow 585.9 casts/hr.
|
||||
# xp mid = 741.8 x 31 = 22,995 -> pin 23,000. REALIZED GP/hr derives from
|
||||
# the FILL-PRICE SCENARIOS below (the SAME ledger as the batch economics):
|
||||
# worst = -29,463 = scenario B x session-fast 977.2 (-30.15 x 977.2 = -29,462.6)
|
||||
# best = -1,758 = scenario A x session-slow 585.9 (-3.00 x 585.9 = -1,757.7)
|
||||
# midpoints: scenario A x 741.8 = -2,225; scenario B x 741.8 = -22,365.
|
||||
# BATCH ECONOMICS (901 casts; deposits 901 x 540 = 486,540 gp per batch):
|
||||
# input cost at live low: 901 x 543 = 489,243 -> net -2,703/batch
|
||||
# input cost at MAX fill (live low + 5 %): 901 x 570.15 = 513,705 -> net
|
||||
# -27,165/batch (worst case). Reserve = 6 worst-case batches (Magic
|
||||
# 21 -> 55 = ~5,300 casts = ~6 batches) = 162,990 gp; the capital stop
|
||||
# checks TOTAL ASSETS, not raw cash (see accounts + stop_conditions).
|
||||
# FILL-PRICE SCENARIOS (one ledger for capital, batch economics, AND the
|
||||
# realized GP bounds — every number below comes from the same scenario):
|
||||
# A live-low fill: candidate 403 + nature 140 = 543/cast vs
|
||||
# 540 lowalch -> net -3.00/cast (the anchor)
|
||||
# B max allowed fill (+5%): candidate 403x1.05 = 423.15 + nature
|
||||
# 140x1.05 = 147 -> 570.15/cast vs 540 lowalch
|
||||
# -> net -30.15/cast (the executable buy policy
|
||||
# permits live low + 5%; this scenario, not the
|
||||
# -3 live-low margin, bounds the downside)
|
||||
# gp_per_hour_low = -29,463 | gp_per_hour_high = -1,758 derive from these
|
||||
# scenarios at the session rates above; the batch ledger uses the same
|
||||
# 543 / 570.15 per-cast costs (901-cast batch: -2,703 live-low, -27,165
|
||||
# max fill), so capital accounting and GP bounds can never diverge.
|
||||
supplies:
|
||||
- item: rune javelin tips
|
||||
qty_per_cycle: 1
|
||||
source: ge
|
||||
ge_limit: 10000 # mapping + Module:Exchange/Rune javelin tips agree (re-verified 2026-08-27); one carried_batch of 1,000 fits the 4h window
|
||||
- item: nature rune
|
||||
qty_per_cycle: 1
|
||||
source: ge
|
||||
ge_limit: 18000
|
||||
profit_drivers:
|
||||
- "No guaranteed profit to chase — this is loss-minimizing training: XP is the product. Pick the least-loss (least-negative net) candidate live each run."
|
||||
- "Least-loss liquid anchors 2026-08-27 (net = lowalch - low buy - nature 140): rune javelin tips (19580, -3/cast, limit 10,000) is the modeled anchor; rune arrow (892, -25/cast, limit 11,000) is the runner-up."
|
||||
- "Jade ring (21084) is currently MARGINAL-PROFIT (+67/cast, low/high 303/326) — flips sign as prices move, so it is not the loss anchor. Opal amulet (u) (21099) shows a 20x low/high spread (17/365) = stale-low artifact, disqualified per data-sources.md thin-fill trap."
|
||||
- "Alch-margin candidates by (lowalch - buy) 2026-08-27 (thin limits cannot sustain a run; 'low' fills on thin items can be stale):"
|
||||
- "1. Trailblazer top (t1) (id 25031, lowalch 6000, buy 555, margin +5445, GE limit 5)"
|
||||
- "2. Twisted trousers (t2) (id 24401, lowalch 4000, buy 100, margin +3900, GE limit 5)"
|
||||
- "3. Adamant hasta(p) (id 11407, lowalch 832, buy 1, margin +831, GE limit 125)"
|
||||
- "4. Adamant spear(p++) (id 5726, lowalch 832, buy 10, margin +822, GE limit 125)"
|
||||
- "5. Bagged flower (id 8451, lowalch 2000, buy 1250, margin +750, GE limit 10000 — deep limit but thin market: a 10k fill at 1,250 is unverifiable, do not model a run on it)"
|
||||
- "6. Dual sai (id 23206, lowalch 2400, buy 1750, margin +650, GE limit 8)"
|
||||
- "7. Frying pan (id 7441, lowalch 664, buy 101, margin +563, GE limit 50)"
|
||||
- "8. Maple blackjack(d) (id 6420, lowalch 640, buy 100, margin +540, GE limit 125)"
|
||||
- "9. Adamant spear(p) (id 1259, lowalch 832, buy 450, margin +382, GE limit 125)"
|
||||
- "10. Mystic mist staff (id 20733, lowalch 17000, buy 75727, margin -58727, GE limit 8) — buy spiked ABOVE alch value 2026-08-27; a true loss trap, do not buy"
|
||||
- "Caveat: thin items (limits 5-125) cannot sustain a run and their 'low' fills can be stale — a bot cannot reliably buy at those prices. The dependable loss-minimizers are the liquid high-limit pair modeled above: rune javelin tips (-3/cast) and rune arrow (-25/cast)."
|
||||
- "Batch-scale economics (default staff-of-fire path, worst case 1 nature/cast): carried_batch 1,000/1,000, restock every 901 casts — one batch = -2,703 gp at live-low fills (-27,165 at MAX allowed fills), +27,931 xp, deposits 486,540 gp; unfilled-offer float (up to 570,150 gp) is separate from loss (see accounts)."
|
||||
|
||||
state_machine:
|
||||
initial: idle
|
||||
terminal: [complete, failed]
|
||||
states:
|
||||
idle:
|
||||
goal: "session start at the GE bank booth. Open GE (a2), then place the initial offers: staff of fire (a3), candidate 1,000 (a4), nature runes 1,000 (a5) at live low + 5% (staff: +25% up to the 5,000 cap). Record initial capital in the cash ledger (total assets = cash + supplies at current buy cost). Nothing is bought or collected without an open GE interface"
|
||||
next: [wait_offers, complete, failed]
|
||||
wait_offers:
|
||||
goal: "wait for the pending offers (ge_offers_done); when filled, collect every purchase + refund from the collection box (a6 — never leave purchases in the box). PARTIAL FILLS: collect the filled portion, re-place the remainder at the current edge (a3/a4/a5); 3 consecutive no-fill rounds (offer cannot fill at the accepted edge) -> failed. An offer that is still unfilled at session end is left resting by design — its coins are tied up (float reserve) and return when it fills or is aborted by the operator (the single operator-touch point)"
|
||||
next: [ready, wait_offers, failed]
|
||||
ready:
|
||||
goal: "equip the staff of fire (a7 — bounded retries, then FAIL CLOSED) and build the loadout to carried_batch 1,000 candidate + 1,000 nature (a8; purchases from a6 are already in the inventory; a8 deposits overflow and confirms the counts)"
|
||||
next: [casting, failed]
|
||||
casting:
|
||||
goal: "cast Low Level Alchemy on the candidate (a9) — each cast consumes 1 candidate + 1 nature and pays 540 gp (lowalch), +31 xp, net -3 gp; take a break when the completed-cast counter hits run_control.break.every ([400, 700] casts, via a13); the counter is NEVER reset by restocking"
|
||||
next: [bank_supplies, complete, failed]
|
||||
bank_supplies:
|
||||
goal: "restock to carried_batch: deposit coins + products and withdraw ALL candidate/natures the bank holds (a10 — bank-stock fast path when the bank already holds enough); for the remaining deficit, open GE (a2), buy the deficit (a11, live low + 5%), collect it (a12) — never withdraw a batch that was never bought. Restocking never resets the cast counter or the break counter; the break cadence (550/1,100/1,650, ...) is independent of the restock cadence (901/1,802, ...)"
|
||||
next: [casting, bank_supplies, failed]
|
||||
complete:
|
||||
goal: "reached the run's XP/level target (default 55 -> hand off to HLA) or the 2 h session horizon"
|
||||
failed:
|
||||
goal: "hard failure — stop, log ERROR sentinel, alert"
|
||||
transitions:
|
||||
- from: idle
|
||||
when: "staff + candidate + nature offers all placed (a3/a4/a5)"
|
||||
to: wait_offers
|
||||
- from: idle
|
||||
when: "magic level >= target (55) at session start"
|
||||
to: complete
|
||||
- from: idle
|
||||
when: "3 consecutive offer-place failures at session start (no fallback candidate)"
|
||||
to: failed
|
||||
- from: wait_offers
|
||||
when: "ge_offers_done AND all purchases collected (a6)"
|
||||
to: ready
|
||||
- from: wait_offers
|
||||
when: "partial fill: filled portion collected (a6), remainder re-placed at the current edge (a3/a4/a5) — still waiting"
|
||||
to: wait_offers
|
||||
- from: wait_offers
|
||||
when: "3 consecutive no-fill rounds (offer cannot fill at the accepted edge)"
|
||||
to: failed
|
||||
- from: ready
|
||||
when: "staff equipped (a7) AND loadout at carried_batch 1,000/1,000 (a8)"
|
||||
to: casting
|
||||
- from: ready
|
||||
when: "staff equip failed after bounded retries — FAIL CLOSED"
|
||||
to: failed
|
||||
- from: casting
|
||||
when: "candidate count < restock_threshold (100) OR nature count < restock_threshold (100) — deterministic restock trigger, pinned numbers"
|
||||
to: bank_supplies
|
||||
- from: casting
|
||||
when: "magic level >= target (55, hand off to HLA)"
|
||||
to: complete
|
||||
- from: casting
|
||||
when: "3 consecutive failed casts (client issue — do not loop)"
|
||||
to: failed
|
||||
- from: bank_supplies
|
||||
when: "restock complete: candidate == carried_batch (1,000) AND nature == carried_batch (1,000) — bank fast path or re-bought deficit collected"
|
||||
to: casting
|
||||
- from: bank_supplies
|
||||
when: "restock partial: deficit re-bought but offers still filling (ge_offers_done false) — loop the restock phase"
|
||||
to: bank_supplies
|
||||
- from: bank_supplies
|
||||
when: "3 consecutive no-fill restock rounds OR a capital stop fires (assets / loss-reserve checks in stop_conditions)"
|
||||
to: failed
|
||||
|
||||
# --- handoff note (wiki-verified 2026-08-27, do not copy from other entries) ---
|
||||
# Low Level Alchemy: level 21, 31 xp/cast, 1 nature + 3 fire (spellbook: Normal).
|
||||
# Superheat Item: level 43, 53 xp/cast, 1 nature + 4 fire — a SMITHING
|
||||
# production spell (ore -> bar), NOT the magic-XP handoff; as pure Magic
|
||||
# training it is only a soft side option (e.g. training Smithing alongside).
|
||||
# High Level Alchemy: level 55, 65 xp/cast, 1 nature + 5 fire.
|
||||
# Handoff: the LA page itself states "Players with level 55 Magic are
|
||||
# generally recommended to use High Level Alchemy instead, as it provides
|
||||
# more coins and experience per cast than Low Level Alchemy". So the switch
|
||||
# is level 55: a HARD level gate (HLA uncastable below 55) plus strict
|
||||
# dominance afterwards (with a fire staff both cost 1 nature/cast, and HLA
|
||||
# pays 60% of value + 65 xp vs LA's 40% + 31 xp) — after 55, LA is strictly
|
||||
# worse for Magic XP and is never the right call except when deliberately
|
||||
# burning GP at minimum XP (never recommended).
|
||||
|
||||
actions:
|
||||
- id: a1
|
||||
step: reach-bank
|
||||
verb: move_to
|
||||
target: bank
|
||||
params:
|
||||
location: "GE-adjacent bank (alch spot = the bank booth, so restock is a single short leg)"
|
||||
method: [teleport, walk]
|
||||
on_fail: retry
|
||||
anti_ban:
|
||||
delay: [400, 1600]
|
||||
|
||||
- id: a2
|
||||
step: open-ge
|
||||
verb: ge_open
|
||||
target: grand-exchange
|
||||
params:
|
||||
note: "open the GE interface at the booth. The entry NEVER buys (a3/a4/a5/a11) or collects (a6/a12) without an open GE interface — the action grammar defines ge_open, ge_buy, ge_collect as separate verbs, so the open step is explicit"
|
||||
on_fail: retry
|
||||
anti_ban:
|
||||
delay: [600, 1400]
|
||||
|
||||
- id: a3
|
||||
step: buy-staff
|
||||
verb: ge_buy
|
||||
target: grand-exchange
|
||||
params:
|
||||
item: "staff of fire"
|
||||
qty: 1
|
||||
max_price: "live low + 25 % (wiki value 1,500; hard cap 5,000)"
|
||||
note: "offer rests until filled or the run ends; on a partial/no fill, collect what filled (a6) and re-place the remainder at the current edge — bounded retries, then the run fails closed (no fire-rune fallback exists)"
|
||||
on_fail: retry
|
||||
anti_ban:
|
||||
delay: [1200, 3000]
|
||||
|
||||
- id: a4
|
||||
step: buy-supplies
|
||||
verb: ge_buy
|
||||
target: grand-exchange
|
||||
params:
|
||||
item: "current least-loss live candidate (default: rune javelin tips)"
|
||||
qty: "carried_batch = 1,000 (fits the 4h GE limit window of 10,000)"
|
||||
max_price: "live low + 5 %"
|
||||
note: "offer rests until filled or the run ends; on a partial fill, collect the filled portion (a6) and re-place the remainder at the current edge"
|
||||
on_fail: retry
|
||||
anti_ban:
|
||||
delay: [1200, 3000]
|
||||
|
||||
- id: a5
|
||||
step: buy-natures
|
||||
verb: ge_buy
|
||||
target: grand-exchange
|
||||
params:
|
||||
item: "nature rune"
|
||||
qty: "carried_batch = 1,000 (4h GE limit 18,000)"
|
||||
max_price: "live low + 5 %"
|
||||
note: "offer rests until filled or the run ends; partial fills handled like a4"
|
||||
on_fail: retry
|
||||
anti_ban:
|
||||
delay: [1200, 3000]
|
||||
|
||||
- id: a6
|
||||
step: collect-purchases
|
||||
verb: ge_collect
|
||||
target: grand-exchange
|
||||
params:
|
||||
note: "collect every filled purchase and refund from the GE collection box into the inventory — staff, candidate, natures (and the equivalent on every restock). Partial offers: the collected portion is added to the ledger; nothing is left in the box at session start"
|
||||
on_fail: retry
|
||||
anti_ban:
|
||||
delay: [600, 1400]
|
||||
|
||||
- id: a7
|
||||
step: equip-staff
|
||||
verb: menu_option
|
||||
target: "staff of fire in inventory"
|
||||
params:
|
||||
option: "Wield"
|
||||
note: "Equip requirement is NONE documented on the OSRS wiki Staff of fire page (no requirements row in the infobox, none in the prose; compare Battlestaff which states 'requires 30 Attack and 30 Magic'; verified 2026-08-28). FAIL-CLOSED path: bounded retries (3), then log ERROR + alert + stop — no fire-rune fallback is modeled (never loop on equip attempts)"
|
||||
on_fail: retry
|
||||
anti_ban:
|
||||
delay: [600, 1800]
|
||||
|
||||
- id: a8
|
||||
step: build-loadout
|
||||
verb: bank_inventory
|
||||
target: bank
|
||||
params:
|
||||
deposit: [overflow products]
|
||||
withdraw: "to carried_batch: candidate 1,000 + nature runes 1,000 (only the shortfall — collected purchases from a6 are already in the inventory; both stackable, two inventory slots)"
|
||||
on_fail: retry
|
||||
anti_ban:
|
||||
delay: [500, 1100]
|
||||
|
||||
- id: a9
|
||||
step: cast-alch
|
||||
verb: use_item_on
|
||||
target: "candidate item in inventory"
|
||||
params:
|
||||
spell: "Low Level Alchemy"
|
||||
item: "least-loss live candidate in inventory"
|
||||
on: "itself"
|
||||
consumption: "1 candidate + 1 nature per cast (staff of fire default); pays 540 gp (lowalch) + 31 xp, net -3 gp"
|
||||
on_fail: retry
|
||||
anti_ban:
|
||||
delay: [1600, 3400] # MANDATORY post-action sleep, ADDITIVE to the 1.8s cast (3 ticks x 0.6s; action-grammar.md:135): cycle = 1.8s + 1.6-3.4s = 3.4-5.2s, midpoint 4.3s -> session-realized ~742 casts/hr incl. startup + restock + breaks (pinned in economy)
|
||||
|
||||
- id: a10
|
||||
step: restock-deposit
|
||||
verb: bank_inventory
|
||||
target: bank
|
||||
params:
|
||||
deposit: [all coins, all products]
|
||||
withdraw: "ALL candidate + nature runes currently in the bank, up to carried_batch (1,000/1,000) — the bank-stock FAST PATH applies only when the bank actually holds enough stock; the remaining deficit is bought at the GE (a11) and collected (a12)"
|
||||
on_fail: alert
|
||||
anti_ban:
|
||||
delay: [500, 1100]
|
||||
|
||||
- id: a11
|
||||
step: restock-buy
|
||||
verb: ge_buy
|
||||
target: grand-exchange
|
||||
params:
|
||||
item: "current least-loss live candidate + nature runes"
|
||||
qty: "deficit to carried_batch = carried_batch - inventory count after the a10 fast path (e.g. 901 candidate + 901 nature after a 901-cast batch with an empty bank)"
|
||||
max_price: "live low + 5 %"
|
||||
note: "the deficit is RE-BOUGHT at the GE on every restock — the entry never withdraws a batch that was never purchased; partial/failed fills use the same collect + re-place recovery as the initial acquisition"
|
||||
on_fail: retry
|
||||
anti_ban:
|
||||
delay: [1200, 3000]
|
||||
|
||||
- id: a12
|
||||
step: restock-collect
|
||||
verb: ge_collect
|
||||
target: grand-exchange
|
||||
params:
|
||||
note: "collect the re-bought deficit into the inventory — inventory returns to carried_batch 1,000/1,000 before casting resumes"
|
||||
on_fail: retry
|
||||
anti_ban:
|
||||
delay: [600, 1400]
|
||||
|
||||
- id: a13
|
||||
step: take-break
|
||||
verb: take_break
|
||||
target: "current location"
|
||||
params:
|
||||
duration: "[45, 120] seconds of idle AFK per break (matches run_control.break.for)"
|
||||
note: "executed by the translator when the completed-cast counter reaches run_control.break.every — this IS run_control.break's executor, fired on CAST COUNTS, not on bank visits; the cast counter is NOT reset by restocking (breaks land at 550/1,100/1,650 in the midpoint example, independent of the 901-cast restock cadence)"
|
||||
on_fail: skip
|
||||
|
||||
run_control:
|
||||
iteration: "one cast" # = one Low Level Alchemy cast (+31 xp, -3 gp at the anchor). Per docs/schema.md run_control, break.every counts ITERATIONS — so it counts CASTS here, NOT bank trips (owner decision 2026-08-28)
|
||||
max_iterations: null # null = until complete condition
|
||||
break:
|
||||
every: [400, 700] # COMPLETED CASTS between AFK breaks (midpoint 550 = ~39 min at the 4.3s midpoint cycle). Cast-count-based and independent of bank visits; a restock trip NEVER counts as a break and does not reset this counter
|
||||
for: [45, 120] # seconds of idle AFK per break
|
||||
stop_conditions:
|
||||
- "magic level >= target (55 -> hand off to HLA)"
|
||||
- "session elapsed >= 2 h (session horizon — startup is amortised over this window in the rate model)"
|
||||
- "total assets (cash + supplies at current buy cost) < 575,150 — cannot fund the next batch at max fill (570,150) plus staff replacement (5,000); assets, NOT raw cash: cash that became supplies is not a loss"
|
||||
- "session net loss: initial capital - total assets > 162,990 — a loss makes 'initial capital - total assets' POSITIVE, so this strict-greater comparison fires on losses and never on profits; 162,990 = the 6-batch worst-case reserve (exactly AT the boundary it does NOT fire — strict >)"
|
||||
- "3 consecutive failed casts (client issue — do not loop)"
|
||||
- "3 consecutive no-fill GE rounds (initial acquisition or restock) with no fallback candidate available"
|
||||
- "staff of fire equip failed after bounded retries — FAIL CLOSED (log ERROR, alert, stop)"
|
||||
|
||||
# ================= WORKED EXAMPLE (pinned, deterministic — owner guidance 2026-08-28) =================
|
||||
# Constants: carried_batch = 1,000 candidate + 1,000 nature; restock_threshold = 100;
|
||||
# break.every = [400, 700] casts — the example pins the MIDPOINT 550;
|
||||
# break.for = [45, 120] s; restock cadence = 901 casts (1,000 - 99); the
|
||||
# staff-of-fire path consumes 1 candidate + 1 nature per cast (worst case 1:1 —
|
||||
# with Bryophyta's staff the nature side drains slower, so the candidate stays binding).
|
||||
# The two counters are MONOTONIC and INDEPENDENT: breaks at 550, 1,100, 1,650,
|
||||
# 2,200... (multiples of 550) and restocks at 901, 1,802, 2,703... (multiples of
|
||||
# 901) — gcd(550, 901) = 1, so they interleave forever and restocking NEVER
|
||||
# resets or shifts the break counter (validator regression: the old buggy
|
||||
# 'restart the interval at the restock' example is rejected by the gate).
|
||||
#
|
||||
# Step 0 — preparation (one-time startup, ~300-600 s): account holds >= ~1.31M gp
|
||||
# cash (staff cap 5,000 + first batch max 570,150 + reserve 162,990 + float
|
||||
# 570,150). At the GE bank: open GE (a2); place offers — staff (a3), 1,000
|
||||
# rune javelin tips (a4), 1,000 nature runes (a5); wait for fills
|
||||
# (ge_offers_done) and collect everything (a6); equip the staff (a7); loadout
|
||||
# = carried_batch 1,000/1,000 (a8). Cast counter := 0; cash ledger opened.
|
||||
# Step 1 — casts #1..#550: each cast consumes 1 candidate + 1 nature (-543 gp)
|
||||
# and pays 540 gp (lowalch) + 31 xp -> net -3 gp. Cast counter 0 -> 550
|
||||
# (~39 min at midpoint).
|
||||
# Step 2 — FIRST SCHEDULED BREAK at cast 550 (the interval since cast 0):
|
||||
# take-break 45-120 s (a13). Resume casting at cast 551. The break fired on
|
||||
# the CAST COUNT, not on any bank visit.
|
||||
# Step 3 — casts #551..#901: countdown candidate 1,000-901 = 99 < 100
|
||||
# (restock_threshold) and nature 99 < 100 simultaneously (worst case 1:1) ->
|
||||
# the transition fires at the end of cast #901: FIRST RESTOCK at cast 901.
|
||||
# Deposit 901 x 540 = 486,540 gp (a10); the bank holds 0 supplies (all sales
|
||||
# were GE buys), so the fast path yields nothing -> open GE (a2), RE-BUY the
|
||||
# deficit 901 candidate + 901 nature (a11, live low + 5 %), collect (a12) ->
|
||||
# inventory back to 1,000/1,000. Batch net: -2,703 gp (live-low fills) or
|
||||
# -27,165 gp (max fills). The cast counter (901) and break counter continue.
|
||||
# Step 4 — SECOND BREAK at cast 1,100: the interval since break #1 is exactly
|
||||
# 550 casts (1,100 - 550); the cast-901 restock did NOT reset or shift it.
|
||||
# Step 5 — THIRD BREAK at cast 1,650 (1,100 + 550); SECOND RESTOCK at cast
|
||||
# 1,802 (901-cadence, 1,802 = 901 x 2). The sequences 550/1,100/1,650/2,200
|
||||
# and 901/1,802/2,703 interleave independently.
|
||||
# Step 6 — continue until a stop_condition fires: Magic >= 55 (HLA handoff),
|
||||
# the 2 h session horizon, the capital stops (assets floor 575,150, or
|
||||
# loss = initial capital - total assets > 162,990), 3 failed casts, or 3
|
||||
# consecutive no-fill GE rounds.
|
||||
# ============================================================================================
|
||||
|
||||
risk:
|
||||
ban_level: low
|
||||
notes:
|
||||
- "High cast cadence is the main detection vector; keep the mandatory anti_ban delays (cadence pin: additive post-cast sleep [1.6, 3.4] s; session-realized ~742 casts/hr midpoint incl. startup + restock + breaks over a 2 h session; steady-state ~791; loop-only 837 is a ceiling reference, never the output)."
|
||||
- "Breaks are CAST-COUNT-based (every [400, 700] completed casts, 45-120 s AFK) and independent of bank visits — never treat a restock trip as a break, and never reset the break counter on restock (break/restock gcd(550, 901) = 1)."
|
||||
- "GE buy bursts: pace offers, respect the 4h limit window (10,000 javelin tips / 18,000 natures); thin items (limit < 100) cannot sustain a long run; offers persist until filled or aborted and a 4h limit reset is NOT an offer expiry."
|
||||
- "Thin-item 'latest low' fills can be stale — confirm fill price with a live GE offer before committing a run; the liquid high-limit pair (rune javelin tips / rune arrow) is the dependable loss-minimizer."
|
||||
|
||||
# --- recovery block (owner guidance 2026-08-28: every activity must cover its complete runnable lifecycle) ---
|
||||
# - Missing materials / depleted supplies: the restock_threshold transition (casting ->
|
||||
# bank_supplies at candidate OR nature < 100) catches both mid-run depletions; the
|
||||
# restock phase (a10 -> a2 -> a11 -> a12) rebuilds to carried_batch and the loop
|
||||
# resumes (worked example steps 3-4). The entry NEVER withdraws a batch that was
|
||||
# never purchased — every restock re-buys the deficit at the GE.
|
||||
# - GE offers: partial fills are collected (a6/a12) and the remainder is re-placed at
|
||||
# the current edge (a3/a4/a5/a11); after 3 consecutive no-fill rounds (initial or
|
||||
# restock) the run stops (failed) — never sit in an unfillable offer loop. Unfilled
|
||||
# offers at session end are left resting by design (float reserve; coins return when
|
||||
# they fill or the operator aborts them — the single operator-touch point).
|
||||
# - Staff of fire cannot be equipped (a7): bounded retries (3), then FAIL CLOSED —
|
||||
# log ERROR + alert + stop. No fire-rune fallback is modeled (it would need its own
|
||||
# purchase/supply/rate path; the owner decision rejects a prose-only fallback).
|
||||
# - Invalid loadout (wrong item in hand / spell unavailable): re-run build-loadout
|
||||
# (a8); 3 consecutive failed casts -> failed (stop, log ERROR sentinel, alert).
|
||||
# - Capital: the stop checks TOTAL ASSETS (cash + supplies at current buy cost), and
|
||||
# both capital stops are assets-based (see stop_conditions) — cash converted into
|
||||
# supplies is not a loss and never fires a stop by itself. Loss direction is pinned
|
||||
# everywhere as 'initial capital - total assets > 162,990': a loss makes that
|
||||
# difference positive, so the stop fires on losses and never on profits (the
|
||||
# reversed sign form can never exceed the threshold on a loss and is rejected by
|
||||
# the validator regression; boundary fixtures: below/at/above + profit side).
|
||||
# - Explicit completion/stop: stop_conditions above (Magic 55 HLA handoff, 2 h session
|
||||
# horizon, assets floor 575,150, loss reserve 162,990, repeated failures, fail-closed
|
||||
# staff equip).
|
||||
|
||||
sources:
|
||||
- "OSRS wiki — Low Level Alchemy (level 21, 31 xp/cast, 3-tick, 1 nature + 3 fire; spellbook Normal; infobox 2026-08-27)"
|
||||
- "OSRS wiki — High Level Alchemy (level 55, 65 xp/cast, 1 nature + 5 fire; 60% of item value; infobox 2026-08-27)"
|
||||
- "OSRS wiki — Superheat Item (level 43, 53 xp/cast, 1 nature + 4 fire; smithing production spell; infobox 2026-08-27)"
|
||||
- "OSRS wiki — Low Level Alchemy (handoff quote: 'Players with level 55 Magic are generally recommended to use High Level Alchemy instead, as it provides more coins and experience per cast than Low Level Alchemy')"
|
||||
- "prices.runescape.wiki mapping 2026-08-27 (members/lowalch/highalch/limit catalog; rune javelin tips id 19580: lowalch 540, highalch 810, limit 10000, members=true; lowalch == highalch*2//3 asserted for every cited item)"
|
||||
- "prices.runescape.wiki latest 2026-08-27, re-fetched at bounce fix (low = per-repo buy basis: rune javelin tips 403, rune arrow 45, nature rune 140, jade ring 303, opal amulet (u) 17)"
|
||||
- "OSRS wiki — Module:Exchange/Rune javelin tips (limit 10000, lowalch 540, highalch 810, members=true — matches mapping; re-verified 2026-08-27) + Module:Exchange/Nature rune, Staff of fire and all cited candidates (GE limits agree with mapping; verified 2026-08-27)"
|
||||
- "OSRS wiki — Rune javelin tips / Rune arrow / Bagged flower / Mystic mist staff etc. item pages (members = Yes for every cited catalog item; verified 2026-08-27)"
|
||||
- "OSRS wiki — Grand Exchange (offers persist until filled or aborted; filled items and coin refunds are collected from the booth/collection box; partial fills are separately collectable) — verified 2026-08-28"
|
||||
- "OSRS wiki — Grand Exchange/Buying limits (4-hour window; the limit resets 4 hours after the FIRST unit is bought; outstanding offers remain active and a limit reset is NOT an offer expiry) — verified 2026-08-28"
|
||||
- "OSRS wiki — Nature rune (members craft at level 44 Runecraft from pure essence, 9 xp; Nature Altar south-east of Tai Bwo Wannai)"
|
||||
- "OSRS wiki — Abyss (access to runic altars without talismans via the Mage of Zamorak)"
|
||||
- "OSRS wiki — Bryophyta's staff (level 30 Attack + Magic; up to 1,000 nature charges; 1/15 chance to not consume a charge on any nature-rune spell; alchemy combo with Tome of Fire)"
|
||||
- "OSRS wiki — Tome of Fire (members, level 50 Magic to wield; charged tome is an infinite fire-rune source and non-combat spells do not consume charges)"
|
||||
- "OSRS wiki — Staff of fire (page verified 2026-08-28 for the equip requirement: id 1387, F2P item, tradeable, options Wield — NO requirements row in the Infobox Item and NO requirement statement in the prose; contrast Battlestaff, whose prose states 'requires 30 Attack and 30 Magic to wield')"
|
||||
- "docs/schema.md run_control (iteration unit; break.every counts iterations) + docs/action-grammar.md:135 (anti_ban.delay = additive post-action sleep) + verbs ge_open/ge_buy/ge_collect/ge_offers_done (separate GE primitives, never assumed) — the cadence + break + GE + recovery model"
|
||||
verified: "2026-08-27" # keep quoted (YAML 1.1 date parsing); prices last re-fetched live 2026-08-27
|
||||
|
||||
related:
|
||||
- low-alch-f2p
|
||||
- high-alch-p2p
|
||||
- mule-topup
|
||||
@ -124,6 +124,110 @@ def ge_floor_tax_error(rel, data):
|
||||
return None
|
||||
|
||||
|
||||
def alch_worked_example_regression(rel, data, raw_text):
|
||||
"""One-cast model worked-example regression (owner guidance 2026-08-28,
|
||||
PR #8 repair, merged via origin/main into issue-1): entries with the
|
||||
owner-selected one-cast iteration model must pin a worked example with
|
||||
MONOTONIC, INDEPENDENT counters — breaks every 550 casts (1,100, 1,650,
|
||||
...) regardless of restocks, and restocks every 901 casts (1,802, ...),
|
||||
so restocking never resets or shifts the break counter. Scoped to
|
||||
entries that declare the one-cast model AND carry a worked example, so
|
||||
unrelated entries are untouched.
|
||||
"""
|
||||
ruco = data.get("run_control") or {}
|
||||
if "one cast" not in str(ruco.get("iteration", "")):
|
||||
return []
|
||||
if "worked example" not in raw_text.lower():
|
||||
return []
|
||||
errs = []
|
||||
if "1,452" in raw_text:
|
||||
errs.append(f"{rel}: worked example resets the break interval at a restock "
|
||||
f"(cast 1,452 = 901 + restarted interval) — breaks must stay "
|
||||
f"monotonic at 550/1,100/1,650 independently of restocks")
|
||||
for marker, label in (("550", "break #1 (cast 550)"),
|
||||
("901", "restock #1 (cast 901)"),
|
||||
("1,100", "break #2 (cast 1,100)"),
|
||||
("1,650", "break #3 (cast 1,650)"),
|
||||
("1,802", "restock #2 (cast 1,802)")):
|
||||
if marker not in raw_text:
|
||||
errs.append(f"{rel}: worked example missing monotonic counter pin "
|
||||
f"{label} — breaks/restocks must be pinned literally")
|
||||
return errs
|
||||
|
||||
|
||||
def _loss_reserve_stop(initial_capital, total_assets, reserve):
|
||||
"""Loss-reserve stop comparator in the pinned direction (owner guidance
|
||||
2026-08-29, governor review 10361, PR #8): stop fires only when the
|
||||
loss EXCEEDS the reserve, i.e. when (initial capital - total assets)
|
||||
> reserve. A loss makes the LHS positive; a profit (total assets >
|
||||
initial capital) makes it negative and can never fire this stop. The
|
||||
reversed 'total assets - initial capital' form is invalid because it
|
||||
is negative exactly when a loss exists.
|
||||
"""
|
||||
return (initial_capital - total_assets) > reserve
|
||||
|
||||
|
||||
def alch_accounting_regression(rel, data, raw_text):
|
||||
"""Loss-reserve + fill-price accounting regression (owner guidance
|
||||
2026-08-29 for governor review 10361, PR #8): the low-alch-members
|
||||
entry must express the loss-reserve stop in the correct direction
|
||||
EVERYWHERE ('initial capital - total assets > 162,990'), the reversed
|
||||
sign form must never reappear, and the realized GP/hour bounds must be
|
||||
derived from the declared fill-price scenarios (A live-low -3.00/cast
|
||||
vs B max allowed +5% fill -30.15/cast), exposing the +5% fill loss
|
||||
instead of recycling the live-low margin. Includes below/at/above
|
||||
boundary fixtures and a profit-side negative regression for the
|
||||
comparator itself. Scoped to the members low-alchemy entry only.
|
||||
"""
|
||||
if data.get("id") != "low-alch-members":
|
||||
return []
|
||||
errs = []
|
||||
|
||||
# 1. comparator boundary fixtures (pure function, entry's own constants)
|
||||
INIT, RESERVE = 1_308_290, 162_990 # entry capital / 6-batch reserve
|
||||
fixtures = [
|
||||
("below (loss 162,989)", INIT - 162_989, False),
|
||||
("at boundary (loss 162,990, strict >)", INIT - 162_990, False),
|
||||
("above (loss 162,991)", INIT - 162_991, True),
|
||||
("profit side (assets 50,000 above initial)", INIT + 50_000, False),
|
||||
("breakeven (assets == initial)", INIT, False),
|
||||
]
|
||||
for label, total_assets, expect in fixtures:
|
||||
got = _loss_reserve_stop(INIT, total_assets, RESERVE)
|
||||
if got != expect:
|
||||
errs.append(f"{rel}: loss-reserve comparator fixture '{label}' "
|
||||
f"returned {got}, expected {expect}")
|
||||
|
||||
# 2. entry direction: correct form present, reversed form absent
|
||||
if "initial capital - total assets > 162,990" not in raw_text:
|
||||
errs.append(f"{rel}: loss-reserve stop must be expressed as "
|
||||
f"'initial capital - total assets > 162,990' everywhere")
|
||||
if "total assets - initial capital" in raw_text:
|
||||
errs.append(f"{rel}: loss-reserve stop uses the reversed sign form — "
|
||||
f"on a loss it is negative and can never exceed the "
|
||||
f"reserve; use 'initial capital - total assets > 162,990'")
|
||||
|
||||
# 3. realized GP bounds must come from the fill-price scenarios
|
||||
eco = data.get("economy", {})
|
||||
if eco.get("gp_per_hour_low") != -29463:
|
||||
errs.append(f"{rel}: gp_per_hour_low must be -29,463 (max-fill "
|
||||
f"scenario B -30.15/cast x session-fast 977.2 casts/hr "
|
||||
f"= -29,462.6), got {eco.get('gp_per_hour_low')}")
|
||||
if eco.get("gp_per_hour_high") != -1758:
|
||||
errs.append(f"{rel}: gp_per_hour_high must be -1,758 (live-low "
|
||||
f"scenario A -3.00/cast x session-slow 585.9 casts/hr "
|
||||
f"= -1,757.7), got {eco.get('gp_per_hour_high')}")
|
||||
for pin, label in (("-30.15", "max-fill per-cast loss -30.15"),
|
||||
("570.15", "max-fill per-cast cost 570.15"),
|
||||
("423.15", "max-fill candidate price 423.15")):
|
||||
if pin not in raw_text:
|
||||
errs.append(f"{rel}: economy FILL-PRICE SCENARIOS missing {label} "
|
||||
f"({pin}) — the +5% accepted-fill scenario must be "
|
||||
f"declared so the realized GP bounds cannot silently "
|
||||
f"revert to the live-low-only margin")
|
||||
return errs
|
||||
|
||||
|
||||
# --- GE-flip lifecycle gates (owner guidance 2026-08-28, governor 10323 + 10390) ---
|
||||
# Canonical per-slot sub-states: each GE slot is an INDEPENDENT asynchronous
|
||||
# machine driven by a global scheduler pass; a pass never closes or advances
|
||||
@ -858,6 +962,15 @@ def main():
|
||||
if term not in declared:
|
||||
errors.append(f"{rel}: terminal '{term}' not declared")
|
||||
|
||||
# one-cast worked-example regression (owner guidance 2026-08-28,
|
||||
# PR #8): monotonic break/restock counters, scoped to the one-cast model
|
||||
errors += alch_worked_example_regression(rel, data, path.read_text())
|
||||
|
||||
# loss-reserve direction + fill-price GP bound regression (owner
|
||||
# guidance 2026-08-29 for governor 10361, PR #8), scoped to
|
||||
# low-alch-members
|
||||
errors += alch_accounting_regression(rel, data, path.read_text())
|
||||
|
||||
# action id/step uniqueness
|
||||
a_ids = [a["id"] for a in data["actions"]]
|
||||
a_steps = [a["step"] for a in data["actions"]]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user