class TimeToReachLookupTable {
public:
	static constexpr int XY_DIST_SUBDIVISIONS = 100;
	static constexpr int Z_DIST_SUBDIVISIONS = 50;
	static constexpr int VEL_TO_TARGET_SUBDIVISIONS = 50;
	static constexpr int ROT_TIME_SUBDIVISIONS = 20;
	static constexpr int SURFACE_TIME_SUBDIVISIONS = 20;

	static constexpr float MAX_XY_DIST = 12000.0f;
	static constexpr float MAX_Z_DIST = 2044.0f;
	static constexpr float MAX_VEL = 2300.0f;
	static constexpr float MAX_ROT_TIME = 2.0f;
	static constexpr float MAX_SURFACE_TIME = 4.0f;

	TimeToReachLookupTable() { initializeLookupTable(); }

	float getTimeToReach(float xyDist, float zDist, float velToTarget, float rotTime, float surfaceTime) {
		int xyIndex = static_cast<int>(xyDist / MAX_XY_DIST * XY_DIST_SUBDIVISIONS);
		int zIndex = static_cast<int>(zDist / MAX_Z_DIST * Z_DIST_SUBDIVISIONS);
		int velIndex = static_cast<int>((velToTarget + MAX_VEL) / (2 * MAX_VEL) * VEL_TO_TARGET_SUBDIVISIONS);
		int rotIndex = static_cast<int>(rotTime / MAX_ROT_TIME * ROT_TIME_SUBDIVISIONS);
		int surfaceIndex = static_cast<int>(surfaceTime / MAX_SURFACE_TIME * SURFACE_TIME_SUBDIVISIONS);

		xyIndex = std::clamp(xyIndex, 0, XY_DIST_SUBDIVISIONS - 1);
		zIndex = std::clamp(zIndex, 0, Z_DIST_SUBDIVISIONS - 1);
		velIndex = std::clamp(velIndex, 0, VEL_TO_TARGET_SUBDIVISIONS - 1);
		rotIndex = std::clamp(rotIndex, 0, ROT_TIME_SUBDIVISIONS - 1);
		surfaceIndex = std::clamp(surfaceIndex, 0, SURFACE_TIME_SUBDIVISIONS - 1);

		return lookupTable[xyIndex][zIndex][velIndex][rotIndex][surfaceIndex];
	}

private:
	std::vector<std::vector<std::vector<std::vector<std::vector<float>>>>> lookupTable;

	void initializeLookupTable() {
		lookupTable.resize(XY_DIST_SUBDIVISIONS,
			std::vector<std::vector<std::vector<std::vector<float>>>>(Z_DIST_SUBDIVISIONS,
				std::vector<std::vector<std::vector<float>>>(VEL_TO_TARGET_SUBDIVISIONS,
					std::vector<std::vector<float>>(ROT_TIME_SUBDIVISIONS,
						std::vector<float>(SURFACE_TIME_SUBDIVISIONS)))));

		for (int xy = 0; xy < XY_DIST_SUBDIVISIONS; ++xy) {
			for (int z = 0; z < Z_DIST_SUBDIVISIONS; ++z) {
				for (int vel = 0; vel < VEL_TO_TARGET_SUBDIVISIONS; ++vel) {
					for (int rot = 0; rot < ROT_TIME_SUBDIVISIONS; ++rot) {
						for (int surface = 0; surface < SURFACE_TIME_SUBDIVISIONS; ++surface) {
							float xyDist = (xy + 0.5f) * MAX_XY_DIST / XY_DIST_SUBDIVISIONS;
							float zDist = (z + 0.5f) * MAX_Z_DIST / Z_DIST_SUBDIVISIONS;
							float velToTarget = (vel + 0.5f) * 2 * MAX_VEL / VEL_TO_TARGET_SUBDIVISIONS - MAX_VEL;
							float rotTime = (rot + 0.5f) * MAX_ROT_TIME / ROT_TIME_SUBDIVISIONS;
							float surfaceTime = (surface + 0.5f) * MAX_SURFACE_TIME / SURFACE_TIME_SUBDIVISIONS;

							lookupTable[xy][z][vel][rot][surface] = calculateTimeToReach(xyDist, zDist, velToTarget, rotTime, surfaceTime);
						}
					}
				}
			}
		}
	}

	float calculateTimeToReach(float xyDist, float zDist, float velToTarget, float rotTime, float surfaceTime) {
		constexpr float DIST_REACH_THRESH = 80.0f;
		constexpr float STEP_INTERVAL = 1.0f / 60.0f;
		constexpr float FLIP_FORCE = 495.0f;
		constexpr float AIR_ACCEL = 133.334f;
		constexpr float BOOST_ACCEL_AIR = 2116.666f;
		constexpr float MAX_TOTAL_SPEED = 2300.0f;
		constexpr float BRAKE_ACCEL = 3500.0f;
		constexpr float BOOST_ACCEL_GROUND = 1983.332f;
		constexpr float DRIVE_ACCEL = 1600.0f;
		constexpr float MAX_DRIVE_SPEED = 1410.0f;

		float dist = std::sqrt(xyDist * xyDist + zDist * zDist);
		float vel = velToTarget;

		// Determine whether to use aerial acceleration
		float aerialTime = 0;
		float aerialDist = dist;
		float aerialVel = vel;
		while (aerialDist > DIST_REACH_THRESH && aerialTime < surfaceTime) {
			aerialVel += (AIR_ACCEL + BOOST_ACCEL_AIR) * STEP_INTERVAL;
			if (aerialVel > MAX_TOTAL_SPEED) aerialVel = MAX_TOTAL_SPEED;
			aerialDist -= aerialVel * STEP_INTERVAL;
			aerialTime += STEP_INTERVAL;
		}
		bool useAerial = aerialTime < surfaceTime;

		float t = 0;
		for (float curDist = dist, curVel = vel; curDist > DIST_REACH_THRESH; t += STEP_INTERVAL, curDist -= curVel * STEP_INTERVAL) {
			bool hasRotated = t >= rotTime;
			if (curVel < MAX_TOTAL_SPEED && t == 0) {
				// Flip force
				curVel += FLIP_FORCE;
			}
			if (useAerial) {
				// Aerial acceleration
				if (curVel < MAX_DRIVE_SPEED) {
					curVel += (AIR_ACCEL + BOOST_ACCEL_AIR * hasRotated) * STEP_INTERVAL;
				}
				else if (curVel < MAX_TOTAL_SPEED) {
					curVel += BOOST_ACCEL_AIR * hasRotated * STEP_INTERVAL;
				}
			}
			else {
				// Ground acceleration
				if (curVel < 0) {
					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;
				}
			}

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

class TimeToReachCalculator {
public:
	static float calculateTimeToReach(const PlayerData& player, const Vec& targetPos) {
		static TimeToReachLookupTable lookupTable;

		Vec toTarget = targetPos - player.phys.pos;
		float xyDist = toTarget.To2D().Length();
		float zDist = std::abs(toTarget.z);
		float velToTarget = player.phys.vel.Dot(toTarget.Normalized());
		float rotTime = calculateRotationTime(player, targetPos);
		float surfaceTime = calculateTimeToReachAnySurface(player);

		return lookupTable.getTimeToReach(xyDist, zDist, velToTarget, rotTime, surfaceTime);
	}

private:
	static float calculateRotationTime(const PlayerData& player, const Vec& targetPos) {
		constexpr float MAX_ANG_VEL = 5.5f * 0.9f;
		Vec dirToTarget = (targetPos - player.phys.pos).Normalized();
		float dotProduct = player.phys.rotMat.forward.Dot(dirToTarget);
		float angleDiff = std::acos(std::clamp(dotProduct, -1.0f, 1.0f));
		return angleDiff / MAX_ANG_VEL;
	}

	static float calculateTimeToReachAnySurface(const PlayerData& player) {
		return std::min({
			calculateTimeToSideWalls(player),
			calculateTimeToBackWalls(player),
			calculateTimeToFloor(player)
			});
	}

	static float calculateTimeToSideWalls(const PlayerData& player) {
		constexpr float SIDE_WALL_X = 4096.0f;
		constexpr float NEAR_WALL_DISTANCE = 4077.0f;

		const Vec& pos = player.phys.pos;
		const Vec& vel = player.phys.vel;

		if (std::abs(pos.x) >= NEAR_WALL_DISTANCE && std::abs(pos.x) <= SIDE_WALL_X) {
			return 0.0f;
		}

		float minTime = std::numeric_limits<float>::infinity();
		float a = 0.5f * 1600.0f;
		float b = vel.x;
		float c = pos.x - SIDE_WALL_X * (pos.x > 0 ? 1 : -1);

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

			if (t1 > 0 && t1 < minTime) minTime = t1;
			if (t2 > 0 && t2 < minTime) minTime = t2;
		}

		return minTime;
	}

	static float calculateTimeToBackWalls(const PlayerData& player) {
		constexpr float BACK_WALL_Y = 5120.0f;
		constexpr float NEAR_WALL_DISTANCE = 5100.0f;

		const Vec& pos = player.phys.pos;
		const Vec& vel = player.phys.vel;

		if (std::abs(pos.y) >= NEAR_WALL_DISTANCE && std::abs(pos.y) <= BACK_WALL_Y) {
			return 0.0f;
		}

		float minTime = std::numeric_limits<float>::infinity();
		float a = 0.5f * 1600.0f;
		float b = vel.y;
		float c = pos.y - BACK_WALL_Y * (pos.y > 0 ? 1 : -1);

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

			if (t1 > 0 && t1 < minTime) minTime = t1;
			if (t2 > 0 && t2 < minTime) minTime = t2;
		}

		return minTime;
	}

	static float calculateTimeToFloor(const PlayerData& player) {
		constexpr float FLOOR_Z = 17.0f;
		constexpr float GRAVITY_Z = -650.0f;

		const Vec& pos = player.phys.pos;
		const Vec& vel = player.phys.vel;

		if (pos.z <= FLOOR_Z) {
			return 0.0f;
		}

		float a = 0.5f * GRAVITY_Z;
		float b = vel.z;
		float c = pos.z - FLOOR_Z;

		float discriminant = b * b - 4.0f * a * c;
		if (discriminant < 0.0f) {
			return std::numeric_limits<float>::infinity();
		}

		float sqrtD = std::sqrt(discriminant);
		float t1 = (-b - sqrtD) / (2.0f * a);
		float t2 = (-b + sqrtD) / (2.0f * a);

		return (t1 > 0) ? t1 : t2;
	}
}; class PossessionReward : public RewardFunction{
public:
	PossessionReward() = default;

	float findBestInterceptTime(const PlayerData& player, float maxTime = 4.0f) const {
		float leftTime = 0.0f;
		float rightTime = maxTime;
		float bestTime = maxTime;
		float bestDiff = std::numeric_limits<float>::max();

		while (rightTime - leftTime > 0.1f) { // Precision of 0.1 seconds
			float midTime = (leftTime + rightTime) / 2;
			PhysObj ballState = BallPred::getInstance().GetAtTime(midTime);
			float timeToReach = TimeToReachCalculator::calculateTimeToReach(player, ballState.pos);

			float diff = std::abs(timeToReach - midTime);
			if (diff < bestDiff) {
				bestDiff = diff;
				bestTime = midTime;
			}

			if (timeToReach < midTime) {
				rightTime = midTime;
			}
 else {
  leftTime = midTime;
}
}

return bestTime;
}

	virtual float GetReward(const PlayerData& player, const GameState& state, const Action& prevAction) override {
		constexpr float PREDICTION_DURATION = 4.0f;
		constexpr float TIME_THRESHOLD = 0.25f;  // Changed to 0.25 seconds

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

		// Calculate best intercept times
		float localBestTime = findBestInterceptTime(player, PREDICTION_DURATION);
		float opponentBestTime = opponent ? findBestInterceptTime(*opponent, PREDICTION_DURATION) : std::numeric_limits<float>::max();

		// Determine possession
		float timeDifference = opponentBestTime - localBestTime;

		float reward;
		if (std::abs(timeDifference) <= TIME_THRESHOLD) {
			// Times are within 0.25 seconds, no one has possession
			reward = 0;
		}
		else {
			// Player has possession if their time is less than the opponent's
			reward = (timeDifference > 0) ? 1 : 0;
		}

		// Log possession (only for blue team, to avoid duplicate logging)
		if (player.team == Team::BLUE) {
			std::string possessionStatus = (std::abs(timeDifference) <= TIME_THRESHOLD) ? "No one" :
				(reward == 1) ? "Blue" : "Orange";
			//("Possession: " << possessionStatus);
		}

		return reward;
	}
}; make this lookuptable be exactly how zealan does it Zealan — 10/10/2024 8:24 AM
its O(1) for the minimum time to pos for all cars
or a car
i mean im not counting the number of cars as n cause its max 6 so
fredrik._. — Yesterday at 11:35 PM
Do you like precompile your time to reach?
Zealan — Yesterday at 11:47 PM
precompile??
do you mean precompute?
fredrik._. — Yesterday at 11:48 PM
Yeah lol mb not familiar with terms yet 
fredrik._. — Today at 1:18 AM
Erm🤓 for your time to reach lookup table to only be 500mb isnt it like really innacurate?
fredrik._. — Today at 1:28 AM
Simce after my calculations accounting for every possible, position, xy and z, and also all orientations and lets not forget the target pos the lookuptable will be prob many many terabytes, so im wondering eth youre doing to only get 500mb lmao
Zealan — Today at 1:29 AM
every possible xyz pos xyz vel xyz target pos angvel fraction and rotation diff is like
impossible
4 billion ^ 3 * 4 billion ^ 3 *  4 billion ^ 3 * 4 billion ^ 2
thats 4e105
thats more than there are atoms in universe i think
fredrik._. — Today at 1:30 AM
😂
Then how can i make it not be more atoms than in the universe😂
Zealan — Today at 1:31 AM
divide
decide how many subdivisions
but that will make accuracy ass, so you then need to interpolate between subdimensions to get most of that accuracy back 
then you're basically good
fredrik._. — Today at 1:32 AM
Ok ill try that thx
Zealan — Today at 1:34 AM
also i forgor how i made it smaller
cause initially it needed 500gb
oh yea right
fredrik._. — Today at 1:34 AM
💀
Zealan — Today at 1:34 AM
i only calculated delta pos
so we go from 30^9 which is an insane number
to 30^6
which is doable (700mb) 
and due to the way my system works i reduced it further
basically you only want a lookup table for that expensive loop you're doing
fredrik._. — Today at 1:35 AM
Delta pos too what? The target?
Zealan — Today at 1:35 AM
no to your mom
fredrik._. — Today at 1:35 AM
Not velocity etc
😂😭
Bro shooting strays
But yeah so i should do deltapos instead of all the velocity checks, is that what youre suggesting?
Zealan — Today at 1:36 AM
haha i dont even do 3d deltapos
i forgor i compressed it much further for higher accuracy
i do 2d deltapos
fredrik._. — Today at 1:37 AM
How do you eeven calculate aerial time then?
Zealan — Today at 1:37 AM
no no
not xy
or xz or something
fredrik._. — Today at 1:37 AM
Ah i see
Zealan — Today at 1:37 AM
the first dimension is the distance to the ball on xy 
the second is the z delta
fredrik._. — Today at 1:37 AM
Ok makes sense
But wont this systems accuracy be really bad? Compared to the normal one?
Zealan — Today at 1:38 AM
i compute time to surface not using the lookup table cause it would be too many dimensions
Zealan — Today at 1:38 AM
nah i compared them side by side
its pretty damn close
fredrik._. — Today at 1:38 AM
Damn
fredrik._. — Today at 1:39 AM
Too many dimentions?
Zealan — Today at 1:40 AM
that is what i said
fredrik._. — Today at 1:40 AM
Oh holy shit, i just figured out yeah looking at delta pos you can easily figure out the players velocity toot he target lmao
Thats soo fucking smart😂
Zealan — Today at 1:41 AM
yeah my thing is just
[xy dist][z dist][vel to target (1d float)][rot time][surface time] 
5 dimensions
fredrik._. — Today at 1:42 AM
Vel to target 1d float? How would that work?
What if its heading upwards
Zealan — Today at 1:43 AM
bruh
do you know how vpb works
💀
its like that
fredrik._. — Today at 1:43 AM
Oh shit
True🫣
Forgor
fredrik._. — Today at 1:55 AM
With this i assume you account for all possible values of these 4 dimentions excluding time to surfsce
Zealan — Today at 1:56 AM
"all possible values" wtf
a float has 4 billion possible values
fredrik._. — Today at 1:57 AM
Ohhh….
Zealan — Today at 1:58 AM
so with just 1 float
we need 4 billion floats in our lookup table
which is 16gb of ram