fix this // The main function to perform wall extrapolation and rendering
void RLBotBot::timewallexsrap(const GameState& gs) {
    // Check if rendering is enabled
    

    // Initialize the renderer
    rlbot::ScopedRenderer renderer("time_wall_extrap_renderer");

    // Get the player's current position and velocity
    const PlayerData& player = gs.players[index];
    Vec carPos = player.phys.pos;
    Vec carVel = player.phys.vel;

    // Calculate time to reach the closest wall
    float timeToWall = TimeToClosestWallAir(gs);

    // Extrapolate the car's position when it reaches the wall
    Vec extrapPos = carPos + (carVel * timeToWall);
    // Apply gravity to the Z position
    extrapPos.z += 0.5f * GRAVITY_Z * timeToWall * timeToWall;

    // Check if the extrapolated time and position are valid
    if (timeToWall <= MIN_SCORE_TIME || extrapPos.z >= MAX_EXTRAP_Z) {
        // Disable rendering if conditions are not met
        return;
    }

    // Determine the closest wall based on current position
    float dist_neg_x = std::abs(carPos.x + ARENA_LENGTH);
    float dist_pos_x = std::abs(ARENA_LENGTH - carPos.x);
    float dist_neg_y = std::abs(carPos.y + ARENA_WIDTH);
    float dist_pos_y = std::abs(ARENA_WIDTH - carPos.y);

    // Find the minimum distance to determine the closest wall
    float min_dist = std::min({ dist_neg_x, dist_pos_x, dist_neg_y, dist_pos_y });
    Vec wallNormal(0.0f, 0.0f, 0.0f);

    if (min_dist == dist_neg_x) {
        wallNormal = Vec(1.0f, 0.0f, 0.0f); // Negative X wall
    }
    else if (min_dist == dist_pos_x) {
        wallNormal = Vec(-1.0f, 0.0f, 0.0f); // Positive X wall
    }
    else if (min_dist == dist_neg_y) {
        wallNormal = Vec(0.0f, 1.0f, 0.0f); // Negative Y wall
    }
    else {
        wallNormal = Vec(0.0f, -1.0f, 0.0f); // Positive Y wall
    }

    // Project the extrapolated position onto the closest wall
    Vec wallPoint = extrapPos;
    if (wallNormal.x != 0.0f) {
        wallPoint.x = (wallNormal.x > 0.0f) ? -ARENA_LENGTH : ARENA_LENGTH;
    }
    if (wallNormal.y != 0.0f) {
        wallPoint.y = (wallNormal.y > 0.0f) ? -ARENA_WIDTH : ARENA_WIDTH;
    }

    // Ensure the Z position is above the ground
    
    Vec wallPoint.z = std::max(extrapPos.z, GROUND_Z); // Corrected line

    // Prepare vectors for rendering
    rlbot::flat::Vector3 carVector = {
        carPos.x,
        carPos.y,
        carPos.z
    };
    rlbot::flat::Vector3 wallVector = {
        wallPoint.x,
        wallPoint.y,
        wallPoint.z
    };

    // Define colors based on team
    rlbot::Color rocketLeagueBlue{ 0, 153, 255, 255 };
    rlbot::Color rocketLeagueOrange{ 235, 141, 31, 255 };
    rlbot::Color renderColor = (player.team == RocketSim::Team::BLUE) ? rocketLeagueBlue : rocketLeagueOrange; // Corrected comparison

    // Render the line from the car to the predicted wall landing point
    renderer.DrawLine3D(renderColor, carVector, wallVector);

    // Render the time to reach the wall near the wall's position
    std::stringstream timeSS;
    timeSS << std::fixed << std::setprecision(2) << timeToWall << "s";
    renderer.DrawString3D(timeSS.str(), renderColor, wallVector, 1.0f, 1.0f);

    // Optionally, render additional information such as extrapolated position
    std::stringstream posSS;
    posSS << "Extrap Pos: ("
        << std::fixed << std::setprecision(2)
        << wallPoint.x << ", " << wallPoint.y << ", " << wallPoint.z << ")";
    renderer.DrawString2D(posSS.str(), renderColor, rlbot::flat::Vector3{ 10.0f, 100.0f, 0.0f }, 1.5f, 1.5f);
} here is other codes so you can fix the first code 
void RLBotBot::RenderBallInfo(const RLGSC::GameState& gs) {
    rlbot::ScopedRenderer renderer("ball_info_renderer");

    float x = 1550.0f;  // Right side of the screen
    float y = 500.0f;   // Moved further down
    float marginLeft = 20.0f;
    float marginTop = 20.0f;
    float nameSpacing = 150.0f;
    float lineSpacing = 25.0f;
    float padding = 20.0f;
    float width = 350.0f;  // Increased width to fit all text
    float extraBottomPadding = 20.0f;

    std::vector<std::pair<std::string, std::string>> lines;

    auto addLine = [&](const std::string& label, const std::string& value) {
        lines.emplace_back(label, value);
        };

    auto formatFloat = [](float value) -> std::string {
        std::ostringstream ss;
        ss << std::fixed << std::setprecision(2) << value;
        return ss.str();
        };

    // Ball position
    addLine("Ball X", formatFloat(gs.ball.pos.x));
    addLine("Ball Y", formatFloat(gs.ball.pos.y));
    addLine("Ball Z", formatFloat(gs.ball.pos.z));

    // Ball velocity in UU
    float velocityUU = gs.ball.vel.Length();
    addLine("Velocity (UU/s)", formatFloat(velocityUU));

    float height = marginTop + (lines.size() + 1) * lineSpacing + padding + extraBottomPadding;
    renderer.DrawRect2D(rlbot::Color{ 64, 64, 64, 192 }, rlbot::flat::Vector3{ x, y, 0 }, width, height, true);

    float currentLine = y + marginTop;
    renderer.DrawString2D("Ball Info", rlbot::Color{ 255, 255, 255, 255 }, rlbot::flat::Vector3{ x + marginLeft, currentLine, 0 }, 1, 1);
    currentLine += lineSpacing * 1.5f;

    for (const auto& line : lines) {
        const auto& [label, value] = line;
        renderer.DrawString2D(label, rlbot::Color{ 255, 255, 255, 255 }, rlbot::flat::Vector3{ x + marginLeft, currentLine, 0 }, 1, 1);
        renderer.DrawString2D(value, rlbot::Color{ 255, 255, 255, 255 }, rlbot::flat::Vector3{ x + marginLeft + nameSpacing, currentLine, 0 }, 1, 1);
        currentLine += lineSpacing;
    }
}

void RLBotBot::RenderCarInfo(const RLGSC::GameState& gs) {
    rlbot::ScopedRenderer renderer("car_info_renderer");

    
    float x = 1550.0f;  // Right side of the screen
    float y = 750.0f;   // Positioned below the ball info panel
    float marginLeft = 20.0f;
    float marginTop = 20.0f;
    float nameSpacing = 150.0f;
    float lineSpacing = 25.0f;
    float padding = 20.0f;
    float width = 350.0f;
    float extraBottomPadding = 20.0f;

    std::vector<std::pair<std::string, std::string>> lines;

    auto addLine = [&](const std::string& label, const std::string& value) {
        lines.emplace_back(label, value);
        };

    auto formatFloat = [](float value) -> std::string {
        std::ostringstream ss;
        ss << std::fixed << std::setprecision(2) << value;
        return ss.str();
        };

    const auto& car = gs.players[index];

    // Car position
    addLine("Car X", formatFloat(car.phys.pos.x));
    addLine("Car Y", formatFloat(car.phys.pos.y));
    addLine("Car Z", formatFloat(car.phys.pos.z));

    // Car velocity in UU
    float velocityUU = car.phys.vel.Length();
    addLine("Velocity (UU/s)", formatFloat(velocityUU));


    // Additional car-specific info
    addLine("Boost", formatFloat(car.boostFraction * 100) + "%");
    addLine("On Ground", car.carState.isOnGround ? "Yes" : "No");
    addLine("Has Flip", car.hasFlip ? "Yes" : "No");

    float height = marginTop + (lines.size() + 1) * lineSpacing + padding + extraBottomPadding;
    renderer.DrawRect2D(rlbot::Color{ 64, 64, 64, 192 }, rlbot::flat::Vector3{ x, y, 0 }, width, height, true);

    float currentLine = y + marginTop;
    renderer.DrawString2D("Car Info", rlbot::Color{ 255, 255, 255, 255 }, rlbot::flat::Vector3{ x + marginLeft, currentLine, 0 }, 1, 1);
    currentLine += lineSpacing * 1.5f;

    for (const auto& line : lines) {
        const auto& [label, value] = line;
        renderer.DrawString2D(label, rlbot::Color{ 255, 255, 255, 255 }, rlbot::flat::Vector3{ x + marginLeft, currentLine, 0 }, 1, 1);
        renderer.DrawString2D(value, rlbot::Color{ 255, 255, 255, 255 }, rlbot::flat::Vector3{ x + marginLeft + nameSpacing, currentLine, 0 }, 1, 1);
        currentLine += lineSpacing;
    }
}

void RLBotBot::RenderReward(const RLGSC::GameState& gs) {
    rlbot::ScopedRenderer renderer("reward_renderer");
    if (!renderRewardEnabled) {
        return;
    }
    float totalReward = 0.0f;
    static thread_local std::vector<float> individualRewards;
    static thread_local int counter = 0;
    counter++;

    if (counter % 8 == 0 || true) {

        individualRewards.clear();
        for (auto& rewardFunction : rewardFunctions) {
            if (rewardFunction == nullptr) {
                individualRewards.push_back(0.0f);
                continue;
            }
            try {
                float reward = rewardFunction->GetReward(gs.players[index], gs, controls);
                totalReward += reward;
                individualRewards.push_back(reward);
            }
            catch (...) {
                individualRewards.push_back(0.0f);
            }
        }
    }

    
    lastReward = totalReward;
    if (totalReward < minReward) minReward = totalReward;
    if (totalReward > maxReward) maxReward = totalReward;
    totalRewards += totalReward;
    numRewards++;
    float avgReward = totalRewards / numRewards;

    // Changed positioning to top-left corner
    float x = 20.0f;
    float y = 20.0f;
    float z = 0.0f;  // Z-axis for 3D space
    float marginLeft = 20.0f;
    float marginTop = 20.0f;
    float nameSpacing = 200.0f;  // Spacing between name and value
    float lineSpacing = 25.0f;
    float padding = 20.0f;
    float width = 350.0f;
    float extraBottomPadding = 20.0f;
    float lineThickness = 2.0f;  // Thickness of the horizontal line
    std::vector<std::pair<std::string, std::string>> lines;
    auto addLine = [&](const std::string& label, float value) {
        lines.emplace_back(label, formatFloat(value));
        };
    auto rewardNames = GetRewardNames();
    for (size_t i = 0; i < rewardFunctions.size() && i < individualRewards.size(); ++i) {
        addLine(rewardNames[i], individualRewards[i]);
    }
    addLine("Total Reward", totalReward);
    addLine("Min", minReward);
    addLine("Max", maxReward);
    addLine("Average", avgReward);
    float height = marginTop + (lines.size() + 1) * lineSpacing + padding + extraBottomPadding;
    renderer.DrawRect2D(rlbot::Color{ 64, 64, 64, 192 }, rlbot::flat::Vector3{ x, y, 0 }, width, height, true);
    float currentLine = y + marginTop;
    renderer.DrawString2D("Rewards", rlbot::Color{ 255, 255, 255, 255 }, rlbot::flat::Vector3{ x + marginLeft, currentLine, 0 }, 1, 1);
    currentLine += lineSpacing * 1.5f;
    for (const auto& line : lines) {
        const auto& [label, value] = line;
        renderer.DrawString2D(label, rlbot::Color{ 255, 255, 255, 255 }, rlbot::flat::Vector3{ x + marginLeft, currentLine, 0 }, 1, 1);
        renderer.DrawString2D(value, rlbot::Color{ 255, 255, 255, 255 }, rlbot::flat::Vector3{ x + marginLeft + nameSpacing, currentLine, 0 }, 1, 1);
        // Adjust line position to be further down between the current text and the next
        float lineX = x + marginLeft;
        float lineY = currentLine + lineSpacing * 0.75f;  // Increase the offset to lower the line further down
        // Draw the horizontal line using DrawRect2D
        renderer.DrawRect2D(
            rlbot::Color{ 255, 255, 255, 128 }, // Line color with some transparency
            rlbot::flat::Vector3{ lineX, lineY, 0 },
            width - 2 * marginLeft,  // Line width
            lineThickness,           // Line height (thickness)
            true                     // Filled rectangle for solid line
        );
        currentLine += lineSpacing;
    }
    rlbot::flat::Vector3 localPlayerLocation = {
        gs.players[index].phys.pos.x,
        gs.players[index].phys.pos.y,
        gs.players[index].phys.pos.z + 150.0f
    };
    std::string currentRewardText = " " + formatFloat(lastReward);
    renderer.DrawString3D(currentRewardText, rlbot::Color{ 255, 255, 255, 255 }, localPlayerLocation, 2, 2);
}

RLGSC::Action ConvertToAction(const rlbot::Controller& controller) {
    RLGSC::Action action;
    action.throttle = controller.throttle;
    action.steer = controller.steer;
    action.pitch = controller.pitch;
    action.yaw = controller.yaw;
    action.roll = controller.roll;
    action.jump = controller.jump;
    action.boost = controller.boost;
    action.handbrake = controller.handbrake;
    return action;
}

void RLBotBot::RenderBallPredictions(const RLGSC::GameState& gs) {
    rlbot::ScopedRenderer renderer("rocketsim_ballpred");

    if (!ballPredTracker) {
        return;
    }

    // Define color variables for conditional transparency
    rlbot::Color visibleColorBlue = rlbot::Color::blue;
    rlbot::Color visibleColorGreen = rlbot::Color::green;
    rlbot::Color transparentColor{ 0, 0, 0, 0 };

    if (!renderBallPredictionsEnabled) {
        visibleColorBlue = transparentColor;
        visibleColorGreen = transparentColor;
    }

    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);
    ballPredTracker->UpdatePredManual(currentBallState, 0);

    std::vector<rlbot::flat::Vector3> points;
    std::vector<const rlbot::flat::Vector3*> pointPointers;
    size_t numPoints = RS_MIN(size_t(240), ballPredTracker->predData.size());
    points.reserve(numPoints);
    pointPointers.reserve(numPoints);

    for (size_t i = 0; i < numPoints; ++i) {
        const auto& slice = ballPredTracker->predData[i];
        points.push_back(rlbot::flat::Vector3{
            static_cast<float>(slice.pos.x),
            static_cast<float>(slice.pos.y),
            static_cast<float>(slice.pos.z)
            });
        pointPointers.push_back(&points.back());
    }

    // Draw the prediction line with conditional color
    renderer.DrawPolyLine3D(visibleColorBlue, pointPointers);

    // Draw a green dot at the end of the prediction line with conditional color
    if (!points.empty()) {
        renderer.DrawRect3D(
            visibleColorGreen,    // Color of the dot
            points.back(),        // Position of the dot
            10,                   // Width of the dot
            10,                   // Height of the dot
            true,                 // Filled
            true                  // Centered
        );
    }
}










void RLBotBot::UpdatePredictionArena(const RLGSC::GameState& gs) {
    if (!updatePredictionArenaEnabled) return;
    if (predictionArena == nullptr) {
        std::cout << "Prediction arena is null" << std::endl;
        return;
    }

    // Update ball state
    RocketSim::BallState ballState = predictionArena->ball->GetState();
    ballState.pos = RocketSim::Vec(gs.ball.pos.x, gs.ball.pos.y, gs.ball.pos.z);
    ballState.vel = RocketSim::Vec(gs.ball.vel.x, gs.ball.vel.y, gs.ball.vel.z);
    ballState.angVel = RocketSim::Vec(gs.ball.angVel.x, gs.ball.angVel.y, gs.ball.angVel.z);
    predictionArena->ball->SetState(ballState);

    auto cars = predictionArena->GetCars();
    if (cars.empty()) {
        std::cout << "No cars in prediction arena" << std::endl;
        return;
    }

    auto localCar = *cars.begin();

    // Update local car state
    const auto& localPlayer = gs.players[index];
    RocketSim::CarState localCarState = localCar->GetState();
    localCarState.pos = RocketSim::Vec(localPlayer.phys.pos.x, localPlayer.phys.pos.y, localPlayer.phys.pos.z);
    localCarState.vel = RocketSim::Vec(localPlayer.phys.vel.x, localPlayer.phys.vel.y, localPlayer.phys.vel.z);
    localCarState.angVel = RocketSim::Vec(localPlayer.phys.angVel.x, localPlayer.phys.angVel.y, localPlayer.phys.angVel.z);
    localCarState.rotMat = localPlayer.phys.rotMat;
    localCarState.isDemoed = localPlayer.carState.isDemoed;
    localCar->SetState(localCarState);

    // Update local car controls
    RocketSim::CarControls localControls;
    localControls.throttle = controls.throttle;
    localControls.steer = controls.steer;
    localControls.pitch = controls.pitch;
    localControls.yaw = controls.yaw;
    localControls.roll = controls.roll;
    localControls.boost = controls.boost;
    localControls.jump = controls.jump;
    localControls.handbrake = controls.handbrake;
    localCar->controls = localControls;

    // Update opponent car if present
    if (cars.size() > 1) {
        auto opponentCar = *(std::next(cars.begin()));
        const PlayerData* opponent = nullptr;
        for (const auto& player : gs.players) {
            if (player.team != localPlayer.team) {
                opponent = &player;
                break;
            }
        }

        if (opponent) {
            RocketSim::CarState opponentCarState = opponentCar->GetState();
            opponentCarState.pos = RocketSim::Vec(opponent->phys.pos.x, opponent->phys.pos.y, opponent->phys.pos.z);
            opponentCarState.vel = RocketSim::Vec(opponent->phys.vel.x, opponent->phys.vel.y, opponent->phys.vel.z);
            opponentCarState.angVel = RocketSim::Vec(opponent->phys.angVel.x, opponent->phys.angVel.y, opponent->phys.angVel.z);
            opponentCarState.rotMat = opponent->phys.rotMat;
            opponentCarState.hasJumped = opponent->carState.hasJumped;
            opponentCarState.hasDoubleJumped = opponent->carState.hasDoubleJumped;
            opponentCarState.isDemoed = opponent->carState.isDemoed;
            opponentCar->SetState(opponentCarState);
        }
        else {
            //std::cout << "Opponent not found in game state" << std::endl;
        }
    }

    predictionArena->Step(1);
}



// Constants related to the arena dimensions
constexpr float SIDE_WALL_X = 4096.0f;
constexpr float CLOSE_DISTANCE = 10.0f;        // Distance threshold for proximity to the walls
constexpr float NEAR_WALL_DISTANCE = 4077.0f;  // Threshold for x close to the wall
constexpr float Z_THRESHOLD = 256.0f;          // Threshold for z axis proximity to the ground
constexpr float EPSILON = 1e-3f;               // Small value for floating point comparison

// Movement-related constants
constexpr float DIST_REACH_THRESH = 80.0f;
constexpr float STEP_INTERVAL = 1.0f / 60.0f;    // Time step (e.g., 60 FPS)
constexpr float FLIP_FORCE = 495.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;
constexpr float GRAVITY_Z = -650.0f; // Example gravity value, adjust as needed

float calculateTimeToSideWalls(const PlayerData& player) {
    const Vec& pos = player.phys.pos;
    const Vec& vel = player.phys.vel;

    // Initialize minimum time to infinity
    float minTime = std::numeric_limits<float>::infinity();

    if (std::abs(pos.x) >= NEAR_WALL_DISTANCE && std::abs(pos.x) <= SIDE_WALL_X) {
        return 0.0f; // Player is very close to the side walls
    }

    // ** Calculate Time to Positive Side Wall (x = SIDE_WALL_X) **
    if (vel.x > EPSILON) { // Player is moving towards the positive side wall
        float a = 0.5f * DRIVE_ACCEL;   // Acceleration coefficient (positive)
        float b = vel.x;                // Initial velocity towards positive x
        float c = pos.x - SIDE_WALL_X;  // Initial position offset from positive side wall

        // Quadratic equation: a*t^2 + b*t + c = 0
        float discriminant = b * b - 4.0f * a * c;

        if (discriminant >= 0.0f) { // Real solutions exist
            float sqrtD = std::sqrt(discriminant);
            float t1 = (-b + sqrtD) / (2.0f * a);
            float t2 = (-b - sqrtD) / (2.0f * a);

            // Select the smallest positive time
            if (t1 > EPSILON && t1 < minTime) {
                minTime = t1;
            }
            if (t2 > EPSILON && t2 < minTime) {
                minTime = t2;
            }
        }
    }

    // ** Calculate Time to Negative Side Wall (x = -SIDE_WALL_X) **
    if (vel.x < -EPSILON) { // Player is moving towards the negative side wall
        float a = 0.5f * (-DRIVE_ACCEL);   // Acceleration coefficient (negative)
        float b = vel.x;                   // Initial velocity towards negative x
        float c = pos.x + SIDE_WALL_X;     // Initial position offset from negative side wall

        // Quadratic equation: a*t^2 + b*t + c = 0
        float discriminant = b * b - 4.0f * a * c;

        if (discriminant >= 0.0f) { // Real solutions exist
            float sqrtD = std::sqrt(discriminant);
            float t1 = (-b + sqrtD) / (2.0f * a);
            float t2 = (-b - sqrtD) / (2.0f * a);

            // Select the smallest positive time
            if (t1 > EPSILON && t1 < minTime) {
                minTime = t1;
            }
            if (t2 > EPSILON && t2 < minTime) {
                minTime = t2;
            }
        }
    }

    return minTime;
}


// Constants related to the arena dimensions
constexpr float BACK_WALL_Y = 5120.0f;
constexpr float NEG_BACK_WALL_Y = -5120.0f;     // Negative back wall at y = -5120.0f
constexpr float MAX_BACKWALL_Y = 5100.0f;       // New threshold as per user request
constexpr float BOOST_ACCEL_POS = 1000.0f;      // Boost acceleration towards positive back wall (units/s²)
constexpr float BOOST_ACCEL_NEG = -1000.0f;     // Boost acceleration towards negative back wall (units/s²)

float calculateTimeToBackWalls(const PlayerData& player) {
    const Vec& pos = player.phys.pos;
    const Vec& vel = player.phys.vel;

    // Initialize minTime to infinity
    float minTime = std::numeric_limits<float>::infinity();

    // ** New Condition: If y is >=5100 or <=-5100, set time to back wall as 0 **
    if (pos.y >= MAX_BACKWALL_Y || pos.y <= -MAX_BACKWALL_Y) {
        return 0.0f; // Immediate collision with back wall
    }

    // ** Existing Immediate Collision Checks **
    if (pos.y >= BACK_WALL_Y) {
        return 0.0f; // Immediate collision with positive back wall
    }

    if (pos.y <= NEG_BACK_WALL_Y) {
        return 0.0f; // Immediate collision with negative back wall
    }

    // ** Calculate Time to Positive Back Wall (y = BACK_WALL_Y) **
    if (vel.y > EPSILON) { // Player is moving towards the positive back wall
        float a = 0.5f * BOOST_ACCEL_POS;          // Acceleration coefficient (positive)
        float b = vel.y;                           // Initial velocity towards positive y
        float c = pos.y - BACK_WALL_Y;            // Initial position offset from positive back wall

        // Quadratic equation: a*t^2 + b*t + c = 0
        float discriminant = b * b - 4.0f * a * c;

        if (discriminant >= 0.0f) { // Real solutions exist
            float sqrtD = std::sqrt(discriminant);
            float t1 = (-b + sqrtD) / (2.0f * a);
            float t2 = (-b - sqrtD) / (2.0f * a);

            // Select the smallest positive time
            if (t1 > EPSILON && t1 < minTime) {
                minTime = t1;
            }
            if (t2 > EPSILON && t2 < minTime) {
                minTime = t2;
            }
        }
    }

    // ** Calculate Time to Negative Back Wall (y = NEG_BACK_WALL_Y) **
    if (vel.y < -EPSILON) { // Player is moving towards the negative back wall
        float a = 0.5f * BOOST_ACCEL_NEG;         // Acceleration coefficient (negative)
        float b = vel.y;                           // Initial velocity towards negative y
        float c = pos.y - NEG_BACK_WALL_Y;        // Initial position offset from negative back wall

        // Quadratic equation: a*t^2 + b*t + c = 0
        float discriminant = b * b - 4.0f * a * c;

        if (discriminant >= 0.0f) { // Real solutions exist
            float sqrtD = std::sqrt(discriminant);
            float t1 = (-b + sqrtD) / (2.0f * a);
            float t2 = (-b - sqrtD) / (2.0f * a);

            // Select the smallest positive time
            if (t1 > EPSILON && t1 < minTime) {
                minTime = t1;
            }
            if (t2 > EPSILON && t2 < minTime) {
                minTime = t2;
            }
        }
    }

    return minTime;
}

// Constants related to the arena dimensions and gravity
constexpr float FLOOR_Z = 18.0f;      // Assuming the floor is at z = 0
constexpr float BOOST_ACCEL_DOWN = -1000.0f;

float calculateTimeToFloor(const PlayerData& player) {
    const Vec& pos = player.phys.pos;
    const Vec& vel = player.phys.vel;

    // ** Immediate Floor Proximity Check **
    if (pos.z <= FLOOR_Z) {
        return 0.0f; // Player is already at or below the floor
    }

    // ** Calculate Total Downward Acceleration **
    // Sum of gravity and boosting acceleration (both downward, hence negative)
    float totalAccel = GRAVITY_Z + BOOST_ACCEL_DOWN; // e.g., -650 + (-1000) = -1650 units/s²

    // ** Define Quadratic Coefficients **
    // Using the kinematic equation: 0.5 * a * t² + v₀ * t + s₀ = 0
    float a = 0.5f * totalAccel;    // Acceleration coefficient (negative)
    float b = vel.z;                 // Initial vertical velocity
    float c = pos.z;                 // Initial position above the floor

    // ** Calculate Discriminant **
    float discriminant = b * b - 4.0f * a * c;

    // ** Check for Real Solutions **
    if (discriminant < 0.0f) {
        return std::numeric_limits<float>::infinity(); // No real solution; player won't reach the floor
    }

    // ** Calculate the Two Possible Times Using the Quadratic Formula **
    float sqrtD = std::sqrt(discriminant);
    float t1 = (-b - sqrtD) / (2.0f * a); // Smaller time
    float t2 = (-b + sqrtD) / (2.0f * a); // Larger time

    // ** Select the Positive and Smaller Time **
    float timeToFloor = std::numeric_limits<float>::infinity();

    if (t1 > 0.0f) {
        timeToFloor = t1;
    }
    if (t2 > 0.0f && t2 < timeToFloor) {
        timeToFloor = t2;
    }

    return timeToFloor;
}


#include <algorithm>  // for std::min

float calculateTimeToReachAnySurface(const PlayerData& player) {
    // Calculate individual times to each surface
    float timeToSideWalls = calculateTimeToSideWalls(player);
    // RG_LOG(timeToSideWalls); // Uncomment if logging is needed

    float timeToBackWalls = calculateTimeToBackWalls(player);
    //RG_LOG(timeToBackWalls); // Uncomment if logging is needed

    float timeToFloor = calculateTimeToFloor(player);
    // RG_LOG(timeToFloor); // Uncomment if logging is needed

    // Assign identifier variables for clarity
    float minTimeBetweenBackWallsAndFloor = RS_MIN(timeToBackWalls, timeToFloor);

    // Now find the minimum time among side walls and the previously computed minimum
    float minTime = RS_MIN(timeToSideWalls, minTimeBetweenBackWallsAndFloor);

    return minTime;
}


float RLBotBot::calculateTimeToReach(const RLGSC::GameState& gs, const Vec& targetPos) {
    constexpr float STEP_INTERVAL = 1 / 60.f;
    constexpr float DIST_REACH_THRESH = 80;
    constexpr float FLIP_FORCE = 495;
    constexpr float MAX_TOTAL_SPEED = 2300.0f;
    constexpr float MAX_DRIVE_SPEED = 1410.0f;
    constexpr float BREAK_ACCEL = 3500.0f;
    constexpr float BOOST_ACCEL = 991.667f;
    constexpr float DRIVE_ACCEL = 1600.0f;

    const auto& car = gs.players[this->index];
    Vec carPos = car.phys.pos;
    Vec carVel = car.phys.vel;
    Vec dirToTarget = (targetPos - carPos).Normalized();
    float dist = (targetPos - carPos).Length() - CommonValues::BALL_RADIUS;
    float vel = carVel.Dot(dirToTarget);
    float rotTime = calculateRotationTime(gs, targetPos);
    float timeToSurface = calculateTimeToReachAnySurface(car);

    float t = 0;
    for (float curDist = dist, curVel = vel; curDist > DIST_REACH_THRESH; t += STEP_INTERVAL, curDist -= curVel * STEP_INTERVAL) {
        bool hasRotated = t >= rotTime;
        bool onSurface = t >= timeToSurface;

        if (curVel < MAX_TOTAL_SPEED && t == 0) {
            // Flip force
            curVel += FLIP_FORCE;
        }

        if (onSurface) {
            if (curVel < 0) {
                curVel += (BREAK_ACCEL + BOOST_ACCEL * hasRotated) * STEP_INTERVAL;
            }
            else if (curVel < MAX_DRIVE_SPEED) {
                curVel += (DRIVE_ACCEL + BOOST_ACCEL * hasRotated) * STEP_INTERVAL;
            }
            else if (curVel < MAX_TOTAL_SPEED) {
                curVel += BOOST_ACCEL * hasRotated * STEP_INTERVAL;
            }
        }

        if (curVel >= MAX_TOTAL_SPEED) {
            return t + (curDist / curVel);
        }
    }
    return t;
}

float RLBotBot::calculateRotationTime(const RLGSC::GameState& gs, const Vec& targetPos) {
    constexpr float MAX_ANG_VEL = 5.5f * 0.9f;  // Maximum angular velocity in radians per second

    const auto& car = gs.players[this->index];

    Vec playerPos = car.phys.pos;
    Vec playerForward = car.phys.rotMat.forward;

    Vec dirToTarget = (targetPos - playerPos).Normalized();
    float angleDiff = std::acos(RS_CLAMP(playerForward.Dot(dirToTarget), -1.0f, 1.0f)); // radians

    // Assuming constant maximum angular velocity
    return angleDiff / MAX_ANG_VEL;
}

void RLBotBot::RenderTimeToReachBall(const RLGSC::GameState& gs) {
    if (!renderTimeToReachBallEnabled || !ballPredTracker) return;
    rlbot::ScopedRenderer renderer("time_to_reach_renderer");

    constexpr float PREDICTION_DURATION = 6.0f;
    constexpr int NUM_PREDICTION_POINTS = 8;

    // 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);
    ballPredTracker->UpdatePredManual(currentBallState, numTicks);

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

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

    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] = calculateTimeToReach(gs, ballPos);

        if (opponent) {
            RLGSC::GameState opponentGs = gs;
            opponentGs.players[this->index] = *opponent;
            opponentTimeToReach[j] = calculateTimeToReach(opponentGs, ballPos);
            if (localTimeToReach[j] < opponentTimeToReach[j]) {
                localPossession += 1.0f;
            }
            else {
                opponentPossession += 1.0f;
            }
        }
        else {
            localPossession += 1.0f;
        }
    }

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

    // Define Rocket League colors
    rlbot::Color rocketLeagueBlue{ 0, 153, 255, 255 };
    rlbot::Color rocketLeagueOrange{ 235, 141, 31, 255 };

    // Determine the dominant team and color
    bool isLocalDominant = localPossession >= opponentPossession;
    rlbot::Color dominantColor = isLocalDominant ?
        (gs.players[this->index].team == Team::BLUE ? rocketLeagueBlue : rocketLeagueOrange) :
        (gs.players[this->index].team == Team::BLUE ? rocketLeagueOrange : rocketLeagueBlue);

    // Get the position of the car with the best possession
    rlbot::flat::Vector3 bestCarPos;
    if (isLocalDominant) {
        bestCarPos = {
            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)
        };
    }
    else if (opponent) {
        bestCarPos = {
            static_cast<float>(opponent->phys.pos.x),
            static_cast<float>(opponent->phys.pos.y),
            static_cast<float>(opponent->phys.pos.z)
        };
    }

    // Render the single line from the best car to the ball
    rlbot::flat::Vector3 ballPos = {
        static_cast<float>(gs.ball.pos.x),
        static_cast<float>(gs.ball.pos.y),
        static_cast<float>(gs.ball.pos.z)
    };
    renderer.DrawLine3D(dominantColor, bestCarPos, ballPos);

    // Render time to ball inside the ball
    float timeToReach = isLocalDominant ? localTimeToReach[0] : opponentTimeToReach[0];
    std::stringstream timeSS;
    timeSS << std::fixed << std::setprecision(2) << timeToReach << "s";
    renderer.DrawString3D(timeSS.str(), dominantColor, ballPos, 1.0f, 1.0f);

    // Render possession information
    std::stringstream localSS;
    localSS << (gs.players[this->index].team == Team::BLUE ? "Blue" : "Orange") << " possession: "
        << std::fixed << std::setprecision(2) << localPossession * 100 << "%";
    renderer.DrawString2D(localSS.str(), gs.players[this->index].team == Team::BLUE ? rocketLeagueBlue : rocketLeagueOrange,
        rlbot::flat::Vector3{ 10, 10, 0 }, 2.0f, 2.0f);

    if (opponent) {
        std::stringstream opponentSS;
        opponentSS << (gs.players[this->index].team == Team::BLUE ? "Orange" : "Blue") << " possession: "
            << std::fixed << std::setprecision(2) << opponentPossession * 100 << "%";
        renderer.DrawString2D(opponentSS.str(), gs.players[this->index].team == Team::BLUE ? rocketLeagueOrange : rocketLeagueBlue,
            rlbot::flat::Vector3{ 10, 40, 0 }, 2.0f, 2.0f);
    }

    // Render overall possession summary
    std::stringstream summaryStream;
    summaryStream << "Dominant team: " << (isLocalDominant ?
        (gs.players[this->index].team == Team::BLUE ? "Blue" : "Orange") :
        (gs.players[this->index].team == Team::BLUE ? "Orange" : "Blue"));
    renderer.DrawString2D(summaryStream.str(), dominantColor, rlbot::flat::Vector3{ 10, 70, 0 }, 2.0f, 2.0f);
}

#include <algorithm>