your goal is to maek the globall ballpred code correct using everything correct and leaving nothing undefined, #include "GlobalBallPred.h"

BallPredEnv* BallPred::GetOrMake(const Arena* arena) {
    static thread_local std::unordered_map<const Arena*, BallPredEnv*> predEnvs;

    auto& env = predEnvs[arena];
    if (env == NULL)
        env = new BallPredEnv(arena);

    return env;
}

PhysObj BallPredEnv::GetAtTime(float time, const PhysObj& curBall) {
    if (!ballPredTracker.predData.empty()) {
        return PhysObj(ballPredTracker.GetBallStateForTime(time));
    }
    else {
        return curBall;
    }
} also make the heqader for this #pragma once
#include "../../BaseInc.h"
#include "../Car/Car.h"
#include "../Ball/Ball.h"
#include "../BoostPad/BoostPad.h"
#include "../CollisionMasks.h"

#include "../../CollisionMeshFile/CollisionMeshFile.h"
#include "../BoostPad/BoostPadGrid/BoostPadGrid.h"
#include "../SuspensionCollisionGrid/SuspensionCollisionGrid.h"
#include "../MutatorConfig/MutatorConfig.h"
#include "ArenaConfig/ArenaConfig.h"

#include "../../../libsrc/bullet3-3.24/BulletCollision/BroadphaseCollision/btDbvtBroadphase.h"
#include "../../../libsrc/bullet3-3.24/BulletCollision/CollisionShapes/btStaticPlaneShape.h"
#include "../../../libsrc/bullet3-3.24/BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h"
#include "../../../libsrc/bullet3-3.24/BulletCollision/CollisionDispatch/btCollisionDispatcher.h"
#include "../../../libsrc/bullet3-3.24/BulletDynamics/ConstraintSolver/btSequentialImpulseConstraintSolver.h"
#include "../../../libsrc/bullet3-3.24/BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h"
#include "../../../libsrc/bullet3-3.24/BulletCollision/CollisionDispatch/btDefaultCollisionConfiguration.h"

RS_NS_START

typedef std::function<void(class Arena* arena, Team scoringTeam, void* userInfo)> GoalScoreEventFn;
typedef std::function<void(class Arena* arena, Car* bumper, Car* victim, bool isDemo, void* userInfo)> CarBumpEventFn;

// The container for all game simulation
// Stores cars, the ball, all arena collisions, and manages the overall game state
class Arena {
public:

	GameMode gameMode;

	uint32_t _lastCarID = 0;
	std::unordered_set<Car*> _cars;
	bool ownsCars = true; // If true, deleting this arena instance deletes all cars

	std::unordered_map<uint32_t, Car*> _carIDMap;
	
	Ball* ball;
	bool ownsBall = true; // If true, deleting this arena instance deletes the ball
	
	std::vector<BoostPad*> _boostPads;
	bool ownsBoostPads = true; // If true, deleing this arena instance deletes all boost pads
	
	BoostPadGrid _boostPadGrid;

	SuspensionCollisionGrid _suspColGrid;

	MutatorConfig _mutatorConfig;

	const MutatorConfig& GetMutatorConfig() { return _mutatorConfig; }
	RSAPI void SetMutatorConfig(const MutatorConfig& mutatorConfig);

	// Time in seconds each tick (1/tickrate)
	float tickTime; 

	// Returns (1 / tickTime)
	float GetTickRate() const {
		return 1 / tickTime;
	}

	// Total ticks this arena instance has been simulated for, never resets
	uint64_t tickCount = 0;

	const std::unordered_set<Car*>& GetCars() { return _cars; }
	const std::vector<BoostPad*>& GetBoostPads() { return _boostPads; }

	// Returns true if added, false if car was already added
	bool _AddCarFromPtr(Car* car);
	RSAPI Car* AddCar(Team team, const CarConfig& config = CAR_CONFIG_OCTANE);

	// Returns false if the car ID was not found in the cars list
	RSAPI bool RemoveCar(uint32_t id);

	// Returns false if the car was not found in the cars list
	// NOTE: If the car was removed, the car will be freed and the pointer will be made invalid
	bool RemoveCar(Car* car) {
		return RemoveCar(car->id);
	}

	RSAPI Car* GetCar(uint32_t id);

	btDiscreteDynamicsWorld _bulletWorld;
	struct {
		btDefaultCollisionConfiguration collisionConfig;
		btCollisionDispatcher collisionDispatcher;
		btOverlappingPairCache* overlappingPairCache;
		btBroadphaseInterface* broadphase;
		btSequentialImpulseConstraintSolver constraintSolver;
	} _bulletWorldParams;

	btRigidBody* _worldCollisionRBs = NULL;
	size_t _worldCollisionRBAmount = 0;
	btBvhTriangleMeshShape* _worldCollisionBvhShapes = NULL;
	btStaticPlaneShape* _worldCollisionPlaneShapes = NULL;

	struct {
		GoalScoreEventFn func = NULL;
		void* userInfo = NULL;
	} _goalScoreCallback;
	RSAPI void SetGoalScoreCallback(GoalScoreEventFn callbackFn, void* userInfo = NULL);

	struct {
		CarBumpEventFn func = NULL;
		void* userInfo = NULL;
	} _carBumpCallback;
	RSAPI void SetCarBumpCallback(CarBumpEventFn callbackFn, void* userInfo = NULL);

	// NOTE: Arena should be destroyed after use
	RSAPI static Arena* Create(GameMode gameMode, const ArenaConfig& arenaConfig = {}, float tickRate = 120);
	
	// Serialize entire arena state including cars, ball, and boostpads
	RSAPI void Serialize(DataStreamOut& out) const;

	// Load new arena from serialized data
	RSAPI static Arena* DeserializeNew(DataStreamIn& in);

	Arena(const Arena& other) = delete; // No copy constructor, use Arena::Clone() instead
	Arena& operator =(const Arena& other) = delete; // No copy operator, use Arena::Clone() instead

	Arena(Arena&& other) = delete; // No move constructor
	Arena& operator =(Arena&& other) = delete; // No move operator

	// Get a deep copy of the arena
	RSAPI Arena* Clone(bool copyCallbacks);

	// NOTE: Car ID will not be restored
	RSAPI Car* DeserializeNewCar(DataStreamIn& in, Team team);

	// Simulate everything in the arena for a given number of ticks
	RSAPI void Step(int ticksToSimulate = 1);

	RSAPI void ResetToRandomKickoff(int seed = -1);

	// Returns true if the ball is probably going in, does not account for wall or ceiling bounces
	// NOTE: Purposefully overestimates, just like the real RL's shot prediction
	// To check which goal it will score in, use the ball's velocity
	// Margin can be manually adjusted with extraMargin (negative to prevent overestimating)
	RSAPI bool IsBallProbablyGoingIn(float maxTime = 2.f, float extraMargin = 0, Team* goalTeamOut = NULL) const;

	// Returns true if the ball is in the net
	// Works for all gamemodes (and does nothing in THE_VOID)
	RSAPI bool IsBallScored() const;

	// Free all associated memory
	RSAPI ~Arena();

	// NOTE: Passed shape pointer will be freed when arena is deconstructed
	template <class T>
	btRigidBody* _AddStaticCollisionShape(
		size_t rbIndex, size_t meshListIndex, T* shape, T* meshList, btVector3 posBT = btVector3(0, 0, 0), 
		bool isHoopsNet = false) {

		static_assert(std::is_base_of<btCollisionShape, T>::value);
		meshList[meshListIndex] = *shape;

		assert(rbIndex < _worldCollisionRBAmount);
		btRigidBody& shapeRB = _worldCollisionRBs[rbIndex];
		shapeRB = btRigidBody(0, NULL, &meshList[meshListIndex]);
		shapeRB.setWorldTransform(btTransform(btMatrix3x3::getIdentity(), posBT));
		shapeRB.setUserPointer(this);
		if (isHoopsNet) {
			_bulletWorld.addRigidBody(&shapeRB, CollisionMasks::HOOPS_NET, CollisionMasks::HOOPS_NET);
		} else {
			_bulletWorld.addRigidBody(&shapeRB);
		}
		return &shapeRB;
	}

	void _SetupArenaCollisionShapes();

	// Static function called by Bullet internally when adding a collision point
	static bool _BulletContactAddedCallback(
		btManifoldPoint& cp,
		const btCollisionObjectWrapper* colObjA, int partID_A, int indexA,
		const btCollisionObjectWrapper* colObjB, int partID_B, int indexB
	);

	void _BtCallback_OnCarBallCollision(Car* car, Ball* ball, btManifoldPoint& manifoldPoint, bool ballIsBodyA);
	void _BtCallback_OnCarCarCollision(Car* car1, Car* car2, btManifoldPoint& manifoldPoint);
	void _BtCallback_OnCarWorldCollision(Car* car, btCollisionObject* worldObject, btManifoldPoint& manifoldPoint);

	const ArenaConfig& GetArenaConfig() const {
		return _config;
	}

	// Backwards compatability
	ArenaMemWeightMode GetMemWeightMode() {
		return _config.memWeightMode;
	}

private:
	
	// Constructor for use by Arena::Create()
	Arena(GameMode gameMode, const ArenaConfig& config, float tickRate = 120);

	// Making this private because horrible memory overflows can happen if you changed it
	ArenaConfig _config;
};

RS_NS_END #include "BallPredTracker.h"

RS_NS_START

BallPredTracker::BallPredTracker(Arena* arena, size_t numPredTicks) : numPredTicks(numPredTicks) {
	// Make ball pred arena
	this->ballPredArena = Arena::Create(arena->gameMode, arena->GetArenaConfig(), arena->GetTickRate());
	lastUpdateTickCount = 0;

	predData.reserve(numPredTicks);
	UpdatePredFromArena(arena);
}

BallPredTracker::~BallPredTracker() {
	delete this->ballPredArena;
}

void BallPredTracker::UpdatePredFromArena(Arena* arena) {
	BallState bs = arena->ball->GetState();

	int ticksSinceLastUpdate = arena->tickCount - lastUpdateTickCount;
	UpdatePredManual(bs, ticksSinceLastUpdate);
}

void BallPredTracker::UpdatePredManual(const BallState& curBallState, int ticksSinceLastUpdate) {

	bool needsFullRepred;
	if (ticksSinceLastUpdate < predData.size()) {
		
		if (predData[ticksSinceLastUpdate].Matches(curBallState)) {
			// We can re-use ball prediction data
			needsFullRepred = false;

			if (ticksSinceLastUpdate > 0) {
				// Remove the states from the front that are too old, move the rest to the front
				predData.erase(predData.begin(), predData.begin() + ticksSinceLastUpdate);

				// Predict new states until we reach numPredTicks
				ballPredArena->ball->SetState(predData.back());
				while (predData.size() < numPredTicks) {
					ballPredArena->Step(1);
					predData.push_back(ballPredArena->ball->GetState());
				}
			} else {
				// No change, no update needed
			}
		} else {
			needsFullRepred = true;
		}
 
	} else {
		needsFullRepred = true;
	}

	if (needsFullRepred) {
		// Full re-simulation required
		ForceUpdateAllPred(curBallState);
	}

	lastUpdateTickCount += ticksSinceLastUpdate;
}

void BallPredTracker::ForceUpdateAllPred(const BallState& initialBallState) {
	ballPredArena->ball->SetState(initialBallState);
	predData.resize(numPredTicks);
	predData[0] = initialBallState;
	for (size_t i = 1; i < numPredTicks; i++) {
		ballPredArena->Step();
		predData[i] = ballPredArena->ball->GetState();
	}
}

BallState BallPredTracker::GetBallStateForTime(float predTime) const {
	if (predData.empty())
		RS_ERR_CLOSE("BallPredTracker::GetBallStateForTime(): Predicted ball data is empty, update prediction before calling");

	int index = RS_CLAMP(predTime / ballPredArena->tickTime, 0, predData.size() - 1);
	return predData[index];
}

RS_NS_END #pragma once
#include "../Arena/Arena.h"

RS_NS_START

// An external tool struct that predicts the ball of a given arena
struct BallPredTracker {
	Arena* ballPredArena;
	std::vector<BallState> predData;
	size_t numPredTicks;

	int lastUpdateTickCount;

	// arena: The arena you want to predict the ball for (BallPredTracker will make a copy of it without the cars)
	// You do not need to make another arena for BallPredTracker, it does that itself
	BallPredTracker(Arena* arena, size_t numPredTicks);
	~BallPredTracker();

	// No copying
	BallPredTracker(const BallPredTracker& other) = delete;
	BallPredTracker& operator=(const BallPredTracker& other) = delete;

	// Update the prediction data from the arena the ball is in, does not need to be called every tick
	// The arena is needed for the current ball state, as well as the tick count to determine time since last update
	void UpdatePredFromArena(Arena* arena);

	// An alternate version of UpdatePred which doesn't require the arena, 
	//	but instead you manually provide the current ball state and the ticks since this tracker was last updated
	void UpdatePredManual(const BallState& curBallState, int ticksSinceLastUpdate);

	// Forcefully re-predicts all ticks
	void ForceUpdateAllPred(const BallState& initialBallState);

	// Get the predicted ball state at a given future time delta
	BallState GetBallStateForTime(float predTime) const;
};

RS_NS_END #pragma once
#include "../../Framework.h"

namespace RLGSC {
	// https://github.com/AechPro/rocket-league-gym-sim/blob/main/rlgym_sim/utils/gamestates/physics_object.py
	struct PhysObj {
		Vec pos;
		RotMat rotMat;
		Vec vel, angVel;

		PhysObj() = default;
		PhysObj(const BallState& ballState);
		PhysObj(const CarState& carState);

		// Rotate 180 degrees around Z axis, scales everything by (-1, -1, 1)
		PhysObj Invert() const;

		// Mirror along X axis
		PhysObj MirrorX() const;
	};
}