Load engines from TOML and improve scenario audio and carb tuning
Migrate 32 engine definitions into validated startup-loaded TOML files and separate configuration and snapshots from scenario assembly. Add camera-based stereo propagation from shared pipe geometry, preserve exhaust character, tune carburetor mixtures and idle, use ignition cuts for carb engines, and support returning to the scenario menu. Validation: full Windows build and all 14 CTest checks passed.
This commit is contained in:
80
tools/carb_calibration.cpp
Normal file
80
tools/carb_calibration.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "FluidSim/ScenarioCatalog.hpp"
|
||||
#include "FluidSim/Scenario.hpp"
|
||||
#include "FluidSim/TwoStrokeScenario.hpp"
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
using namespace fluidsim::port;
|
||||
int main(int argc,char** argv) {
|
||||
const bool sweep=argc>1 && std::string(argv[1])=="--sweep";
|
||||
const bool check=argc>1 && std::string(argv[1])=="--check";
|
||||
bool failed=false;
|
||||
int examples_checked=0;
|
||||
std::cout<<"example,pedal,rpm,opening,carb_afr,charge_afr,free_idle_rpm,free_idle_afr,repairs\n";
|
||||
for(auto e:scenario_examples()) {
|
||||
if(e.kind!=ScenarioKind::TwoStroke && !e.engine.carbureted) continue;
|
||||
if(sweep && argc>2 && e.id!=argv[2]) continue;
|
||||
if(argc>1 && !check && !sweep && e.id!=argv[1]) continue;
|
||||
++examples_checked;
|
||||
const bool two=e.kind==ScenarioKind::TwoStroke;
|
||||
if(!sweep && argc>2) {float k=std::stof(argv[2]);e.two.pilot_jet_scale*=k;e.engine.fuel.pilot_jet_diameter*=k;}
|
||||
if(!sweep && argc>3) {float k=std::stof(argv[3]);e.two.main_jet_scale*=k;e.engine.fuel.main_jet_diameter*=k;}
|
||||
if(!sweep && argc>4) {float k=std::stof(argv[4]);e.two.needle_jet_scale*=k;e.engine.fuel.needle_jet_diameter*=k;}
|
||||
if(!sweep && argc>5) {float k=std::stof(argv[5]);e.two.idle_slide_opening=k;e.engine.ecu.idle_base_opening=k;}
|
||||
if(!sweep && argc>6) {float k=std::stof(argv[6]);e.two.needle_low_scale*=k;e.engine.fuel.needle_low_scale*=k;}
|
||||
if(!sweep && argc>7) {float k=std::stof(argv[7]);e.two.needle_mid_scale*=k;e.engine.fuel.needle_mid_scale*=k;}
|
||||
for(int index=0;index<((sweep||check)?8:4);++index) {
|
||||
const float pedal=(sweep||check)?std::array<float,8>{0,.05f,.1f,.15f,.25f,.5f,.75f,1}[index]:std::array<float,4>{0,.15f,.5f,1}[index];
|
||||
const int point=pedal==0?0:pedal<.3f?1:pedal<.8f?2:3;
|
||||
const float rpm=two?std::array<float,4>{3000,4000,6500,9000}[point]:
|
||||
e.engine.cylinders==1?std::array<float,4>{1400,2500,4000,6000}[point]:std::array<float,4>{800,1800,3000,5000}[point];
|
||||
std::unique_ptr<TwoStrokeScenario> t;
|
||||
std::unique_ptr<FourStrokeScenario> f;
|
||||
if(two) {t=std::make_unique<TwoStrokeScenario>(44100,e.two);t->set_controls(pedal,0,1);}
|
||||
else {f=std::make_unique<FourStrokeScenario>(44100,e);f->set_controls(pedal,0,1);}
|
||||
double afr=0,charge=0,opening=0;std::uint64_t repairs=0;
|
||||
for(int i=0;i<55125;++i) {
|
||||
if(two) {t->crankshaft.omega=rpm*2*pi/60;t->process();}
|
||||
else {f->crankshaft.omega=rpm*2*pi/60;f->process();}
|
||||
if(i>=44100 && i%10==0) {
|
||||
afr+=(two?t->carburetor.indicated_afr():f->snapshot().target_afr)/1103.;
|
||||
charge+=(two?t->crankcase.mass()*t->crankcase.air_fraction()/std::max(t->crankcase.fuel_mass(),1e-12f):f->intake_plenum.mass()*f->intake_plenum.air_fraction()/std::max(f->intake_plenum.fuel_mass(),1e-12f))/1103.;
|
||||
opening+=(two?t->ecu.throttle_opening():f->ecu.throttle_opening())/1103.;
|
||||
}
|
||||
}
|
||||
if(!std::isfinite(afr) || afr<11.5 || afr>(point==3?14.5:15.5)) {
|
||||
std::cerr<<e.id<<": AFR outside calibration envelope at pedal "<<pedal<<": "<<afr<<'\n';failed=true;
|
||||
}
|
||||
float free_rpm=0,free_afr=0;
|
||||
if(point==0) {
|
||||
for(int i=0;i<132300;++i) {
|
||||
if(two)t->process();else f->process();
|
||||
if(i>=110250 && i%10==0) free_afr+=(two?t->carburetor.indicated_afr():f->snapshot().target_afr)/2205.;
|
||||
}
|
||||
free_rpm=(two?t->crankshaft.omega:f->crankshaft.omega)*60/(2*pi);
|
||||
}
|
||||
for(const auto& p:two?t->pipes:f->pipes)repairs+=p.positivity_repairs;
|
||||
if(point==0) {
|
||||
float idle_min=3000, idle_max=4000;
|
||||
if(e.id=="fifty") {idle_min=3200;idle_max=4000;}
|
||||
if(e.id=="fivehundred") {idle_min=2800;idle_max=3500;}
|
||||
if(e.id=="single-450") {idle_min=1200;idle_max=1600;}
|
||||
if(e.id=="big-carb") {idle_min=700;idle_max=900;}
|
||||
if(!std::isfinite(free_rpm) || free_rpm<idle_min || free_rpm>idle_max ||
|
||||
!std::isfinite(free_afr) || free_afr<11.5 || free_afr>15.5) {
|
||||
std::cerr<<e.id<<": idle outside target RPM/AFR ("<<free_rpm<<", "<<free_afr<<")\n";failed=true;
|
||||
}
|
||||
}
|
||||
if(repairs) {std::cerr<<e.id<<": pipe positivity repairs\n";failed=true;}
|
||||
if(e.id=="yz-racing" && std::abs(opening-e.two.idle_slide_opening)>1e-6) {
|
||||
if(point==0) {std::cerr<<"two-stroke idle stop did not reach its catalog setting\n";failed=true;}
|
||||
}
|
||||
if(e.id=="single-450" && (!f->cylinder1.transported_fuel || !e.engine.carbureted)) {
|
||||
std::cerr<<"450 single must transport carburetor fuel\n";failed=true;
|
||||
}
|
||||
std::cout<<e.id<<','<<pedal<<','<<rpm<<','<<opening<<','<<afr<<','<<charge<<','<<free_rpm<<','<<free_afr<<','<<repairs<<std::endl;
|
||||
}
|
||||
}
|
||||
if(!examples_checked) {std::cerr<<"No matching carbureted scenario\n";return 1;}
|
||||
return (check||sweep) && failed ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user