local x={number={springType=LinearSpring.new,toIntermediate=function(x)return{x}end,fromIntermediate=function(x)return x[1]end},NumberRange={springType=LinearSpring.new,toIntermediate=function(x)return{x.Min,x.Max}end,fromIntermediate=function(x)return NumberRange.new(x[1],x[2])end},UDim={springType=LinearSpring.new,toIntermediate=function(x)return{x.Scale;x.Offset}end,fromIntermediate=function(x)return UDim.new(x[1],x[2])end};UDim2={springType=LinearSpring.new,toIntermediate=function(x)local N=x.X local M=x.Y return{N.Scale;N.Offset;M.Scale;M.Offset}end,fromIntermediate=function(x)return UDim2.new(x[1],x[2],x[3],x[4])end},Vector2={springType=LinearSpring.new;toIntermediate=function(x)return{x.X,x.Y}end;fromIntermediate=function(x)return Vector2.new(x[1],x[2])end},Vector3={springType=LinearSpring.new,toIntermediate=function(x)return{x.X;x.Y,x.Z}end;fromIntermediate=function(x)return Vector3.new(x[1],x[2],x[3])end},Color3={springType=LinearSpring.new;toIntermediate=function(x)local N,M,f=x.R,x.G,x.B N=N<.040448236277108 and N/12.92 or.87941546140213*(N+.055)^2.4 M=M<.040448236277108 and M/12.92 or.87941546140213*(M+.055)^2.4 f=f<.040448236277108 and f/12.92 or.87941546140213*(f+.055)^2.4 local R=(.92570639729519*N-.83337363237799*M)-.092098206660859*f local p=(.2125862307856*N+.71517030370341*M)+.072200498643336*f local h=(3.6590806972266*N+11.442689580057*M)+4.1149915024265*f local u=p>.0088564516790356 and 116*p^.33333333333333-16 or 903.2962962963*p local i,y if h>1e-14 then i=(u*R)/h y=u*((9*p)/h-.46832)else i=-0.19783*u y=-0.46832*u end return{u,i,y}end;fromIntermediate=function(x)local N=x[1]if N<.0197955 then return Color3.new(0,0,0)end local M=x[2]/N+.19783 local f=x[3]/N+.46832 local R=(N+16)/116 R=R>.20689655172414 and(R*R)*R or.12841854934602*R-.017712903358071 local p=(R*M)/f local h=R*((3-.75*M)/f-5)local u=(7.2914074*p-1.537208*R)-.4986286*h local i=(-2.180094*p+1.8757561*R)+.0415175*h local y=(.1253477*p-.2040211*R)+1.0569959*h if u<0 and(u<i and u<y)then u,i,y=0,i-u,y-u elseif i<0 and i<y then u,i,y=u-i,0,y-i elseif y<0 then u,i,y=u-y,i-y,0 end return Color3.new(min(u<.0031306684425 and 12.92*u or 1.055*u^.41666666666667-.055,1),min(i<.0031306684425 and 12.92*i or 1.055*i^.41666666666667-.055,1),min(y<.0031306684425 and 12.92*y or 1.055*y^.41666666666667-.055,1))end}}local N={}RunService.Heartbeat:Connect(function(x)debug.profilebegin("\115\112\114 \104\101\097\114\116\098\101\097\116")for M,f in pairs(N)do for N,R in pairs(f)do if R:canSleep()then f[N]=nil M[N]=R.rawTarget else M[N]=R:step(x)end end if not next(f)then N[M]=nil end end debug.profileend()end)local function M(x,N,M,f)if not STRICT_TYPES then return end if not M:find(typeof(f))then error(("\098\097\100 \097\114\103\117\109\101\110\116 \035\037\100 \116\111 \037\115 \040\037\115 \101\120\112\101\099\116\101\100\044 \103\111\116 \037\115\041"):format(x,N,M,typeof(f)),3)end end local f={}function f.target(f,R,p,h)M(1,"\115\112\114\046\116\097\114\103\101\116","\073\110\115\116\097\110\099\101",f)M(2,"\115\112\114\046\116\097\114\103\101\116","\110\117\109\098\101\114",R)M(3,"\115\112\114\046\116\097\114\103\101\116","\110\117\109\098\101\114",p)M(4,"\115\112\114\046\116\097\114\103\101\116","\116\097\098\108\101",h)if R~=R or R<0 then error(("\101\120\112\101\099\116\101\100 \100\097\109\112\105\110\103 \114\097\116\105\111 \062\061 \048\059 \103\111\116 \037\046\050\102"):format(R),2)end if p~=p or p<0 then error(("\101\120\112\101\099\116\101\100 \117\110\100\097\109\112\101\100 \102\114\101\113\117\101\110\099\121 \062\061 \048\059 \103\111\116 \037\046\050\102"):format(p),2)end local u=N[f]if not u then u={}N[f]=u end for N,M in pairs(h)do local h=f[N]if STRICT_TYPES and typeof(M)~=typeof(h)then error(("\098\097\100 \112\114\111\112\101\114\116\121 \037\115 \116\111 \115\112\114\046\116\097\114\103\101\116 \040\037\115 \101\120\112\101\099\116\101\100\044 \103\111\116 \037\115\041"):format(N,typeof(h),typeof(M)),2)end local i=u[N]if not i then local f=x[typeof(M)]if not f then error("\117\110\115\117\112\112\111\114\116\101\100 \116\121\112\101\058 "..typeof(M),2)end i=f.springType(R,p,h,f,M)u[N]=i end i.d=R i.f=p i:setGoal(M)end end function f.stop(x,f)M(1,"\115\112\114\046\115\116\111\112","\073\110\115\116\097\110\099\101",x)M(2,"\115\112\114\046\115\116\111\112","\115\116\114\105\110\103\124\110\105\108",f)if f then local M=N[x]if M then M[f]=nil end else N[x]=nil end end if STRICT_API_ACCESS then return tableLock(f)else return f end
-- transforms Roblox types into intermediate types, converting
-- between spaces as necessary to preserve perceptual linearity
local typeMetadata = {
	number = {
		springType = LinearSpring.new,

		toIntermediate = function(value)
			return { value }
		end,

		fromIntermediate = function(value)
			return value[1]
		end,
	},

	NumberRange = {
		springType = LinearSpring.new,

		toIntermediate = function(value)
			return { value.Min, value.Max }
		end,

		fromIntermediate = function(value)
			return NumberRange.new(value[1], value[2])
		end,
	},

	UDim = {
		springType = LinearSpring.new,

		toIntermediate = function(value)
			return { value.Scale, value.Offset }
		end,

		fromIntermediate = function(value)
			return UDim.new(value[1], value[2])
		end,
	},

	UDim2 = {
		springType = LinearSpring.new,

		toIntermediate = function(value)
			local x = value.X
			local y = value.Y
			return { x.Scale, x.Offset, y.Scale, y.Offset }
		end,

		fromIntermediate = function(value)
			return UDim2.new(value[1], value[2], value[3], value[4])
		end,
	},

	Vector2 = {
		springType = LinearSpring.new,

		toIntermediate = function(value)
			return { value.X, value.Y }
		end,

		fromIntermediate = function(value)
			return Vector2.new(value[1], value[2])
		end,
	},

	Vector3 = {
		springType = LinearSpring.new,

		toIntermediate = function(value)
			return { value.X, value.Y, value.Z }
		end,

		fromIntermediate = function(value)
			return Vector3.new(value[1], value[2], value[3])
		end,
	},

	Color3 = {
		springType = LinearSpring.new,

		toIntermediate = function(value)
			-- convert RGB to a variant of cieluv space
			local r, g, b = value.R, value.G, value.B

			-- D65 sRGB inverse gamma correction
			r = r < 0.0404482362771076 and r / 12.92 or 0.87941546140213 * (r + 0.055) ^ 2.4
			g = g < 0.0404482362771076 and g / 12.92 or 0.87941546140213 * (g + 0.055) ^ 2.4
			b = b < 0.0404482362771076 and b / 12.92 or 0.87941546140213 * (b + 0.055) ^ 2.4

			-- sRGB -> xyz
			local x = 0.9257063972951867 * r - 0.8333736323779866 * g - 0.09209820666085898 * b
			local y = 0.2125862307855956 * r + 0.71517030370341085 * g + 0.0722004986433362 * b
			local z = 3.6590806972265883 * r + 11.4426895800574232 * g + 4.1149915024264843 * b

			-- xyz -> modified cieluv
			local l = y > 0.008856451679035631 and 116 * y ^ (1 / 3) - 16 or 903.296296296296 * y

			local u, v
			if z > 1e-14 then
				u = l * x / z
				v = l * (9 * y / z - 0.46832)
			else
				u = -0.19783 * l
				v = -0.46832 * l
			end

			return { l, u, v }
		end,

		fromIntermediate = function(value)
			-- convert back from modified cieluv to rgb space

			local l = value[1]
			if l < 0.0197955 then
				return Color3.new(0, 0, 0)
			end
			local u = value[2] / l + 0.19783
			local v = value[3] / l + 0.46832

			-- cieluv -> xyz
			local y = (l + 16) / 116
			y = y > 0.206896551724137931 and y * y * y or 0.12841854934601665 * y - 0.01771290335807126
			local x = y * u / v
			local z = y * ((3 - 0.75 * u) / v - 5)

			-- xyz -> D65 sRGB
			local r = 7.2914074 * x - 1.5372080 * y - 0.4986286 * z
			local g = -2.1800940 * x + 1.8757561 * y + 0.0415175 * z
			local b = 0.1253477 * x - 0.2040211 * y + 1.0569959 * z

			-- clamp minimum sRGB component
			if r < 0 and r < g and r < b then
				r, g, b = 0, g - r, b - r
			elseif g < 0 and g < b then
				r, g, b = r - g, 0, b - g
			elseif b < 0 then
				r, g, b = r - b, g - b, 0
			end

			-- gamma correction from D65
			-- clamp to avoid undesirable overflow wrapping behavior on certain properties (e.g. BasePart.Color)
			return Color3.new(
				min(r < 3.1306684425e-3 and 12.92 * r or 1.055 * r ^ (1 / 2.4) - 0.055, 1),
				min(g < 3.1306684425e-3 and 12.92 * g or 1.055 * g ^ (1 / 2.4) - 0.055, 1),
				min(b < 3.1306684425e-3 and 12.92 * b or 1.055 * b ^ (1 / 2.4) - 0.055, 1)
			)
		end,
	},
}

local springStates = {} -- {[instance] = {[property] = spring}

RunService.Heartbeat:Connect(function(dt)
	debug.profilebegin("spr heartbeat")
	for instance, state in pairs(springStates) do
		for propName, spring in pairs(state) do
			if spring:canSleep() then
				state[propName] = nil
				instance[propName] = spring.rawTarget
			else
				instance[propName] = spring:step(dt)
			end
		end

		if not next(state) then
			springStates[instance] = nil
		end
	end
	debug.profileend()
end)

local function assertType(argNum, fnName, expectedType, value)
	if not STRICT_TYPES then
		return
	end

	if not expectedType:find(typeof(value)) then
		error(("bad argument #%d to %s (%s expected, got %s)"):format(argNum, fnName, expectedType, typeof(value)), 3)
	end
end

local spr = {}

function spr.target(instance, dampingRatio, frequency, properties)
	assertType(1, "spr.target", "Instance", instance)
	assertType(2, "spr.target", "number", dampingRatio)
	assertType(3, "spr.target", "number", frequency)
	assertType(4, "spr.target", "table", properties)

	if dampingRatio ~= dampingRatio or dampingRatio < 0 then
		error(("expected damping ratio >= 0; got %.2f"):format(dampingRatio), 2)
	end

	if frequency ~= frequency or frequency < 0 then
		error(("expected undamped frequency >= 0; got %.2f"):format(frequency), 2)
	end

	local state = springStates[instance]

	if not state then
		state = {}
		springStates[instance] = state
	end

	for propName, propTarget in pairs(properties) do
		local propValue = instance[propName]

		if STRICT_TYPES and typeof(propTarget) ~= typeof(propValue) then
			error(
				("bad property %s to spr.target (%s expected, got %s)"):format(
					propName,
					typeof(propValue),
					typeof(propTarget)
				),
				2
			)
		end

		local spring = state[propName]
		if not spring then
			local md = typeMetadata[typeof(propTarget)]

			if not md then
				error("unsupported type: " .. typeof(propTarget), 2)
			end

			spring = md.springType(dampingRatio, frequency, propValue, md, propTarget)
			state[propName] = spring
		end

		spring.d = dampingRatio
		spring.f = frequency
		spring:setGoal(propTarget)
	end
end

function spr.stop(instance, property)
	assertType(1, "spr.stop", "Instance", instance)
	assertType(2, "spr.stop", "string|nil", property)

	if property then
		local state = springStates[instance]
		if state then
			state[property] = nil
		end
	else
		springStates[instance] = nil
	end
end

if STRICT_API_ACCESS then
	return tableLock(spr)
else
	return spr
end
