#include "FluidSim/Scenario.hpp" #include #include #include #include #include #include using namespace fluidsim::port; namespace { void check(bool condition, const char* message) { if (!condition) throw std::runtime_error(message); } } int main() { SaabB205Scenario s; const auto cylinders = s.cylinders(); check(s.cylinder_count() == 4 && s.pipes.size() == 10, "B205 topology is not four-cylinder"); check(std::abs(4*s.cylinder1.swept_volume()*1e6f-1984.9f) < 1, "B205 displacement incorrect"); check(s.vehicle.gear_ratios.size() == 5 && s.vehicle.mass == 1675, "wagon configuration incorrect"); for (int i = 0; i < 4; ++i) { check(cylinders[i]->compression == 8.8f && cylinders[i]->intake_valve_count == 2 && cylinders[i]->exhaust_valve_count == 2, "B205 compression/valve count incorrect"); } s.set_controls(.5f, 0, 1); std::array burning{}; std::vector firing_order; float peak = 0; const auto started = std::chrono::steady_clock::now(); const auto step = [&] { const auto audio = s.process(); check(std::isfinite(audio.left) && std::isfinite(audio.right), "nonfinite B205 audio"); peak = std::max({peak, std::abs(audio.left), std::abs(audio.right)}); for (const auto* c : cylinders) check(std::isfinite(c->pressure) && c->pressure > 0 && c->total_mass() > 0, "nonphysical B205 cylinder"); }; // Prescribed-speed absorbing dyno isolates phasing from acceleration. for (int sample = 0; sample < 44100; ++sample) { s.crankshaft.omega = 2000 * 2*pi/60; step(); for (int i = 0; i < 4; ++i) { if (sample > 22050 && cylinders[i]->combustion_active && !burning[i]) firing_order.push_back(i); burning[i] = cylinders[i]->combustion_active; } } check(firing_order.size() >= 28 && firing_order.size() <= 36, "B205 did not fire twice per revolution"); constexpr std::array next{2, 0, 3, 1}; for (std::size_t i = 1; i < firing_order.size(); ++i) check(firing_order[i] == next[firing_order[i-1]], "B205 firing order is not 1-3-4-2"); s.set_first_gear(); for (int sample = 0; sample < 4*44100; ++sample) { const float clutch = std::max(0.f, 1-sample/(.8f*44100)); s.set_controls(.6f, 0, clutch); step(); } check(s.vehicle.speed > 1 && s.vehicle.current_gear == 1, "B205 failed to launch wagon"); const float speed = s.vehicle.speed*3.6f; for (int i = 0; i < 8; ++i) s.shift_up(); check(s.vehicle.current_gear == 5, "wagon has a phantom sixth gear"); check(std::abs(s.vehicle.total_gear_ratio()-2.67f) < 1e-5f, "wagon top overall ratio incorrect"); s.set_neutral(); s.set_controls(0, 0, 1); for (int sample = 0; sample < 3*44100; ++sample) step(); const auto final = s.snapshot(); check(final.rpm > 300 && final.rpm < 2500, "B205 did not recover toward idle"); check(final.cylinder_count == 4 && !final.pipe_states[9].empty(), "B205 desktop snapshot incomplete"); for (const auto& pipe : s.pipes) check(pipe.positivity_repairs == 0, "B205 required pipe positivity repairs"); const double wall = std::chrono::duration(std::chrono::steady_clock::now()-started).count(); std::cout << "B205 passed: firing events=" << firing_order.size() << ", wagon speed=" << speed << " km/h, recovered RPM=" << final.rpm << ", audio peak=" << peak << ", wall seconds=" << wall << " for 8 simulated seconds\n"; }