Skip to content

feat(engine): research upgrades — time-aware lanes, ride-along, rotation, surveys, 409 retry (v2.7.0)#46

Merged
mabry1985 merged 4 commits into
mainfrom
feat/research-upgrades-v270
Jul 4, 2026
Merged

feat(engine): research upgrades — time-aware lanes, ride-along, rotation, surveys, 409 retry (v2.7.0)#46
mabry1985 merged 4 commits into
mainfrom
feat/research-upgrades-v270

Conversation

@mabry1985

@mabry1985 mabry1985 commented Jul 4, 2026

Copy link
Copy Markdown
Member

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:

  1. Time-aware lane scoring (analysis.rank_routes): with an optional pure {waypoint: (x, y)} map, the score becomes net-per-secondnet = margin × volume − fuel (CRUISE fuel ≈ distance) over time = 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-blind margin × volume score, so existing callers/tests are unchanged. Engine plumbing: a _WP_COORDS cache fed by the window-open marketplace scan (no signature churn, ranker stays pure); st_trade_routes passes its own scan's coords. Every price_map waypoint 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.
  2. Contract ride-along (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) importsmin_margin-respecting, bounded by max_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.
  3. Proactive route rotation (plan.reconcile + new route_rotate_cycles knob, default 4, 0 = off): route_cycles counts 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.
  4. Survey-fed mining (fleet._mining_loop): a MOUNT_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.
  5. Price book: _SHIP_COST corrected to observed yard prices (hauler 450k → 393k, probe 40k → 28k). Affordability gates only — the one test pinning the old book row updated.
  6. Extract-409 retry (mining + siphon loops): a cooldown-skew rejection ([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)

  • scoring: near-beats-fatter-far with coords; time-blind fallback (no map, and per-unmapped-route); exchange fallback time-aware too
  • ride-along: pure picker (margin/exclude/detour-proof/no-overlap) and end-to-end job_contract glue (buys only when margin+space+budget allow; sells after delivery; dead price store / no-pick degrade to plain flow)
  • rotation: counter increments only on profitable holds, swap at threshold, no-steal + never-idle, knob 0 disables, legacy plan state safe
  • surveys: chart-once + targeted extracts, spent survey falls back same swing, no-surveyor = plain (all through _mining_loop)
  • 409: one retry after the server-stated wait (glue proves the rejection text reaches the parser), second rejection bails, non-409 terminal immediately; the SIPHON loop's own copy of the glue driven end to end; classifier + wait parser unit-tested

Gates

  • pytest -q --ignore=test_retrofit_smoke.py: 288 passed (retrofit smoke separately: 9 passed)
  • ruff check .: clean

README documents lane scoring / ride-along / rotation / surveys / the 409 wait; version bumped to 2.7.0.

🤖 Generated with Claude Code

mabry1985 and others added 4 commits July 4, 2026 01:44
…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
mabry1985 marked this pull request as ready for review July 4, 2026 10:20
@mabry1985
mabry1985 merged commit e99b6a5 into main Jul 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant