From a197b34aa8067f590de3622a50cdb50e2b59f0da Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 03:36:45 -0500 Subject: [PATCH] =?UTF-8?q?codex:=20low-alch-members=20=E2=80=94=20complet?= =?UTF-8?q?e=20GE=20lifecycle,=20capital=20model,=20monotonic=20counters,?= =?UTF-8?q?=20fail-closed=20staff=20(bounce-fix=20attempt=204)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Owner guidance 10325 (governor 10297 findings, exactly): - GE acquisition path for initial buy AND every restock: open -> place -> wait/fill -> collect (ge_open/ge_buy/ge_collect; new wait_offers state), partial-fill collect + re-place recovery, bank-stock fast path only when the bank actually holds stock — never withdraw an unbought batch. - Capital model: staff cap 5,000 + first batch at max fill 570,150 + 6-batch worst-case loss reserve 162,990 + float 570,150 (~1.31M); capital stops use TOTAL ASSETS (cash + supplies), never raw cash. - Monotonic counters: breaks at 550/1,100/1,650 independent of restocks at 901/1,802 (gcd 550,901=1); validator regression rejects the old 1,452 restart bug and enforces the pins. - Rates: loop-only 837.2 labelled ceiling; steady-state 639-1020 (mid 791.2); session-realized 586-977 (mid 741.8) over the explicit 2h session horizon -> xp 23,000, gp -2,930..-1,760. - Staff equip FAIL-CLOSED after bounded retries; fire-rune fallback prose removed everywhere. Preserves one-cast iteration, cast-count breaks, rune javelin tips anchor, pacing/equipment fixes. version 4 -> 5. --- codex/economy/low-alch-members.yaml | 342 +++++++++++++++++++--------- tools/validate.py | 35 +++ 2 files changed, 266 insertions(+), 111 deletions(-) diff --git a/codex/economy/low-alch-members.yaml b/codex/economy/low-alch-members.yaml index 002074f..cc3b7c7 100644 --- a/codex/economy/low-alch-members.yaml +++ b/codex/economy/low-alch-members.yaml @@ -3,7 +3,7 @@ title: Low-Level Alchemy (Members) domain: economy tags: [alching, magic-training, members, loss-training] status: verified -version: 4 # 4: bounce-fix 3 2026-08-28 (owner guidance) — iteration = one cast, break.every in CAST counts (independent of bank visits), numeric carried_batch/restock_threshold, realized rates incl. setup+restock+breaks, full-lifecycle actions, pinned worked example +version: 5 # 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-28" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date summary: > @@ -13,28 +13,35 @@ summary: > (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 (-3/cast). Model pin (owner guidance 2026-08-28): one + javelin tips (-3/cast). Model pins (owner guidance 2026-08-28): one iteration = one cast; breaks every 400-700 COMPLETED CASTS (cast-count - based, independent of bank visits); restock when candidate or nature - drops below the numeric restock_threshold (100) against a carried_batch - of 1,000/1,000 — pinned worked example below. Handoff: switch to High - Level Alchemy at level 55 (hard level gate + strictly more coins AND XP - per cast; see sources). + 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)" - - "A bank with GE access within reach (alch spot = GE bank booth; restock trip 60-120s)" - - "Working capital >= 543,000 gp for one carried_batch (1,000 x 403 gp javelin tips + 1,000 x 140 gp natures, live 2026-08-27)" + - "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)" + 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 @@ -52,30 +59,42 @@ economy: # 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 3 (2026-08-28, owner guidance): all rate fields are REALIZED - # figures — setup/restock/breaks included, not just the cast loop. + # 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) + 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: -2960 # realized (incl. restock+breaks), fastest cycle 3.4s -> 986.4 casts/hr x -3 = -2,959 - gp_per_hour_high: -1980 # realized (incl. restock+breaks), slowest cycle 5.2s -> 660.6 casts/hr x -3 = -1,982 - xp_per_hour: 24500 # realized midpoint 791.3 casts/hr x 31 xp = 24,530 (pin 24,500) - # ---- realized-rate derivation (pinned; owner guidance 2026-08-28) ---- - # LOOP: cycle = 1.8s cast (3 ticks x 0.6s) + anti_ban.delay [1.6, 3.4]s ADDITIVE - # (docs/action-grammar.md:135) = 3.4-5.2s, midpoint 4.3s -> loop-only - # casts/hr = 3600/4.3 = 837.2; loop-only xp = 25,953 (837.2 x 31); - # loop-only gp midpoint = -2,512 (837.2 x -3). - # OVERHEAD per 901-cast batch (carried_batch 1000 - restock_threshold 100): - # restock trip [60, 120]s (midpoint 90s, GE-adjacent booth); - # breaks: every [400, 700] casts (midpoint 550) for [45, 120]s (midpoint - # 82.5s) -> 901/550 = 1.64 breaks x 82.5s = 135s. - # Total overhead = 90 + 135 = 225s per 901 casts. - # REALIZED casts/hr = 901 / ((901 x cycle_s + 225) / 3600): - # fast 3.4s -> 986.4; midpoint 4.3s -> 791.3; slow 5.2s -> 660.6. - # xp_per_hour = 791.3 x 31 = 24,530 (pin 24,500; range 20,480..30,580). - # gp_per_hour = realized casts/hr x -3: fast -2,959 (low), slow -1,982 (high), midpoint -2,374. + gp_per_hour_low: -2930 # session-realized fast bound: 977.2 casts/hr x -3 = -2,932 (3.4 s cycle, 300 s startup, 2 h horizon) + gp_per_hour_high: -1760 # session-realized slow bound: 585.9 casts/hr x -3 = -1,758 (5.2 s cycle, 600 s startup, 2 h horizon) + 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; gp = casts/hr x -3: + # low -2,932 (fast), high -1,758 (slow), mid -2,225. + # 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). supplies: - item: rune javelin tips qty_per_cycle: 1 @@ -101,37 +120,58 @@ economy: - "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, +27,931 xp, 2 x 540 gp coin deposits." + - "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: "acquire + equip the staff of fire, GE-buy candidate + natures, build the starting inventory to carried_batch (1,000 candidate + 1,000 nature)" - next: [casting, complete, failed] + 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 current candidate item — 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)" + 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: "deposit coins + products, withdraw candidate + natures back to carried_batch (1,000/1,000), resume casting" - next: [casting, failed] + 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)" + 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 equipped AND candidate == carried_batch AND nature == carried_batch (1,000 and 1,000)" - to: casting + when: "staff + candidate + nature offers all placed (a3/a4/a5)" + to: wait_offers - from: idle - when: "magic level >= target (55, hand off to HLA)" + when: "magic level >= target (55) at session start" to: complete - from: idle - when: "3 consecutive purchase/equip failures with no fallback available (recovery block below)" + 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 rune count < restock_threshold (100) — deterministic restock trigger, pinned numbers" + 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)" @@ -140,8 +180,14 @@ state_machine: when: "3 consecutive failed casts (client issue — do not loop)" to: failed - from: bank_supplies - when: "restocked to carried_batch (1,000 candidate + 1,000 nature)" + 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). @@ -171,27 +217,27 @@ actions: 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)" - on_fail: retry # retry once; on a second failure use the fire-rune fallback (see a3 note / recovery block) - anti_ban: - delay: [1200, 3000] - - - id: a3 - 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). FAILURE PATH — if Wield is unavailable or the staff cannot be equipped, fall back to buying 3 fire runes per cast (3 fire + 1 nature = 155 gp rune cost vs 140, net -18/cast at the anchor), log WARN and continue; never loop on equip attempts." + 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: [600, 1800] + delay: [1200, 3000] - id: a4 step: buy-supplies @@ -200,8 +246,9 @@ actions: 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%" - on_fail: retry # re-place at live low +5% with paced offers; 3 consecutive failures -> failed (recovery block) + 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] @@ -212,21 +259,45 @@ actions: params: item: "nature rune" qty: "carried_batch = 1,000 (4h GE limit 18,000)" - max_price: "live low + 5%" + 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: "candidate 1,000 + nature runes 1,000 (= carried_batch; both stackable, two inventory slots)" + 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: a7 + - id: a9 step: cast-alch verb: use_item_on target: "candidate item in inventory" @@ -237,24 +308,49 @@ actions: 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 -> realized ~791 casts/hr incl. restock+breaks (pinned in economy) + 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: a8 - step: restock + - id: a10 + step: restock-deposit verb: bank_inventory target: bank params: deposit: [all coins, all products] - withdraw: "candidate + nature runes up to carried_batch (1,000/1,000)" + 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: a9 + - 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" + 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: @@ -265,63 +361,85 @@ run_control: for: [45, 120] # seconds of idle AFK per break stop_conditions: - "magic level >= target (55 -> hand off to HLA)" - - "gp under floor (working capital below one carried_batch = 543,000 gp)" + - "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 (total assets - initial capital) > 162,990 — the 6-batch worst-case loss reserve is exhausted" - "3 consecutive failed casts (client issue — do not loop)" - - "3 consecutive failed GE purchases with no fallback candidate available" + - "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 (midpoint 550); break.for = [45, 120] s; - # default staff-of-fire path consumes 1 candidate + 1 nature per cast (worst case 1:1 — + # 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 — before the first inventory: account holds >= 543,000 gp working capital - # (1,000 x 403 + 1,000 x 140). At the GE bank: GE-buy staff of fire, Wield it - # (no documented requirement), GE-buy 1,000 rune javelin tips + 1,000 nature runes, - # withdraw both -> loadout = carried_batch 1,000/1,000. Cast counter := 0. - # 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 (midpoint of break.every [400, 700]): run - # take-break for 45-120 s. Resume casting at cast 551. The break fired on the CAST - # COUNT, not on any bank visit; the restock counter is untouched. - # Step 3 — casts #551..#901: countdown candidate 1000-901 = 99 < 100 (restock_threshold) - # and nature 1000-901 = 99 < 100 simultaneously (worst case 1:1) -> the transition - # fires at the end of cast #901: FIRST RESTOCK/BANK EVENT at cast 901. - # Coins deposited at this restock: 901 x 540 = 486,540 gp; batch net so far -2,703 gp; - # batch xp so far 901 x 31 = 27,931. - # Step 4 — restock: deposit coins + products, withdraw 901 candidate + 901 nature -> - # carried_batch restored to 1,000/1,000. Resume casting at cast 902 (cast counter - # continues; the break counter also continues — restocking never resets either). - # Step 5 — SECOND BREAK when the cast-count interval since break #1 completes - # ([400, 700], midpoint: cast 902 + 550 = 1,452): take-break, resume at 1,453. - # Step 6 — restock #2 fires at cast 1,802 (901-cast batch cadence), and so on, until a - # stop_condition fires (Magic >= 55 -> HLA handoff; gp under floor; 3 failed casts). - # Bank visits and breaks are INDEPENDENT by construction: restock cadence = 901 casts; - # break cadence = 400-700 casts, and the two counters do not interact. + # 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 net + # loss > 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; realized ~791 casts/hr at the 4.3s midpoint cycle incl. restock + breaks; loop-only 837)." - - "Breaks are CAST-COUNT-based (every [400, 700] completed casts, 45-120s AFK) and independent of bank visits — never treat a restock trip as a break." - - "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." + - "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; restock -# action a8 rebuilds to carried_batch and the loop resumes (worked example steps 3-4). -# - Failed GE purchases (offer unfilled): re-place at live low + 5% with paced offers -# (a4/a5); after 3 consecutive failures, alert + stop (failed) unless a fallback -# candidate is switched in — never sit in an unfillable offer loop. -# - Staff of fire cannot be equipped (a3): fall back to buying 3 fire runes per cast -# (3 fire + 1 nature = 155 gp/cast rune cost vs 140; net -18/cast at the anchor), -# log WARN, continue; never loop on equip attempts. -# - Invalid loadout (wrong item in hand / spell unavailable): re-run build-loadout (a6); -# 3 consecutive failed casts -> failed (stop, log ERROR sentinel, alert). -# - Explicit completion/stop: stop_conditions above (Magic 55 HLA handoff, gp floor, -# repeated failures). +# 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. +# - 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)" @@ -332,12 +450,14 @@ sources: - "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) + verb take_break — the cadence + break + recovery model" + - "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: diff --git a/tools/validate.py b/tools/validate.py index 7667c3d..0068a6f 100644 --- a/tools/validate.py +++ b/tools/validate.py @@ -41,6 +41,37 @@ def load_entries(): return sorted(out) +def alch_worked_example_regression(rel, data, raw_text): + """One-cast model worked-example regression (owner guidance 2026-08-28, + PR #8 repair): 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. The old 'restart the interval at the restock' bug (break + #2 at cast 1,452 = 901 + restarted 549) must never reappear. 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 main(): global STALE_DAYS if "--stale-days" in sys.argv: @@ -99,6 +130,10 @@ 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): + # monotonic break/restock counters, scoped to the one-cast model + errors += alch_worked_example_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"]]