feat: Physics-based optimal P estimation with prop-size-aware recommendations#132
Conversation
Implements BrianWhite's (PIDtoolbox) theory that optimal response timing is aircraft-specific, determined by power-to-rotational-inertia ratio. Features: - Frame-class-aware Td (time to 50%) targets (3", 5", 7", 10") - Response consistency quality control (CV, std dev tracking) - 10+ recommendation scenarios based on Td deviation analysis - Formatted console output with clear reasoning CLI flags: --estimate-optimal-p: Enable optimal P analysis --frame-class <size>: Specify 3inch, 5inch, 7inch, or 10inch (default: 5inch) Theory/math documented in Discord conversation with BrianWhite, MikeNomatter, demvlad, and eyes.fpv regarding step-response timing targets. Tested on 5", 7", and 10" aircraft logs with accurate frame-class detection. Changes: - New module: src/data_analysis/optimal_p_estimation.rs (567 lines) - Constants: Frame-class Td targets, noise thresholds, P multipliers - Integration: Collects individual Td samples, performs analysis per axis - CLI: Added --estimate-optimal-p and --frame-class flags with help text
… recommendations Improvements: - Show frame class in output with reminder that it can be overridden - Add note in P:D recommendations clarifying they focus on D-term tuning - Cross-reference optimal P estimation when both features are active - Warn if --frame-class is specified without --estimate-optimal-p Behavior clarification: - Optimal P estimation only runs when --estimate-optimal-p is specified - Frame class defaults to 5inch if --estimate-optimal-p is used without --frame-class - P:D ratio recommendations (existing feature) and optimal P estimation (new feature) are complementary: * P:D recommendations: analyze peak overshoot/undershoot → recommend D changes * Optimal P estimation: analyze Td vs frame-class targets → recommend P magnitude changes - Both can run simultaneously to provide complete tuning guidance
Updated all documentation to include the new --estimate-optimal-p and --frame-class features: README.md: - Added flags to usage syntax - Added detailed descriptions for both new flags - Added example command showing optimal P estimation usage - Added console output description for optimal P feature OVERVIEW.md: - Added comprehensive "Optimal P Estimation (Optional)" section - Documented theory foundation (BrianWhite's physics insight) - Listed frame-class targets for all aircraft sizes - Explained analysis components and recommendation types - Clarified relationship between P:D and optimal P features src/main.rs: - Added Examples section to --help output - Included example showing optimal P estimation usage All examples now correctly use 'BlackBox_CSV_Render' binary name.
Extended optimal P estimation to support all common prop sizes from 1" tiny
whoops to 13" heavy-lift platforms. Uses simple numeric values (1-13) instead
of size names ("3inch", "5inch") for cleaner CLI experience.
Physics-based Td targets with ~25% tolerance:
- 1" tiny whoop: 40ms \u00b1 10.0ms
- 2" micro: 35ms \u00b1 8.75ms
- 3" toothpick/cinewhoop: 30ms \u00b1 7.5ms
- 4" racing: 25ms \u00b1 6.25ms
- 5" freestyle/racing: 20ms \u00b1 5.0ms (optimal power/weight)
- 6" long-range: 28ms \u00b1 7.0ms
- 7" long-range: 37.5ms \u00b1 9.5ms
- 8" long-range: 47ms \u00b1 11.75ms
- 9" cinelifter: 56ms \u00b1 14.0ms
- 10" cinelifter: 65ms \u00b1 16.25ms
- 11" heavy-lift: 75ms \u00b1 18.75ms
- 12" heavy-lift: 85ms \u00b1 21.25ms
- 13" heavy-lift: 95ms \u00b1 23.75ms
Note: 5" has fastest response due to optimal power-to-weight ratio.
Response times increase for both smaller (lower power) and larger
(higher rotational inertia) sizes.
Changes:
- Extended FrameClass enum: OneInch through ThirteenInch
- Added Td targets for all 13 frame classes in constants.rs
- Updated CLI to accept 1-13 instead of named sizes
- Updated all documentation (README, OVERVIEW, help text)
Tested on 1", 5", 10", and 13" configurations.
Extended optimal P estimation to appear on the step-response PNG plots when --estimate-optimal-p flag is specified. PNG Display (when enabled): - Separator line to distinguish from P:D recommendations - Section header: "Optimal P (5\")" - Td measurement with target comparison - Deviation percentage and classification - Recommendation summary with suggested P value The optimal P information appears in the legend below the P:D ratio recommendations, providing complete tuning guidance in a single image. Behavior: - Shows ONLY when --estimate-optimal-p is specified - Otherwise PNG shows only traditional P:D recommendations - Console output remains comprehensive in both cases Changes: - Store OptimalPAnalysis results in main.rs for PNG rendering - Pass analysis data to plot_step_response() - Add formatted legend entries with color coding: * Blue header for section * Gray for measurements/deviation * Green for recommendation summary - Handle all PRecommendation variants correctly Tested with and without --estimate-optimal-p flag.
Fixed multiple issues identified in code review:
1. OVERVIEW.md (lines 200-215):
- Removed absolute claim that 5\" has fastest response time
- Updated Theory Foundation with correct physics scaling:
"Td ∝ (rotational inertia)⁻¹ ≈ 1/(mass × radius²)"
- Clarified targets are provisional estimates requiring validation
- Changed 5\" label from "(optimal)" to "(common baseline)"
- Fixed 7\" tolerance: 9.5ms → 9.375ms (correct ±25%)
- Added TODO for bench/flight test validation
2. src/constants.rs (line 329 and around 284-285):
- Removed duplicate comment header "// src/constants.rs"
- Fixed TD_TARGET_7INCH_TOLERANCE: 9.5 → 9.375 (matches 25% of 37.5)
- Updated header comments to reflect provisional nature
- Added physics formula reference and TODO for validation
- Fixed inline comment alignment for P multipliers
- Changed 5\" comment from "(optimal)" to "(common baseline)"
3. src/data_analysis/optimal_p_estimation.rs (lines 560-568):
- Added divide-by-zero checks for all percentage calculations
- Conservative P percentage: safe when current_p == 0 (shows "N/A")
- Moderate P percentage: safe when current_p == 0 (shows "N/A")
- Decrease P percentage: safe when current_p == 0 (shows "N/A")
- Prevents panic when analyzing logs with P gain = 0
All changes verified with cargo check and cargo clippy.
Replaced 26 individual constants (TD_TARGET_*INCH and TD_TARGET_*INCH_TOLERANCE) with a single consolidated TdTargetSpec struct and TD_TARGETS array. Benefits: - Eliminates duplication: tolerance is now computed as target_ms * 0.25 - Prevents drift: all tolerances are guaranteed to be exactly 25% - Reduces verbosity: 13 array entries vs 26 separate constants - Easier maintenance: adding new frame classes only requires one array entry - Type safety: TdTargetSpec struct groups related values together Changes: 1. src/constants.rs: - Added TdTargetSpec struct with target_ms and tolerance_ms fields - Added const fn new() constructor that auto-calculates 25% tolerance - Replaced 26 individual constants with TD_TARGETS: [TdTargetSpec; 13] - Array is indexed 0-12 for 1\"-13\" frame classes 2. src/data_analysis/optimal_p_estimation.rs: - Updated FrameClass::td_target() to use TD_TARGETS array - Added array_index() helper method to map enum variants to indices - Removed all direct references to individual TD_TARGET_* constants All functionality preserved, code is more maintainable and DRY compliant.
Refactored Td (time-to-50%) targets from 26 individual constants to a single maintainable data structure with automatic tolerance calculation. Implemented robust statistical analysis with Bessel's correction for sample variance and epsilon-based float comparison. Key improvements: - Consolidated TD_TARGETS into structured array with TdTargetSpec - Added safe indexing helper (TdTargetSpec::for_frame_inches) - Reduced process_file parameters via AnalysisOptions struct - Added FrameClass::from_inches() constructor for cleaner parsing - Applied Bessel's correction (n-1) for unbiased sample variance - Replaced exact float equality with epsilon-based comparison - Improved documentation clarity on tolerance ranges and validation plan - Removed misleading bench test references (Td requires full system inertia) - Enhanced --help output with frame-class dependency warnings
Added spectral analysis of D-term data to calculate high-frequency energy ratio for noise assessment in optimal P recommendations. Previously always reported 'D-term data unavailable' despite D-term being present in logs. Changes: - Added calculate_hf_energy_ratio() to spectral_analysis.rs - Collects and analyzes D-term data per axis during optimal P estimation - Uses Welch's method PSD to measure energy above DTERM_HF_CUTOFF_HZ (200Hz) - Integrates noise level (Low/Moderate/High) into P gain recommendations - Minimum 100 samples required for reliable spectral analysis
Reformatted optimal P analysis output to match the concise style of P:D ratio recommendations. Reduced from ~40 lines per axis to ~5-7 lines while maintaining all essential information.
Changes:
- Single-line header with key metrics: Td, target, deviation, noise, consistency
- Inline warnings for low consistency instead of separate sections
- Collapsed recommendation format matching P:D style
- Removed verbose 70-char separator lines and multi-section layout
- Marked unused public API fields with #[allow(dead_code)] for future verbose mode
- All information preserved: Td, target comparison, noise level, consistency, recommendations
Example output:
Roll: Td=18.8ms (target 28.0±7.0ms, -33% dev), Noise=Low, Consistency=76%
Current P=53
→ Optimal (no change recommended)
Response is faster than target...
Updated help text, README, and OVERVIEW to explicitly state that --frame-class should match PROPELLER diameter, not frame size. This resolves confusion when running non-standard configurations (e.g., 6" frame with 5" props). Critical clarification: - Rotational inertia scales with prop radius² (I ∝ r²) - Propeller size determines response time, not frame size - Example: 6" frame + 5" props → use --frame-class 5 - Example: 7" frame + 6" props → use --frame-class 6 This explains why INVESTIGATION RECOMMENDED appears when frame class doesn't match actual prop size - the measured Td is significantly different from target because the wrong prop size was specified. Changes: - Updated --help to show 'PROP SIZE' and example usage - Updated README.md with clarification and example - Updated OVERVIEW.md Theory Foundation to emphasize prop size primacy - Added inline example: 6-inch frame with 5-inch props → use --frame-class 5
Breaking change: Renamed --frame-class to --prop-size for clarity. This better communicates that the parameter should match propeller diameter, not frame size. Fixed recommendation logic for faster response + low noise: - Previously: Triggered INVESTIGATION RECOMMENDED (incorrectly implied problem) - Now: Recommends P increase (correctly identifies headroom available) - Rationale: Low noise + faster response = excellent build quality with headroom This fixes the user confusion where clean builds with slightly smaller props (e.g., 6" frame with 5" props) would trigger investigation warnings instead of positive recommendations. Changes: - Renamed all --frame-class references to --prop-size - Updated help text, README, OVERVIEW to use prop-size terminology - Fixed Case 8 logic: SignificantlyFaster + Low noise now recommends P increase - Added note about verifying prop size in recommendation text - Console output now shows 'Prop size:' instead of 'Frame class:' Example fixed scenario: Before: 7" frame + 6" props (--frame-class 6) → INVESTIGATION (confusing) After: 7" frame + 6" props (--prop-size 6) → Increase recommended (helpful)
Added comprehensive validation section to OVERVIEW.md explaining: - How to validate Td targets using physics (I ∝ r²) - Scaling check examples showing targets match theory - Reference to constants file for technical details - Common deviation interpretations - Acceptance criteria for measured vs. target Td Updated OPTIMAL_P_IMPLEMENTATION_STATUS.md: - Marked feature as complete and tested - Added real-world test results section - Documented all implementation phases - Added usage examples - Ready-for-merge checklist Fixed README.md example command: - Changed --frame-class to --prop-size in example All documentation now reflects: - Complete feature implementation - Proper prop-size terminology - Validation methodology - Testing results
Enhanced step response plot legends to include critical information from console output:
Added to PNG legend:
- Noise level (Low/Moderate/High) - helps pilots understand thermal headroom
- Consistency warning (only shown if poor) - alerts to unreliable measurements
- Displayed with color coding (orange for warnings)
This ensures pilots who primarily review PNG plots (not console output) have the essential information for tuning decisions.
Legend format per axis when optimal P enabled:
Optimal P (5")
Td: 18.8ms (target: 20.0ms)
Deviation: -6% (WITHIN TARGET)
Noise: HIGH
⚠ Consistency: 76% (CV=34.7%)
Rec: Current P optimal
Previously only showed Td/deviation/recommendation, missing noise level which is critical for understanding if P can be increased safely.
Improved PNG legend readability for pilots:
Changes:
- Removed warning emoji (⚠) - replaced with [LOW CONSISTENCY] text
* Better cross-platform compatibility (avoids emoji rendering issues)
* Clearer and more explicit
- Spelled out 'Recommendation' in full (was 'Rec')
* Less ambiguous for pilots reading the plots
* PNG width (2560px) provides ample space
- Made recommendation text more explicit:
* 'Increase P to X (+Y)' instead of 'P≈X (+Y)'
* 'Current P is optimal' instead of 'Current P optimal'
* 'See console output for details' instead of abbreviated message
Example new legend format:
Optimal P (5")
Td: 18.8ms (target: 20.0ms)
Deviation: -6% (WITHIN TARGET)
Noise: HIGH
[LOW CONSISTENCY] 76% (CV=34.7%)
Recommendation: Current P is optimal
This makes PNG legends self-contained and comprehensible without referring to console output.
When recommending new P values, now also calculates and displays recommended D values to maintain current P:D ratio. This provides complete tuning guidance in one place.
Changes:
1. Optimal P section now shows D with P recommendations:
- Format: 'P to 54 (+2), D to 45 (+3)'
- Calculates D based on maintaining current P:D ratio
- Shows both conservative and moderate tiers when applicable
- Only displays when D data is available
2. Standardized output format across all sections:
- Console and PNG use 'P to X (+Y)' format consistently
- Removed percentage-based display (simpler absolute values)
- Clear delta indicators (+X for increase, -X for decrease)
3. P:D section labels updated:
- 'Conservative:' → 'Conservative recommendation:'
- 'Moderate:' → 'Moderate recommendation:'
- Clarifies these are separate from optimal-P recommendations
- Applied to both console output and PNG legends
4. PNG legend enhancements:
- Shows D recommendation alongside P in optimal-P section
- Maintains consistent format with console output
- P:D section labels updated to match console
Example output:
Optimal P (5")
...
Recommendation: P to 54 (+2), D to 45 (+3)
This ensures pilots have complete, actionable tuning information whether reading console or PNG plots.
Critical bug fix + format improvement
Changed confusing '[LOW CONSISTENCY] 77%' format to clearer '[WARNING] High variability' format in PNG legends. The old format showed a percentage (77%) that looked good but was labeled 'LOW CONSISTENCY', causing confusion. The new format focuses on what matters: - Emphasizes the problem: High variability (CV=44%) - Clear actionable message: 'results may be unreliable' - Removed confusing percentage that wasn't intuitive Console output already had clear wording and remains unchanged. This makes it immediately obvious to pilots reviewing PNG plots that measurements are unreliable without needing to understand what 'consistency percentage' means.
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds an experimental Optimal P Estimation feature: new CLI flags (--estimate-optimal-p, --prop-size), frame-size Td targets (1–15"), Td statistics and consistency checks, HF D-term energy metric, decision logic producing P recommendations, integration into processing and plotting, supporting constants, spectral helper, docs, and unit tests. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
OVERVIEW.md (1)
3-15: Update Table of Contents for the new section.As per coding guidelines, the Table of Contents should be updated when adding new sections. The new "Optimal P Estimation (Optional)" section at line 192 should be added to the TOC.
Proposed TOC addition
- [Compared to PIDtoolbox/Matlab (PTstepcalc.m)](`#compared-to-pidtoolboxmatlab-ptstepcalcm`) - [Compared to PlasmaTree/Python (PID-Analyzer.py)](`#compared-to-plasmatreepython-pid-analyzerpy`) + - [Optimal P Estimation (Optional)](`#optimal-p-estimation-optional`)Add this entry in the appropriate location within the TOC hierarchy (under the output/tuning section or as a top-level entry).
🤖 Fix all issues with AI agents
In `@README.md`:
- Line 29: The README usage line is inconsistent with the actual CLI flag
rename: replace the incorrect flag `--frame-class <size>` with the correct
`--prop-size <size>` so the Usage string matches the help text, example and the
flag defined in main.rs (ensure any other occurrences of `--frame-class` in
README are updated to `--prop-size` for consistency with the CLI).
- Around line 113-116: Change the terminology "Frame-class-aware Td" to
"Prop-size-aware Td" in the README to match the renamed CLI flag and PR
language; specifically update the section describing "Optimal P estimation (when
--estimate-optimal-p is used)" so the bulleted line reads "Prop-size-aware Td
(time to 50%) analysis" and scan nearby documentation for any other occurrences
of "Frame-class-aware" to replace with "Prop-size-aware" to keep terminology
consistent with the --prop-size flag and the PR title "prop-size-aware
recommendations".
In `@src/data_analysis/optimal_p_estimation.rs`:
- Line 286: The current mapping let current_pd_ratio = current_d.map(|d|
(current_p as f64) / (d as f64)); can divide by zero when current_d is Some(0);
change the mapping to guard against d == 0 by using and_then (or map +
conditional) so that when d == 0 you return None instead of computing an
infinite ratio, e.g. replace map with current_d.and_then(|d| if d == 0 { None }
else { Some((current_p as f64) / (d as f64)) }) to keep current_pd_ratio as
Option<f64>.
- Around line 583-591: Guard the D-calculation against rec_pd being zero before
dividing: in the block that checks (self.current_d,
self.recommended_pd_conservative) and computes conservative_d using
((*conservative_p as f64) / rec_pd).round() as u32, first test if rec_pd == 0.0
(or rec_pd.is_normal()) and if so skip adding the D estimate (or append a
sensible placeholder like "D≈N/A") instead of performing the division; apply the
same guard to the other two places where you compute D using
recommended_pd_conservative/recommended_pd_aggressive (the analogous blocks that
compute conservative_d/aggressive_d and their deltas) so no division by zero can
produce f64::INFINITY and u32::MAX.
In `@src/main.rs`:
- Around line 415-421: Update the example usage string in main.rs where the
eprintln! examples are printed: replace the outdated flag name "--frame-class"
with the current flag "--prop-size" in the eprintln! call that contains
"{program_name} flight.csv --step --estimate-optimal-p --frame-class 5" so the
example matches the actual CLI flag; keep the rest of the eprintln! messages
unchanged.
🧹 Nitpick comments (2)
src/data_analysis/optimal_p_estimation.rs (2)
188-218: MoveMEAN_EPSILONtosrc/constants.rs.Per coding guidelines, all constants should be in
src/constants.rs— no hardcoded magic numbers in function code. The local constantMEAN_EPSILONshould be moved to the constants module.Suggested refactor
In
src/constants.rs, add:/// Epsilon threshold for near-zero mean values in Td statistics pub const TD_MEAN_EPSILON: f64 = 1e-12;Then update this file:
pub fn from_samples(td_samples_ms: &[f64]) -> Option<Self> { - const MEAN_EPSILON: f64 = 1e-12; // Threshold for near-zero mean values - if td_samples_ms.is_empty() { return None; } // ... - if mean.abs() <= MEAN_EPSILON { + if mean.abs() <= TD_MEAN_EPSILON { return None; }
294-303: Move deviation threshold constants tosrc/constants.rs.Per coding guidelines, the magic numbers
30.0,15.0, and-15.0for deviation classification should be defined as named constants in the constants module.Suggested refactor
In
src/constants.rs, add:/// Td deviation thresholds for P recommendation classification (percentages) pub const TD_DEVIATION_SIGNIFICANTLY_SLOWER_THRESHOLD: f64 = 30.0; pub const TD_DEVIATION_MODERATELY_SLOWER_THRESHOLD: f64 = 15.0; pub const TD_DEVIATION_SIGNIFICANTLY_FASTER_THRESHOLD: f64 = -15.0;Then update the classification logic:
// Classify deviation - let td_deviation = if td_deviation_percent > 30.0 { + let td_deviation = if td_deviation_percent > TD_DEVIATION_SIGNIFICANTLY_SLOWER_THRESHOLD { TdDeviation::SignificantlySlower - } else if td_deviation_percent > 15.0 { + } else if td_deviation_percent > TD_DEVIATION_MODERATELY_SLOWER_THRESHOLD { TdDeviation::ModeratelySlower - } else if td_deviation_percent < -15.0 { + } else if td_deviation_percent < TD_DEVIATION_SIGNIFICANTLY_FASTER_THRESHOLD { TdDeviation::SignificantlyFaster } else { TdDeviation::WithinTarget };
Simplified output to show only one recommendation tier instead of two, reducing complexity for pilots.
Changes:
- Console now shows only Conservative recommendation (was showing Conservative + Moderate)
- PNG already showed only Conservative (now consistent with console)
- P:D section still shows both Conservative and Moderate (unchanged)
Rationale:
- Reduces decision paralysis for pilots
- Conservative is safer starting point
- P:D section already provides comparison tiers
- Cleaner, less cluttered output
- Pilots can reference P:D recommendations if they want more aggressive values
Example output now:
→ Increase recommended:
P≈54 (+1), D≈45 (+3)
Instead of:
→ Increase recommended:
Conservative: P≈54 (+1), D≈45 (+3)
Moderate: P≈56 (+3), D≈47 (+5)
Removed moderate tier from optimal-P recommendations for simplicity and clarity. Rationale: - PNG legend only showed conservative (space constraints) - Console showed both conservative and moderate (inconsistent) - P:D section already provides conservative and moderate for comparison - Conservative recommendation is safer for pilots to try first - Reduces decision paralysis - one clear recommendation to follow Changes: - Console now shows only conservative P and D recommendations - Matches PNG legend format (consistent output) - Removed unused recommended_pd_moderate field and parameter - Pilots can still see moderate recommendations in P:D section if desired This makes the output cleaner and more actionable while maintaining full information in the separate P:D analysis section.
Removed unused fields and calculations that were being stored but never read: Removed: - current_pd_ratio field (calculated but never used) - hf_energy_percent field (stored but never read) - Simplified noise_level calculation (no longer creates unused hf_percent) Kept #[allow(dead_code)] only where appropriate: - std_dev_ms: Used internally in from_samples() for consistency calculation - P_HEADROOM_AGGRESSIVE_MULTIPLIER: Reserved for potential future use This cleans up the codebase by removing genuinely unused data rather than just suppressing warnings.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/data_analysis/optimal_p_estimation.rs`:
- Around line 191-299: Extract the hard-coded numeric constants into
src/constants.rs (e.g., TD_MEAN_EPSILON, TD_DEVIATION_SIGNIFICANT_PERCENT,
TD_DEVIATION_MODERATE_PERCENT) and replace usages: swap the local MEAN_EPSILON
inside TdStatistics::from_samples with TD_MEAN_EPSILON, and replace the
30.0/15.0/-15.0 deviation literals in OptimalPAnalysis::analyze (the
td_deviation_percent comparisons) with TD_DEVIATION_SIGNIFICANT_PERCENT and
TD_DEVIATION_MODERATE_PERCENT (derive the negative cutoff from the moderate
constant or add a TD_DEVIATION_NEGATIVE_MODERATE_PERCENT if preferred); add the
necessary use/import of the constants module where TdStatistics::from_samples
and OptimalPAnalysis::analyze are defined. Ensure names are clear and update any
documentation comments accordingly.
In `@src/main.rs`:
- Around line 352-356: The usage string in print_usage_and_exit still references
the removed flag `--frame-class`, causing incorrect help output; update the
usage synopsis inside the print_usage_and_exit function to replace
`--frame-class <size>` with the current flag `--prop-size <size>` so the CLI
help matches the parser and documentation (ensure the string interpolation for
program_name remains intact and adjust any surrounding spacing/line breaks to
keep formatting consistent).
- Around line 963-1056: Replace hard-coded numeric literals and manual axis
range with constants and axis helpers: move the 1000.0 millisecond multiplier
into src/constants.rs (e.g., TIME_MS_PER_S) and the minimum D-term sample cutoff
100 into a constant (e.g., DTERM_MIN_SAMPLES), then use those constants in
td_samples_ms conversion and the d_term_data.len() check; replace the raw axis
loop "for axis_index in 0..2" with the axis_names helper (use
crate::axis_names::AXIS_NAMES with enumerate()/iter().take() or the project’s
provided axis index helper) so the loop uses the canonical axis set; update
references in this block (td_samples_ms, axis_index,
calc_step_response::calculate_delay_time, and the d-term HF calculation call) to
use the new constants and helper.
♻️ Duplicate comments (3)
src/main.rs (1)
415-420: Example still uses the old flag name.The example should use
--prop-sizeinstead of--frame-class.🛠️ Suggested fix
- {program_name} flight.csv --step --estimate-optimal-p --frame-class 5 + {program_name} flight.csv --step --estimate-optimal-p --prop-size 5src/data_analysis/optimal_p_estimation.rs (2)
282-283: Guardcurrent_pd_ratioagainst D=0.If
current_disSome(0), this yieldsinf. The ratio is stored and can leak into outputs.🛠️ Suggested fix
- let current_pd_ratio = current_d.map(|d| (current_p as f64) / (d as f64)); + let current_pd_ratio = current_d.and_then(|d| { + if d > 0 { + Some((current_p as f64) / (d as f64)) + } else { + None + } + });
526-620: Guard D recommendations againstrec_pd == 0.If
rec_pdis0.0, the division yieldsinfand rounds tou32::MAX, producing nonsensical D values.🛠️ Suggested fix
- if let (Some(current_d), Some(rec_pd)) = - (self.current_d, self.recommended_pd_conservative) - { - let conservative_d = ((*conservative_p as f64) / rec_pd).round() as u32; - let conservative_d_delta = conservative_d as i32 - current_d as i32; - output.push_str(&format!( - ", D≈{} ({:+})", - conservative_d, conservative_d_delta - )); - } + if let (Some(current_d), Some(rec_pd)) = + (self.current_d, self.recommended_pd_conservative) + { + if rec_pd > 0.0 { + let conservative_d = ((*conservative_p as f64) / rec_pd).round() as u32; + let conservative_d_delta = conservative_d as i32 - current_d as i32; + output.push_str(&format!( + ", D≈{} ({:+})", + conservative_d, conservative_d_delta + )); + } + }
Added back 'Conservative:' label to optimal-P recommendations in both console and PNG legend for clarity. Console: 'Conservative: P≈54 (+1), D≈45 (+3)' PNG: 'Recommendation (Conservative): P≈54 (+1), D≈45 (+3)' This makes it clear that the recommendation is the conservative tier, distinguishing it from the separate P:D section which shows both conservative and moderate tiers.
Fixed all references to old --frame-class flag to use correct --prop-size flag: - README.md usage line and features section - main.rs help synopsis and example - Changed 'Frame-class-aware' to 'Prop-size-aware' for consistency Addresses CodeRabbit critical and minor issues about flag name inconsistency.
Added safety checks before D gain calculations to prevent: - Division by zero if rec_pd (recommended P:D ratio) is 0.0 - Invalid calculations if current_d is 0 Guards added in both: - Console output (optimal_p_estimation.rs) - PNG legend (plot_step_response.rs) Addresses CodeRabbit minor issues about potential infinity values.
Moved all magic numbers and hardcoded thresholds to constants.rs per project guidelines: New constants: - TD_MEAN_EPSILON (1e-12): Near-zero mean threshold - TD_SAMPLES_MIN_FOR_STDDEV (2): Minimum samples for std dev - TD_DEVIATION_*_THRESHOLD: Td deviation classification thresholds (30%, 15%, -15%) - OPTIMAL_P_MS_TO_SECONDS_MULTIPLIER (1000.0): Seconds to milliseconds conversion - OPTIMAL_P_MIN_DTERM_SAMPLES (100): Minimum D-term samples for noise analysis All hardcoded values now use named constants for better maintainability and documentation. Addresses CodeRabbit major refactor suggestion.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/constants.rs`:
- Around line 346-348: The constant OPTIMAL_P_MS_TO_SECONDS_MULTIPLIER is
misnamed (it actually multiplies seconds to produce milliseconds); rename it to
a clearer identifier such as OPTIMAL_P_SECONDS_TO_MS_MULTIPLIER or
OPTIMAL_P_SECONDS_TO_MILLIS_MULTIPLIER, update its declaration accordingly, and
update every call-site that references OPTIMAL_P_MS_TO_SECONDS_MULTIPLIER to use
the new name (search for the symbol and replace all occurrences to keep
compilation consistent).
In `@src/data_analysis/optimal_p_estimation.rs`:
- Around line 234-238: The is_consistent method currently only checks
consistency and coefficient_of_variation (using TD_CONSISTENCY_MIN_THRESHOLD and
TD_COEFFICIENT_OF_VARIATION_MAX) but treats single-sample Td sets as valid;
update is_consistent to first return false for any dataset with fewer than two
measurements (check the struct's sample count field or measurements.len()), and
only then apply the existing consistency and coefficient_of_variation thresholds
in is_consistent.
In `@src/main.rs`:
- Around line 1034-1038: The condition that gates D-term analysis uses a strict
greater-than, skipping analysis when d_term_data.len() equals the minimum
threshold; change the comparison in the if that references d_term_data and
crate::constants::OPTIMAL_P_MIN_DTERM_SAMPLES from ">" to ">=" so analysis runs
when the sample count is exactly the configured minimum (i.e., ensure the if
block that checks !d_term_data.is_empty() && d_term_data.len() >
crate::constants::OPTIMAL_P_MIN_DTERM_SAMPLES uses >=).
Fixed three minor CodeRabbit issues: 1. Renamed constant for clarity: - OPTIMAL_P_MS_TO_SECONDS_MULTIPLIER → OPTIMAL_P_SECONDS_TO_MS_MULTIPLIER - The name now correctly reflects seconds→milliseconds conversion 2. Added minimum sample check for consistency: - Single-sample Td sets now trigger consistency warning - Requires num_samples >= 2 for reliable consistency metrics - Prevents false "high consistency" with insufficient data 3. D-term threshold already uses '>=' (was fixed in previous commit) All CodeRabbit nitpicks now addressed.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@OVERVIEW.md`:
- Around line 192-265: Add a Table of Contents entry for the new "Optimal P
Estimation (Optional, Experimental)" section in OVERVIEW.md so the TOC includes
the new heading; locate the TOC block at the top of OVERVIEW.md and insert a
link pointing to the section header (use the exact header text "Optimal P
Estimation (Optional, Experimental)" as the anchor), and ensure the entry
mentions the related flags (--estimate-optimal-p, --prop-size) and the constants
reference (TD_TARGETS) so readers can find usage and the src/constants.rs
reference from the TOC.
In `@src/data_analysis/optimal_p_estimation.rs`:
- Around line 34-35: MIN_TD_MS is a domain constant that should be moved out of
optimal_p_estimation.rs into the shared constants module; remove the local const
MIN_TD_MS from optimal_p_estimation.rs, add a pub const MIN_TD_MS: f64 = 0.1; to
the central constants module, and update any references in
optimal_p_estimation.rs to refer to the new constant (e.g.,
crate::constants::MIN_TD_MS or by importing constants::MIN_TD_MS) so visibility
and usage remain correct.
In `@src/data_analysis/spectral_analysis.rs`:
- Around line 333-399: The function calculate_hf_energy_ratio contains a magic
numeric guard (1e-12); replace it with a shared constant PSD_EPSILON from the
project's constants module: add a public PSD_EPSILON (value 1e-12 or a named
value you prefer) to the constants module if it doesn't exist, import it into
src/data_analysis/spectral_analysis.rs (e.g., use crate::constants::PSD_EPSILON)
and change the final energy check from `if total_energy > 1e-12` to `if
total_energy > PSD_EPSILON`; ensure constants are exported publicly so the
function compiles.
🧹 Nitpick comments (2)
src/main.rs (1)
969-976: Move magic number1000.0to constants.Line 975 uses a hardcoded
1000.0for seconds-to-milliseconds conversion. Per coding guidelines, all constants should go insrc/constants.rs.♻️ Suggested fix
In
src/constants.rs, add (if not already present):/// Multiplier to convert seconds to milliseconds pub const SECONDS_TO_MS: f64 = 1000.0;Then update line 975:
- td_samples_ms.push(td_seconds * 1000.0); + td_samples_ms.push(td_seconds * crate::constants::SECONDS_TO_MS);As per coding guidelines, no hardcoded magic numbers in function code.
src/plot_functions/plot_step_response.rs (1)
19-20: UseAXIS_COUNTfromaxis_namesmodule instead of defining locally.This creates a redundant constant that duplicates the centralized axis system in
src/axis_names.rs. Per coding guidelines, axis indices must use theaxis_namesmodule.♻️ Suggested fix
+use crate::axis_names::AXIS_COUNT; use crate::axis_names::AXIS_NAMES; // ... other imports -/// Number of control axes (Roll, Pitch, Yaw) -const AXIS_COUNT: usize = 3;The structs at lines 25-72 will then use the imported constant, ensuring consistency with the centralized axis naming system.
As per coding guidelines, axis-related constants must use the
axis_namesmodule.
- Add MIN_TD_MS (0.1ms) to constants module for Td validation - Remove local MIN_TD_MS const from optimal_p_estimation.rs - Replace magic 1e-12 with PSD_EPSILON constant in spectral_analysis.rs - Add TOC entry for 'Optimal P Estimation (Optional, Experimental)' section in OVERVIEW.md All domain-specific constants now centralized for better maintainability and consistency.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
README.md (1)
52-53: README mirrors main.rs help text—same clarity improvement applies.The same bidirectional dependency clarification suggested for
src/main.rs(lines 385-386) should be applied here to keep documentation synchronized.Based on learnings: "Keep
README.mdusage/examples synchronized with actual CLI flags insrc/main.rs"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 52 - 53, Update README.md to clarify the dependency between the CLI flags by modifying the description lines for --estimate-optimal-p and --prop-size: make it explicit that --prop-size only takes effect when --estimate-optimal-p is also provided and that --estimate-optimal-p may require a valid --prop-size value for certain behaviors; mirror the same bidirectional wording used in src/main.rs (flags --estimate-optimal-p and --prop-size) so the README stays synchronized with the actual CLI help text.src/main.rs (2)
1381-1386: Dead code: warning will never trigger.The guard
(1..=15).contains(&size)at line 1374 ensuressizeis in valid range beforeFrameClass::from_inches()is called. Sincefrom_inches()returnsSomefor all values 1–15,frame_class_override.is_none()will always be false here.This serves as defensive programming against future changes to
from_inches(), which is reasonable. Consider adding a comment to clarify the intent:+ // Defensive check: from_inches() should never return None for validated 1-15 range if frame_class_override.is_none() {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main.rs` around lines 1381 - 1386, The warning eprintln for frame_class_override being None is dead code because FrameClass::from_inches(size) currently returns Some for 1..=15; replace the eprintln with a short clarifying comment (near the check that uses frame_class_override and the call to FrameClass::from_inches) that documents this is an intentional defensive check against future changes to from_inches, referencing the variables/methods frame_class_override, from_inches, and size so future readers understand why the None branch exists.
385-386: Help text understates the--prop-sizerequirement.The help says
--prop-size"Requires --estimate-optimal-p to have effect" but doesn't clarify that--estimate-optimal-prequires--prop-sizeto be specified (see validation at lines 1449-1456 which errors out if missing). Consider clarifying the bidirectional dependency:📝 Suggested clarification
- eprintln!(" --estimate-optimal-p: Enable optimal P estimation with frame-class targets."); - eprintln!(" --prop-size <size>: Propeller diameter in inches (1-15, whole-number only). Requires --estimate-optimal-p to have effect."); + eprintln!(" --estimate-optimal-p: Enable optimal P estimation with frame-class targets (requires --prop-size)."); + eprintln!(" --prop-size <size>: Propeller diameter in inches (1-15, whole-number only). Required with --estimate-optimal-p.");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main.rs` around lines 385 - 386, Update the help text printed in main (the eprintln calls that emit the CLI usage lines for "--estimate-optimal-p" and "--prop-size") to state the mutual dependency: that --prop-size requires --estimate-optimal-p to have effect and --estimate-optimal-p also requires --prop-size to be provided; mirror the exact validation logic used later (the check that errors when --estimate-optimal-p is set without a --prop-size) so users know both flags must be supplied together.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@README.md`:
- Line 106: The README claims "std dev" is shown but format_console_output()
actually prints Consistency percent and CV (with warnings) not standard
deviation; update the documentation to match actual output by removing "std dev"
from the list or change the function to also compute and display standard
deviation. Refer to format_console_output() and any Consistency/CV output text
when making the change so the README and console output remain consistent.
---
Nitpick comments:
In `@README.md`:
- Around line 52-53: Update README.md to clarify the dependency between the CLI
flags by modifying the description lines for --estimate-optimal-p and
--prop-size: make it explicit that --prop-size only takes effect when
--estimate-optimal-p is also provided and that --estimate-optimal-p may require
a valid --prop-size value for certain behaviors; mirror the same bidirectional
wording used in src/main.rs (flags --estimate-optimal-p and --prop-size) so the
README stays synchronized with the actual CLI help text.
In `@src/main.rs`:
- Around line 1381-1386: The warning eprintln for frame_class_override being
None is dead code because FrameClass::from_inches(size) currently returns Some
for 1..=15; replace the eprintln with a short clarifying comment (near the check
that uses frame_class_override and the call to FrameClass::from_inches) that
documents this is an intentional defensive check against future changes to
from_inches, referencing the variables/methods frame_class_override,
from_inches, and size so future readers understand why the None branch exists.
- Around line 385-386: Update the help text printed in main (the eprintln calls
that emit the CLI usage lines for "--estimate-optimal-p" and "--prop-size") to
state the mutual dependency: that --prop-size requires --estimate-optimal-p to
have effect and --estimate-optimal-p also requires --prop-size to be provided;
mirror the exact validation logic used later (the check that errors when
--estimate-optimal-p is set without a --prop-size) so users know both flags must
be supplied together.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0ce9b82b-29d7-43f7-b720-cd0c237c8905
📒 Files selected for processing (2)
README.mdsrc/main.rs
|
looks interesting! Thanks for referencing me |
…dead code comment)
…ERVIEW targets table
|
Thoughts:
What if:
Logistics:
@coderabbitai, review this comment and offer recommendations based on this branch's codebase. I'm willing to fork or re-implement. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 12102 🏁 Script executed: Length of output: 8133 🏁 Script executed: Length of output: 2498 🏁 Script executed: Length of output: 367 🏁 Script executed: Length of output: 15132
What Data Is Already Available
And critically, the hook is already wired in None, // Don't use physics_td_target - empirical targets more accurate
The Core PhysicsThe aircraft-specific quantity that eliminates the lookup table is the effective angular acceleration per unit motor command — essentially τ/I without needing to know τ or I separately: From this, the aircraft-specific Td target is: No table. No prop size. Pure physics from the log. How Many Logs?Can 1 log work? Yes — if it contains ≥5 identifiable throttle-punch events (rapid throttle increase ≥20% within ~50ms). Most normal freestyle/racing logs have these organically. Recommended: 3–5 logs from the same aircraft, same tune. You average the You do NOT need a dedicated full-sweep log. Throttle punches embedded in normal flight are sufficient. A dedicated 0→100% sweep log would give the cleanest estimate, but it's optional. Proposed Pipelineflowchart TD
A["Multiple .csv logs\n(same aircraft)"] --> B["Phase 1:\nThrottle-punch detection\nper log"]
B --> C["Measure α_eff during\neach punch event\n(d(gyro)/dt vs cmd_step)"]
C --> D["Aggregate:\nmedian(torque_inertia_ratio)\nper axis across all logs"]
D --> E{"Sufficient events?\n≥5 punches,\nCV < 30%"}
E -- Yes --> F["Compute physics Td_target\n= π/(2×√(Kp×τI_ratio))×1000"]
E -- No --> G["Fallback: prop-size\nlookup table"]
F --> H["Pass as\nphysics_td_target_ms\nto analyze()"]
G --> H
H --> I["Existing OptimalPAnalysis\n(unchanged)"]
Logistics for the CodebaseNew module:
|
|
@coderabbitai, if you agree i prefer to purge --prop-size and not have it is a fallback. the empirical 5-in was my 2 different aircraft and in no way should broadly represent all aircraft. Also the file-name grouping should be intelligent enough to accommodate only grouping similar logs (aircraft name grouping) |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 2742 🏁 Script executed: Length of output: 8550
1. Purge
|
| Scenario | Behavior |
|---|---|
| Mixed aircraft in one invocation | Each group gets its own physics Td target |
| Non-Betaflight filename (no date pattern) | Full stem = group key → treated as unique aircraft |
| Single log, ≥5 punches | Works fine |
| Single log, <5 punches | Warn + skip; suggest providing more logs |
--recursive with multiple aircraft dirs |
Each aircraft groups naturally by filename key |
Summary of Changes Needed
- Remove
--prop-size,FrameClassTd-target logic,TD_TARGETS(or archive to a comment) - Add
extract_aircraft_key()filename parser - Add
group_files_by_aircraft()inmain.rs - Implement
torque_inertia_profiler::estimate_from_logs()(new module) - Change
process_file()signature to acceptphysics_td_target: Option<(f64, f64)>directly - No-fallback failure path with clear console warnings
Shall I open a new issue (or plan the implementation here) with these specifics?
✏️ Learnings added
Learnt from: nerdCopter
URL: https://github.com/nerdCopter/BlackBox_CSV_Render/pull/132
Timestamp: 2026-05-17T22:46:46.857Z
Learning: In the BlackBox_CSV_Render project, nerdCopter wants to purge the --prop-size flag and the empirical TD_TARGETS lookup table entirely from the optimal-P estimation feature. There is to be NO fallback to any empirical table. If physics-based Td cannot be derived from log data (insufficient throttle-punch events, minimum 5), the analysis should be skipped with a clear console warning. FrameClass may remain as a display label only, not as a Td-target source.
Learnt from: nerdCopter
URL: https://github.com/nerdCopter/BlackBox_CSV_Render/pull/132
Timestamp: 2026-05-17T22:46:46.857Z
Learning: In the BlackBox_CSV_Render project, nerdCopter wants intelligent aircraft-based grouping of input log files for the physics-derived Td estimation feature. Betaflight blackbox filenames follow the pattern {PREFIX}_BLACKBOX_LOG_{CRAFT_NAME}_{YYYYMMDD}_{HHMMSS}.{SEQ}.csv. The grouping key is extracted by stripping the _YYYYMMDD_HHMMSS and everything after it from the filename stem. Files with the same key are treated as the same aircraft and their log data is aggregated for the torque-inertia profiler phase before individual file processing.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
|
Tip For best results, initiate chat on the files or code changes.
Agreed Direction (from the full thread)Your core questions, answered:
What the Hook Already Looks Like
Proposed Next StepsThe implementation is a meaningful architecture change from the current PR's design — I'd recommend:
Would you like me to open a GitHub issue capturing all the above specs (physics Td derivation, no-fallback requirement, filename grouping algorithm) so it can be tracked independently of this PR? |
|
as a result I(AI) opened #143 and #141 (141 needs re-basing after 143 potentially merges). side-note: i'm overusing Claude (usage limits), so i started using Gemini which seems to be okay with math/physics, but is too eager to agree with human-engineer, has horrible patching-logic, and also fails to follow directions often. Crossing my fingers 🤞 ... but also was forced to reproduce #143 via Claude-Code from scratch/documentation. @coderabbitai, can you investigate #143 on the optimal P/D tuning aspects? then investigate #141 as the solution to our former conversation about "feature/torque-inertia-profiler" |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainSure, 🏁 Script executed: Length of output: 11260 Got 🏁 Script executed: Length of output: 5030 Good, now let me pull the actual code from both branches for deeper analysis: 🏁 Script executed: Length of output: 17995 🏁 Script executed: Length of output: 15473
PR
|
| Zone | Range | Recommendation |
|---|---|---|
| Undershoot | < 1.00 | Proportional D decrease → target 1.05 |
| Near Optimal | 1.00–1.02 | None (optional D−1 hint) |
| Sweet Spot | 1.02–1.08 | None |
| Acceptable | 1.08–1.12 | Conservative only (+2% D) |
| Overshoot | 1.12–1.20 | Conservative + Moderate |
| Significant | > 1.20 | All three stacked |
The old PEAK_OPTIMAL_MIN = 0.95 threshold was wrong — calling zero-overshoot "optimal" contradicted the entire rationale from Brian White's insight. Good removal.
Specific observations
-
PEAK_OPTIMAL_TARGETis computed, not hardcoded ((1.02 + 1.08) / 2.0 = 1.05) — correct approach; if the sweet-spot bounds shift in the future, the target follows automatically. -
Proportional undershoot formula
P:D × (PEAK_OPTIMAL_TARGET / peak.max(PEAK_VALUE_MIN_CLAMP))— elegant and correct.PEAK_VALUE_MIN_CLAMP = 0.1correctly guards against divide-by-zero. -
Near-optimal D−1 hint — small concern: when
current_dis already at some pilot-set minimum (e.g., D=5), the hint produces D=4, which could visually look like an active recommendation rather than an optional fine-tune. The word "hint" vs "Recommendation" in the label is important — verify the output text makes the optionality clear. -
+2% D via
PEAK_ACCEPTABLE_MULTIPLIER = 0.98— for small P values (e.g., P=30, P:D=1.5 → D=20), the computed D change would be20 × 1/0.98 ≈ 20.4 → D=20. That rounds to no change. Worth adding a minimum integer step guard (e.g.,max(recommended_d, current_d + 1)) when the zone isn't optimal. -
MIN_REASONABLE_PD_RATIOlowered from 0.5 → 0.3 — correct for large aircraft. Worth noting in the PR description that this accommodates 10"+ builds where D legitimately exceeds P. -
PD_RATIO_AGGRESSIVE_MULTIPLIER = 0.65— only used for peak > 1.20. At this multiplier, D increases ~54% which is significant. Ensure this only triggers with the full stacked recommendation text so pilots know the severity.
PR #141 — Torque-Inertia Profiler
Architecture ✅ Correct Direction
The two-phase approach (Phase 1: profiling → Phase 2: per-file analysis) is the right design. The hook in main.rs is clean:
flowchart LR
A["input files"] --> B["group_files_by_aircraft()"]
B --> C["profile_aircraft_group()\nPhase 1: punch detection"]
C --> D["AircraftProfile per group"]
D --> E["process_file() per log\nPhase 2: step analysis\nwith physics Td target"]
Key observations
✅ Strengths
TORQUE_PROFILER_TD_CALC_K = 1570.796... = π/2 × 1000— correctly derived fromTd = π/(2ωn) × 1000ms. The math checks out.AxisProfile::from_ratiosuses median (robust to outliers from noisy punches) + half-IQR spread as tolerance — better than mean/std for this use case.- Graceful degradation: profiling parse failures silently skip the file, don't abort.
- No fallback to empirical table — exactly as discussed. Skip + clear console message.
⚠️ Issues to Address
1. BTFL filename grouping failure (important)
extract_aircraft_key scans for _YYYYMMDD_HHMMSS left-to-right. For Betaflight-style filenames like:
BTFL_BLACKBOX_LOG_20250517_130413_STELLARH7DEV.02.csv
The date pattern _20250517_130413 occurs at byte position 15, giving key BTFL_BLACKBOX_LOG — meaning all Betaflight-named files in a directory would be grouped as one aircraft. The code comment acknowledges this. This is a serious problem with --recursive on mixed-aircraft folders.
One approach: check if the extracted key is a generic prefix with no craft-identifying content (e.g., BTFL_BLACKBOX_LOG without anything meaningful after LOG_). If so, fall back to filename-unique grouping and print a warning:
[torque-profile] Warning: Could not identify craft name in BTFL_BLACKBOX_LOG_20250517_*.csv
Files will be treated as individual aircraft.
Rename files with a craft identifier before the date for grouping.
Alternatively, detect whether the suffix after stripping the date contains non-trivial content and use that as the key.
2. Double file parsing (performance)
Each file is parsed by parse_log_file twice — once in profile_aircraft_group and once in process_file. For a typical 10-minute flight log at 1kHz this is ~600K rows parsed twice. For a 20-file --recursive run this matters. Consider either:
- Caching
LogRowDataper file during Phase 1 and passing it to Phase 2 (uses more RAM), or - Documenting the double-parse as a known cost in a
TODOcomment.
3. TORQUE_PROFILER_ACHIEVABILITY_FACTOR = 2.50 is empirically derived from HELIO only
The PR says this was validated on HELIO (~16ms). A factor of 2.5× means the raw theoretical Td must be ~6.4ms before scaling. This factor will differ for:
- A heavy 7" long-range at lower power-to-inertia ratio
- A tiny 3" toothpick with very high ratio
It's better than a static prop-size table, but it's still one empirical constant. Document clearly in constants.rs that this was calibrated against a 5" 6S freestyle build and may need user adjustment as community data expands.
4. TORQUE_PROFILER_SETTLE_SAMPLES = 3 may be too short
At 1kHz, 3 samples = 3ms settle time before measuring angular acceleration. For slower-responding large aircraft (8"+), the ESC and motor lag alone can be 5–10ms. You might miss the actual acceleration peak and measure noise instead. Consider increasing to 5–8 samples, or making it proportional to the detected sample rate.
5. Yaw axis profiled but never used
extract_punch_ratios returns ratios for all 3 axes, and AircraftProfile::from_axis_ratios stores all 3, but the optimal-P analysis only iterates ROLL_PITCH_AXIS_COUNT = 2. Yaw profiling work is silently discarded. Minor cleanup: either skip Yaw in extract_punch_ratios or add a note in the profiler summary.
Merge Order / Rebase Note
Since #141 depends on #143's constants (PEAK_* thresholds), the order is:
- Merge
#143first - Rebase
#141onto the updated master #141carries theAnalysisOptionsstruct andestimate_optimal_pflag withoutframe_class— verify the rebase removes any remainingFrameClassimports cleanly
Summary
#143 Tuning Zones |
#141 Torque Profiler |
|
|---|---|---|
| Core approach | ✅ Correct | ✅ Correct direction |
| Critical issues | None | BTFL filename grouping |
| Important concerns | D-1 hint UX, +2% rounding | Double parse, achievability factor docs |
| Minor | None | Yaw axis profiling unused |
| Test coverage | 212 CSVs ✅ | HELIO/FOXEER/APEX ✅ |
Both PRs are structurally solid. The BTFL grouping issue in #141 is the one item worth addressing before merging — it would silently produce incorrect Td targets for mixed-aircraft --recursive runs. Everything else is either polish or documentation.
🐇 (•ω•) [torque profiling intensifies]
✏️ Learnings added
Learnt from: nerdCopter
URL: https://github.com/nerdCopter/BlackBox_CSV_Render/pull/132
Timestamp: 2026-05-20T18:24:29.670Z
Learning: In the BlackBox_CSV_Render project, PR `#141` (feature/torque-inertia-profiler) introduces a two-phase aircraft-based file grouping architecture in main.rs: Phase 1 calls profile_aircraft_group() across all files with the same aircraft key, Phase 2 calls process_file() per file with the resulting AircraftProfile. The aircraft key is extracted by scanning for _YYYYMMDD_HHMMSS in the filename stem. A known limitation: Betaflight-style filenames (BTFL_BLACKBOX_LOG_YYYYMMDD_HHMMSS_CRAFTNAME.csv) have the date before the craft name, causing all BTFL files to share key "BTFL_BLACKBOX_LOG". EMUF-style filenames (EMUF_BLACKBOX_LOG_CRAFTNAME_YYYYMMDD_HHMMSS.csv) group correctly.
Learnt from: nerdCopter
URL: https://github.com/nerdCopter/BlackBox_CSV_Render/pull/132
Timestamp: 2026-05-20T18:24:29.670Z
Learning: In the BlackBox_CSV_Render project, PR `#143` (step-response-thresholds) reworks the P:D tuning zone thresholds to 6 zones: <1.00 undershoot (proportional D decrease targeting PEAK_OPTIMAL_TARGET=1.05), 1.00-1.02 near-optimal (none + optional D-1 hint), 1.02-1.08 optimal sweet spot (none), 1.08-1.12 acceptable (conservative +2% D via PEAK_ACCEPTABLE_MULTIPLIER=0.98), 1.12-1.20 overshoot (conservative + moderate), >1.20 significant (all three). Also adds PD_RATIO_AGGRESSIVE_MULTIPLIER=0.65 and lowers MIN_REASONABLE_PD_RATIO from 0.5 to 0.3 for large aircraft where D > P.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
|
@coderabbitai — feedback on your PR #143 observations, items 3 and 4: Item 3 — D−1 hint label optionality: Already handled. The current output reads Item 4 — +2% rounding to no change: Confirmed real. For small P values (e.g. P=30, D=20, P:D=1.5), Two approaches were considered:
Decision: leaving as a known limitation for now. The edge case is narrow (only bites when P is small relative to D), and adding the guard introduces zone-specific logic in the computation path that adds complexity disproportionate to the benefit. If it surfaces in real logs it will be revisited. |
|
Tip For best results, initiate chat on the files or code changes.
Item 3: Good design choice — Item 4: Understood. The decision is sound — the edge case only bites on low-P builds where the Acceptable zone (+2%) rounds to exactly zero delta, and adding zone-specific integer clamping buys little against the complexity cost. Recording this as a documented known limitation so it doesn't get re-raised in future reviews. ✏️ Learnings added
|
|
mistakenly merged, then reverted, then force-pushed old master. |
|
@coderabbitai — Following up on the CodeRabbit review at #132 (comment 4501386876) regarding PR #141. #141 has been closed and superseded by #145 ( Changes made in response to that feedback in #145: [Important] BTFL filename grouping fix —
Yaw axis — Added comment to Double-parse TODO — Noted as a performance concern; not fixed in this PR (correctness is unaffected). Additionally, two verbiage/plot issues found during rebase review were fixed:
|
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 6055
The BTFL grouping logic is particularly clean — the dot-stripping for trailing numeric log sequences ( Shall I trigger a full review of 🐇 ✏️ Learnings added
|
test-binaries. commit
84a56e2binary-windows.zip
binary-macos.zip
binary-linux.zip
TL;DR: "generates/analyzes step response, then compares that to physics-based targets per prop-size. targets are empirical values that need statistical research still."
Overview
Adds physics-aware optimal P gain estimation based on Brian White's (PIDtoolbox author @bw1129) theory: optimal response timing is aircraft-specific, determined by power-to-rotational-inertia ratio.
What This Does
Analyzes your flight logs and recommends optimal P gains (and corresponding D values) based on:
Testing Instructions
Basic Usage (generates step response plots only):
./target/release/BlackBox_CSV_Render ./input/*.csv --step --estimate-optimal-p --prop-size 5 --output-dir ./outputImportant: Use
--stepflag to skip other graphs (faster testing). The feature only affects step response analysis.Prop Size Selection:
--prop-size 5--prop-size 6What to Look For
Console Output:
PNG Legend (appears on step response plots):
Warning Example:
This means your stick inputs were inconsistent. Fly cleaner flips/rolls for better measurements.
Key Features
Prop-size-aware targets (1"-13" props supported)
Complete tuning guidance
Noise analysis
Quality control
What to Test
Changes vs. Main
--estimate-optimal-pflag (optional, off by default)--prop-size <size>flag (only used with optimal-P)Documentation
See
OVERVIEW.mdfor:Known Limitations
Feedback Wanted
*Testing this branch helps validate the physics theory with real-world data 🚁
Summary by CodeRabbit
New Features
Plots
Documentation
Tests