How do I make a boat stick to the surface of the skinned mesh ocean?

I tried to do this myself but have no idea how to do this. Just curious if anyone knows how and could help. Thanks!

Local Wave Script in StarterPlayerScripts:

local Wave = require(script.Wave)
local Plane = workspace:WaitForChild("Wave"):WaitForChild("Plane")
local Settings = {
	WaveLength    = 90,
	Direction     = Vector2.new(0, 0),
	Steepness     = 0.6,
	TimeModifier  = 8,
	MaxDistance   = 1500,
}
local TestWave = Wave.new(Plane, Settings)
TestWave:ConnectRenderStepped()
_G.OceanWave = TestWave

Module Script | Child of Local Wave Script:

local Wave = {}
Wave.__index = Wave

local newCFrame       = CFrame.new
local IdentityCFrame  = newCFrame()
local EmptyVector2    = Vector2.new()
local math_noise      = math.noise
local Stepped         = game:GetService("RunService").RenderStepped
local Player          = game:GetService("Players").LocalPlayer

local default = {
	WaveLength    = 85,
	Gravity       = 1.5,
	Direction     = Vector2.new(1, 0),
	PushPoint     = nil,
	Steepness     = 1,
	TimeModifier  = 4,
	MaxDistance   = 1500,
}

local function Gerstner(pos, wavelength, direction, steepness, gravity, time)
	local k    = (2 * math.pi) / wavelength
	local a    = steepness / k
	local d    = direction.Unit
	local c    = math.sqrt(gravity / k)
	local f    = k * d:Dot(Vector2.new(pos.X, pos.Z)) - c * time
	local cosF = math.cos(f)

	local dX = d.X * (a * cosF)
	local dY = a * math.sin(f)
	local dZ = d.Y * (a * cosF)
	return Vector3.new(dX, dY, dZ)
end

local function CreateSettings(s, o)
	o = o or {}
	s = s or default
	return {
		WaveLength   = s.WaveLength   or o.WaveLength   or default.WaveLength,
		Gravity      = s.Gravity      or o.Gravity      or default.Gravity,
		Direction    = s.Direction    or o.Direction    or default.Direction,
		PushPoint    = s.PushPoint    or o.PushPoint    or default.PushPoint,
		Steepness    = s.Steepness    or o.Steepness    or default.Steepness,
		TimeModifier = s.TimeModifier or o.TimeModifier or default.TimeModifier,
		MaxDistance  = s.MaxDistance  or o.MaxDistance  or default.MaxDistance,
	}
end

local function GetDirection(settings, worldPos)
	if settings.PushPoint then
		local target = settings.PushPoint:IsA("Attachment")
			and settings.PushPoint.WorldPosition
			or settings.PushPoint.Position
		local dir3 = (target - worldPos).Unit
		return Vector2.new(dir3.X, dir3.Z)
	end
	return settings.Direction
end

function Wave.new(instance, waveSettings, bones)
	bones = bones or {}
	for _, v in pairs(instance:GetDescendants()) do
		if v:IsA("Bone") then
			table.insert(bones, v)
		end
	end

	return setmetatable({
		_instance    = instance,
		_bones       = bones,
		_time        = 0,
		_connections = {},
		_settings    = CreateSettings(waveSettings),
	}, Wave)
end

function Wave:Update()
	local center   = self._instance.Position
	local settings = self._settings

	for _, bone in pairs(self._bones) do
		local worldPos = bone.WorldPosition
		local dir = settings.Direction == EmptyVector2
			and Vector2.new(math_noise(worldPos.X, worldPos.Z, 1), math_noise(worldPos.X, worldPos.Z, 0))
			or GetDirection(settings, worldPos)

		local dist = (worldPos - center).Magnitude
		local minSteep, maxSteep = 0.1, 0.2
		local steepness = dist <= 100
			and (minSteep + (maxSteep - minSteep) * (dist / 100))
			or settings.Steepness

		bone.Transform = newCFrame(
			Gerstner(worldPos, settings.WaveLength, dir, steepness, settings.Gravity, self._time)
		)
	end
end

function Wave:Refresh()
	for _, bone in pairs(self._bones) do
		bone.Transform = IdentityCFrame
	end
end

function Wave:UpdateSettings(waveSettings)
	self._settings = CreateSettings(waveSettings, self._settings)
end

function Wave:ConnectRenderStepped()
	local conn = Stepped:Connect(function()
		if not game:IsLoaded() then return end
		local char = Player.Character
		if not char or (char.PrimaryPart.Position - self._instance.Position).Magnitude < self._settings.MaxDistance then
			self._time = (DateTime.now().UnixTimestampMillis / 1000) / self._settings.TimeModifier
			self:Update()
		else
			self:Refresh()
		end
	end)
	table.insert(self._connections, conn)
	return conn
end

function Wave:GetHeightAtXZ(worldX, worldZ)
	local settings = self._settings
	local worldPos = Vector3.new(worldX, 0, worldZ)
	local dir = settings.Direction == EmptyVector2
		and Vector2.new(math_noise(worldX, worldZ, 1), math_noise(worldX, worldZ, 0))
		or GetDirection(settings, worldPos)
	local disp = Gerstner(worldPos, settings.WaveLength, dir, settings.Steepness, settings.Gravity, self._time)
	return self._instance.Position.Y + disp.Y
end

function Wave:Destroy()
	self._instance = nil
	for _, conn in pairs(self._connections) do
		pcall(function() conn:Disconnect() end)
	end
	self._bones    = {}
	self._settings = {}
end

return Wave

Boat Positioner Local Script in StarterPlayerScripts:

local RunService = game:GetService("RunService")
local wave      = _G.OceanWave
local boat      = workspace:WaitForChild("Boat")
local hull      = boat.PrimaryPart

RunService.Heartbeat:Connect(function()
	local pos    = hull.Position
	local height = wave:GetHeightAtXZ(pos.X, pos.Z)
	boat:SetPrimaryPartCFrame(CFrame.new(pos.X, height, pos.Z))
end)

Boat Model:

Any ideas would be a big help. Thanks in advance.