NPC position different on client and server

hello roblox developers! as you can see in the title, npc positions are different on the client and server and this is a massive roadblock to the rest of my game, a fundamental mechanic, whatever you would like to call it. the reason for this is that i am trying to make a stealth game and i need the positions to stay the same for dragging unconcious npcs. I can’t have them be diffeent because then other alive npcs may see an unconcious npc in a different area than where the client sees them, if that makes sense. basically when i change the humanoidstatetype on the humanoid of the npc to “Physics” (on client or server) it does set it to physics, but when the npc falls it is different on the client and server because of the constraints that i have added to the npc on the client (it doesn’t work as well on server, otherwise problem solved) i should also note that the rig that i’m using is R6. here are the 2 scripts:

in my opinion i did not write this good, so chatgpt probably can explain it better:

When I change the HumanoidStateType of an NPC to “Physics” to enable dragging, it introduces discrepancies between the client and server. Despite successfully setting the state to physics, the NPCs’ positions differ upon falling due to constraints applied on the client side. This discrepancy poses a major obstacle as it can lead to inconsistencies where alive NPCs might see unconscious NPCs in different locations depending on the player’s perspective.

video showcasing:


anyways yeah, here’s the scripts:

server script:

local debris=game:GetService('Debris')

local event=game:GetService('ReplicatedStorage'):WaitForChild('Events'):WaitForChild('Ragdoll')

wait(4)
for i,v in pairs(workspace:GetChildren()) do
	if v:IsA("Model") and v:FindFirstChildOfClass("Humanoid") and not game:GetService("Players"):GetPlayerFromCharacter(v) then
		wait(.25)
		v.Humanoid:ChangeState(Enum.HumanoidStateType.Physics)
		local function push()
			local velRange = math.random() < 0.5 and {-100, -80} or {80, 100}
			local angularVelRange = math.random() < 0.5 and {-500, -90} or {90, 500}

			v.Torso:ApplyImpulse(Vector3.new(math.random(velRange[1], velRange[2]), 0, math.random(velRange[1], velRange[2])))
			v.Torso:ApplyAngularImpulse(Vector3.new(0,math.random(angularVelRange[1], angularVelRange[2]),0)) --(Vector3.new(math.random(angularVelRange[1], angularVelRange[2]), math.random(angularVelRange[1], angularVelRange[2]), math.random(angularVelRange[1], angularVelRange[2])))
		end
		push()
		event:FireAllClients(v)
	end
end

local script:

local player = game:GetService('Players').LocalPlayer
local Character = player.Character or player.CharacterAdded:Wait()
local debris = game:GetService('Debris')
local event = game:GetService('ReplicatedStorage'):WaitForChild('Events'):WaitForChild('Ragdoll')

Character:WaitForChild('Humanoid').Died:Connect(function()
	event:FireServer(Character.Name)
end)

event.OnClientEvent:Connect(function(model)
	local character = model

	if not character then return end

	if character.Name ~= Character.Name then
		local attachmentCFrames = {
			["Neck"] = {CFrame.new(0, 1, 0, 0, -1, 0, 1, 0, -0, 0, 0, 1), CFrame.new(0, -0.5, 0, 0, -1, 0, 1, 0, -0, 0, 0, 1)},
			["Left Shoulder"] = {CFrame.new(-1.3, 0.75, 0, -1, 0, 0, 0, -1, 0, 0, 0, 1), CFrame.new(0.2, 0.75, 0, -1, 0, 0, 0, -1, 0, 0, 0, 1)},
			["Right Shoulder"] = {CFrame.new(1.3, 0.75, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1), CFrame.new(-0.2, 0.75, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1)},
			["Left Hip"] = {CFrame.new(-0.5, -1, 0, 0, 1, -0, -1, 0, 0, 0, 0, 1), CFrame.new(0, 1, 0, 0, 1, -0, -1, 0, 0, 0, 0, 1)},
			["Right Hip"] = {CFrame.new(0.5, -1, 0, 0, 1, -0, -1, 0, 0, 0, 0, 1), CFrame.new(0, 1, 0, 0, 1, -0, -1, 0, 0, 0, 0, 1)},
		}
		
		character.Archivable = true
		local clone = character:Clone()
		clone.Name = ''
		clone.HumanoidRootPart.CanCollide = false

		local joints = clone:GetDescendants()

		clone.Humanoid.WalkSpeed = 0
		clone.Humanoid.JumpHeight = 0
		clone.Humanoid.PlatformStand = true
		clone.Humanoid.AutoRotate = false

		character.Parent = game:GetService('ReplicatedStorage'):WaitForChild('RagdollCache')
		debris:AddItem(character, 5)

		clone.Parent = workspace
		clone:FindFirstChild("Humanoid"):ChangeState(Enum.HumanoidStateType.Physics)

		if clone:FindFirstChild('Clothing') then
			for _, v in clone:FindFirstChild('Clothing'):GetDescendants() do
				if v:IsA('BasePart') or v:IsA('UnionOperation') or v:IsA('MeshPart') then
					v.CanCollide = false
				end
			end
		end
		
		local function push()
			local velRange = math.random() < 0.5 and {-100, -80} or {80, 100}
			local angularVelRange = math.random() < 0.5 and {-500, -90} or {90, 500}

			clone.Torso:ApplyImpulse(Vector3.new(math.random(velRange[1], velRange[2]), 0, math.random(velRange[1], velRange[2])))
			clone.Torso:ApplyAngularImpulse(Vector3.new(0,math.random(angularVelRange[1], angularVelRange[2]),0)) --(Vector3.new(math.random(angularVelRange[1], angularVelRange[2]), math.random(angularVelRange[1], angularVelRange[2]), math.random(angularVelRange[1], angularVelRange[2])))
		end
		
		local CPP = PhysicalProperties.new(3,.2,0.5)
		for i,v in pairs(clone:GetChildren()) do
			if v:IsA("BasePart") and not v.Name:find("Leg") then
				v.CustomPhysicalProperties = CPP
			end
		end
		push()
		
		local function createColliderPart(part: BasePart)
			if not part then return end
			local rp = Instance.new("Part")
			rp.Name = "ColliderPart"
			rp.Size = part.Size/1.7
			rp.Massless = true			
			rp.CFrame = part.CFrame
			rp.Transparency = 1

			local wc = Instance.new("WeldConstraint")
			wc.Part0 = rp
			wc.Part1 = part

			wc.Parent = rp
			rp.Parent = part
		end
		
		for _, motor in pairs(joints) do
			if motor:IsA("Motor6D") then
				if not attachmentCFrames[motor.Name] then return end
				motor.Enabled = false;
				local a0, a1 = Instance.new("Attachment"), Instance.new("Attachment")
				a0.CFrame = attachmentCFrames[motor.Name][1]
				a1.CFrame = attachmentCFrames[motor.Name][2]

				a0.Name = "RagdollAttachment"
				a1.Name = "RagdollAttachment"

				createColliderPart(motor.Part1)

				local b = Instance.new("BallSocketConstraint")
				b.Attachment0 = a0
				b.Attachment1 = a1
				b.Name = "RagdollConstraint"

				-- Set different constraint properties based on the joint
				if motor.Name == "Neck" then
					b.Radius = 0.1
					b.LimitsEnabled = true
					b.TwistLimitsEnabled = true
					b.UpperAngle = 45
					b.TwistLowerAngle = -70
					b.TwistUpperAngle = 70
				elseif motor.Name == "Left Shoulder" or motor.Name == "Right Shoulder" then
					b.Radius = 0.2
					b.LimitsEnabled = true
					b.TwistLimitsEnabled = true
					b.UpperAngle = 50
					b.TwistLowerAngle = -30
					b.TwistUpperAngle = 30
				elseif motor.Name == "Left Hip" or motor.Name == "Right Hip" then
					b.Radius = 0.3
					b.LimitsEnabled = true
					b.TwistLimitsEnabled = true
					b.UpperAngle = 25
					b.TwistLowerAngle = -30
					b.TwistUpperAngle = 30
				end

				b.MaxFrictionTorque = 0
				b.Restitution = 0

				a0.Parent = motor.Part0
				a1.Parent = motor.Part1
				b.Parent = motor.Parent
			end
		end
	end
end)

thanks very much for bothering to look at this topic, any and all help is greatly appreciated!