#include "FluidSim/TwoStrokeScenario.hpp" #include "FluidSim/DynoCurve.hpp" #include #include #include #include using namespace fluidsim::port; int main() { TwoStrokeScenario scenario; bool reed_open_seen = false, reed_closed_seen = false, above_old_limit = false; DynoCurve dyno; float peak = 0, maximum_rpm = 0; std::uint64_t release_firings = 0, one_second_coast_firings = 0; float release_rpm = 0; for (int sample = 0; sample < 14 * 44100; ++sample) { scenario.set_throttle(sample >= 2 * 44100 && sample < 6 * 44100 ? .6f : 0.f); const auto audio = scenario.process(); if (sample == 6 * 44100) { release_firings = scenario.cylinder.combustion_count(); release_rpm = scenario.snapshot().rpm; } else if (sample == 7 * 44100) { one_second_coast_firings = scenario.cylinder.combustion_count() - release_firings; } if (!std::isfinite(audio.left) || !std::isfinite(audio.right)) throw std::runtime_error("Nonfinite two-stroke audio"); peak = std::max({peak, std::abs(audio.left), std::abs(audio.right)}); if (sample % 44 != 0) continue; const auto state = scenario.snapshot(); if (!std::isfinite(state.rpm) || !(state.cylinder_pressure > 0) || !(state.crankcase_pressure > 0) || !(state.intake_pressure > 0)) throw std::runtime_error("Invalid two-stroke physical state"); if (scenario.ecu.rev_limited() || scenario.cylinder.rev_limit_active) throw std::runtime_error("Two-stroke rev limiter is active"); if (sample >= 2 * 44100 && sample < 6 * 44100) { if (state.fuel_cut) throw std::runtime_error("Two-stroke cut fuel with the pedal open"); above_old_limit |= state.rpm > 10200; } if (sample >= 6 * 44100 && sample < 7 * 44100 && state.rpm > 5000 && state.fuel_cut) throw std::runtime_error("Carbureted two-stroke cut fuel on overrun"); dyno.update(state.rpm, scenario.crankshaft.average_power / 1000, scenario.crankshaft.average_torque); reed_open_seen |= state.reed_open; reed_closed_seen |= !state.reed_open; maximum_rpm = std::max(maximum_rpm, state.rpm); } const auto final = scenario.snapshot(); if (!above_old_limit) throw std::runtime_error("Two-stroke did not exceed the old rev limit: peak=" + std::to_string(maximum_rpm) + ", idle=" + std::to_string(final.rpm)); if (!reed_open_seen || !reed_closed_seen) throw std::runtime_error("Reed did not open and close"); if (final.rpm < 2000 || final.rpm > 4000) throw std::runtime_error("Two-stroke idle did not recover after throttle release: " + std::to_string(final.rpm) + " RPM, coast firings=" + std::to_string(one_second_coast_firings) + ", carb AFR=" + std::to_string(scenario.carburetor.indicated_afr()) + ", cylinder fuel=" + std::to_string(scenario.cylinder.fuel_mass)); if (peak >= 1) throw std::runtime_error("Two-stroke audio exceeds device range"); for (const auto &pipe : scenario.pipes) if (pipe.positivity_repairs != 0) throw std::runtime_error("Two-stroke required pipe positivity repairs"); // Launch the same motorcycle used by the twin, then exercise gear controls. scenario.shift_up(); scenario.set_controls(.7f, 0, 0); for (int sample = 0; sample < 6 * 44100; ++sample) { const auto audio = scenario.process(); if (!std::isfinite(audio.left) || !std::isfinite(audio.right)) throw std::runtime_error("Nonfinite motorcycle launch audio"); if (scenario.ecu.rev_limited()) throw std::runtime_error("Limiter re-enabled under motorcycle load"); } if (scenario.vehicle.current_gear != 1 || scenario.vehicle.speed < 1 || !std::isfinite(scenario.vehicle.kinetic_energy()) || scenario.vehicle.engagement < .9f) throw std::runtime_error("Two-stroke motorcycle did not launch with clutch engaged"); scenario.shift_up(); if (scenario.vehicle.current_gear != 2) throw std::runtime_error("Two-stroke did not shift up"); scenario.shift_down(); if (scenario.vehicle.current_gear != 1) throw std::runtime_error("Two-stroke did not shift down"); scenario.set_neutral(); if (scenario.vehicle.current_gear != 0) throw std::runtime_error("Two-stroke neutral selection failed"); bool positive_power = false, positive_torque = false; for (const auto& point : dyno.bins()) { positive_power |= point.power_kw > 0; positive_torque |= point.torque_nm > 0; } if (!positive_power || !positive_torque) throw std::runtime_error("Two-stroke dyno did not record power and torque"); for (const auto& pipe : scenario.pipes) if (pipe.positivity_repairs != 0) throw std::runtime_error("Motorcycle launch required pipe positivity repairs"); std::cout << "Two-stroke regression passed: peak=" << peak << ", maximum RPM=" << maximum_rpm << ", recovered RPM=" << final.rpm << ", bike speed=" << scenario.vehicle.speed * 3.6f << " km/h, release RPM=" << release_rpm << ", first coast-second firings=" << one_second_coast_firings << '\n'; }