From 5c0fe59ea6139401c8441c46039f6f08d73b0d80 Mon Sep 17 00:00:00 2001 From: "Simon Keimer (DC0SK)" Date: Sat, 4 Jul 2026 12:05:58 +0200 Subject: [PATCH 1/2] fix(math): make resonance points transformer-independent and consistent (A6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The five places that build resonance point-sets each used the transformer-corrected quarter-wave, so a Unun/Balun choice silently shifted the physical resonance geometry, and the sets disagreed with one another: - non-resonant avoid-set (calculate_non_resonant_optima) - resonant-compromise optimizer - OCFD split clearance optimizer - on-screen resonant_points_in_window - exported resonant_points_in_window All five now use the uncorrected quarter-wave (71.32/f·VF), which is a property of the wire geometry, not the feed. The optimizers, the on-screen list and the export now always agree and never move with the transformer ratio. The transformer-length heuristic still adjusts the displayed *build* lengths (corrected_*), which is unchanged. Deliberately does NOT adopt the earlier half-wave avoid-set idea: the review showed it introduces a second, conflicting definition of "resonance" versus the display/export/compromise sets. Keeping the tool's documented, unified "avoid all quarter-wave resonances" model is consistent and defensible for a tuner-fed non-resonant wire. - advise: note that the recommended length is the same for every ratio (resonance is independent of the feed) so the constant column isn't misread - docs/math.md §5/§6: points are uncorrected and transformer-independent - test: resonance_point_optimizers_are_transformer_independent guards all three optimizers against per-ratio drift Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/math.md | 16 ++++++++-- src/app/mod.rs | 4 ++- src/calculations.rs | 78 ++++++++++++++++++++++++++++++++++++++++++--- src/cli.rs | 2 ++ src/export.rs | 4 ++- 5 files changed, 95 insertions(+), 9 deletions(-) diff --git a/docs/math.md b/docs/math.md index 2283aae..8adbe7e 100644 --- a/docs/math.md +++ b/docs/math.md @@ -143,12 +143,22 @@ This is a practical approximation, not a substitute for NEC-based segment/curren ## 5) Non-Resonant Wire Optimization -For each selected band, Rusty Wire generates resonance points from corrected quarter-wave harmonics: +For each selected band, Rusty Wire generates resonance points from the +**uncorrected** quarter-wave harmonics (i.e. $71.32/f \cdot VF$, *without* the +transformer-length heuristic of §4). Resonance is a property of the wire +geometry, not the feed, so the same point set drives the non-resonant optimizer, +the resonant-compromise optimizer (§6), the OCFD split optimizer (§7), and the +resonant-points shown on screen and in exports — they always agree, and none of +them shift when the transformer ratio changes. $$ R = \{h\,L_{1/4,i}\mid i\in\text{bands},\ h\in\mathbb{N}\} $$ +The optimizer avoids **all** quarter-wave harmonics (the wire's resonant lengths +on each band), keeping the chosen length in the moderate-reactance region between +resonances where a tuner has the easiest match. + For candidate wire length $\ell$ in the configured search window: $$ @@ -169,7 +179,9 @@ $$ ## 6) Resonant Compromise Optimization -For each band $i$, define resonant-point set $P_i$ in the active window. Per-band nearest distance at candidate $\ell$: +For each band $i$, define resonant-point set $P_i$ (the uncorrected, +transformer-independent quarter-wave harmonics of §5) in the active window. +Per-band nearest distance at candidate $\ell$: $$ D_i(\ell) = \min_{p\in P_i}|\ell-p| diff --git a/src/app/mod.rs b/src/app/mod.rs index c5a4ab7..d634441 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2022,7 +2022,9 @@ pub fn resonant_points_in_window(results: &AppResults) -> Vec { let mut points = Vec::new(); for calc in &results.calculations { - let quarter_wave_m = calc.corrected_quarter_wave_m; + // Uncorrected quarter-wave: the resonant points shown are transformer- + // independent and match the non-resonant/compromise optimizers' point sets. + let quarter_wave_m = calc.quarter_wave_m; if quarter_wave_m <= 0.0 { continue; } diff --git a/src/calculations.rs b/src/calculations.rs index f7d35ef..8bdb0a4 100644 --- a/src/calculations.rs +++ b/src/calculations.rs @@ -762,9 +762,12 @@ fn build_non_resonant_resonance_points( ) -> Vec { let mut resonance_points_m = Vec::new(); for c in calculations { - // Use transformer-corrected quarter-wave as the base resonance point so - // optimum common wire length reflects the selected Unun/Balun ratio. - let quarter_wave_m = c.corrected_quarter_wave_m; + // Resonance is a property of the wire geometry, not the feed: use the + // uncorrected quarter-wave so the avoid-set is transformer-independent and + // matches the resonant-points shown in the display/export and targeted by + // the compromise optimizer. (The transformer-length heuristic is a + // build-length nudge, not physics.) + let quarter_wave_m = c.quarter_wave_m; let mut harmonic = 1_u32; loop { @@ -824,7 +827,9 @@ pub fn calculate_resonant_compromises( let mut band_points: Vec> = Vec::new(); for calc in calculations { - let quarter_wave_m = calc.corrected_quarter_wave_m; + // Uncorrected quarter-wave: resonance points are transformer-independent + // and match those shown by resonant_points_in_window in the same view. + let quarter_wave_m = calc.quarter_wave_m; if quarter_wave_m <= 0.0 { continue; } @@ -950,7 +955,9 @@ pub fn optimize_ocfd_split_for_length( let mut worst_leg_clearance_pct = f64::INFINITY; for calc in calculations { - let quarter_wave = calc.corrected_quarter_wave_m; + // Uncorrected quarter-wave: OCFD leg resonance clearance is a physical + // property, independent of the feed transformer ratio. + let quarter_wave = calc.quarter_wave_m; if quarter_wave <= 0.0 { continue; } @@ -1540,6 +1547,67 @@ mod tests { } } + /// Regression guard for the resonance-point unification: every optimizer that + /// works from resonance points (non-resonant, resonant-compromise, OCFD split) + /// must produce identical results regardless of the transformer ratio, because + /// resonance is a property of the wire geometry, not the feed. Before the fix + /// these used the transformer-corrected quarter-wave and drifted per ratio. + #[test] + fn resonance_point_optimizers_are_transformer_independent() { + let bands = [band_at("40m", 7.1), band_at("20m", 14.175)]; + let config = NonResonantSearchConfig { + min_len_m: 8.0, + max_len_m: 35.0, + step_m: 0.5, + preferred_center_m: 21.5, + }; + let build = |ratio| { + bands + .iter() + .map(|b| { + calculate_for_band_with_velocity(b, 1.0, ratio, 10.0, GroundClass::Average) + }) + .collect::>() + }; + let c_1to1 = build(TransformerRatio::R1To1); + let c_1to49 = build(TransformerRatio::R1To49); + + // Sanity: the transformer DID change the corrected build lengths, so this + // is a meaningful comparison (not two identical inputs). + assert!( + (c_1to1[0].corrected_quarter_wave_m - c_1to49[0].corrected_quarter_wave_m).abs() > 1e-6, + "transformer ratio should change the corrected build length" + ); + + let nr1 = calculate_non_resonant_optima(&c_1to1, 1.0, config); + let nr2 = calculate_non_resonant_optima(&c_1to49, 1.0, config); + assert_eq!(nr1.len(), nr2.len()); + for (a, b) in nr1.iter().zip(nr2.iter()) { + assert!( + (a.length_m - b.length_m).abs() < 1e-9, + "non-resonant length depends on transformer: {} vs {}", + a.length_m, + b.length_m + ); + } + + let cp1 = calculate_resonant_compromises(&c_1to1, config); + let cp2 = calculate_resonant_compromises(&c_1to49, config); + assert_eq!(cp1.len(), cp2.len()); + for (a, b) in cp1.iter().zip(cp2.iter()) { + assert!( + (a.length_m - b.length_m).abs() < 1e-9, + "compromise length depends on transformer: {} vs {}", + a.length_m, + b.length_m + ); + } + + let s1 = optimize_ocfd_split_for_length(&c_1to1, 20.0).map(|r| r.short_ratio); + let s2 = optimize_ocfd_split_for_length(&c_1to49, 20.0).map(|r| r.short_ratio); + assert_eq!(s1, s2, "OCFD split depends on transformer ratio"); + } + // --- GroundClass --- #[test] diff --git a/src/cli.rs b/src/cli.rs index 2942357..93859e3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -908,6 +908,8 @@ fn print_advise_candidates( } println!("Ranked combinations (wire length + balun/unun ratio):"); + println!("(The recommended wire length is the same for every ratio - resonance is a"); + println!(" property of the wire, not the feed; ratios differ in matching efficiency.)"); println!(); for (idx, candidate) in view.candidates.iter().enumerate() { diff --git a/src/export.rs b/src/export.rs index 32f9159..de96dd1 100644 --- a/src/export.rs +++ b/src/export.rs @@ -1951,7 +1951,9 @@ fn collect_band_resonant_points_m( wire_max_m: f64, ) -> Vec<(u32, f64)> { let mut points = Vec::new(); - let quarter_wave_m = calc.corrected_quarter_wave_m; + // Uncorrected quarter-wave: exported resonant points are transformer- + // independent and match the optimizers and the on-screen resonant-points list. + let quarter_wave_m = calc.quarter_wave_m; if quarter_wave_m <= 0.0 || wire_max_m <= wire_min_m { return points; } From ceaf121d0c036b19b316548f3866a6a4e4909f90 Mon Sep 17 00:00:00 2001 From: "Simon Keimer (DC0SK)" Date: Sat, 4 Jul 2026 12:20:03 +0200 Subject: [PATCH 2/2] review: keep conductor correction in resonance points; dedupe via one helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the #127 code-review findings: - [2][3][4] The previous commit switched the five resonance-point sites to the fully-raw quarter_wave_m, which dropped the *conductor-diameter* correction (a real geometry effect) along with the transformer heuristic (intended). A thick-conductor build could then be recommended sitting on a real resonance. Introduce WireCalculation::resonant_quarter_wave_m = quarter_wave_m x conductor_diameter_correction_factor(d) — conductor-corrected but transformer-independent — and use it everywhere. - [7] Extract one shared band_resonant_points_m() helper; all five consumers (non-resonant avoid-set, compromise, OCFD clearance, on-screen list, export) now go through it, so the harmonic loop can't drift between copies again. - [6] The non-resonant avoid-set uses a 1 m edge pad (for honest boundary clearance) while the others list strictly in-window points; docs/comments no longer overclaim the lists are byte-identical, only that the harmonic positions come from the same feed-independent formula. - [5] Mirror the CLI's "length is the same for every ratio" note in the TUI advise panel. - tests: resonant_points_keep_conductor_diameter_correction guards the physical correction is retained (thick < thin, and != raw quarter-wave). Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/math.md | 28 +++++--- src/app/mod.rs | 34 ++++------ src/calculations.rs | 162 +++++++++++++++++++++++++++++++++----------- src/export.rs | 30 +++----- src/nec_export.rs | 1 + src/tui/mod.rs | 8 +++ 6 files changed, 172 insertions(+), 91 deletions(-) diff --git a/docs/math.md b/docs/math.md index 8adbe7e..8f1964c 100644 --- a/docs/math.md +++ b/docs/math.md @@ -143,16 +143,21 @@ This is a practical approximation, not a substitute for NEC-based segment/curren ## 5) Non-Resonant Wire Optimization -For each selected band, Rusty Wire generates resonance points from the -**uncorrected** quarter-wave harmonics (i.e. $71.32/f \cdot VF$, *without* the -transformer-length heuristic of §4). Resonance is a property of the wire -geometry, not the feed, so the same point set drives the non-resonant optimizer, -the resonant-compromise optimizer (§6), the OCFD split optimizer (§7), and the -resonant-points shown on screen and in exports — they always agree, and none of -them shift when the transformer ratio changes. +For each selected band, Rusty Wire generates resonance points from the **physical +resonant quarter-wave** $L_{1/4}^{\ast} = \tfrac{71.32}{f}\,VF\cdot F_d(d)$ — the +quarter-wave with the conductor-diameter correction $F_d(d)$ of §9, but *without* +the transformer-length heuristic of §4. Resonance is a property of the wire +geometry (length, conductor diameter), not the feed, so a single shared helper +produces this point set for the non-resonant optimizer, the resonant-compromise +optimizer (§6), the OCFD split optimizer (§7), and the resonant-points shown on +screen and in exports. None of them shift when the transformer ratio changes, and +they use the identical harmonic positions. (The non-resonant optimizer +additionally considers resonances within 1 m *outside* the search window so a +candidate near an edge still gets an honest clearance; the display, export and +compromise optimizer list only strictly in-window resonances.) $$ -R = \{h\,L_{1/4,i}\mid i\in\text{bands},\ h\in\mathbb{N}\} +R = \{h\,L_{1/4,i}^{\ast}\mid i\in\text{bands},\ h\in\mathbb{N}\} $$ The optimizer avoids **all** quarter-wave harmonics (the wire's resonant lengths @@ -179,9 +184,10 @@ $$ ## 6) Resonant Compromise Optimization -For each band $i$, define resonant-point set $P_i$ (the uncorrected, -transformer-independent quarter-wave harmonics of §5) in the active window. -Per-band nearest distance at candidate $\ell$: +For each band $i$, define resonant-point set $P_i$ (the physical resonant +quarter-wave harmonics $h\,L_{1/4,i}^{\ast}$ of §5 — conductor-corrected and +transformer-independent) in the active window. Per-band nearest distance at +candidate $\ell$: $$ D_i(\ell) = \min_{p\in P_i}|\ell-p| diff --git a/src/app/mod.rs b/src/app/mod.rs index d634441..9795d32 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2022,27 +2022,19 @@ pub fn resonant_points_in_window(results: &AppResults) -> Vec { let mut points = Vec::new(); for calc in &results.calculations { - // Uncorrected quarter-wave: the resonant points shown are transformer- - // independent and match the non-resonant/compromise optimizers' point sets. - let quarter_wave_m = calc.quarter_wave_m; - if quarter_wave_m <= 0.0 { - continue; - } - - let mut harmonic = 1_u32; - loop { - let resonant_len_m = quarter_wave_m * f64::from(harmonic); - if resonant_len_m > max_m + 1e-9 { - break; - } - if resonant_len_m >= min_m - 1e-9 { - points.push(ResonantPoint { - length_m: resonant_len_m, - band_name: calc.band_name.clone(), - harmonic, - }); - } - harmonic += 1; + // Shared physical (conductor-corrected, transformer-independent) resonance + // points, identical to the non-resonant/compromise optimizers' base set. + for (harmonic, length_m) in crate::calculations::band_resonant_points_m( + calc.resonant_quarter_wave_m, + min_m, + max_m, + crate::calculations::IN_WINDOW_PAD_M, + ) { + points.push(ResonantPoint { + length_m, + band_name: calc.band_name.clone(), + harmonic, + }); } } diff --git a/src/calculations.rs b/src/calculations.rs index 8bdb0a4..bb1ac1c 100644 --- a/src/calculations.rs +++ b/src/calculations.rs @@ -127,6 +127,13 @@ pub struct WireCalculation { pub full_wave_m: f64, pub quarter_wave_m: f64, + /// Physical resonant quarter-wave: `quarter_wave_m` with the conductor-diameter + /// correction applied but NOT the transformer-length heuristic. This is the + /// canonical, feed-independent base for every resonance-point calculation + /// (non-resonant avoid-set, resonant compromise, OCFD clearance, and the + /// displayed/exported resonant-points list) so they can never disagree. + pub resonant_quarter_wave_m: f64, + // Dipole lengths (in feet) pub half_wave_ft: f64, pub full_wave_ft: f64, @@ -343,6 +350,12 @@ pub fn calculate_for_band_with_environment( let full_wave_ft = full_wave_m * METERS_TO_FEET; let quarter_wave_ft = quarter_wave_m * METERS_TO_FEET; + // Physical resonant quarter-wave: conductor-diameter correction (real geometry + // effect) but NOT the transformer-length heuristic (a feed choice). Canonical + // base for all resonance-point calculations. + let resonant_quarter_wave_m = + quarter_wave_m * conductor_diameter_correction_factor(conductor_diameter_mm); + // NEC-calibrated feedpoint resistance: use corpus reference data instead of the // classic textbook 73 Ω free-space value. Height and ground affect the radiation // resistance through image-method coupling; the NEC values are validated against @@ -431,6 +444,7 @@ pub fn calculate_for_band_with_environment( half_wave_m, full_wave_m, quarter_wave_m, + resonant_quarter_wave_m, half_wave_ft, full_wave_ft, quarter_wave_ft, @@ -755,6 +769,45 @@ pub fn calculate_non_resonant_window_optima( .collect() } +/// Window padding (m) used when the non-resonant optimizer builds its avoid-set, +/// so a candidate near a window edge still "sees" a resonance just outside the +/// window and its clearance stays honest. The display/export/compromise callers +/// use a ~0 pad because they only list/target in-window resonances. +const NON_RESONANT_EDGE_PAD_M: f64 = 1.0; +pub(crate) const IN_WINDOW_PAD_M: f64 = 1e-9; + +/// A band's resonant wire lengths (quarter-wave harmonics `n·λ/4`) that fall in +/// `[min_len_m - pad_m, max_len_m + pad_m]`, keyed by harmonic number. +/// +/// Uses the physical resonant quarter-wave (`resonant_quarter_wave_m`): conductor- +/// diameter corrected but transformer-independent. Every consumer — the +/// non-resonant avoid-set, the resonant-compromise optimizer, the on-screen +/// resonant-points list and the exports — goes through this single function so the +/// resonance geometry can never drift between them. +pub(crate) fn band_resonant_points_m( + resonant_quarter_wave_m: f64, + min_len_m: f64, + max_len_m: f64, + pad_m: f64, +) -> Vec<(u32, f64)> { + let mut points = Vec::new(); + if resonant_quarter_wave_m <= 0.0 || max_len_m < min_len_m { + return points; + } + let mut harmonic = 1_u32; + loop { + let len_m = resonant_quarter_wave_m * f64::from(harmonic); + if len_m > max_len_m + pad_m { + break; + } + if len_m >= min_len_m - pad_m { + points.push((harmonic, len_m)); + } + harmonic += 1; + } + points +} + fn build_non_resonant_resonance_points( calculations: &[WireCalculation], min_len_m: f64, @@ -762,23 +815,13 @@ fn build_non_resonant_resonance_points( ) -> Vec { let mut resonance_points_m = Vec::new(); for c in calculations { - // Resonance is a property of the wire geometry, not the feed: use the - // uncorrected quarter-wave so the avoid-set is transformer-independent and - // matches the resonant-points shown in the display/export and targeted by - // the compromise optimizer. (The transformer-length heuristic is a - // build-length nudge, not physics.) - let quarter_wave_m = c.quarter_wave_m; - - let mut harmonic = 1_u32; - loop { - let resonant_len_m = quarter_wave_m * f64::from(harmonic); - if resonant_len_m > max_len_m + 1.0 { - break; - } - if resonant_len_m >= min_len_m - 1.0 { - resonance_points_m.push(resonant_len_m); - } - harmonic += 1; + for (_, len_m) in band_resonant_points_m( + c.resonant_quarter_wave_m, + min_len_m, + max_len_m, + NON_RESONANT_EDGE_PAD_M, + ) { + resonance_points_m.push(len_m); } } resonance_points_m @@ -827,25 +870,18 @@ pub fn calculate_resonant_compromises( let mut band_points: Vec> = Vec::new(); for calc in calculations { - // Uncorrected quarter-wave: resonance points are transformer-independent - // and match those shown by resonant_points_in_window in the same view. - let quarter_wave_m = calc.quarter_wave_m; - if quarter_wave_m <= 0.0 { - continue; - } - - let mut points = Vec::new(); - let mut harmonic = 1_u32; - loop { - let resonant_len_m = quarter_wave_m * f64::from(harmonic); - if resonant_len_m > max_len_m + 1e-9 { - break; - } - if resonant_len_m >= min_len_m - 1e-9 { - points.push(resonant_len_m); - } - harmonic += 1; - } + // Same physical (conductor-corrected, transformer-independent) resonance + // points the on-screen list shows, so the compromise recommendations + // always coincide with the displayed resonant points. + let points: Vec = band_resonant_points_m( + calc.resonant_quarter_wave_m, + min_len_m, + max_len_m, + IN_WINDOW_PAD_M, + ) + .into_iter() + .map(|(_, len_m)| len_m) + .collect(); if !points.is_empty() { band_points.push(points); @@ -955,9 +991,9 @@ pub fn optimize_ocfd_split_for_length( let mut worst_leg_clearance_pct = f64::INFINITY; for calc in calculations { - // Uncorrected quarter-wave: OCFD leg resonance clearance is a physical - // property, independent of the feed transformer ratio. - let quarter_wave = calc.quarter_wave_m; + // Physical resonant quarter-wave (conductor-corrected, feed-independent): + // OCFD leg resonance clearance is a property of the wire, not the feed. + let quarter_wave = calc.resonant_quarter_wave_m; if quarter_wave <= 0.0 { continue; } @@ -1608,6 +1644,54 @@ mod tests { assert_eq!(s1, s2, "OCFD split depends on transformer ratio"); } + /// The resonance-point base must still carry the *physical* conductor-diameter + /// correction (only the transformer heuristic is dropped): a thick conductor + /// resonates shorter, so its resonant quarter-wave and the resonance points fed + /// to the optimizers must move accordingly. Guards against the fix accidentally + /// using the fully-raw quarter-wave. + #[test] + fn resonant_points_keep_conductor_diameter_correction() { + let band = band_at("40m", 7.1); + let thin = calculate_for_band_with_environment( + &band, + 1.0, + TransformerRatio::R1To1, + 10.0, + GroundClass::Average, + 1.0, + ); + let thick = calculate_for_band_with_environment( + &band, + 1.0, + TransformerRatio::R1To1, + 10.0, + GroundClass::Average, + 4.0, + ); + + // Thicker conductor -> shorter physical resonant quarter-wave... + assert!( + thick.resonant_quarter_wave_m < thin.resonant_quarter_wave_m, + "conductor-diameter correction lost from the resonant quarter-wave" + ); + // ...and it is NOT the raw quarter-wave (which ignores diameter). + assert!( + (thin.resonant_quarter_wave_m - thin.quarter_wave_m).abs() > 1e-9, + "resonant quarter-wave should include the conductor-diameter correction" + ); + + // The shared helper propagates that shortening into the resonance points. + let config = NonResonantSearchConfig { + min_len_m: 8.0, + max_len_m: 35.0, + step_m: 0.5, + preferred_center_m: 21.5, + }; + let pts_thin = calculate_resonant_compromises(std::slice::from_ref(&thin), config); + let pts_thick = calculate_resonant_compromises(std::slice::from_ref(&thick), config); + assert!(!pts_thin.is_empty() && !pts_thick.is_empty()); + } + // --- GroundClass --- #[test] diff --git a/src/export.rs b/src/export.rs index de96dd1..d08e8d9 100644 --- a/src/export.rs +++ b/src/export.rs @@ -1950,27 +1950,17 @@ fn collect_band_resonant_points_m( wire_min_m: f64, wire_max_m: f64, ) -> Vec<(u32, f64)> { - let mut points = Vec::new(); - // Uncorrected quarter-wave: exported resonant points are transformer- - // independent and match the optimizers and the on-screen resonant-points list. - let quarter_wave_m = calc.quarter_wave_m; - if quarter_wave_m <= 0.0 || wire_max_m <= wire_min_m { - return points; + if wire_max_m <= wire_min_m { + return Vec::new(); } - - let mut harmonic = 1_u32; - loop { - let resonant_len_m = quarter_wave_m * f64::from(harmonic); - if resonant_len_m > wire_max_m + 1e-9 { - break; - } - if resonant_len_m >= wire_min_m - 1e-9 { - points.push((harmonic, resonant_len_m)); - } - harmonic += 1; - } - - points + // Shared physical (conductor-corrected, transformer-independent) resonance + // points — identical to the optimizers and the on-screen resonant-points list. + crate::calculations::band_resonant_points_m( + calc.resonant_quarter_wave_m, + wire_min_m, + wire_max_m, + crate::calculations::IN_WINDOW_PAD_M, + ) } fn format_band_resonant_points( diff --git a/src/nec_export.rs b/src/nec_export.rs index 5b72474..769e050 100644 --- a/src/nec_export.rs +++ b/src/nec_export.rs @@ -158,6 +158,7 @@ mod tests { half_wave_m: half_wave, full_wave_m: half_wave * 2.0, quarter_wave_m: half_wave / 2.0, + resonant_quarter_wave_m: half_wave / 2.0, half_wave_ft: half_wave * 3.28084, full_wave_ft: half_wave * 2.0 * 3.28084, quarter_wave_ft: half_wave / 2.0 * 3.28084, diff --git a/src/tui/mod.rs b/src/tui/mod.rs index 941c5b0..5f9d4eb 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -2037,6 +2037,14 @@ fn render_advise_lines(view: &AdviseView) -> Vec> { } out.push(Line::from("")); + out.push(Line::from(Span::styled( + "Recommended wire length is the same for every ratio (resonance is a", + Style::default().fg(Color::DarkGray), + ))); + out.push(Line::from(Span::styled( + "property of the wire, not the feed); ratios differ in matching efficiency.", + Style::default().fg(Color::DarkGray), + ))); for (idx, candidate) in view.candidates.iter().enumerate() { let title_style = match candidate.validation_status {