how could i improve the time to reach algorithm t oalso account for the players current angvel? void RLBotBot::RenderTimeToReachBall(const RLGSC::GameState& gs) {
    if (!renderTimeToReachBallEnabled || !ballPredTracker) return;
    rlbot::ScopedRenderer renderer("time_to_reach_renderer");

    auto calculateMinAirTime = [](const Vec& playerPos, const Vec& playerVel, bool hasFlip) {
        constexpr float GRAVITY = 650.0f;
        float timeToGround = RS_MAX(0.0f, (-playerVel.z + std::sqrt(playerVel.z * playerVel.z + 2 * GRAVITY * playerPos.z)) / GRAVITY);
        float timeToSideWall = (playerVel.x != 0.0f) ? RS_MIN(
            std::abs((CommonValues::SIDE_WALL_X - playerPos.x) / playerVel.x),
            std::abs((-CommonValues::SIDE_WALL_X - playerPos.x) / playerVel.x)
        ) : std::numeric_limits<float>::infinity();
        float timeToBackWall = (playerVel.y != 0.0f) ? RS_MIN(
            std::abs((CommonValues::BACK_WALL_Y - playerPos.y) / playerVel.y),
            std::abs((-CommonValues::BACK_WALL_Y - playerPos.y) / playerVel.y)
        ) : std::numeric_limits<float>::infinity();
        float timeToCeiling = (playerVel.z > 0.0f) ? (CommonValues::CEILING_Z - playerPos.z) / playerVel.z : std::numeric_limits<float>::infinity();
        return RS_MIN(RS_MIN(timeToGround, timeToSideWall), RS_MIN(timeToBackWall, timeToCeiling));
        };

    auto getTimeToReachBall = [&calculateMinAirTime](const PlayerData& player, const Vec& ballPos) {
        constexpr float MAX_ROTATION_SPEED = 5.5f;
        constexpr float DIST_REACH_THRESH = 10.0f;
        constexpr float STEP_INTERVAL = 1.0f / 60.0f;
        constexpr float FLIP_FORCE = 500.0f;
        constexpr float AIR_ACCEL = 66.66667f;
        constexpr float BOOST_ACCEL_AIR = 1058.33333f;
        constexpr float MAX_TOTAL_SPEED = 2300.0f;
        constexpr float BRAKE_ACCEL = 3500.0f;
        constexpr float BOOST_ACCEL_GROUND = 991.66667f;
        constexpr float DRIVE_ACCEL = 1600.0f;
        constexpr float MAX_DRIVE_SPEED = 1410.0f;

        const Vec& playerPos = player.phys.pos;
        const Vec& playerForward = player.phys.rotMat.forward;
        const Vec& playerVel = player.phys.vel;
        bool hasFlip = player.carState.HasFlipOrJump();
        float dist = (ballPos - playerPos).Length();
        Vec dirToBall = ballPos - playerPos;
        Vec normalizedDirToBall = dirToBall.Normalized();
        float curVel = playerVel.Dot(normalizedDirToBall);
        float angleDiff = std::acos(RS_CLAMP(playerForward.Dot(normalizedDirToBall), -1.0f, 1.0f));
        float rotTime = angleDiff / MAX_ROTATION_SPEED;
        float t = 0.0f;
        float curDist = dist;

        float minAirTime = calculateMinAirTime(playerPos, playerVel, hasFlip);

        while (t < minAirTime && curDist > DIST_REACH_THRESH) {
            bool hasRotated = t >= rotTime;
            if (hasFlip && t == 0.0f) {
                curVel += FLIP_FORCE;
            }
            float airAccel = AIR_ACCEL + (BOOST_ACCEL_AIR * hasRotated);
            curVel += airAccel * STEP_INTERVAL;
            curVel = RS_MIN(curVel, MAX_TOTAL_SPEED);
            curDist -= curVel * STEP_INTERVAL;
            t += STEP_INTERVAL;
        }

        while (curDist > DIST_REACH_THRESH) {
            bool hasRotated = t >= rotTime;
            if (curVel < 0.0f) {
                curVel += (BRAKE_ACCEL + BOOST_ACCEL_GROUND * hasRotated) * STEP_INTERVAL;
            }
            else if (curVel < MAX_DRIVE_SPEED) {
                curVel += (DRIVE_ACCEL + BOOST_ACCEL_GROUND * hasRotated) * STEP_INTERVAL;
            }
            else if (curVel < MAX_TOTAL_SPEED) {
                curVel += BOOST_ACCEL_GROUND * hasRotated * STEP_INTERVAL;
            }
            else {
                float remainingTime = curDist / curVel;
                return t + remainingTime;
            }
            curVel = RS_MIN(curVel, MAX_TOTAL_SPEED);
            curDist -= curVel * STEP_INTERVAL;
            t += STEP_INTERVAL;
        }

        return t;
        };

    constexpr float PREDICTION_DURATION = 6.0f; // Predict 6 seconds into the future
    constexpr int NUM_PREDICTION_POINTS = 8; // We want 8 prediction points

    // Update ball prediction
    RocketSim::BallState currentBallState;
    currentBallState.pos = RocketSim::Vec(gs.ball.pos.x, gs.ball.pos.y, gs.ball.pos.z);
    currentBallState.vel = RocketSim::Vec(gs.ball.vel.x, gs.ball.vel.y, gs.ball.vel.z);
    currentBallState.angVel = RocketSim::Vec(gs.ball.angVel.x, gs.ball.angVel.y, gs.ball.angVel.z);
    currentBallState.rotMat = RocketSim::RotMat(gs.ball.rotMat.forward, gs.ball.rotMat.right, gs.ball.rotMat.up);
    int numTicks = static_cast<int>(PREDICTION_DURATION * 120); // Assuming 120 ticks per second
    ballPredTracker->UpdatePredManual(currentBallState, numTicks);

    const auto& ballPredData = ballPredTracker->predData;
    size_t predictionInterval = ballPredData.size() / NUM_PREDICTION_POINTS;

    // Find the opponent
    const PlayerData* opponent = nullptr;
    for (const auto& player : gs.players) {
        if (player.team != gs.players[index].team) {
            opponent = &player;
            break;
        }
    }

    if (!opponent) {
        // No opponent found, return early
        return;
    }

    std::vector<float> localTimeToReach(NUM_PREDICTION_POINTS);
    std::vector<float> opponentTimeToReach(NUM_PREDICTION_POINTS);
    float localPossession = 0.0f;
    float opponentPossession = 0.0f;

    for (size_t j = 0; j < NUM_PREDICTION_POINTS; ++j) {
        size_t index = j * predictionInterval;
        if (index >= ballPredData.size()) break;

        Vec ballPos(ballPredData[index].pos.x, ballPredData[index].pos.y, ballPredData[index].pos.z);
        localTimeToReach[j] = getTimeToReachBall(gs.players[this->index], ballPos);
        opponentTimeToReach[j] = getTimeToReachBall(*opponent, ballPos);

        if (localTimeToReach[j] < opponentTimeToReach[j]) {
            localPossession += 1.0f;
        }
        else {
            opponentPossession += 1.0f;
        }
    }

    float totalPossession = localPossession + opponentPossession;
    localPossession /= totalPossession;
    opponentPossession /= totalPossession;

    // Render possession information
    std::stringstream localSS, opponentSS;
    localSS << "Local player possession: " << std::fixed << std::setprecision(2) << localPossession * 100 << "%";
    opponentSS << "Opponent possession: " << std::fixed << std::setprecision(2) << opponentPossession * 100 << "%";

    renderer.DrawString2D(localSS.str(), rlbot::Color::blue, rlbot::flat::Vector3{ 10, 10, 0 }, 2.0f, 2.0f);
    renderer.DrawString2D(opponentSS.str(), rlbot::Color::white, rlbot::flat::Vector3{ 10, 40, 0 }, 2.0f, 2.0f);

    // Render the path and time to reach for both players
    std::vector<rlbot::flat::Vector3> points;
    for (size_t j = 0; j < NUM_PREDICTION_POINTS; ++j) {
        size_t index = j * predictionInterval;
        if (index >= ballPredData.size()) break;

        points.push_back({
            static_cast<float>(ballPredData[index].pos.x),
            static_cast<float>(ballPredData[index].pos.y),
            static_cast<float>(ballPredData[index].pos.z)
            });

        std::stringstream localSS, opponentSS;
        localSS << std::fixed << std::setprecision(2) << localTimeToReach[j] << "s";
        opponentSS << std::fixed << std::setprecision(2) << opponentTimeToReach[j] << "s";

        rlbot::flat::Vector3 localTextPos = {
            static_cast<float>(ballPredData[index].pos.x),
            static_cast<float>(ballPredData[index].pos.y),
            static_cast<float>(ballPredData[index].pos.z + 50.0f)
        };
        rlbot::flat::Vector3 opponentTextPos = {
            static_cast<float>(ballPredData[index].pos.x),
            static_cast<float>(ballPredData[index].pos.y),
            static_cast<float>(ballPredData[index].pos.z - 50.0f)
        };

        renderer.DrawString3D(localSS.str(), rlbot::Color::blue, localTextPos, 1.0f, 1.0f);
        renderer.DrawString3D(opponentSS.str(), rlbot::Color::white, opponentTextPos, 1.0f, 1.0f);
    }

    // Render the linked path
    if (!points.empty()) {
        // Draw a line from the car to the first prediction point
        rlbot::flat::Vector3 carPos = {
            static_cast<float>(gs.players[this->index].phys.pos.x),
            static_cast<float>(gs.players[this->index].phys.pos.y),
            static_cast<float>(gs.players[this->index].phys.pos.z)
        };
        renderer.DrawLine3D(rlbot::Color::yellow, carPos, points[0]);

        // Draw lines between prediction points
        for (size_t i = 1; i < points.size(); ++i) {
            renderer.DrawLine3D(rlbot::Color::yellow, points[i - 1], points[i]);
        }
    }

    // Render overall possession summary
    std::stringstream summaryStream;
    summaryStream << "Team Possession - "
        << (gs.players[this->index].team == Team::BLUE ? "Blue: " : "Orange: ")
        << std::fixed << std::setprecision(2) << localPossession * 100 << "% | "
        << (gs.players[this->index].team == Team::BLUE ? "Orange: " : "Blue: ")
        << opponentPossession * 100 << "%";
    renderer.DrawString2D(summaryStream.str(), rlbot::Color::white, rlbot::flat::Vector3{ 10, 70, 0 }, 2.0f, 2.0f);
}