Help Utilizing a Parabolic Curve

Hello, I’m attempting to utilize a parabolic curve to simulate a feature in my game. The player is a battery and spawns in at 100% charged. As the round begins, the player’s battery charge should gradually begin to decline towards 0% at a relatively slow pace, then pick up the pace once it hits about 60%, then slow back down to the original pace around 20 or 30%.

I decided to attempt to use a parabolic curve to simulate the rate of change of how fast the battery’s charge declines, but no matter what I do, the battery charge always goes all the way to 0 in basically less than a second. Below is a desmos illustration of the graph I’m attempting to follow:


The formula I’m using is:
y = -a(h-0.5)^2 + c
a = 4.0
c = 1.05
The graph is centered at x = 0.5 so that if I’m understanding correctly, the battery will be draining at it’s fastest around 50% power. The code I’m using is below:

Module Script: GameLoop

local function drainRate(charge)
	local a = 4.0 -- Steepness of curve
	local c = 1.05
	local rate = -a * (charge - 0.5)^2 + c -- "a" is negative so the curve faces down and not up
	return math.max(rate * (1 / 600), 0) -- 1 / 600 multiplier supposed to slow decay rate down
end

function GameLoop.DecreaseCharge(dt)	
	for i, v in pairs(Players:GetPlayers()) do
		if v:GetAttribute("Playing") and v.Character:GetAttribute("Charging") == false then
			local character = v.Character
			
			local currentCharge = character:GetAttribute("Charge") or 100
			local normalizedCharge = math.clamp(currentCharge / 100, 0, 1) -- Set the charge to a number between 0 and 1 so it's easier to work with
			
			if normalizedCharge <= 0 then
				character.Humanoid:TakeDamage(100) -- Player is out of charge so they should die
				v:SetAttribute("Playing", false)
			end
			
			local rate = drainRate(normalizedCharge) -- Drain rate function utilization
		
			normalizedCharge = math.clamp(normalizedCharge - rate * dt, 0, 1) -- Something might be wrong here
			
			character:SetAttribute("Charge", math.floor(normalizedCharge * 100)) -- Set the new charge value to a percentage number between 0 and 100 percent so client can receive it
		end
	end
end

Main Server Script:

if workspace:GetAttribute("TestingMode") == false then
	local connection
	connection = RunService.Heartbeat:Connect(function(dt)
		GameLoopModule.DecreaseCharge(dt)
	end)
end

My suspicion heavily lies in how the Heartbeat function is handling the DecreaseCharge execution. I tested it at 240 fps and the battery ran out faster than when I tested it at 60 fps, but in both cases the rate at which the battery depleted was unbelievably fast (a second or less when it should be more like 30 seconds).

The weird thing is that before I tried the parabolic curve approach to this, I had been using an exponential decay function which worked completely fine, so I’m not sure why the Heartbeat would now be affecting things.

I added some comments to hopefully help people digest the code better, but I understand this is a lengthy and probably complex problem, so anyone who is willing to help is very much appreciated and I will do my best to provide any extra information. I’m not a super math wizard but I’m trying.

Okay well after moving everything to a separate place to make things a bit easier to process, I discovered that by doing
character:SetAttribute("Charge", math.floor(normalizedCharge * 100))
the math.floor(normalizedCharge * 100) was basically negating my entire function for the parabolic-like decay because math.floor removes all decimals and changes the number into a whole number, and because the changes in battery power were so small - like decimals - it basically looked as if I were decreasing the battery charge by 1% every frame instead of it’s actual decimal value.

If you look earlier in the code, the currentCharge variable utilizes the Charge attribute which is set to that percentage and it doesn’t even matter if I normalize it because the damage would’ve already been done in flooring the value and removing any kind of weight the parabolic function would’ve had in the rate of decay of the battery charge. I’ve made some changes, and to make the decay time as accurate as possible I went ahead and integrated the area under the parabolic curve so that it will take exactly 30 seconds for the battery charge to go to 0%.

A slight but crucial oversight, hopefully someone in 10 years finds this helpful.