Magic/Alchemy PR #10 landed on main; this integrates it into the woodcutting
branch and resolves the predicted tools/validate.py conflict:
- union of all validator gates: magic/alchemy (recipe/rate/tier/loadout,
supply_limit_conflicts, document_error + _end_to_end_bad_mapping),
mining (pickaxe suite), GE-flip lifecycle (floor-tax + scheduler +
conservation + slot-phase + buy-capacity + slot-reuse), and the
woodcutting gates (canonical axe table, per-mode ladders, per-selected-
row 27/28 dynamic rates, drop-GP-zero, prepare/failed/drop-gate wiring).
- governor 10637: removed the run_flip_selftest alias duplication — the
combined GE/Woodcutting suite no longer runs twice. Each suite now runs
exactly once per invocation: run_selftest (cross-field/alchemy),
run_flip_selftest (GE lifecycle), run_mining_selftest, and the new
run_woodcutting_selftest. Truthful unique selftest count: 144 PASS.
- tree scan: 6 entries, 0 errors, 0 warnings; git diff --check clean.
Owner guidance 2026-08-29 for governor review 10476 (PR #11), finding by finding:
1. Explicit tier rank/tie-break for equal Mining gates. pickaxe_tiers
declared bronze and iron both at Mining 1 / Attack 1 while the
level-1 worked example called bronze canonical and carried it (27
slots) — internally contradictory under the entry's own
highest-unlocked acquisition policy. pickaxe_tiers now carries an
explicit rank (bronze 1 .. rune 6) and the selection rule resolves
equal Mining gates by rank: Mining 1 deterministically selects IRON
(7-tick roll, rank 2) over bronze (rank 1), and Attack 1 EQUIPS it
(iron attack gate = 1) -> 28 ore slots. Stated at every policy
layer (summary, accounts.requires_other, best_practice.equipment,
prepare state, economy.supplies, a14 equip-tool gate, pickaxe_tiers
head comment). An owned-only bronze run is documented as an operator
choice, never the entry's selection rule.
2. Level-1 lifecycle/rate inputs recomputed. The level-1 worked
example now uses the equipped iron pickaxe: 28 ore slots per drop
load, iron cadence [4200, 6000] ms (midpoint 5.1 s), and the
copper/tin rate re-derived from that cadence: 1.6 rolls x 5.1 + 0.5
drop + 0.39 break = 9.05 s/ore -> 398 ores/hr -> ~6,960 XP/hr (pin
6,900), replacing the bronze-midpoint 360 ores/hr / 6,300 XP/hr.
Updated in mine_sites (se-varrock-copper / se-varrock-tin),
economy comment block, profit_drivers, worktree example, and risk
notes; no other rate changes (fast-xp iron 28,000 and banked rates
untouched).
3. Recomputed 1-Attack/1-Mining fixture + capacity proof. Schema:
pickaxe_tiers gains required rank (1..6); pickaxe_examples gain
required expected_slots (28 equipped / 27 carried). Validator:
select_pickaxe() ties by rank; every pickaxe_examples row is
recomputed including expected_slots, the 1-Attack/41-Mining
rune-carry fixture is preserved, and the 1-Attack/1-Mining row is
now mandatory (selects iron, equipped, 28). Committed negative
fixtures f18-f22: bronze-at-Mining-1, carried-at-Attack-1, wrong
slots, missing 1/1 fixture, and a tier missing rank are all
rejected (22 selftests pass).
Preserved: complete GE tool acquisition loop, structured copper/tin
below-15 fallback, fast-xp/banked-income modes, dynamic 27/28
inventory handling, declared banked targets, respawn/world-hop model,
and all prior rate gates. version 6 -> 7, updated 2026-08-29.
Gate: python3 tools/validate.py -> 2 entries, 0 errors, 1 pre-existing
F2P WARN (low-alch-f2p researched, untouched); --selftest -> 22 PASS;
git diff --check clean.
A successful sell followed sell-filled-awaiting-collect -> closed, and
'closed' had no outgoing transition while the scheduler left closed
slots untouched — every GE slot could perform ONE flip and then became
permanently unusable (no rotations, breaks, or longer sessions).
Fix (finding-by-finding, governor comment 10471):
1. 'closed' is no longer a sink. The completed cycle's ledger is
reconciled and ARCHIVED at close, and the physical slot is released
to free via the explicit archival transition closed -> free
(flip-candidates.yaml slot_transitions, whens, scheduler_iteration,
a9 archive step, summary, risk, sources). The scheduler pass
dispatches the release (SLOT_DISPATCH['closed'] = 'release'), so a
closed slot is only ever transient.
2. A deliberately closed cancelled sell releases identically:
sell-cancelled-awaiting-collect -> closed -> free (whens updated).
3. Committed two-rotation regression (validator, wired into
ge_lifecycle_errors for every flipping entry): slot_reuse_violations
walks one physical slot through free -> buy-open ->
buy-filled-awaiting-collect -> ready-to-sell -> sell-open ->
sell-filled-awaiting-collect -> closed -> free -> buy-open (second
buy in the SAME slot), validating every ledger frame against both
conservation gates; archived_ledger_preserved_after_second_buy
proves the rotation-1 archived ledger survives rotation 2 bit-for-bit
(no loss, duplication, or mutation). Negative fixtures prove a
closed sink or a closed that does not release to free FAILS.
4. ge_lifecycle_errors now requires closed == ['free'] exactly and
rejects any machine where a state has no transitions.
5. Worked example: t5/t8 walk the release, t8b runs rotation 2 in the
same physical slots.
All prior fixtures preserved (tax x4, async-slot scheduler,
collection-box, limit-window, side-aware conservation x25) — now 35
selftest checks.
Evidence: python3 tools/validate.py -> 2 entries, 0 errors, 1
pre-existing warning; --selftest -> 35 PASS, 0 errors; git diff
--check clean.
Owner guidance 2026-08-29 for governor review 10361 (PR #8), finding by finding:
1. Loss-reserve stop direction — the stop read 'session net loss (total
assets - initial capital) > 162,990'; a loss makes that LHS negative so
it could never exceed the positive reserve (the stop could never fire).
Now expressed everywhere as 'initial capital - total assets > 162,990'
(stop_conditions, worked-example step 6, recovery block, version pin):
a loss makes the difference positive, the strict-greater comparison
fires on losses and never on profits, and exactly-at-boundary does not
fire. tools/validate.py gains alch_accounting_regression(): comparator
boundary fixtures (below 162,989 / at 162,990 / above 162,991 / profit
side +50,000 / breakeven) plus entry-text pins rejecting the reversed
form anywhere in the file.
2. Fill-price scenarios for realized GP bounds — previously gp_per_hour_low
/high derived only from the live-low -3.00/cast margin while the buy
policy permits live low + 5% (candidate 423.15 + nature 147 = 570.15 vs
540 lowalch = -30.15/cast). The economy block now declares FILL-PRICE
SCENARIOS A (live-low 543 -> -3.00/cast) and B (max allowed +5% 570.15
-> -30.15/cast) as the single ledger behind capital, batch economics
AND the GP bounds: gp_per_hour_low -29,463 (scenario B x session-fast
977.2), gp_per_hour_high -1,758 (scenario A x session-slow 585.9),
midpoints -22,365 / -2,225 pinned; validator enforces the exact bound
values and the -30.15/570.15/423.15 scenario pins.
Preserved: complete GE acquisition/restock flow, one-cast iteration,
monotonic break/restock counters, total-assets accounting (floor
575,150), fail-closed staff path, and the earlier fixed pacing/equip
findings. version 5 -> 6, updated 2026-08-29.
Gate: python3 tools/validate.py -> 2 entries, 0 errors, 1 pre-existing
F2P WARN (low-alch-f2p researched, untouched); git diff --check clean;
regression negative-tested (old gp pins, reversed form, missing scenario
pins all rejected).
Governor bounce 5 (2026-08-28) repaired finding by finding:
1. Tool purchase is now executable end to end: new a10 ge_open before any
ge_buy; a12 wait-ge-fill is BOUNDED (one 3-6 min fill window) and on
no-fill/partial-fill TIMEOUT cancels the resting offer -> a13
collects/refunds -> fail closed via recover (3 loops -> failed); a resting
offer is never re-placed indefinitely. Actions renumbered a11..a17.
2. min_level 1 copper/tin below-15 path now has STRUCTURED fallback rows
(mine_sites se-varrock-copper / se-varrock-tin, level 1, 17.5 xp, safe
East Varrock) added to mining_modes.fast-xp.sites; level-1 and level-14
selections are pinned in a new mining_selections block and recomputed by a
new select_fast_site() validator gate (+ 3 committed selftest fixtures).
3. a2 no longer advertises silver/coal/gold/mithril banked targets: it
restricts banked-income to the declared rows (iron @ se-varrock-bank,
adamantite @ al-kharid-south-adamantite); superheat-coal technique is
marked informational-only. New ge_open/ge_offers_done/ge_collect
validator gate (+ 1 fixture). Modeled below-15 rate 6,300 XP/hr (was
the unbacked '~8,800' prose) recomputed from the bronze cadence midpoint.
Validator: 2 entries, 0 errors, 1 pre-existing WARN; --selftest 17 PASS;
git diff --check clean.
- Buy-limit reset NEVER cancels an offer: quote_age_policy now aborts
only on a stale scanned edge or explicit strategy reallocation; the
4h window is a capacity clock (limit_ledger), not an offer deadline.
Removed every lapse-as-cancel trigger from best_practice, whens,
quote_age_policy, a5/a8, worked example, risk, and the a6 verdict.
- Completed offers pass through the collection box: new canonical
sub-states buy-filled-awaiting-collect / sell-filled-awaiting-collect;
a full buy cannot sell and a full sell cannot close before ge_collect
(scheduler dispatches collect for all four *-awaiting-collect states).
- Works the arrowtips example side-aware: a 4,000/10,000 partial BUY
cancel returns 4,000 units + refunded CASH for the unfilled 6,000 —
never 'refunded units'; the phantom 10,000-unit withdrawal is gone.
- conservation_violations() is now side-aware (buy: quoted =
filled + still_open + cancelled_unfilled with acquired items = filled
only, cash = spent + still_committed + refunded; sell: quoted =
sold + still_open + returned_unsold) with slot_phase_violations()
phase gates and committed negative fixtures: phantom refunded buy
units, a full buy sold before collection, a full sell closed before
proceeds collection. Existing tax/scheduler/limit fixtures preserved.
- grammar (ge_cancel/ge_collect), schema sub_states minItems, entry
version 5 + sources updated accordingly.
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.
Governor bounce 4 + owner guidance 2026-08-28 (structured, provable, full lifecycle):
1. site model is data, not prose: new mine_sites table (ore, gates, rock_count,
respawn_seconds, rolls_per_ore, modeled_ores_per_hour, world_hop, selectable,
inventory_policy, access, safety, route). Adamantite @ Al Kharid south is now
respawn-BOUND (2 rocks, 240 s respawn): modeled 120 ores/hr ONLY with world
hopping (wiki F2P adamantite guide) = 70,080 gp/hr; no-hop ceiling ~30/hr =
17,520; the 839 ores/hr iron-cadence fantasy is gone and a cadence-ceiling
gate rejects it. Runite is selectable:false (Wilderness PK risk, ~12 min
respawn) — refused, not hand-waved. New hop state + hop_world action (a14)
implement the world-hop policy; long-respawn selectable sites REQUIRE
world_hop + a hop_world action (validator).
2. fast-xp success model: rolls_per_ore 1.33 (61+, derived from the wiki 52,500
benchmark: 1,500 ores/hr / 2,000 3-tick rolls/hr); rates recomputed with
success rolls + respawn math (3 rocks x 3.59 s = 10.8 s > 5.4 s respawn ->
zero idle, pinned) + drop/bank + breaks: xp_per_hour 28,000 (was 32,400 with
a flat 0.3 s and 1 ore/click); 52,500 stays a labeled external benchmark.
3. sub-29 fast-xp preserved: South-east Varrock DROP site (safe, same mode) —
the mode never silently switches to banking.
4. tool prep executable end to end: check-tool (has_item) -> withdraw-tool
(owned branch, bank_inventory) or buy-tool + wait-ge-fill (ge_offers_done) +
collect-tool (ge_collect, verify) -> equip-tool via menu_option Wield with
the Attack gate; recovery on purchase/equip failure (3 -> failed).
5. VALIDATOR PROVES THE CONTRACT (no phrase-search): pickaxe_tiers must match
the canonical F2P table exactly (Mining=use gate, Attack=equip gate,
roll_ticks); pickaxe_examples recomputed per row (1-Attack/41-Mining must
select + CARRY rune; 40-Attack/70-Mining equips; 5-Attack/30-Mining carries
mithril); both modes declared with fixed inventory policies; mode refs must
resolve to declared selectable policy-matching sites; Wilderness selectable
-> error; modeled ores/hr <= 3600/(rolls_per_ore x 2.7 s); xp_per_hour
within 2% of the modeled fast-xp rate. 13 committed --selftest fixtures
(negative cases included) wired into every gate run.
Evidence: validate 2 entries 0 errors 1 pre-existing WARN; --selftest 13 PASS;
synthetic 3-way merge main 79cab78 (incl. catalog) + head clean + gate green;
git diff --check clean. version 4 -> 5.
Owner decision 2026-08-28: expose separate fast-xp (powermine, drop) and
banked-income (deterministic target ore + mine, bank) modes; never select
highest-available rocks. Separate pickaxe gates per tier: Mining level
decides usability (rune 41), Attack only equipping (rune 40) — pinned
1-Attack/41-Mining carry-and-use example. Modeled policy rates recomputed
with cadence, respawn idle, drop/bank legs and breaks (fast-xp ~32,400
XP/hr; banked iron ~56,500 gp/hr); 52,500 kept as labeled external
benchmark only. Worked examples per mode covering preparation -> first
full inventory -> first break -> resume. Validator now proves the gate
distinction and both modes for mining entries.
Owner decision 2026-08-28: single global GE tax formula for all entries:
min(floor(0.02 * sell_price), 5_000_000). Apply the per-item cap inside
ge_floor_tax_error(); the margin-tag scope (flipping/merchanting) and the
generic-ge non-margin exemption are unchanged.
Regression fixtures now cover:
- fractional-tax flipping entry must FAIL (kept)
- non-margin entry with only ge + processing must PASS (kept)
- correctly-floored flipping entry must PASS (kept)
- high-value flip above the 5,000,000 GP cap (sell 300,000,000 / buy
294,000,000 / net 1,000,000) must PASS — the old uncapped rule computed
6,000,000 tax, demanded net 0, and rejected this valid entry (new)
Closes the governor's attempt-3 blocker on PR !7.
Governor-review (PR #10, attempt 2) findings, same issue-5 branch:
1. Pacing is additive. action-grammar.md defines anti_ban.delay as sleep
AFTER the action, so a cast takes 1.8s + 1.6-3.4s = 3.4-5.2s (mid
4.3s), ~837 casts/hr, not 1,400 or 1,800. Both entries now pin the
midpoint rate with the formula documented: LA 25,953 / SII 44,372 /
HLA 54,419 xp/hr. low-alch-f2p gp_per_hour bracketed by the delay
endpoints (-119,000 / -78,000).
2. run_control units agree: schema.md 'every' counts iterations and
iteration is one cast, so break.every [8, 14] is casts (comment no
longer claims bank cycles).
3. SII is now a concrete recipe. New top-level recipes: table models all
eight F2P smithable bars (bronze/iron/silver/steel/gold/mithril/
adamantite/runite) with every input and quantity (coal 2/4/6/8,
Smithing gates, bar ids) from the OSRS wiki smelting table. The
cast-superheat action pins params.recipe (steel-bar anchor) plus a
recipe_rule: smithing gate + complete input cost + one nature rune <
bar price; buy/withdraw the exact unnoted inputs; bank the bar.
entry.schema.json gains the optional 'recipes' section. Validator
gains recipe/SII checks and a deterministic steel-bar fixture
(1 iron + 2 coal = 3 inputs; margin 600 - (100 + 2*150 + 145) = 55).
4. Validator no longer crashes on malformed docs: scalar/empty YAML is
rejected with a normal ERROR (document shape check) and
supply_limit_conflicts guards non-mapping rows; regression fixtures
cover (bad.yaml, None), scalar docs, and undeclared recipe ids.
Tests: python3 tools/validate.py -> 2 entries, 0 errors, 0 warnings
(exit 0); --selftest -> 14 PASS; live negatives: scalar + empty docs
error cleanly (exit 1, no traceback), reintroduced 12500 divergence is
caught (supply-fact gate), git diff --check clean.
Governor-review bounce (PR #10) fixes on issue-5:
1. Timing units + rates: one OSRS tick = 0.6s so a 3-tick cast is 1.8s,
not 5.4s as both action comments claimed. The mandatory 1.6-3.4s
post-action delay (mid ~2.5s) allows ~1,440 casts/hr before overhead;
rates are now pinned at 1,400 casts/hr: LA 43,400 / SII 74,200 / HLA
91,000 xp/hr (was 55,800 / 95,400 / 117,000 @ 1,800). The old 1,800
figures were unreachable under the entries' own pacing.
2. Nature rune limit unified to 18000 (mapping 'limit' + Module:Exchange
agree, fetched live 2026-08-27): low-alch-f2p 12500 -> 18000, matching
magic-alch-handoff. tools/validate.py gains a cross-entry supply-fact
consistency check (same item+source must agree on ge_limit; run on
every gate invocation) plus --selftest regression fixtures: diverging
limits must fail, agreeing limits must pass, missing ge_limit is not
gated.
3. SII tier is now phase-aware and translatable: actions split into
cast-alch (LA/HLA -> cheapest alch candidate, noted stack) and
cast-superheat (SII 43-55 -> unnoted ore whose live bar margin is
positive; never a noted stack, never a price-only pick; on_fail skip
when no positive-margin ore). State machine pins each tier path
(LA <43, SII window chosen/skipped 43-55, HLA >=55), bank_supplies
banks smelted bars, buy-supplies restricts ores to positive-margin
recipes.
Tests: python3 tools/validate.py -> 2 entries, 0 errors, 0 warnings;
--selftest -> 3 PASS; negative test (reintroducing 12500) caught by the
new check; git diff --check clean.
Governor-review bounce (PR #7, attempt 2) fix on issue-1:
- The floor-tax rule previously triggered on any entry carrying the
generic 'ge' tag (tags & {flipping, merchanting, ge}). A training,
processing, or procurement entry may legitimately carry 'ge' while
its income/cost model is unrelated to flip tax, so it could fail the
global gate unless its economics accidentally satisfied the flip
equation.
- Rule now gates ONLY the unambiguous margin tags (flipping and/or
merchanting); the bare 'ge' tag no longer triggers it.
- Rule extracted into ge_floor_tax_error() and a --selftest mode added
with three regression fixtures, run on every gate invocation:
* fractional-tax flipping entry -> must FAIL
* non-margin entry with only 'ge' + 'processing' (economics
deliberately violate the flip equation) -> must PASS
* correctly-floored flipping entry -> must PASS
- Demo (old scope): ge+processing fixture with net 2000 vs floored 1900
errored under the old tag set; low-alch-f2p's -112 vs -115 model
would also false-positive if it carried 'ge'.
Tests: python3 tools/validate.py -> 2 entries, 0 errors, 1 pre-existing
warning (researched flag on low-alch-f2p); --selftest -> 3 PASS, 0 errors;
git diff --check clean.
Governor-review bounce (PR #7) fixes on issue-1:
1. GE 2% convenience fee rounds DOWN per item: net = sell - floor(0.02*sell) - buy.
Anchor net 16.12 -> 17 (244-4-223); top-8 members table recomputed with
floored tax -> 598,000/4h = 149,500 gp/h (was 138,000); gp_per_hour_low/high
updated. Validator now rejects fractional-tax arithmetic for GE-margin entries.
2. State machine split wait_fill into wait_buy_fill / wait_sell_fill so the full
buy -> buy-fill -> sell -> sell-fill -> collect path is explicit; sell cycle
can no longer loop back into sell without collecting.
3. Economy qualified as the members path (8 slots); F2P path added (3 slots,
F2P-tradeable rows only): Iron bar + Cosmic rune + Silver ore = 227,000/4h
= 56,750 gp/h. Members/F2P flags cross-checked against wiki item pages.