fix(low-alch/handoff)...

This commit is contained in:
Claude 2026-08-28 15:15:23 -05:00
parent 589767b195
commit 8d85c65801
4 changed files with 128 additions and 44 deletions

View File

@ -3,7 +3,7 @@ title: Low-Level Alchemy (F2P)
domain: economy
tags: [alching, magic-training, f2p, loss-training]
status: verified
version: 3 # 3: bounce-fix 4 2026-08-28 (owner guidance 10331, PR #10) — explicit rate_model (session-realized rates incl. startup over a 2h horizon: xp 23,000, gp -109,500..-65,600), complete GE acquisition path (open -> fill -> collect) for the initial buy AND every restock (bank-stock fast path), fail-closed staff equip, monotonic break example (550/1,100/1,650 independent of 901-cast restocks), assets-based capital stops
version: 4 # 4: bounce-fix 6 2026-08-28 (governor comment 10394, PR #10) — real build-loadout action a8 (verified collected staff/candidate/natures, overflow deposit, bank-stock-shortfall-only withdrawal, carried counts PROVEN before the first cast a9; action ids renumbered a9-a13), capital-loss stop sign corrected (initial capital - total assets > 684,400), Magic target pinned at 55 (the LA -> HLA handoff; 54 is a continue), GP bounds tied to the two declared fill scenarios (max-fill net -126.6 -> low -123,700; live-low net -112 -> high -65,600). 3: bounce-fix 4 2026-08-28 (owner guidance 10331, PR #10) — explicit rate_model (session-realized rates incl. startup over a 2h horizon: xp 23,000, gp -109,500..-65,600), complete GE acquisition path (open -> fill -> collect) for the initial buy AND every restock (bank-stock fast path), fail-closed staff equip, monotonic break example (550/1,100/1,650 independent of 901-cast restocks), assets-based capital stops
updated: "2026-08-28" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date
summary: >
@ -30,7 +30,7 @@ accounts:
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). Equip failure is FAIL-CLOSED: bounded retries (3), then log ERROR + alert + stop — there is no fire-rune fallback in this model"
- "A bank with GE access within reach (alch spot = the bank booth; restock trip [60, 120] s)"
- "Starting capital >= ~1.30M gp CASH (staff hard cap 5,000 + first batch 1,000/1,000 at MAX allowed fill 306,600 + session loss reserve 684,400 = 6 worst-case 901-cast batches at live-low+5% fills (covers Magic 21 -> 54) + resting-offer float 306,600). The capital stop checks TOTAL ASSETS (cash + supplies at current buy cost), never raw cash — cash legitimately converts into supplies mid-run"
- "Starting capital >= ~1.30M gp CASH (staff hard cap 5,000 + first batch 1,000/1,000 at MAX allowed fill 306,600 + session loss reserve 684,400 = 6 worst-case 901-cast batches at live-low+5% fills (covers Magic 21 -> 55) + resting-offer float 306,600). The capital stop checks TOTAL ASSETS (cash + supplies at current buy cost), never raw cash — cash legitimately converts into supplies mid-run"
best_practice:
equipment:
@ -48,16 +48,20 @@ best_practice:
economy:
# Verified 2026-08-27 against prices.runescape.wiki (live 'latest')
# + OSRS wiki Low_Level_Alchemy infobox.
# Bounce-fix 4 (2026-08-28, owner guidance 10331): all rate fields are
# SESSION-REALIZED — startup + restock + mandatory breaks included over
# the declared 2 h session horizon (rate_model).
# Bounce-fix 6 (2026-08-28, governor comment 10394): the GP bounds are tied
# to the DECLARED actual-fill scenarios with the SAME costs as the capital
# model — live-low fills (net -112/cast: 148 + 144) and MAX allowed fills
# (live low + 5 %: 155.4 + 151.2 = 306.6 -> net -126.6/cast), because the
# executable purchase accepts live low + 5 % (a4/a10 max_price).
item: Gold necklace # example anchor; rank by cheapest candidate live
item_id: 1654
cost_per_cycle: 292 # necklace buy 148 + nature rune 144 (staff of fire, live low)
cost_per_cycle_max_fill: 306.6 # live low + 5 %: 155.4 + 151.2 (same costs as the capital model)
income_per_cycle: 180 # lowalch of gold necklace (40% of value 450)
net_per_cycle: -112
gp_per_hour_low: -109500 # session-realized fast bound: 977.1 casts/hr x -112 = -109,435 (3.4 s cycle, 300 s startup, 2 h horizon)
gp_per_hour_high: -65600 # session-realized slow bound: 585.9 casts/hr x -112 = -65,621 (5.2 s cycle, 600 s startup, 2 h horizon)
net_per_cycle: -112 # at live-low fills
net_per_cycle_max_fill: -126.6 # at MAX allowed fills (worst declared scenario)
gp_per_hour_low: -123700 # session-realized WORST declared scenario: 977.1 casts/hr x -126.6 (max-fill) = -123,700 (fast 3.4s cycle, 300s startup, 2h horizon)
gp_per_hour_high: -65600 # session-realized BEST declared scenario: 585.9 casts/hr x -112 (live-low) = -65,621 (slow 5.2s cycle, 600s startup, 2h horizon)
xp_per_hour: 23000 # session-realized midpoint: 741.8 casts/hr x 31 = 22,995 (pin 23,000)
# ---- rate model (derived; enforced by tools/validate.py rate_model_errors) ----
# Cast: 1.8s (3 ticks x 0.6s, rate_model.cast_ticks) + anti_ban.delay
@ -71,12 +75,16 @@ economy:
# SESSION-REALIZED (2 h horizon = 7,200 s; startup 300-600 s, mid 450 s ->
# 450/7200 = 6.25 % amortised): 977.1 / 741.8 / 585.9 casts/hr.
# xp_per_hour = 741.8 x 31 = 22,995 (pin 23,000);
# gp_per_hour = casts/hr x -112: low -109,435, high -65,621 (mid -83,082).
# gp_per_hour = casts/hr x net-per-cast over the TWO declared fill
# scenarios (same costs as the capital model):
# worst = fast casts/hr x max-fill net: 977.1 x -126.6 = -123,700 (low)
# best = slow casts/hr x live-low net: 585.9 x -112 = -65,621 (high)
# (mid x mid-fill -119.3 = -88,497 is inside the band)
# BATCH ECONOMICS (901 casts; deposits 901 x 180 = 162,180 gp per batch):
# input cost at live low: 901 x 292 = 263,092 -> net -100,912/batch.
# input cost at MAX fill (live low + 5 %: 1,000 x 155.4 + 1,000 x 151.2 =
# 306,600 per batch): 901 x 306.6 = 276,247 -> net -114,067/batch (worst
# case). Reserve = 6 worst-case batches (Magic 21 -> 54) = 684,400 gp;
# case). Reserve = 6 worst-case batches (Magic 21 -> 55) = 684,400 gp;
# the capital stops check TOTAL ASSETS, not raw cash (accounts + stop_conditions).
# Superseded figures (all fail the derived gate on purpose): loop-only 837
# casts/hr (25,953 xp/hr); the 8-14-cast-break regime (~305 casts/hr); the
@ -120,16 +128,16 @@ state_machine:
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 (a4/a5); 3 consecutive no-fill rounds -> failed. An offer still unfilled at session end is left resting by design — its coins are tied up (float reserve) and return when it fills or the operator aborts it (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 — deposit overflow, withdraw only the shortfall)"
next: [casting, failed]
goal: "equip the staff of fire (a7 — bounded retries, then FAIL CLOSED) and build the VERIFIED loadout (a8 build-loadout): verify the collected staff + candidate + natures (from a6), deposit overflow, withdraw only the real bank-stock shortfall, and PROVE the carried counts equal carried_batch 1,000/1,000 BEFORE the first cast (a9)"
next: [casting, bank_supplies, failed]
casting:
goal: "cast Low Level Alchemy on the current candidate item (a8) — each cast consumes 1 candidate + 1 nature and pays 180 gp (lowalch), +31 xp, net -112 gp; take a break when the completed-cast counter hits run_control.break.every ([400, 700] casts, via a12); the counter is NEVER reset by restocking"
goal: "cast Low Level Alchemy on the current candidate item (a9) — each cast consumes 1 candidate + 1 nature and pays 180 gp (lowalch), +31 xp, net -112 gp at live-low fills; 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 (a9 — bank-stock fast path when the bank already holds enough); for the remaining deficit, open GE (a2), buy the deficit (a10, live low + 5%), collect it (a11) — 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, ...)"
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 54, hand off to HLA at 55) or the 2 h session horizon"
goal: "reached the run's XP/level target (default 55 — the LA -> HLA handoff) or the 2 h session horizon"
failed:
goal: "hard failure — stop, log ERROR sentinel, alert"
transitions:
@ -137,7 +145,7 @@ state_machine:
when: "staff + candidate + nature offers all placed (a3/a4/a5)"
to: wait_offers
- from: idle
when: "magic level >= target (54, hand off to HLA at 55) at session start"
when: "magic level >= target (55 — the LA -> HLA handoff) at session start"
to: complete
- from: idle
when: "3 consecutive offer-place failures at session start (no fallback candidate)"
@ -152,16 +160,19 @@ state_machine:
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)"
when: "staff equipped (a7) AND build-loadout verified the carried counts at carried_batch 1,000/1,000 (a8 — collected staff/candidate/natures present, overflow deposited, only a real bank-stock shortfall withdrawn)"
to: casting
- from: ready
when: "staff equip failed after bounded retries — FAIL CLOSED"
to: failed
- from: ready
when: "build-loadout verification shows a carried shortfall (candidate or nature < carried_batch) that the bank does not hold — restock first"
to: bank_supplies
- 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 (54, hand off to HLA at 55)"
when: "magic level >= target (55 — the LA -> HLA handoff; level 54 is a CONTINUE: LA keeps casting until the handoff boundary)"
to: complete
- from: casting
when: "3 consecutive failed casts (client issue — do not loop)"
@ -259,6 +270,18 @@ actions:
delay: [600, 1800]
- id: a8
step: build-loadout
verb: bank_inventory
target: bank
params:
deposit: [overflow products]
withdraw: "only the real bank-stock shortfall up to carried_batch (1,000 candidate + 1,000 nature) — the purchases collected by a6 are already in the inventory; a deficit the bank does not actually hold is NEVER withdrawn (it is bought at the GE via the bank_supplies phase, a11)"
verify: "staff of fire collected and present (a6), candidate count == carried_batch 1,000, nature count == carried_batch 1,000 — the carried counts are PROVEN before the first cast (a9); a verified shortfall sends the slot to bank_supplies instead of casting"
on_fail: retry
anti_ban:
delay: [500, 1100]
- id: a9
step: cast-alch
verb: use_item_on
target: "candidate item in inventory"
@ -266,36 +289,36 @@ actions:
spell: "Low Level Alchemy"
item: "cheapest live candidate in inventory"
on: "itself"
consumption: "1 candidate + 1 nature per cast (staff of fire default); pays 180 gp (lowalch) + 31 xp, net -112 gp"
consumption: "1 candidate + 1 nature per cast (staff of fire default); pays 180 gp (lowalch) + 31 xp, net -112 gp at live-low fills"
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 (rate_model)
- id: a9
- 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 (a10) and collected (a11)"
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: a10
- 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 a9 fast path (e.g. 901 candidate + 901 nature after a 901-cast batch with an empty bank)"
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: a11
- id: a12
step: restock-collect
verb: ge_collect
target: grand-exchange
@ -305,7 +328,7 @@ actions:
anti_ban:
delay: [600, 1400]
- id: a12
- id: a13
step: take-break
verb: take_break
target: "current location"
@ -321,10 +344,10 @@ run_control:
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 (54 -> hand off to HLA at 55)"
- "magic level >= target (55 — the LA -> HLA handoff)"
- "session elapsed >= 2 h (session horizon — startup is amortised over this window in the rate model)"
- "total assets (cash + supplies at current buy cost) < 311,600 — cannot fund the next batch at max fill (306,600) plus staff replacement (5,000); assets, NOT raw cash: cash that became supplies is not a loss"
- "session net loss (total assets - initial capital) > 684,400 — the 6-batch worst-case loss reserve is exhausted"
- "session net loss = initial capital - total assets > 684,400 — the 6-batch worst-case loss reserve is exhausted (a loss is WHEN ASSETS FALL; total assets - initial capital goes NEGATIVE on losses and can never exceed the reserve — the reversed sign never fires)"
- "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)"
@ -349,14 +372,14 @@ run_control:
# and pays 180 gp (lowalch) + 31 xp -> net -112 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 (a12). Resume casting at cast 551. The break fired on
# 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 180 = 162,180 gp (a9); the bank holds 0 supplies (all sales
# Deposit 901 x 180 = 162,180 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 (a10, live low + 5 %), collect (a11) ->
# deficit 901 candidate + 901 nature (a11, live low + 5 %), collect (a12) ->
# inventory back to 1,000/1,000. Batch net: -100,912 gp (live-low fills) or
# -114,067 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
@ -364,9 +387,10 @@ run_control:
# 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 54+ (HLA handoff at
# 55), the 2 h session horizon, the capital stops (assets floor 311,600 or
# net loss > 684,400), 3 failed casts, or 3 consecutive no-fill GE rounds.
# Step 6 — continue until a stop_condition fires: Magic 55 (the LA -> HLA
# handoff — level 54 CONTINUES casting), the 2 h session horizon, the
# capital stops (assets floor 311,600 or initial capital - total assets >
# 684,400), 3 failed casts, or 3 consecutive no-fill GE rounds.
# ============================================================================================
risk:
@ -390,12 +414,13 @@ risk:
# - 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).
# - Invalid loadout (wrong item in hand / spell unavailable / carried counts below
# carried_batch): re-run build-loadout (a8); a verified shortfall the bank does
# not hold goes to bank_supplies; 3 consecutive failed casts -> failed.
# - 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 54+ HLA handoff at 55,
# - Explicit completion/stop: stop_conditions above (Magic 55 LA -> HLA handoff,
# 2 h session horizon, assets floor 311,600, loss reserve 684,400, repeated
# failures, fail-closed staff equip).
@ -406,7 +431,7 @@ sources:
- "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 — 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) — the cadence + break + GE + lifecycle model; owner guidance 2026-08-28 (rate_model, fail-closed staff, monotonic counters)"
- "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) — the cadence + break + GE + lifecycle model; owner guidance 2026-08-28 (rate_model, fail-closed staff, monotonic counters); governor comment 10394 (build-loadout verified action, sign-corrected loss stop, 55 handoff boundary, fill-scenario GP bounds)"
verified: "2026-08-27" # keep quoted (YAML 1.1 date parsing); prices last re-fetched live 2026-08-27
related:

View File

@ -3,7 +3,7 @@ title: Magic training via alchemy (LA → SII → HLA handoff)
domain: training
tags: [magic-training, alching, f2p, xp-training]
status: verified
version: 3 # 3: bounce-fix 4 2026-08-28 (owner guidance 10331) — explicit rate_model discriminator (derived per-entry lifecycle rates), per-recipe inventory capacity floor(27/qty) with derived min/mid/max rates pinned in recipes.rates, complete GE acquisition path (open -> fill -> collect) for initial buy AND every restock (bank-stock fast path), fail-closed staff equip (bounded retries, no fire-rune fallback), configurable session target (55 = LA/SII->HLA handoff completion; >55 = executable HLA tier to the target; LA stays the 43-54 fallback), capital model (cash vs supplies, max-fill costs, reserve, assets-based stops)
version: 4 # 4: bounce-fix 6 2026-08-28 (governor comment 10394) — build-loadout a7 VERIFIES collected staff/supplies and PROVES carried counts before the first cast (ready/ready->cast strengthened), capital-loss stop sign corrected (initial capital - total assets > 690,075). 3: bounce-fix 4 2026-08-28 (owner guidance 10331) — explicit rate_model discriminator (derived per-entry lifecycle rates), per-recipe inventory capacity floor(27/qty) with derived min/mid/max rates pinned in recipes.rates, complete GE acquisition path (open -> fill -> collect) for initial buy AND every restock (bank-stock fast path), fail-closed staff equip (bounded retries, no fire-rune fallback), configurable session target (55 = LA/SII->HLA handoff completion; >55 = executable HLA tier to the target; LA stays the 43-54 fallback), capital model (cash vs supplies, max-fill costs, reserve, assets-based stops)
updated: "2026-08-28" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date
summary: >
@ -306,8 +306,8 @@ state_machine:
goal: "wait for the pending offers (ge_offers_done); when filled, collect every purchase + refund from the collection box (a5 — never leave purchases in the box). PARTIAL FILLS: collect the filled portion, re-place the remainder at the current edge (a3/a4); 3 consecutive no-fill rounds -> failed. An offer still unfilled at session end is left resting by design — its coins are tied up (float reserve) and return when it fills or the operator aborts it (the single operator-touch point)"
next: [ready, wait_offers, failed]
ready:
goal: "equip the staff of fire (a6 — bounded retries, then FAIL CLOSED) and build the loadout to the tier's carried amounts (a7; purchases from a5 are already in the inventory — deposit overflow, withdraw only the shortfall)"
next: [cast, failed]
goal: "equip the staff of fire (a6 — bounded retries, then FAIL CLOSED) and build the VERIFIED loadout (a7 build-loadout): verify the collected staff + tier supplies (from a5), deposit overflow, withdraw only the real bank-stock shortfall, and PROVE the carried counts equal the tier's carried amounts BEFORE the first cast (a8/a9)"
next: [cast, bank_supplies, failed]
cast:
goal: "cast the active tier spell with the tier-correct target — LA (< 43, or 43-54 when SII is skipped) or HLA (>= 55, only when the session target is above 55) on a noted alch candidate (a8); SII (43-55, selection.mode) on the selected recipe's declared target_input (primary ore, never coal) with its exact allowed-fill inputs withdrawn (a9). A break fires on the completed-cast counter (a13), NEVER on bank visits, and restocking never resets it"
next: [cast, bank_supplies, complete, failed]
@ -338,11 +338,14 @@ state_machine:
when: "3 consecutive no-fill rounds (offer cannot fill at the accepted edge)"
to: failed
- from: ready
when: "staff equipped (a6) AND loadout at the tier's carried amounts (a7)"
when: "staff equipped (a6) AND build-loadout verified the carried counts at the tier's carried amounts (a7 — collected staff/supplies present, overflow deposited, only a real bank-stock shortfall withdrawn; carried counts PROVEN)"
to: cast
- from: ready
when: "staff equip failed after bounded retries — FAIL CLOSED"
to: failed
- from: ready
when: "build-loadout verification shows a carried shortfall the bank does not hold — restock first"
to: bank_supplies
- from: cast
when: "level < 43, or level 43-55 with SII skipped — LA tier: cast on the cheapest alch candidate (LA is executable throughout the 43-54 window when no recipe has a positive margin)"
to: cast
@ -452,7 +455,8 @@ actions:
target: bank
params:
deposit: [overflow products]
withdraw: "to the tier's carried amounts (only the shortfall — collected purchases from a5 are already in the inventory): LA/HLA 1,000 candidate + 1,000 nature; SII one capacity-sized pack of the selected recipe (steel: 9 iron + 18 coal + 9 natures)"
withdraw: "to the tier's carried amounts (only the real bank-stock shortfall — collected purchases from a5 are already in the inventory): LA/HLA 1,000 candidate + 1,000 nature; SII one capacity-sized pack of the selected recipe (steel: 9 iron + 18 coal + 9 natures)"
verify: "the collected staff of fire + tier supplies (a5) are present, and the carried counts EQUAL the tier's carried amounts — the loadout is PROVEN before the first cast (a8/a9); a verified shortfall the bank does not hold sends the slot to bank_supplies instead"
on_fail: retry
anti_ban:
delay: [500, 1100]
@ -540,7 +544,7 @@ run_control:
- "magic level >= session target (default 55: completes at the LA/SII -> HLA handoff; when the configured target is above 55, the HLA tier executes from 55 until the target — HLA is never declared-but-unreachable)"
- "session elapsed >= 2 h (session horizon — the rate model amortises startup over it)"
- "total assets (cash + supplies at current buy cost) < the next tier batch at max fill plus staff replacement (LA/HLA: 312,650 = 307,650 + 5,000; SII steel: 10,150 = 5,150 + 5,000) — assets, NOT raw cash: cash that became supplies is not a loss"
- "session net loss (total assets - initial capital) > 690,075 — the 6-batch worst-case LA reserve (Magic 21 -> 55 at max fills) is exhausted"
- "session net loss = initial capital - total assets > 690,075 — the 6-batch worst-case LA reserve (Magic 21 -> 55 at max fills) is exhausted (a loss is WHEN ASSETS FALL; total assets - initial capital goes NEGATIVE on losses and can never exceed the reserve — the reversed sign never fires)"
- "3 consecutive failed casts (client issue — do not loop)"
- "3 consecutive no-fill GE rounds (initial acquisition or restock) with no fallback recipe/candidate available"
- "staff of fire equip failed after bounded retries — FAIL CLOSED (log ERROR, alert, stop)"
@ -635,7 +639,7 @@ sources:
- "prices.runescape.wiki/api/v1/osrs/mapping (id 561 nature rune: members=false, limit 18000, value 180, lowalch 72; limit cross-checked against Module:Exchange/Nature_rune — agree)"
- "prices.runescape.wiki/api/v1/osrs/latest (nature rune buy 145, snapshot 2026-08-27; steel-bar fixture prices: iron ore 100, coal 150, steel bar 600)"
- "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) — the cadence + break + GE + lifecycle model; owner guidance 2026-08-28 (capacity-derived SII, rate_model, configurable target, fail-closed staff)"
- "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) — the cadence + break + GE + lifecycle model; owner guidance 2026-08-28 (capacity-derived SII, rate_model, configurable target, fail-closed staff); governor comment 10394 (verified build-loadout a7, sign-corrected loss stop)"
verified: "2026-08-27" # keep quoted (YAML 1.1 date parsing); prices last re-fetched live 2026-08-27
related:

View File

@ -39,8 +39,10 @@
"item": { "type": "string" },
"item_id": { "type": "integer" },
"cost_per_cycle": { "type": "number" },
"cost_per_cycle_max_fill": { "type": "number" },
"income_per_cycle": { "type": "number" },
"net_per_cycle": { "type": "number" },
"net_per_cycle_max_fill": { "type": "number" },
"gp_per_hour_low": { "type": "number" },
"gp_per_hour_high": { "type": "number" },
"xp_per_hour": { "type": "number" },

View File

@ -167,6 +167,29 @@ def _mean(pair):
return (pair[0] + pair[1]) / 2.0 if pair else 0.0
def target_reached(level, target):
"""Magic-level stop boundary (governor 10394 finding 3): the run stops
when level >= target. Default sessions target 55 (the LA/SII -> HLA
handoff); a session at level 54 with target 55 CONTINUES (False), and
level 55 hands off (True)."""
return level >= target
def stop_loss_exceeded(initial_capital, total_assets, reserve):
"""Sign-corrected capital-loss stop (governor 10394 finding 2):
exceeded when initial capital - total assets > reserve. total_assets -
initial capital goes NEGATIVE on losses and can never exceed the
reserve — the reversed sign would never fire."""
return (initial_capital - total_assets) > reserve
def loadout_verified(carried, required):
"""Build-loadout proof (governor 10394 finding 1): the carried counts
must EQUAL the required carried amounts before the first cast. A
shortfall is not verified (False) — it routes to bank_supplies."""
return all(carried.get(k, 0) >= qty for k, qty in required.items())
def _casting_delay(data):
"""The mandatory post-action delay of the entry's casting action(s),
converted from ms (anti_ban.delay units) to seconds.
@ -775,6 +798,36 @@ def run_selftest(verbose=False):
any("cannot fit the inventory" in e
for e in rate_model_errors("bad.yaml", oversized)))
# --- governor comment 10394 regression fixtures (bounce-fix 6) ---
# Finding 3: Magic target boundary — the entry may stop at the declared
# target (55 = the LA/SII -> HLA handoff) but must CONTINUE below it.
check("target boundary: level 54 with target 55 is a CONTINUE (not complete)",
target_reached(54, 55) is False)
check("target boundary: level 55 with target 55 HANDS OFF (complete)",
target_reached(55, 55) is True)
check("target boundary: level 56 with target 55 completes too",
target_reached(56, 55) is True)
# Finding 2: sign-corrected capital-loss stop — a loss is when assets
# FALL; the old reversed sign (total_assets - initial_capital) can never
# exceed the reserve on losses.
check("loss stop: assets 800,000 below initial capital (800,000 > 684,400 reserve) FIRES",
stop_loss_exceeded(1_300_000, 500_000, 684_400) is True)
check("loss stop: assets 604,400 below initial capital (604,400 < reserve) does NOT fire",
stop_loss_exceeded(1_300_000, 695_600, 684_400) is False)
check("loss stop reversed-sign trap: a PROFIT of 100,000 must NOT fire the loss stop",
stop_loss_exceeded(1_200_000, 1_300_000, 684_400) is False)
check("loss stop: crossing the reserve boundary exactly (loss == reserve) does NOT fire (strict >)",
stop_loss_exceeded(1_300_000, 615_600, 684_400) is False)
# Finding 1: build-loadout proof — carried counts must equal the carried
# amounts before the first cast; a shortfall is not verified.
required = {"Gold necklace": 1000, "Nature rune": 1000}
check("loadout verified at exactly 1,000/1,000 (proven before the first cast)",
loadout_verified({"Gold necklace": 1000, "Nature rune": 1000}, required) is True)
check("loadout NOT verified at 999/1,000 (shortfall routes to bank_supplies)",
loadout_verified({"Gold necklace": 999, "Nature rune": 1000}, required) is False)
check("loadout NOT verified when natures are missing entirely",
loadout_verified({"Gold necklace": 1000}, required) is False)
return errors