XRebalancer: floor=auto sweep, and log the floor ladder every cycle
Some checks failed
Code Base Sanity Check / tests (push) Has been cancelled
Code Base Sanity Check / coverage (push) Has been cancelled
Code Base Sanity Check / build-clang (push) Has been cancelled

The route-cost-floor option now also accepts "auto". In auto mode the
driver derives the floor ladder from the live joint(N) curve each cycle and
picks one rung at random, so over many Poisson-paced cycles it sweeps the
whole ladder -- the high-value top rungs and the broad low rungs both get
regular shots -- with no exhaustion-detection or advance-trigger state
machine. A tapped tier just yields one cheap no-fill cycle and the next
cycle moves on; you cannot get stuck at a walled floor. A fixed numeric
floor still works exactly as before; "auto" is opt-in via setconfig.

The breakpoint walk no longer stops at the floor: it captures the full
joint(N) curve, the same staircase clboss-xrebalance-view prints. The
ladder routine (ceiling down to the useful floor where both marginal sides
still clear a net-ppm noise floor, log-spaced on the joint axis, snapped to
real rows, deduped, held a minimum ratio apart) is ported verbatim from the
view with identical constants, so the two agree by construction. The view
stays the reference and explanatory artifact; keep them in sync.

Every cycle now logs a greppable "floor levels" line listing the ladder
rungs, in both auto and fixed mode, so the levels can be watched moving as
balances and constraints shift. The cycle line shows the chosen floor and,
in auto mode, which rung was picked.

Uniform random over rungs to start (the tiny top tier is over-attended
slightly but it is high-value and fails cheap, so the downside is nil);
weighting is a clean future knob once there is data from uniform. Pairs
with the per-part fee gate: the chosen rung sets the budget, the gate
enforces it per part.
This commit is contained in:
Ken Sedgwick 2026-06-06 14:05:23 -07:00
parent 9d446e5e5a
commit 9fdfc130b4
No known key found for this signature in database
GPG key ID: DBD2AF0849D711A9

View file

@ -70,6 +70,7 @@ private:
double window_days;
double fill_band;
double drain_band;
bool floor_auto; /* floor option set to "auto" (sweep) */
bool started;
/* One row per CHANNELD_NORMAL channel, built live from
@ -102,6 +103,7 @@ private:
window_days = default_window_days;
fill_band = default_fill_band;
drain_band = default_drain_band;
floor_auto = false;
started = false;
bus.subscribe<Msg::DbResource
@ -121,7 +123,9 @@ private:
"Route-cost floor (ppm): stop growing the "
"matched-pool cycle once the marginal joint "
"NetPpm drops below this. Sets the derived "
"amount and the maxfee budget.")
"amount and the maxfee budget. Or \"auto\": "
"each cycle picks a random rung of the derived "
"floor ladder (sweep).")
+ manifest_option(opt_attenuator, default_attenuator,
"Fraction (0,1] of the derived matched-pool "
"amount to actually request per cycle.")
@ -168,6 +172,18 @@ private:
}
Ev::Io<void> handle_option(Msg::Option const& o) {
/* The floor option also accepts "auto": instead of a fixed
* value, each cycle picks a random rung of the derived floor
* ladder, sweeping the whole ladder over many cycles. */
if (o.name == opt_floor && std::string(o.value) == "auto") {
floor_auto = true;
return Boss::log( bus, Info
, "XRebalancer: %s set to \"auto\" "
"(per-cycle random sweep of the floor ladder)."
, o.name.c_str() );
}
if (o.name == opt_floor)
floor_auto = false;
double* target = nullptr;
if (o.name == opt_per_hour) target = &per_hour;
else if (o.name == opt_floor) target = &floor_ppm;
@ -356,6 +372,76 @@ private:
std::int64_t deficit; /* tgt_fill for fill, tgt_drain for drain */
};
/* One point on the joint(N) curve: cumulative matched volume N and
* the marginal fill/drain NetPpm (and their sum) admitted at that
* depth. Mirrors the curve clboss-xrebalance-view prints. */
struct CurvePoint {
std::int64_t n;
double fill_ppm;
double drain_ppm;
double joint;
};
/* Node-agnostic floor ladder, ported from clboss-xrebalance-view and
* kept in sync deliberately -- the view is the reference and the
* explanatory artifact. Floors are log-spaced on the joint (= budget)
* axis between the ceiling (top row) and the useful floor (lowest row
* where both marginal sides still clear NOISE_PPM net), so the rung
* count auto-scales with the node's span. Targets are snapped to real
* curve rows, deduped, and held at least MIN_GAP apart. Constants are
* dimensionless; see the view for the rationale. */
std::vector<CurvePoint>
derive_ladder(std::vector<CurvePoint> const& curve) {
auto ladder = std::vector<CurvePoint>();
if (curve.empty())
return ladder;
auto constexpr LADDER_RATIO = double(1.6);
auto constexpr NOISE_PPM = double(10.0);
auto constexpr MIN_GAP = double(1.25);
auto ceiling_joint = curve[0].joint;
auto useful_idx = std::size_t(0);
for (auto i = std::size_t(0); i < curve.size(); ++i) {
if (curve[i].fill_ppm >= NOISE_PPM
&& curve[i].drain_ppm >= NOISE_PPM)
useful_idx = i;
else
break;
}
auto useful_joint = curve[useful_idx].joint;
auto targets = std::vector<double>();
for (auto t = ceiling_joint; t > useful_joint; t /= LADDER_RATIO)
targets.push_back(t);
targets.push_back(useful_joint);
auto already = [&ladder](std::int64_t n) {
for (auto const& p : ladder)
if (p.n == n)
return true;
return false;
};
auto last_joint = double(-1.0);
for (auto tgt : targets) {
/* Snap to the row a floor=tgt would select: the
* largest N (within the useful range) whose joint is
* still >= tgt. */
auto pick = curve[0];
for (auto i = std::size_t(0); i <= useful_idx; ++i) {
if (curve[i].joint >= tgt)
pick = curve[i];
else
break;
}
if (already(pick.n))
continue;
if (last_joint > 0.0 && pick.joint > last_joint / MIN_GAP)
continue;
ladder.push_back(pick);
last_joint = pick.joint;
}
if (!already(curve[useful_idx].n))
ladder.push_back(curve[useful_idx]);
return ladder;
}
Ev::Io<void>
plan_and_log( std::shared_ptr<std::vector<Chan>> chans
, std::shared_ptr<std::map<Ln::NodeId, NetPpm>> net
@ -425,31 +511,71 @@ private:
std::sort(bps.begin(), bps.end());
bps.erase(std::unique(bps.begin(), bps.end()), bps.end());
std::int64_t best_n = 0;
double best_fill_ppm = 0.0, best_drain_ppm = 0.0;
double best_joint = 0.0;
/* Full joint(N) curve (every breakpoint), as the view computes
* it -- we no longer stop at the floor, so the whole curve is
* available for the ladder and for logging. */
auto curve = std::vector<CurvePoint>();
for (auto n : bps) {
auto f = threshold_at(fc, n);
auto d = threshold_at(dc, n);
if (f < 0.0 || d < 0.0)
break; /* one side exhausted */
auto joint = f + d;
if (joint >= floor_ppm) {
best_n = n;
best_fill_ppm = f;
best_drain_ppm = d;
best_joint = joint;
curve.push_back(CurvePoint{ n, f, d, f + d });
}
/* Derive the ladder every cycle (cheap) so the levels are
* logged and can be watched moving as balances/constraints
* shift. In "auto" mode the cut is a random rung; otherwise
* the configured fixed floor. */
auto ladder = derive_ladder(curve);
auto effective_floor = floor_ppm;
auto picked_note = std::string();
if (floor_auto && !ladder.empty()) {
auto dist = std::uniform_int_distribution<std::size_t>(
0, ladder.size() - 1);
auto idx = dist(Boss::random_engine);
effective_floor = ladder[idx].joint;
auto os = std::ostringstream();
os << " (auto picked "
<< (long long)std::llround(effective_floor) << ")";
picked_note = os.str();
}
/* One greppable line per cycle listing the ladder rungs, so the
* levels can be tracked over time. */
auto levels = std::ostringstream();
levels << "XRebalancer: floor levels [" << ladder.size()
<< " rungs]: ";
for (auto i = std::size_t(0); i < ladder.size(); ++i) {
if (i) levels << "/";
levels << (long long)std::llround(ladder[i].joint);
}
levels << " ppm" << (floor_auto ? "" : " (fixed floor)");
auto levels_str = levels.str();
/* Select the cut: largest curve row whose joint clears the
* chosen floor (joint is non-increasing, so stop on drop). */
std::int64_t best_n = 0;
double best_fill_ppm = 0.0, best_drain_ppm = 0.0;
double best_joint = 0.0;
for (auto const& pt : curve) {
if (pt.joint >= effective_floor) {
best_n = pt.n;
best_fill_ppm = pt.fill_ppm;
best_drain_ppm = pt.drain_ppm;
best_joint = pt.joint;
} else {
break;
}
}
if (best_n <= 0)
return Boss::log( bus, Info
return Boss::log( bus, Info, "%s", levels_str.c_str() )
+ Boss::log( bus, Info
, "XRebalancer: no viable cycle -- no matched "
"volume clears floor %.1f ppm "
"(fill=%zu drain=%zu, window=%.0fd)."
, floor_ppm, fill.size(), drain.size()
, effective_floor, fill.size(), drain.size()
, window_days );
/* Bold set: channels accumulated to reach best_n on each side. */
@ -472,13 +598,14 @@ private:
* attenuator)));
auto maxfee = std::uint32_t(std::llround(best_joint));
return Boss::log( bus, Info
, "XRebalancer: cycle [flow] floor=%.1f window=%.0fd "
return Boss::log( bus, Info, "%s", levels_str.c_str() )
+ Boss::log( bus, Info
, "XRebalancer: cycle [flow] floor=%.1f%s window=%.0fd "
"-> derived N=%lld sat, joint=%.1f ppm "
"(fill>=%.1f + drain>=%.1f); attenuator=%.3g "
"-> request=%lld sat (maxfee %u ppm); "
"sources=%zu dests=%zu; executing."
, floor_ppm, window_days
, effective_floor, picked_note.c_str(), window_days
, (long long)best_n, best_joint
, best_fill_ppm, best_drain_ppm
, attenuator, (long long)requested, (unsigned)maxfee