Commit Graph

16 Commits

Author SHA1 Message Date
Claude
7ee36da445 Merge remote-tracking branch 'origin/main' into issue-4
# Conflicts:
#	schemas/entry.schema.json
#	tools/validate.py
2026-08-29 03:30:20 -05:00
Claude
2a5cfb30af Merge remote-tracking branch 'origin/main' into issue-4
# Conflicts:
#	tools/validate.py
2026-08-29 03:18:04 -05:00
Claude
d19a4d9de0 Merge remote-tracking branch 'origin/main' into issue-1
# Conflicts:
#	tools/validate.py
2026-08-29 03:13:45 -05:00
Claude
a0b03b87c6 codex: mining-f2p — level-1 pickaxe tie-break: Mining 1 selects+equips iron (bounce-fix attempt 6)
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.
2026-08-29 03:12:40 -05:00
Claude
671d025d9f fix(flip-candidates): release the physical GE slot after a completed flip via closed -> free (governor 10471)
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.
2026-08-29 03:11:20 -05:00
Claude
052c6984ac codex: low-alch-members — loss-reserve stop direction + fill-price scenario GP bounds (bounce-fix attempt 5)
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).
2026-08-29 03:00:56 -05:00
Claude
64b5e4890e codex: mining-f2p — executable GE purchase loop, structured copper/tin below-15 fallback, a2 target restrict (bounce-fix 5)
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.
2026-08-28 15:51:05 -05:00
Claude
76f8e5ad27 fix(flip-candidates): GE asset accounting + offer lifetime per governor 10390 (bounce-fix 6)
- 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.
2026-08-28 14:48:55 -05:00
Claude
02b366c7ef flip-candidates: per-slot async sub-state lifecycle (governor 10323 bounce-fix 5)
- slot_state_machine: canonical 8 sub-states (free/buy-open/
  buy-cancelled-awaiting-collect/ready-to-sell/sell-open/
  sell-cancelled-awaiting-collect/ready-to-requote/closed) + global
  scheduler pass that never advances unrelated slots
- ge_cancel moves filled items/refunds/unsold units/coins to the GE
  collection box; ge_collect + ledger reconciliation before reuse
- 4h buy-limit ledger (limit_used/first_fill_ts/limit_resets_at) distinct
  from offer expiry; cancellation never renews the limit
- per-slot conservation invariants w/ committed validator fixtures
- economy.ceiling_gp_per_hour typed ceiling, gp_per_hour_low/high as
  documented realized scenario bounds (no uncited multipliers)
- capped/floored 2% tax + 4 fixtures preserved (owner decision)
- action-grammar: ge_cancel/ge_collect rows corrected
2026-08-28 04:01:20 -05:00
Claude
a197b34aa8 codex: low-alch-members — complete GE lifecycle, capital model, monotonic counters, fail-closed staff (bounce-fix attempt 4)
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.
2026-08-28 03:36:45 -05:00
Claude
765505ed0f codex: structured mining data model + respawn/success-correct rates + executable tool prep (bounce-fix 4, owner mode decision)
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.
2026-08-28 03:34:48 -05:00
Claude
126cf7b38a codex: mining-f2p — fast-xp/banked-income modes, Mining-vs-Attack pickaxe gates, modeled rates (bounce-fix attempt 3)
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.
2026-08-28 03:15:04 -05:00
Claude
00bfc358cb fix(validate): cap GE floor tax at 5,000,000 GP per item (owner decision A)
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.
2026-08-28 02:35:53 -05:00
Claude
f6675ba543 fix(validate): scope GE floor-tax gate to margin tags, add regression selftest
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.
2026-08-27 12:34:08 -05:00
Claude
864f2c9e1c fix(flip-candidates): floor GE tax to whole coins, pin full sell-cycle, qualify F2P path
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.
2026-08-27 05:27:09 -05:00
Vale
986fc1a6a8 Codex skeleton: schema contract, action grammar, data-sources rules, JSON schema, validator, golden example (low-alch-f2p, wiki+price verified), README 2026-08-27 02:59:55 -05:00