Problem with grid system math

im making grid system but i need help
rn i have an issue like in this image


i kinda bad in math so idk how to caculate the grid thing for dat

RunService.RenderStepped:Connect(function()
	if FuniturePlaceholder ~= nil then
		local FunitureInfo = require(ClientModule:WaitForChild("FunitureInfo")[FuniturePlaceholder.Name])
		local GetMouseRaycast = MainClient.MouseRaycast(FuniturePlaceholder)
		local RaycastInstance = GetMouseRaycast.Instance
				
		if GetMouseRaycast and RaycastInstance and RaycastInstance.Parent.Name == FunitureInfo.Place then
			local MousePos = GetMouseRaycast.Position
			local FuniturePos = Vector3.new(0, FuniturePlaceholder.PrimaryPart.Size.Y / 2, 0)
			FuniturePos += MousePos
			
			local X = math.round(FuniturePos.X / GridSize) * GridSize 
			local Y = math.round(FuniturePos.Y)
			local Z = math.round(FuniturePos.Z / GridSize) * GridSize 
			FuniturePos = CFrame.new(X, Y, Z)
			
			TweenService:Create(FuniturePlaceholder.PrimaryPart, TweenInfo.new(0.5, Enum.EasingStyle.Elastic), {CFrame = FuniturePos * CFrame.Angles(0, math.rad(Rotation), 0)}):Play()
		end
	end
end)

all help will be appreciate

I was recently developing a similar system and encountered the same issue, which is due to a particular behavior when rounding positions. This behavior arises because the rounding process doesn’t consider the boundaries correctly, leading to discrepancies.

I used this code where I set a posAnt variable to store the “axis” position, so to speak, of the object where we want to place it. This posAnt serves as a reference point to help us manage the rounding of the position.

My rounding function works as follows:

local function snap(x: number, posAnt: number, offset: number) : number
	if not offset then
		offset = 0
	end
	local rounded : number = bloqueDimension * math.floor(x / bloqueDimension + offset)

	if x <= posAnt - bloqueDimension/2 then
		rounded = bloqueDimension * math.floor((x-0.01) / bloqueDimension + offset)
	end

	return rounded + bloqueDimension/2
end

And this is how I use it

local rayo = Ray.new(mouse.UnitRay.Origin, (mouse.UnitRay.Direction * 1000)) -- Crear un rayo desde el mouse

		local raycastParams = RaycastParams.new()
		raycastParams.FilterDescendantsInstances = objetosAIgnorar

		local raycastResult = game.Workspace:Raycast(rayo.Origin, rayo.Direction * 1000, raycastParams)

		if raycastResult then
			local hit = raycastResult.Instance
			local punto = raycastResult.Position

			if not posAnterior then
				posAnterior = clonVistaPrevia.Position
			end

			clonPruebas.Position = punto
			clonVistaPrevia.Position = Vector3.new(
				snap(punto.X, posAnterior.X),
				snap(punto.Y, posAnterior.Y, 0.01),
				snap(punto.Z, posAnterior.Z)
			)
			
			if posAnterior ~= clonVistaPrevia.Position then
				posAnterior = clonVistaPrevia.Position
				if hit.Name == "Default" then
					posAnterior = hit.Position
				end
			end

			clonPosAnterior.Position = posAnterior
		end

I’m attaching a video for you to better understand (the red cube represents the posAnt position).