Physical wheel and gui wheel not spinning in sync?

I’m trying to make a wheel that a player clicks, and it opens a gui so that the player has a better view of the spinning wheel.
I kinda got it to work, except the wheels become out of sync sometimes. And not just a little bit out of sync, sometimes the physical wheel lands on something completely different from the gui wheel. (juijjg - Roblox Studio 2021-08-27 14-10-08 - The last spin shows what I’m talking about) Does anyone know why they’re out of sync and how to fix it?

--Server Script
math.randomseed(tick())
local rs = game:GetService("ReplicatedStorage")
local ts = game:GetService("TweenService")
local SpinRouletteEvent = rs:WaitForChild("SpinRouletteEvent")
local wheel = script.Parent.Parent
 
local RouletteResults = {
    [0] = "Eye",
    [1] = "Ear",
    [2] = "Arm",
    [3] = "Leg",
    [4] = "Voice"
}
table.insert(RouletteResults, RouletteResults[0])
local spinning = false
script.Parent.MouseClick:Connect(function(clicker)
    if spinning == false then
        spinning = true
        local degrees = math.random(1, 359)
        local tweentime = math.random(200,1000)/100
        local extraspins = math.random(3, 15)
        SpinRouletteEvent:FireClient(clicker, degrees, RouletteResults, tweentime, extraspins)
        local result = math.round(degrees/(360/#RouletteResults))
        local tween = ts:Create(wheel, TweenInfo.new(tweentime, Enum.EasingStyle.Exponential, Enum.EasingDirection.Out), {Orientation = Vector3.new(degrees + (360 * extraspins), wheel.Orientation.Y, wheel.Orientation.Z)})
        tween:Play()
        tween.Completed:Wait()
        print(RouletteResults[result])
        wait(2.1)
        spinning = false
    end
end)
--Local Script
local player = game.Players.LocalPlayer
local pgui = player:WaitForChild("PlayerGui")
local gui = pgui:WaitForChild("RouletteGui")
local ts = game:GetService("TweenService")
local rs = game:GetService("ReplicatedStorage")
local SpinRouletteEvent = rs:WaitForChild("SpinRouletteEvent")
local runs = game:GetService("RunService")
 
local function spin(degrees, RouletteResults, tweentime, extraspins)
    gui.Enabled = true
    gui.Frame.WheelImage.Size = UDim2.new(1,0,1,0)
    gui.Frame.Pointer.Position = UDim2.new(0.5,0,0,0)
    local result = math.round(degrees/(360/#RouletteResults))
    local tween = ts:Create(gui.Frame.WheelImage, TweenInfo.new(tweentime, Enum.EasingStyle.Exponential, Enum.EasingDirection.Out), {Rotation = degrees + (360 * extraspins)})
    tween:Play()
    local previous = math.round(gui.Frame.WheelImage.Rotation/(360/#RouletteResults))
    while tween.PlaybackState ~= Enum.PlaybackState.Completed do
        if previous ~= math.round(gui.Frame.WheelImage.Rotation/(360/#RouletteResults)) then
            previous = math.round(gui.Frame.WheelImage.Rotation/(360/#RouletteResults))
            gui.Tick:Play()
        end
        runs.RenderStepped:Wait()
    end
    local sizetween = ts:Create(gui.Frame.WheelImage, TweenInfo.new(0.2), {Size = UDim2.new(1.1,0,1.1,0)})
    local postween = ts:Create(gui.Frame.Pointer, TweenInfo.new(0.2), {Position = UDim2.new(0.5,0,-0.1,0)})
    sizetween:Play()
    postween:Play()
    gui.Yay:Play()
    postween.Completed:Wait()
    if sizetween.PlaybackState ~= Enum.PlaybackState.Completed then
        sizetween.Completed:Wait()
    end
    print(RouletteResults[result])
    wait(2)
    gui.Enabled = false
end
 
SpinRouletteEvent.OnClientEvent:Connect(spin)

On that one, you did 2.1, but on this one you did:

If I were you, I would change that. It might work. I hope it does.

The wait(2.1) on the server script is just to be positive that the wheel can’t be spun again before the local script on the player’s side is finished running.
I tried changing it to wait(2) and it still sometimes doesn’t work.

Maybe try a ViewPort Frame
I made it so the wheel in the viewport frame would sync with the wheel using CFrame. That should make it sync and easier than UI.