Character stops exactly below water surface when Weld.C0 is updated continuously

Description:

When a Weld attached to the character (e.g., the Torso) continuously updates its C0 property — even by extremely small amounts — the character becomes stuck just below the water surface.

Normally, Roblox buoyancy brings the head halfway above water when swimming (without pressing Jump). However, with continuous C0 updates, the character rises normally until reaching the surface, then stops exactly under the waterline and cannot rise any further.

This behavior persists until the C0 updates stop, at which point the character immediately continues to rise and behaves normally again.


Steps to Reproduce:

  1. Create a Part (or any instance) with a Weld connected to the character’s Torso.

  2. Place a ClickDetector inside a part in the Workspace named PressMe (used to stop the loop).

  3. Run the following ServerScript:

    local STOP = false
    
    game.Players.PlayerAdded:Connect(function(p)
        p.CharacterAdded:Connect(function(c)
            local w = game.ReplicatedStorage.BugTool:Clone()
            w.Weld.Part0 = c:FindFirstChild("Torso")
            w.Parent = c
    
            local x = 0
            while not STOP do
                task.wait()
                if x ~= 0 then
                    x = 0
                else
                    x += 0.0001
                end
                w.Weld.C0 = CFrame.new(x, 0, 0)
            end
        end)
    end)
    
    game.Workspace.PressMe.ClickDetector.MouseClick:Connect(function()
        STOP = true
    end)
    
  4. Enter water with the character. (R6 is required)

  5. Observe that the character rises to the surface, then stops exactly below it and remains stuck there. (Jumping will not fix it. You will be dragged under the water level)

  6. Click the “PressMe” part — the script stops, and the character immediately continues floating normally.


Repro Summary:

  • Occurs 100% of the time with any continuous C0 modification.
  • Works on R6 only.
  • Reproducible in Studio and Roblox Player.
  • Stops immediately once the Weld updates stop.

Testing Environment:

  • Tested on a fresh Baseplate with water terrain.
  • Roblox Studio and live Player, both consistent.
  • No other scripts, constraints, or plugins active.

Repro file:
Bug_Maybe.rbxl (73.1 KB)

In-Game Footage (Roblox Player)
Clip hosted externally due to file size limit.
Watch on Streamable


Expected Behavior:

The character should continue to rise naturally until (~half of) the head is above the water surface, regardless of small Weld C0 updates.


Actual Behavior:

When Weld.C0 is continuously modified (even by ±0.0001), the buoyancy solver seems to lose its ability to push the character through the surface. The character remains perfectly aligned with the water surface from below until the updates stop.

1 Like

Dear Blivy, thanks for reporting this bug with a well-structured repro file. While it seems to be true that using a weld here and changing it’s offset affects the buoyancy behavior at water surface, you can use the Motor6D [0] class to achieve the same. It behaves more stably, and the water surface behavior works as expected even when the offset for the welded accessory is being continuously updated. Welds are supposed to be used when you expect the part positions to change infrequently. The behavior you are seeing is because on each cframe update, it is resetting buoyancy calculations.

In the studio, you can enable contact visualization from the visualization menu (gear eye icon top left corner of your play window) by turning on Contact Points under Physics Simulations. You can also enable it in Studio settings (‘Are Contact Points Shown’ toggle, 'Are Contact Islands Shown` toggle would also be helpful).

The way the buoyancy system works is that buoyancy contact points are created when a body is under water. These contact points are then used for calculating the buoyant force. If you turn on the visualization, you will see that the contact points flicker a lot when w.Weld.C0 = CFrame.new(x, 0, 0) is run. When the buoyancy contacts are reset, buoyant force goes to zero. Moreover, Weld updates are much more expensive than manipulating Motor6D since Weld updates run more physics calculations. For continuous updates between two parts, Motor6D is the better option.

[0] https://create.roblox.com/docs/reference/engine/classes/Motor6D

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.