From 8d85c658016682c61651e3593fd9c9acd7554c10 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 15:15:23 -0500 Subject: [PATCH] fix(low-alch/handoff)... --- codex/economy/low-alch-f2p.yaml | 99 ++++++++++++++++---------- codex/training/magic-alch-handoff.yaml | 18 +++-- schemas/entry.schema.json | 2 + tools/validate.py | 53 ++++++++++++++ 4 files changed, 128 insertions(+), 44 deletions(-) diff --git a/codex/economy/low-alch-f2p.yaml b/codex/economy/low-alch-f2p.yaml index 02238be..1e2069f 100644 --- a/codex/economy/low-alch-f2p.yaml +++ b/codex/economy/low-alch-f2p.yaml @@ -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: diff --git a/codex/training/magic-alch-handoff.yaml b/codex/training/magic-alch-handoff.yaml index a6f90ae..46bc319 100644 --- a/codex/training/magic-alch-handoff.yaml +++ b/codex/training/magic-alch-handoff.yaml @@ -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: diff --git a/schemas/entry.schema.json b/schemas/entry.schema.json index 22ed647..7f40971 100644 --- a/schemas/entry.schema.json +++ b/schemas/entry.schema.json @@ -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" }, diff --git a/tools/validate.py b/tools/validate.py index eb99614..4593652 100644 --- a/tools/validate.py +++ b/tools/validate.py @@ -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