Merge commit '3cf95b4' into release-0.3.15
This commit is contained in:
commit
9c6a79bf01
7 changed files with 74 additions and 1 deletions
|
|
@ -39,6 +39,15 @@ pub struct CardPref {
|
|||
pub sweep_class: String,
|
||||
#[serde(default)]
|
||||
pub sweep_source: String,
|
||||
/// Miner UI 4 (6 October 2026): this card's goal (efficiency | balanced | rate); empty = the global tune_goal
|
||||
#[serde(default)]
|
||||
pub tune_goal: String,
|
||||
/// the untuned point the last FULL plan measured first (its step 0), kept across confirm plans so the row can
|
||||
/// read "saves 84 W, 0.15% of rate"; 0 = never measured
|
||||
#[serde(default)]
|
||||
pub sweep_before_watts: f64,
|
||||
#[serde(default)]
|
||||
pub sweep_before_mhs: f64,
|
||||
/// Ember 2: the memory clock the last tune chose (0 = the driver's default)
|
||||
#[serde(default)]
|
||||
pub sweep_mem_mhz: u32,
|
||||
|
|
|
|||
|
|
@ -906,6 +906,11 @@ pub fn tuned_line(mhs: f64, watts: f64, eff: f64) -> String {
|
|||
format!("Tuned: {mhs:.1} MH/s at {watts:.0} W ({eff:.3} MH/W)")
|
||||
}
|
||||
|
||||
/// Miner UI 4 (6 October 2026): a card's own goal when set, else the global one.
|
||||
pub fn goal_for(card_goal: &str, global: &str) -> Goal {
|
||||
Goal::parse(if card_goal.trim().is_empty() { global } else { card_goal })
|
||||
}
|
||||
|
||||
/// (D) May a step be SET on this card? Measure-only (no control: Apple, NVIDIA without Power control, an AMD card
|
||||
/// whose probe gave no tune line) sets nothing: the run notices the missing acknowledgement and measures. An AMD
|
||||
/// card also needs its ADLX ordinal. 6 October 2026, PC 1 17:57Z: a measure-only 9070 XT was sent a set, refused.
|
||||
|
|
@ -1029,6 +1034,14 @@ mod tests {
|
|||
assert!(Run::applied(&step, Readback { limit_w: 90.0, acked: false }, 100.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_card_goal_overrides_the_global_goal_and_empty_follows_it() {
|
||||
assert_eq!(goal_for("", "balanced"), Goal::Balanced);
|
||||
assert_eq!(goal_for("rate", "balanced"), Goal::MaxRate);
|
||||
assert_eq!(goal_for("efficiency", "rate"), Goal::Efficiency);
|
||||
assert_eq!(goal_for(" ", "efficiency"), Goal::Efficiency);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_measure_only_card_is_never_set_and_a_back_off_says_when_the_retry_comes() {
|
||||
assert!(!may_set("amd", false, 1), "the probe gave no tune line: measure only");
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@ pub enum Cmd {
|
|||
TuneProgress(Value),
|
||||
/// Ember 2: Settings > goal, electricity price, the hill-climb switch
|
||||
TuneGoal(Option<String>, Option<f64>, Option<bool>),
|
||||
/// one card's goal override (key, goal; "" = back to the global goal)
|
||||
TuneCardGoal(String, String),
|
||||
/// restart the node with the verifier decided again (src/verifier.rs): the trust setting changed, or the
|
||||
/// prover found a host that was not there when the node started
|
||||
RestartNode(String),
|
||||
|
|
@ -1147,6 +1149,26 @@ impl Engine {
|
|||
}
|
||||
}
|
||||
Cmd::TuneProgress(v) => self.tune_progress(&v),
|
||||
Cmd::TuneCardGoal(key, goal) => {
|
||||
{
|
||||
let mut s = self.shared.settings.lock().unwrap();
|
||||
let (enabled, identities) = self.st().mining.cards.iter().find(|c| c.key == key).map(|c| (c.enabled, c.identities)).unwrap_or((true, 1));
|
||||
let e = s.cards.entry(key.clone()).or_insert_with(|| crate::config::CardPref { enabled, identities, ..Default::default() });
|
||||
e.tune_goal = goal.clone();
|
||||
}
|
||||
self.shared.save_settings();
|
||||
let name = {
|
||||
let mut st = self.st();
|
||||
let c = st.mining.cards.iter_mut().find(|c| c.key == key);
|
||||
c.map(|c| {
|
||||
c.tune_goal = goal.clone();
|
||||
c.name.clone()
|
||||
})
|
||||
};
|
||||
if let Some(name) = name {
|
||||
self.shared.event("info", &format!("{name}: tune goal {}", if goal.is_empty() { "follows the global setting".to_string() } else { goal.clone() }));
|
||||
}
|
||||
}
|
||||
Cmd::TuneGoal(goal, price, climb) => {
|
||||
{
|
||||
let mut s = self.shared.settings.lock().unwrap();
|
||||
|
|
@ -2414,7 +2436,8 @@ impl Engine {
|
|||
let full_due = self.tune_full_due.remove(&idx);
|
||||
let (goal, climb_on) = {
|
||||
let s = self.shared.settings.lock().unwrap();
|
||||
(crate::ember::Goal::parse(&s.tune_goal), s.tune_climb)
|
||||
// the card's own goal when set, else the global one
|
||||
(crate::ember::goal_for(s.cards.get(&c.key).map(|p| p.tune_goal.as_str()).unwrap_or(""), &s.tune_goal), s.tune_climb)
|
||||
};
|
||||
let plan = if let Some(why) = control.as_ref() {
|
||||
if let Some(cc) = self.st().mining.cards.get_mut(idx) {
|
||||
|
|
@ -2838,6 +2861,14 @@ impl Engine {
|
|||
e.sweep_driver = c.driver.clone();
|
||||
e.sweep_class = c.program_class.clone();
|
||||
e.sweep_source = kind.name().into();
|
||||
if kind == crate::ember::PlanKind::Full {
|
||||
if let Some(b) = run.rows.first() {
|
||||
e.sweep_before_watts = b.watts;
|
||||
e.sweep_before_mhs = b.mhs;
|
||||
c.tune_before_watts = b.watts;
|
||||
c.tune_before_mhs = b.mhs;
|
||||
}
|
||||
}
|
||||
if !c.pinned && kind != crate::ember::PlanKind::Baseline {
|
||||
e.power_pct = row.point.power_pct;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,6 +167,9 @@ pub fn apply_pref(c: &mut CardState, p: &CardPref) {
|
|||
c.mem_cap_mhz = if p.pinned || p.sweep_source == "baseline" { 0 } else { p.sweep_mem_mhz };
|
||||
c.clock_cap_mhz = if p.pinned || p.sweep_source == "baseline" { 0 } else { p.sweep_clock_mhz };
|
||||
c.tune_source = p.sweep_source.clone();
|
||||
c.tune_goal = p.tune_goal.clone();
|
||||
c.tune_before_watts = p.sweep_before_watts;
|
||||
c.tune_before_mhs = p.sweep_before_mhs;
|
||||
if p.sweep_mhs > 0.0 && p.sweep_watts > 0.0 {
|
||||
c.tune_line = crate::ember::tuned_line(p.sweep_mhs, p.sweep_watts, p.sweep_eff);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,6 +256,14 @@ fn api_post(shared: &Arc<Shared>, path: &str, body: Value) -> Result<Value, Stri
|
|||
let goal = body.get("goal").and_then(|v| v.as_str()).map(|g| crate::ember::Goal::parse(g).name().to_string());
|
||||
let price = body.get("price_pence").and_then(|v| v.as_f64()).filter(|p| (0.0..=500.0).contains(p));
|
||||
let climb = body.get("climb").and_then(|v| v.as_bool());
|
||||
// with "key": that card's goal override ("" or "global" clears it); without: the global goal
|
||||
let key = body.get("key").and_then(|v| v.as_str()).map(|k| k.to_string());
|
||||
if let Some(k) = key {
|
||||
let g = body.get("goal").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let g = if g.is_empty() || g == "global" { String::new() } else { crate::ember::Goal::parse(g).name().to_string() };
|
||||
shared.send(Cmd::TuneCardGoal(k, g));
|
||||
return Ok(json!({ "ok": true }));
|
||||
}
|
||||
shared.send(Cmd::TuneGoal(goal, price, climb));
|
||||
Ok(json!({ "ok": true }))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,9 @@ pub struct CardState {
|
|||
pub tune_clock_mhz: u32, // the clock cap the last tune chose (0 = unlocked)
|
||||
pub tune_source: String, // full | confirm | baseline
|
||||
pub tune_line: String, // "Tuned: 122.3 MH/s at 290 W (0.422 MH/W)" once tuned
|
||||
pub tune_goal: String, // this card's goal override (efficiency | balanced | rate); "" = the global goal
|
||||
pub tune_before_watts: f64, // the untuned point of the last full plan (step 0); 0 = never measured
|
||||
pub tune_before_mhs: f64,
|
||||
// a tune in progress on this card, by this engine or by a measurement engine posting /api/tune-progress
|
||||
// (the project lead, 6 October 2026: "don't we need to show in the app that tuning is in progress?")
|
||||
pub tune_step: u32,
|
||||
|
|
|
|||
|
|
@ -314,6 +314,12 @@ What a user on 0.3.14 sees: Power control on, no prompt, and no card tunes (the
|
|||
|
||||
## 8a. Next-cut notes (for the 0.3.12 shipper)
|
||||
|
||||
For 0.3.16 (Miner UI 4's Cards tab, Ember as its second layer), on ember-tune past the 0.3.15 tip 7fadd4c:
|
||||
|
||||
| Commit | What | Where |
|
||||
|---|---|---|
|
||||
| (past 7fadd4c) | a per-card goal: `POST /api/tune/goal {key, goal}` (efficiency, balanced, rate; "" or "global" clears), persisted as `CardPref.tune_goal`, echoed as `card.tune_goal` ("" = the global goal), resolved at the tune's start (`ember::goal_for`); the untuned point of the last full plan on the row: `card.tune_before_watts`, `card.tune_before_mhs` (its step 0, kept across confirm plans; `CardPref.sweep_before_*`), so the row can read "saves 84 W, 0.15% of rate" against `sweep_watts` / `sweep_mhs`; Retune = `POST /api/sweep/start {key}` (forced: clears a back-off since 0.3.15) | ember.rs, engine.rs, server.rs, config.rs, state.rs, hotplug.rs + test |
|
||||
|
||||
For the 0.3.15 cut (6 October 2026 evening), on ember-tune:
|
||||
|
||||
| Commit | What | Where |
|
||||
|
|
|
|||
Loading…
Reference in a new issue