Rust like building system not working

My system will not snap and the preview disappears a second after I place it

local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local replicatedStorage = game:GetService("ReplicatedStorage")
local userInput = game:GetService("UserInputService")
local runService = game:GetService("RunService")
local placementFolder = workspace:FindFirstChild("GameMechanics").PlacementFolder

-- Settings
local currentPrefab = replicatedStorage.GameMechanics.Prefab.Wall 
local snappingEnabled = true 
local buildingEnabled = false 
local placementObject

local function createPlacementGhost()
	placementObject = currentPrefab:Clone()
	placementObject.Parent = workspace
	placementObject.PrimaryPart = placementObject:FindFirstChild("PlacementPart") -- Ensure PlacementPart is PrimaryPart

	for _, part in ipairs(placementObject:GetDescendants()) do
		if part:IsA("BasePart") then
			part.Transparency = 0.5
			part.CanCollide = false
		end
	end
end

local function getPlacementPart(model)
	if model and model:IsA("Model") then
		return model:FindFirstChild("PlacementPart")
	end
	return nil
end

local function findNearestAttachment(position, parentModel)
	local closestAttachment = nil
	local closestDistance = math.huge

	local placementPart = getPlacementPart(parentModel)
	if not placementPart then return nil end

	for _, attachment in pairs(placementPart:GetChildren()) do
		if attachment:IsA("Attachment") then
			local distance = (position - attachment.WorldPosition).Magnitude
			if distance < closestDistance then
				closestDistance = distance
				closestAttachment = attachment
			end
		end
	end

	return closestAttachment
end

local function updatePlacement()
	if not placementObject or not buildingEnabled then return end

	local mouseRay = workspace.CurrentCamera:ScreenPointToRay(mouse.X, mouse.Y)
	local raycastParams = RaycastParams.new()
	raycastParams.FilterDescendantsInstances = {placementObject} -- Ignore ghost object
	raycastParams.FilterType = Enum.RaycastFilterType.Exclude
	local rayResult = workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 100, raycastParams)

	local targetPosition = rayResult and rayResult.Position or mouse.Hit.Position
	local ghostPlacementPart = getPlacementPart(placementObject)
	if not ghostPlacementPart then return end

	if snappingEnabled then
		local nearestAttachment = nil
		local closestDistance = math.huge

		for _, placedPrefab in pairs(placementFolder:GetChildren()) do
			local candidateAttachment = findNearestAttachment(targetPosition, placedPrefab)
			if candidateAttachment then
				local distance = (targetPosition - candidateAttachment.WorldPosition).Magnitude
				if distance < closestDistance then
					closestDistance = distance
					nearestAttachment = candidateAttachment
				end
			end
		end

		local ghostAttachment = findNearestAttachment(targetPosition, placementObject)

		if nearestAttachment and ghostAttachment then
			
			local offset = ghostAttachment.CFrame:Inverse() * ghostPlacementPart.CFrame
			local newCFrame = nearestAttachment.WorldCFrame * offset

			placementObject:SetPrimaryPartCFrame(newCFrame)
			return
		end
	end


	placementObject:SetPrimaryPartCFrame(CFrame.new(targetPosition))
end


local function placeObject()
	if placementObject and buildingEnabled then
		local newObj = currentPrefab:Clone()
		newObj:SetPrimaryPartCFrame(placementObject.PrimaryPart.CFrame)
		newObj.Parent = placementFolder

		placementObject:Destroy()
		placementObject = nil 
		createPlacementGhost()
	end
end


local tweenService = game:GetService("TweenService")
local playerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")

local notificationTemplate = replicatedStorage.GameMechanics.GUI:WaitForChild("Notification") -- Your notification GUI
local maxNotifications = 5 

local activeNotifications = {}

local function showNotification(text)
	local Cloned = notificationTemplate:Clone()
	Cloned.Parent = playerGui
	Cloned.Enabled = true
	Cloned.Frame.Frame.TextLabel.Text = text

	local baseYPosition = 0.3 -- Where the first notification starts (adjustable)
	local spacing = 0.07 -- Spacing between notifications
	local limit = maxNotifications * spacing -- Prevents going too low

	if #activeNotifications >= maxNotifications then
		local oldest = table.remove(activeNotifications, 1)
		oldest:Destroy()
	end

	local offset = #activeNotifications * spacing
	if offset > limit then offset = limit end

	Cloned.Frame.Position = UDim2.new(1, 50, baseYPosition + offset, 0) -- Start off-screen

	-- Tween into view
	local tweenIn = tweenService:Create(Cloned.Frame, TweenInfo.new(0.5, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), {
		Position = UDim2.new(0.8, 0, baseYPosition + offset, 0) -- Slide into view
	})

	tweenIn:Play()
	table.insert(activeNotifications, Cloned)

	task.delay(3, function()
		local tweenOut = tweenService:Create(Cloned.Frame, TweenInfo.new(0.5, Enum.EasingStyle.Quad, Enum.EasingDirection.In), {
			Position = UDim2.new(1, 50, baseYPosition + offset, 0) -- Slide out to the right
		})
		tweenOut:Play()
		tweenOut.Completed:Wait()
		Cloned:Destroy()
	end)
end

-- Example usage (when toggling snapping)
local function toggleSnapping()
	snappingEnabled = not snappingEnabled
	showNotification("Snapping " .. (snappingEnabled and "Enabled" or "Disabled"))
end

local istoggle = false
local toggled = false

-- Toggle Build Mode
local function toggleBuildMode()
	buildingEnabled = not buildingEnabled

	if buildingEnabled then
		if placementObject then
			placementObject:Destroy()
		end
		createPlacementGhost()
		istoggle = true
	else
		if placementObject then
			placementObject:Destroy()
			placementObject = nil
		end
		istoggle = false	
	end
end

-- Listen for keybinds
userInput.InputBegan:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.B then
		if toggled == false then
			toggled = true
			toggleBuildMode()
		else
			toggled = false
			toggleBuildMode()
		end
	end
end)

userInput.InputBegan:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.T then
		if buildingEnabled then -- Directly check if in build mode
			toggleSnapping()
		else
			showNotification("Can't toggle snapping outside of build mode.")
		end
	end
end)

runService.RenderStepped:Connect(updatePlacement)

mouse.Button1Down:Connect(placeObject)

its client sided on purpose to make sure placement is correct.
image

This seems a bit overcomplicated; am I really stupid or do you not have any code for snapping?

I think so their is no snapping maybe

Why are you saying snapping isn’t working if it isn’t a feature then? I can help you implement it if that is what you are asking. :))

Im trying to implement it. I think its implemented but wont snap for some reason

Look into math.clamp; which to my knowledge is the easiest way.

If you want it to snap to the nearest stud then you can just run math.round() for each coordinate in the position.
For example:

local Part = Instance.new("Part", workspace)
Part.Position = Vector3.new(1.6634, 9.123841, 2.312)

local Position = Part.Position
Part.Position = Vector3.new(math.round(Position.X), math.round(Position.Y), math.round(Position.Z)

-- the position of the part would be (2, 9, 2), snapped to nearest stud

Drawback to that is that it is you can’t have custom grid size.