Make Phantom-Forces-esqe Recoil Patterns

Ive been trying to create FPS recoil for a while now, but i cant seem to replicate the recoil of games like phantom forces or entry point

recoil from games like phantom forces or other major fps games look like this:
Screenshot 2023-05-21 at 8.44.04 AM
notice how the gun’s spread is contained in a small area?

heres my lates attempt to create recoil (i havent bothered constraining it yet because this attempt simply isnt worth the time)


the main problem here is that the gun jumps to a higher “layer”, caused by the gun firing before it has fallen back.

visual representation of what i mean

i have a simplified version my my script here:
local curshot = 0
local drift = Vector3.new()

function lerp(a, b, t) -- Gets a number between two points using an alpha
	return a * (1 - t) + (b * t)
end

function ShootRecoil()
	curshot += 1
	local RecoilStats = WeaponStats[Viewmodel.ItemName]['RecoilPattern'][1]
	if curshot > RecoilStats[1] then RecoilStats = WeaponStats[Viewmodel.ItemName]['RecoilPattern'][2] end
	
	local ticks = 0
	task.spawn(function()
		local VertDrift = math.random(80,120)/100
		local HoriDrift = (1 - math.random(0,1) * 2) * math.random(90,110)/100
		
		local rp = RecoilStats[2] --rp stands for 'recoilpattern'
		while ticks <= rp['h'] * 2 * RecoilStats[3] * VertDrift do
			local dt = RunService.Heartbeat:Wait()
			
			local lastrecoil = (rp['a'] * (ticks - rp['h'])^2 + rp['k'])/2
			ticks += dt
			local recoil = (rp['a'] * (math.min(ticks, rp['h'] * 2) - rp['h'])^2 + rp['k'])/2
			
			local x,y,z = (recoil - lastrecoil), (rp['k'] * (RecoilStats[4])/2) * HoriDrift * dt * 60, 0
			
			workspace.Camera.CFrame *= CFrame.Angles(math.rad(x), math.rad(y), math.rad(z))
		end
	end)
end

local FireViewmodelLoop = true
FireLoop = coroutine.wrap(function()
	while FireViewmodelLoop do
		local dt = RunService.Heartbeat:Wait()
		if MouseButton1Down then
			if not automatic then
				MouseButton1Down = false
			end
			
			--RECOIL--
			ShootRecoil()
			Viewmodel:PlayAnim('Fire')
			----
			
			task.wait(60/WeaponStats[Viewmodel.ItemName]['FireRate'])
		else
			curshot = 0
		end
	end
end)
FireLoop()

does anybody have an idea of how to acomplish this? i havent seen a devforum post that achieves what ive been looking for.

I imagine you’re asking for wanting your spread to be concentrated in a circular area, something like this correct?
image
If so, here’s a good post by a user that translates the mathematical formula behind this into code;
Link