Part position fails to set in game, but works in studio and in isolated BasePlate

Goal:
My end goal is not to get this to work (I have workarounds), but to know why this happens and if this should even be possible

Issue:
My Part position does not correctly get set in the target game. However, it does get set in studio, and in an isolated baseplate world. The code has been tested exactly as written.

Question:
My question is, should this even be possible? This script, to the best of my knowledge, directly sets the position and then prints the results without yielding.

I do not believe it should even be possible for some other script to effect the result here, at least not until after we print it. However, the printed target position and the resulting position of the part do not match. As such, I believe any external code is causing the issue, but how?

ServerScript:

Full example
local myPart = Instance.new("Part")
myPart.Anchored =true
--myPart.Transparency = 1
myPart.CanCollide = false
myPart.Name="ShootingStarSpawner"
myPart.Parent = game.Workspace
while wait(1) do
print("Will Move Star")
local newPos = Vector3.new(10,10,10)
-- Prints correct position
print("Set: X: " .. newPos.X .. " Y: " .. newPos.Y .. " Z:" .. newPos.Z)
-- None of these work in target game. Both work in studio, and in a published baseplate world
--myPart.Position = newPos
myPart.CFrame = CFrame.new(newPos)
-- Prints in game : "Result: X: 0 Y: 0 Z: 0"
-- Prints in studio/baseplate: "Result: X: 10 Y: 10 Z: 10"
print("Result: X: " .. myPart.Position.X .. " Y: " .. myPart.Position.Y .. " Z:" .. myPart.Position.Z)
print("Star Moved")
end

The important question in the code is, why this is possible, and why does it occur in this non-yeilding code segment.

myPart.CFrame = CFrame.new(newPos)
-- Prints in game : "Result: X: 0 Y: 0 Z: 0"
-- Prints in studio/baseplate: "Result: X: 10 Y: 10 Z: 10"
print("Result: X: " .. myPart.Position.X .. " Y: " .. myPart.Position.Y .. " Z:" .. myPart.Position.Z)

Further testing reveals that adding a wait(1) prior to parenting the part to the workspace bypasses the issue. Additionally, the problem only exists if we fail to set Part.Achievable to false before some external script does. This is NOT an issue if we set it to false ourselves in the script. I was unable to reproduce the issue in a baseplate world with another script setting Achievable to false between moves.

What might I be missing that could cause this?

Things I have confirmed:
Part is still parented to workspace

SOLVED! Cause:
External script hooked into a Change event, and changed the values behind the scenes, and in the same thread as the script printing. All extra odd behaviour like in-game only problems, and non-achievable only issues are due to conditions in its code.

Further testing reveals this error exist only in the while loop, making me more confident that external code is not causing it. If this is a bug, the question is what triggers it. Since I can only reproduce the error in this game, and moving parts is like… the #1 thing we do with scripts, it would have to be a very specific edge case.

local myPart = Instance.new("Part")
myPart.Anchored =true
--myPart.Transparency = 1
myPart.CanCollide = false
myPart.Name="ShootingStarSpawner"
myPart.CFrame = CFrame.new(Vector3.new(30,30,30))
myPart.Parent = game.Workspace
myPart.CFrame = CFrame.new(Vector3.new(20,20,20))
while wait(1) do
  print("Will Move Star")
  local newPos = Vector3.new(10,10,10)
  -- Prints correct position
  print("Set: X: " .. newPos.X .. " Y: " .. newPos.Y .. " Z:" .. newPos.Z)
  -- None of these work in target game. All work in studio, and in a published baseplate world
  --myPart.Position = newPos
  myPart.CFrame = CFrame.new(newPos)
  --myPart.CFrame = rand_player.Character.PrimaryPart.CFrame
  -- Prints "Result: X: 20 Y: 20 Z: 20"
  print("Result: X: " .. myPart.Position.X .. " Y: " .. myPart.Position.Y .. " Z:" .. myPart.Position.Z)
  print("Star Moved")
end

Reformatted, Test This Code


local myPart = Instance.new("Part")
myPart.Anchored = true
myPart.Cancollide = false
myPart.Name = "ShootingStarSpawner"
myPart.CFrame = CFrame.new(Vector3.new(20,20,20))
myPart.Parent = workspace

while wait(1) do -- looks like it's setting to 10, 10, 10 every second
    local newPos = Vector3.new(10,10,10)

    wait(3) -- let's give it 3 seconds
    print("Moving...")
    myPart.CFrame = CFrame.new(newPos)
    print(myPart.Position)

    wait(3) -- another one
    print("Moving...")
    myPart.Position = Vector3.new(-10,20,-10)
    print(myPart.Position)
end

Let me know about the output when you test the code above.

Case Scenarios


  • Game not published; but clearly you stated that the game was published.
  • Are you certain that the script is parented correctly?
  • Script is possibly greyed out; disabled.
2 Likes

@anon81993163 Your code gave 20,20,20 both times

Additionally, I have now removed the loop and isolated the issue to the wait/delay. Issue occurs AFTER a wait/delay.

wait(0) succeeds.
wait(1) fails
delay(1) fails

Perhaps some other (malicious? not causing problems with similar scripts though) code is doing something after all, but… is it even possible to lock the position in a way that it is impossible to change from another script? (if it were just setting the position constantly, print would give me the right result)

I’m not entirely sure, but I’ll go check it out in Studio when I have access to it later tomorrow. Remind me in my PMs if I drag it out for too long.

Prepending the code with wait(5) (anywhere prior to parenting it to workspace) bypasses the issue, which just adds more questions for me (who/what is causing it, as the change in results with a yield makes an external script a likely culprit) without solving the original (how is this possible, since the nonyielding portions of the code do not seem like they should be possible to affect).
The only thing I can think of is via setting a property like part.Locked, but printing that at the point of the error returned false so I am out of ideas

Can you confirm for me if this script is a Script or a LocalScript, and where the script is parented?

Script, in ServerScriptService.

Try doing everything you did with the part at first, and connecting a function to its :GetPropertyChangedSignal("Position") event beforehand to see if it’s being changed multiple times each time you set the position:

myPart:GetPropertyChangedSignal("Position"):Connect(function()
	print(myPart.Position)
end)
1 Like

There could be an issue with the replication across the server-client boundary. When testing it in studio, start a local server with one player and enable Network Simulator on the server, see if this affects anything.

If it does, maybe try setting the part’s parent to workspace when you create it?
local myPart = Instance.new("Part", workspace)

1 Like

The Pos changes 1 time per call prior to the wait, with the correct value

The Pos changes 2 times per call when set after the wait, both times it prints the same wrong value that it has somehow locked onto.

So, just to clarify, using pseudocode, this is what you mean in the first statement:

Create myPart

myPart:GetPropertyChangedSignal("Position") Connected (
	prints(Correct Position)
)

And this is what you mean in the second statement:

Create myPart

wait(1 second)

myPart:GetPropertyChangedSignal("Position") Connected (
	prints(Incorrect Position)

	prints(Incorrect Position)
)

Right?

correct. Incorrect position is printed twice after wait, with the correct position never printed after the wait. psudocode

Create & Parent myPart
Setup myPart:GetPropertyChangedSignal

myPart.Position = 20,20,20
-- Prints  20,20,20
wait(1 second)
myPart.Position = 10,10,10
-- Prints  20,20,20
-- Prints  20,20,20
1 Like

I have additionaly connected the Changed event, and durring that wait, the only thing I get is
ā€œProperty ā€˜Achievable’ changed to ā€˜falseā€™ā€.
I did not think that this was suppose to affect it (from what Instance | Documentation - Roblox Creator Hub says, I wouldn’t think it would), but I will try setting it to false before the wait and see. If so, it sort of makes sense, but ought to be documented better. ATM, it seems like it is only meant to stop clones/in-game saves from being made

Setting myPart.Achievable=false right after creating it solves the problem as well…
That is,

Create myPart
myPart.Achievable = false
Setup myPart:GetPropertyChangedSignal

myPart.Position = 20,20,20
-- Prints  20,20,20
wait(1 second)
myPart.Position = 10,10,10
-- Prints  10,10,10

Is setting this property supposed to change the results like this though?
As long as I change the property before the other script does, the code works. If the other script changes it, even if I also set it to false after it, the code doesn’t work. ie.

Create myPart
Setup myPart:GetPropertyChangedSignal

myPart.Position = 20,20,20
-- Prints  20,20,20
wait(1 second)
myPart.Achievable = false
myPart.Position = 10,10,10
-- Prints  20,20,20
-- Prints  20,20,20

Unfortunately, I am unable to reproduce it with network simulator as well.

At this point if you are able to recreate it I would recommend filing a bug report

What is Achievable
Should it be Archivable

Please try disabling every other script in the place. You mentioned this works fine on a baseplate, so disabling all scripts should fix it as well. You need to find whatever is doing this, so try to narrow it down. Make sure you enable hidden services in Studio’s settings, iirc some of those can/could contain children at one point.

Setting archivable to false is preventing other scripts from indexing the part which might be why it starts working when you set it immediately in your script. Are there errors in the output console when you do this? Otherwise something might be listening to DescendantAdded - I’m thinking that doesn’t fire when archivable is false but I’m not sure. I’m also wondering if you might have an anti-exploit script since something is setting archivable to false, and that sounds like something an anti-exploit might do. Scripts can also be aware of whether or not they’re running in Studio which might be why it only happens Ingame.

1 Like

Actually, now that I have had a night to think about it, I think @goldenstein64’s code should have been a dead giveaway at what I now suspect is happening.

Create & Parent myPart
Setup myPart:GetPropertyChangedSignal

myPart.Position = 20,20,20
-- Prints  20,20,20
wait(1 second)
myPart.Position = 10,10,10
-- Prints  20,20,20
-- Prints  20,20,20
print("hi")
-- Prints "hi" after the two prints above

This tells me GetPropertyChangedSignal is runs in-thread. What I am probably not seeing is code like

Create & Parent myPart
Setup myPart:GetPropertyChangedSignal

myPart.Position = 20,20,20
  GetPropertyChangedSignal > Print myPart.Position{20,20,20}
-- Prints  20,20,20
wait(1 second)
  Some other script hooks into another change event like myPart.Changed
myPart.Position = 10,10,10
  SomeChangeEvent > reset myPart.Position{10,10,10} if not default value{20,20,20}
  GetPropertyChangedSignal > Print myPart.Position{20,20,20}
-- Prints  20,20,20
  SomeChangeEvent > dont reset myPart.Position{20,20,20} since it is default value{20,20,20}
  GetPropertyChangedSignal > Print myPart.Position{20,20,20}
-- Prints  20,20,20
print("hi")
-- Prints "hi" after the two prints above

Using this, I was able to reproduce the result in a baseplate world and studio. Adding checks to see if we are in an actual game would allow the odd behavior described. Here are the resulting scripts which reproduce the issue

CreatePart.lua
local myPart = Instance.new("Part")
myPart.Anchored =true

--myPart.Transparency = 1

myPart.CanCollide = false
myPart.Name="ShootingStarSpawner"

myPart.CFrame = CFrame.new(Vector3.new(30,30,30))
myPart:GetPropertyChangedSignal("Position"):Connect(function()
	--print("CHANGED PROPERTY: <" .. a .. "> to some value <" .. tostring(myPart[a]) .. ">")
   print("Pos Change: X: " .. myPart.Position.X .. " Y: " .. myPart.Position.Y .. " Z:" .. myPart.Position.Z)
end)
--wait(5)
myPart.Parent = game.Workspace
myPart.CFrame = CFrame.new(Vector3.new(20,20,20))

print("Move Without delay")
local newPos = Vector3.new(10,10,10)
-- Prints correct position
print("Set: X: " .. newPos.X .. " Y: " .. newPos.Y .. " Z:" .. newPos.Z)
-- None of these work in target game. All work in studio, and in a published baseplate world
--myPart.Position = newPos
myPart.CFrame = CFrame.new(newPos)
-- āœ… Prints "Result: X: 10 Y: 10 Z: 10"
print("Result: X: " .. myPart.Position.X .. " Y: " .. myPart.Position.Y .. " Z:" .. myPart.Position.Z)
print("Star Moved")

wait(0)

print("Move After Wait 0")
local newPos = Vector3.new(3,3,3)
-- Prints correct position
print("Set: X: " .. newPos.X .. " Y: " .. newPos.Y .. " Z:" .. newPos.Z)
-- None of these work in target game. All work in studio, and in a published baseplate world
--myPart.Position = newPos
myPart.CFrame = CFrame.new(newPos)
-- āœ… Prints "Result: X: 3 Y: 3 Z: 3"
print("Result: X: " .. myPart.Position.X .. " Y: " .. myPart.Position.Y .. " Z:" .. myPart.Position.Z)
print("Star Moved")
print(myPart.Locked)

wait(5)
-- āš ļø CauseProblems.lua has now hooked into the change event!
print("Move After Wait 5")
local newPos = Vector3.new(2,2,2)
-- Prints correct position
print("Set: X: " .. newPos.X .. " Y: " .. newPos.Y .. " Z:" .. newPos.Z)
-- None of these work in target game. All work in studio, and in a published baseplate world
--myPart.Position = newPos
myPart.CFrame = CFrame.new(newPos)
-- āŒ Prints "Result: X: 3 Y: 3 Z: 3"
print("Result: X: " .. myPart.Position.X .. " Y: " .. myPart.Position.Y .. " Z:" .. myPart.Position.Z)
print("Star Moved")
CauseProblems.lua
local backupProps = {
	Position = true
	,Orientation = true
	,Name = true
}
local backup = {}
local myPart = game.Workspace:WaitForChild("ShootingStarSpawner")
wait(3)
-- run on all anchored, Archivable basepart descendents of workspace
-- to get the undesired effect
for prop,_ in next,backupProps do
	backup[prop] = myPart[prop]
end
print("Causing Problems")
myPart.Changed:Connect(function(prop)
	if backupProps[prop] and myPart[prop] ~= backup[prop] then
		myPart[prop] = backup[prop]
	end
end)

Setting archivable to false is preventing other scripts from indexing the part which might be why it starts working when you set it immediately in your script

Sounded interesting, but it still shows up for me with game.Workspace:GetChildren(), so I am doubtful I could rely on this.

Since I now know how this could be done, I am marking this as solved.

1 Like