Merge remote-tracking branch 'origin/main' into issue-1

# Conflicts:
#	tools/validate.py
This commit is contained in:
Claude 2026-08-29 03:13:45 -05:00
commit d19a4d9de0
3 changed files with 3038 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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"]]