feat(engine): research upgrades — time-aware lanes, ride-along, rotation, surveys, 409 retry (v2.7.0)#46
Merged
Merged
Conversation
…ion, surveys, 409 retry (v2.7.0)
The externally-sourced improvements from the 7-agent research synthesis, one release:
* TIME-AWARE LANE SCORING (analysis.rank_routes): with waypoint coordinates the
score becomes net-per-second — (margin × volume − fuel≈distance) over
(distance × 25 / nominal speed 30 + 15 s per stop) — so a near lane that cycles
6× as often outranks a fatter-but-farther one. Pure optional {wp: (x, y)} map
arg; any unmapped endpoint (or no map) keeps the exact time-blind score, so all
existing callers rank as before. Engine plumbing via a _WP_COORDS cache fed by
the window-open marketplace scan; st_trade_routes passes its own scan's coords.
* CONTRACT RIDE-ALONG (fleet.job_contract): spare hold room after the contract
load carries a good the buy market exports that the delivery stop (or a
coordinate-proven nearby market) imports — min_margin-respecting, bounded by
max_spend_frac + reserve_floor (hard per-chunk floor) + the confirmed-resale
ceiling, sold after the delivery lands. Strictly opportunistic: any failure
degrades to the plain contract flow (proven end-to-end, the v2.4.2 glue lesson).
* PROACTIVE ROUTE ROTATION (plan.reconcile + route_rotate_cycles knob, default 4,
0 = off): route_cycles counts a hauler's consecutive profitable holds of one
lane; at the threshold the plan prefers the best unused fresh route (no-steal
intact, counter reset) so a sink gets a breather BEFORE saturation kills the
margin. No alternative ⇒ keep the lane — rotation never idles a hauler.
* SURVEY-FED MINING (fleet._mining_loop): a MOUNT_SURVEYOR_* hull charts its rock
once per fill cycle into a shared per-asteroid cache and every miner extracts
against the best fresh survey ({"survey": …} on the POST). Expiry/exhaustion
falls back to a plain extract in the same swing; no free surveyor ⇒ plain
extraction exactly as before.
* EXTRACT-409 RETRY (mining + siphon loops): a cooldown-skew rejection
([4000]/HTTP 409 — five windows died to it in one live night) waits the
cooldown out and retries ONCE; a second consecutive rejection or any other
class stays terminal (the J58 bail guard unweakened).
* PRICE BOOK: _SHIP_COST corrected to observed yard prices (hauler 450k → 393k,
probe 40k → 28k) — affordability gates only; the buy still pays the live price.
Suite: +26 tests (time-aware scoring + fallback, ride-along picker + end-to-end
glue, rotation, survey glue, 409 retry) — 286 passed, ruff clean. README documents
lane scoring / ride-along / rotation / surveys; version 2.7.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t just our re-read Steelman pass on the v2.7.0 branch. The cooldown-skew retry re-read the ship's cooldown and waited it out — but the skew IS our read saying ready while the server's clock disagrees, so the re-read could wait zero and burn the single retry on an instant second 409 (the same window-killer, one call later). The [4000] rejection carries the server's own remaining seconds: parse it (_conflict_wait_s, pure, +1s slack, capped at 70s — extract/survey cooldowns top out around there), sleep it first, keep _wait_cooldown as the backstop. Unparseable messages fall back to exactly the prior behavior. Tests: pure parser coverage (parse / bound / unparseable→0); the mining-loop 409 tests now assert the glue hands the rejection text to the parser; and the siphon loop — whose 409 glue is its own copy — is driven end to end (the v2.4.2 lesson: a twin code path is not covered by its sibling's test). README's 409 paragraph updated to match. Probes that ended in no change (verified against the code): time-aware scores never touch plan._route_key (good/buy/sell), so keep/hold hysteresis and rotation dampen exactly as before; ride-along buys are never gate-stamped (_stamp_gate_cargo is gate-rung-only) and sinks require a recorded IMPORT sell price (confirmed-sink rule); rotation's alt pick excludes used+inbound so no-steal and lead demotion hold; survey objects are cached and replayed verbatim (signature-verified server-side) and every price_map waypoint is rooted in the same window-open scan that fills _WP_COORDS, so mixed time-blind/time-aware rankings can't arise in the engine path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he plugin on Python 3.9 The v2.7.0 diff added `dict | None` signatures to analysis.py — the one core module without `from __future__ import annotations`. PEP 604 unions in signatures evaluate at def time, so on Python <3.10 the module raised at IMPORT (`TypeError: unsupported operand type(s) for |`), and because tools.py/fleet.py/__init__.py all import from analysis, a 3.9 runtime lost the ENTIRE plugin. CI (3.12) never sees it, which is why it nearly shipped. - analysis.py: add the future import (the repo's own convention — every sibling module already carries it). - fresh_start.py: same latent break pre-existing on main (`-> str | None` at line 20), fixed the same way. - test_py39_compat.py: new AST guard, runnable on 3.12 — any module whose annotations use `X | Y` must carry the future import. Red on the unfixed tree (flagged both analysis.py and fresh_start.py), green now. Verified on the 3.9.6 system python: `import analysis` and `import fresh_start` both succeed; full suite 289 passed, ruff clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… rotation breather Three confirmed blockers on the v2.7.0 research upgrades: * analysis._lane_score mixed incomparable units in one ranking: a route with any coord-unmapped endpoint kept its raw margin x volume (credits/VISIT, thousands) while mapped routes scored credits/SECOND (< ~100), so ANY unknown-distance lane structurally dominated every mapped one — a 150/visit lane outranked a 2400/visit lane 10 units away, the exact inversion of the feature. An unmapped endpoint now scores at the pessimistic _UNMAPPED_DIST (400 — an unproven endpoint can't claim to be near, same doctrine as ride_along_pick); the raw time-blind base is reserved for a ranking with no coordinate map at all. The old test that ASSERTED the mixed-scale fallback is rewritten to pin the commensurable behavior instead. * fleet.autopilot built _WP_COORDS from a single unpaginated limit-20 marketplace GET while the price map is a persistent store — in the documented >20-marketplace live system every page-2 market's routes would rank as unknown-distance for the process lifetime (and market_wps lost scout/price-refresh coverage too). The scan is now _market_scan on tools._all_waypoints — the same pagination doctrine _good_markets was hardened with — and is driven end to end (scan -> _ranked_routes) in test_market_scan.py per the v2.4.2 glue lesson. * plan.reconcile's rotation freed the just-vacated lane inside the same window (inbound.discard), so the next at-threshold hauler re-occupied it immediately — and since fresh picks advance route_cycles in lockstep, every threshold window degenerated into a synchronized fleet-wide reshuffle with zero rest for the saturated sink (the churn this module exists to prevent). The vacated lane now stays excluded from both the alt scan and the fresh pick until the next reconcile: exactly one breather window, and the staggered pickup desyncs the counters. Covered by the two-at-threshold- haulers-plus-spare-route case the shipped tests missed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mabry1985
marked this pull request as ready for review
July 4, 2026 10:20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
v2.7.0 implements the externally-sourced improvements from the 7-agent research synthesis (the operator's deep-research run) — six items, one release:
analysis.rank_routes): with an optional pure{waypoint: (x, y)}map, the score becomes net-per-second —net = margin × volume − fuel(CRUISE fuel ≈ distance) overtime = distance × 25 / nominal speed 30 + 15 s per stop(constants documented). A near lane that cycles 6× as often now outranks a fatter-but-farther one. Any unmapped endpoint — and every coord-less caller — keeps the exact time-blindmargin × volumescore, so existing callers/tests are unchanged. Engine plumbing: a_WP_COORDScache fed by the window-open marketplace scan (no signature churn, ranker stays pure);st_trade_routespasses its own scan's coords. Everyprice_mapwaypoint is rooted in the same scan that fills_WP_COORDS, so the engine path never mixes time-blind and time-aware scores in one ranking.fleet.job_contract): after the contract units are aboard and before departing the buy market, spare hold room carries a good the current market exports that the delivery stop (or a coordinate-proven market within a trivial hop) imports —min_margin-respecting, bounded bymax_spend_frac,reserve_floor(hard per-chunk floor) and the confirmed-resale ceiling — sold right after the delivery lands. Strictly opportunistic: any failure degrades to the plain contract flow. Ride-along buys are never gate-stamped, so gate-cargo provenance can't be contaminated.plan.reconcile+ newroute_rotate_cyclesknob, default 4, 0 = off):route_cyclescounts consecutive profitable holds of one lane; at the threshold the plan prefers the best unused fresh route (no-steal rules intact, counter reset) so a sink gets a breather before saturation kills the margin. No alternative ⇒ keep the lane — never idle a hauler for rotation's sake. Route identity (_route_key) is good/buy/sell only, so score changes can't thrash the keep/hold hysteresis.fleet._mining_loop): aMOUNT_SURVEYOR_*hull charts its rock once per fill cycle into a shared per-asteroid cache; every miner on the rock extracts against the best fresh survey ({"survey": …}on the POST, the object replayed verbatim — it's signature-verified server-side). Expiry/exhaustion falls back to a plain extract in the same swing; no free surveyor ⇒ plain extraction exactly as today._SHIP_COSTcorrected to observed yard prices (hauler 450k → 393k, probe 40k → 28k). Affordability gates only — the one test pinning the old book row updated.[4000]/HTTP 409 — LIVE: 5 occurrences in one night, each killing the miner's window via the bail-on-reject guard) waits out the server-stated remainder parsed from the rejection itself (_conflict_wait_s, +1 s slack, capped at 70 s — our own cooldown read saying "ready" is exactly what the skew means, so a bare re-read could burn the retry on an instant second 409), re-reads the cooldown as backstop, retries once; a second consecutive rejection — or any other rejection class — stays terminal (the J58 guard unweakened).Tests (+28)
job_contractglue (buys only when margin+space+budget allow; sells after delivery; dead price store / no-pick degrade to plain flow)_mining_loop)Gates
pytest -q --ignore=test_retrofit_smoke.py: 288 passed (retrofit smoke separately: 9 passed)ruff check .: cleanREADME documents lane scoring / ride-along / rotation / surveys / the 409 wait; version bumped to 2.7.0.
🤖 Generated with Claude Code