# `omath::projectile_prediction::ProjPredEngineLegacy` — Legacy trait-based aim solver > Header: `omath/projectile_prediction/proj_pred_engine_legacy.hpp` > Namespace: `omath::projectile_prediction` > Inherits: `ProjPredEngineInterface` > Template param (default): `EngineTrait = source_engine::PredEngineTrait` > Purpose: compute a world-space **aim point** to hit a (possibly moving) target using a **discrete time scan** and a **closed-form ballistic pitch** under constant gravity. --- ## Overview `ProjPredEngineLegacy` is a portable, trait-driven projectile lead solver. At each simulation time step `t` it: 1. **Predicts target position** with `EngineTrait::predict_target_position(target, t, g)`. 2. **Computes launch pitch** via a gravity-aware closed form (or a direct angle if gravity is zero). 3. **Validates** that a projectile fired with that pitch (and direct yaw) actually reaches the predicted target within a **distance tolerance** at time `t`. 4. On success, **returns an aim point** computed by `EngineTrait::calc_viewpoint_from_angles(...)`. If no time step yields a feasible solution up to `maximum_simulation_time`, returns `std::nullopt`. --- ## API ```cpp template requires PredEngineConcept class ProjPredEngineLegacy final : public ProjPredEngineInterface { public: ProjPredEngineLegacy(float gravity_constant, float simulation_time_step, float maximum_simulation_time, float distance_tolerance); [[nodiscard]] std::optional> maybe_calculate_aim_point(const Projectile& projectile, const Target& target) const override; private: // Closed-form ballistic pitch solver (internal) std::optional maybe_calculate_projectile_launch_pitch_angle(const Projectile& projectile, const Vector3& target_position) const noexcept; bool is_projectile_reached_target(const Vector3& target_position, const Projectile& projectile, float pitch, float time) const noexcept; const float m_gravity_constant; const float m_simulation_time_step; const float m_maximum_simulation_time; const float m_distance_tolerance; }; ``` ### Constructor parameters * `gravity_constant` — magnitude of gravity (e.g., `9.81f`), world units/s². * `simulation_time_step` — Δt for the scan (e.g., `1/240.f`). * `maximum_simulation_time` — search horizon in seconds. * `distance_tolerance` — max allowed miss distance at time `t` to accept a solution. --- ## Trait requirements (`PredEngineConcept`) Your `EngineTrait` must expose **noexcept** static methods with these signatures: ```cpp Vector3 predict_projectile_position(const Projectile&, float pitch_deg, float yaw_deg, float time, float gravity) noexcept; Vector3 predict_target_position(const Target&, float time, float gravity) noexcept; float calc_vector_2d_distance(const Vector3& v) noexcept; // typically length in XZ plane float get_vector_height_coordinate(const Vector3& v) noexcept; // typically Y Vector3 calc_viewpoint_from_angles(const Projectile&, Vector3 target, std::optional maybe_pitch_deg) noexcept; float calc_direct_pitch_angle(const Vector3& from, const Vector3& to) noexcept; float calc_direct_yaw_angle (const Vector3& from, const Vector3& to) noexcept; ``` > This design lets you adapt different game/physics conventions (axes, units, handedness) without changing the solver. --- ## Algorithm details ### Time scan For `t = 0 .. maximum_simulation_time` in steps of `simulation_time_step`: 1. `T = EngineTrait::predict_target_position(target, t, g)` 2. `pitch = maybe_calculate_projectile_launch_pitch_angle(projectile, T)` * If `std::nullopt`: continue 3. `yaw = EngineTrait::calc_direct_yaw_angle(projectile.m_origin, T)` 4. `P = EngineTrait::predict_projectile_position(projectile, pitch, yaw, t, g)` 5. Accept if `|P - T| <= distance_tolerance` 6. Return `EngineTrait::calc_viewpoint_from_angles(projectile, T, pitch)` ### Closed-form pitch (gravity on) Implements the classic ballistic formula (low-arc branch), where: * `v` = muzzle speed, * `g` = `gravity_constant * projectile.m_gravity_scale`, * `x` = horizontal (2D) distance to target, * `y` = vertical offset to target. [ \theta ;=; \arctan!\left(\frac{v^{2} ;-; \sqrt{v^{4}-g!\left(gx^{2}+2yv^{2}\right)}}{gx}\right) ] * If the **discriminant** ( v^{4}-g(gx^{2}+2yv^{2}) < 0 ) ⇒ **no real solution**. * If `g == 0`, falls back to `EngineTrait::calc_direct_pitch_angle(...)`. * Returns **degrees** (internally converts from radians). --- ## Usage example ```cpp using namespace omath::projectile_prediction; ProjPredEngineLegacy solver( /*gravity*/ 9.81f, /*dt*/ 1.f / 240.f, /*Tmax*/ 3.0f, /*tol*/ 0.05f ); Projectile proj; // fill: m_origin, m_launch_speed, m_gravity_scale, etc. Target tgt; // fill: position/velocity as required by your trait if (auto aim = solver.maybe_calculate_aim_point(proj, tgt)) { // Drive your turret/reticle toward *aim } else { // No feasible intercept in the given horizon } ``` --- ## Behavior & edge cases * **Zero gravity or zero distance**: uses direct pitch toward the target. * **Negative discriminant** in the pitch formula: returns `std::nullopt` for that time step. * **Very small `x`** (horizontal distance): the formula’s denominator `gx` approaches zero; your trait’s direct pitch helper provides a stable fallback. * **Tolerance**: `distance_tolerance` controls acceptance; tighten for accuracy, loosen for robustness. --- ## Complexity & tuning * Time: **O(T)** where ( T \approx \frac{\text{maximum_simulation_time}}{\text{simulation_time_step}} ) plus trait costs for prediction and angle math per step. * Smaller `simulation_time_step` improves precision but increases runtime. * If needed, do a **coarse-to-fine** search: coarse Δt scan, then refine around the best hit time. --- ## Testing checklist * Stationary, level target → pitch ≈ 0 for short ranges; accepted within tolerance. * Elevated/depressed targets → pitch positive/negative as expected. * Receding fast target → unsolved within horizon ⇒ `nullopt`. * Gravity scale = 0 → identical to straight-line solution. * Near-horizon shots (large range, small arc) → discriminant near zero; verify stability. --- ## Notes * All angles produced/consumed by the trait in this implementation are **degrees**. * `calc_viewpoint_from_angles` defines what “aim point” means in your engine (e.g., a point along the initial ray or the predicted impact point). Keep this consistent with your HUD/reticle. --- *Last updated: 1 Nov 2025*