I use a frictionless WedgePart, and my jet model with wheels and suspensions installed. Server-sided. This is how I set the constraints up for both models:

Thrust is relative to Attachment, while Drag and Lift to world. AlignOrientation is for testing AoA, and just how airplanes pitch up to lift.
WedgePart
Last time, it works for the first few seconds, where it manages to takeoff, then wobbles and disappear after. The values outputted large numbers. I used @Imperiector’s drag formula which worked for me.
Today, I gave another try with both drag models. Both WedgeParts disintegrated even without thrust. As how I remember Roblox physics is, there’s like a tiny amount of velocity on unanchored parts when the simulation starts.
I tried to print out both drag and lift value to check for large numbers. What’s weird is, there is no large numbers, other than decimals and decimals. It’s last print output when the WedgePart disappears is 0.
There was a Roblox update recently, and that’s when it started to happen even with @Imperiector’s drag formula. This is definitely something to do with their engine.
I cannot share you a clip of it’s wobbly motion, as the WedgePart itself disappears into thin air before doing anything.
Rigged Jet
Jet is pitched upwards 5 degrees.
Not far different, except it doesn’t immediately fling with @Imperiector’s formula.
I’ve tried with both suspensions enabled and disabled, they react the same way.
The worst of all, I did barely any modifications to the code and when I started to clip them, they don’t seem to react anymore. It’s my horrible part of programming when it comes to not knowing the root of an issue.
Latest version of my script:
local pt = script.Parent
local airDensity = 1.2
local liftPower = 100
local CD = 0.01
local S = 2 -- ¯\_(ツ)_/¯
local wingAR = 3
local oswald = 0.7
function calculateLift(velocity: Vector3, AoA)
AoA = math.deg(AoA)
print(AoA)
local CL = math.clamp(math.sin(math.rad(3 * AoA)), -60, 60)
return 0.5 * velocity.Magnitude^2 * CL * liftPower, CL
end
function calculateDrag(velocity: Vector3, pressure, CL, AoA)
local CLAoA = CL * AoA
local CD_i = CLAoA^2 / (3.14 * wingAR * oswald)
return (CD + CD_i) * pressure * S
end
function calculateAOA(velocity, CF)
local AoA
if velocity.Magnitude > 0 then
-- Project the velocity vector onto the plane's vertical plane
local projectedVelocity = velocity.Unit - CF.RightVector * velocity.Unit:Dot(CF.RightVector)
-- Calculate the angle between the projected velocity and look vector
local dotProduct = math.clamp(CF.LookVector:Dot(projectedVelocity.Unit), -1, 1)
local cross = CF.LookVector:Cross(projectedVelocity.Unit)
local sign = cross:Dot(CF.RightVector) > 0 and -1 or 1 -- Check if the aoa is pos/neg, if nose is above/below velocity vector
-- Convert to degrees and apply neg or pos, clamp from -90 to 90
AoA = math.clamp(math.deg(math.acos(dotProduct)) * sign, -90, 90)
else AoA = 0 end -- To avoid NaN AoAs
return math.rad(AoA)
end
game["Run Service"].Heartbeat:Connect(function(dT)
local cF = pt.CFrame
local velocity = pt.AssemblyLinearVelocity
if velocity.Magnitude == 0 then velocity = Vector3.new(0, 0.001, 0) end
local localVelocity = cF:VectorToObjectSpace(velocity)
local AoA = calculateAOA(velocity, cF)--math.atan2(-localVelocity.Y, -localVelocity.Z)
local AoS = math.asin(localVelocity.X / localVelocity.Magnitude)
local windex = cF * CFrame.Angles(-AoA, 0, 0) * CFrame.Angles(0, -AoS, 0)
local dir_lift = windex.UpVector
local dir_side = windex.RightVector
local dir_drag = -windex.LookVector
local pressure = airDensity * 0.5 * velocity.Magnitude^2
local lift, CL = calculateLift(velocity, AoA)
local drag = calculateDrag(localVelocity, pressure, CL, AoA)
pt.Lift.Force = dir_lift * lift
pt.Drag.Force = dir_drag * drag
end)
I am unable to determine what really causes the issue. But if I really have to, I’m saying it’s the Roblox engine itself.