Unpredictable, seemingly non-determinstic behavior of .Touched()

The .Touched() event seems to fire even when a physical collision should not have been possible. This is uncommon, but happens frequently enough to be reproduceable in a few minutes. Considering how ubiquitously this event is relied on, I strongly urge that the internal mechanisms that handle this event should be re-examined, and that more detailed documentation for what exactly registers as a touch be published.

In my testing, I’ve used the classic sword in the toolbox (I believe originally scripted by Shedletsky, the former Creative Director at Roblox), with the Handle’s Massless set to true, and a set of anchored R6 dummies with an R6 avatar.

I ran my game at the maximum 240 FPS to match the 240hz of the internal physics engine, as enabled by the new FPS switcher, and used a LocalScript with a .Touched() listener on the sword’s handle.

When the event is fired, I immediately store the CFrame of the sword’s handle to save its position before the physics simulation completes, as well as the sword’s CFrame after RunService.PostSimulation:Wait(). Using the deltaTime argument, I ensure the check only happens when PostSimulation is running in-tandem with the physics simulation of 240hz.

With RunService and the physics engine in-sync, only one CFrame could have existed between the sword’s PreSimulation CFrame and it’s PostSimulation CFrame, which I obtain via :Lerp() with an alpha of 0.5.

I then check if the touch was possible by using WorldRoot:GetPartsBoundInBox() on the sword’s PreSimulation CFrame, lerped CFrame, and its PostSimulation CFrame.

Many times .Touched() will fire when none of the three checks are valid. To illustrate this, I also create a clone of the sword’s handle using the stored CFrames:

As you can see, this touch should not have been possible, and yet .Touched() was fired. Even when using WorldRoot:ArePartsTouchingOthers() with an overlapIgnored argument of 0, we can confirm the parts are indeed not touching:

Given this, I am not sure how it was possible for the physics engine to report a touch; I believe it was done in error.

Moreover, documentation for the internal physics engine’s handling of .Touched is still painfully lacking, and a fix, or at least a clarification, would be greatly appreciated and in my opinion, needed.

I’ve attached a copy of the testing place I used below. Note that this seems to happen infrequently, and can take several minutes and a few reloads to occur, but does indeed happen.

TestingTouched.rbxl (66.8 KB)

Testing was done on Roblox Studio version 0.628.0.6280391 (64-bit). I used a Windows 10 PC on version 22H2, build 19045.4412 with an AMD Ryzen 5800X, an NVIDIA RTX 3070, and 32 GB of DDR4 3600Mhz RAM.

Expected behavior

I expect .Touched() to fire only when a physical touch should have been possible.

3 Likes

I can confirm, I’ve experienced a lot of similar issues with .touched()

2 Likes

The Touched and TouchEnded events in Roblox are implemented using an internal concept known as contact. When two parts come into close proximity, a contact is generated. The engine then adds the dirty contact to a queue. During the engine’s processing cycle, these dirty contacts are handled, resulting in the creation of Touched events, which are subsequently added to another queue. Finally, the events in the Touched queue are triggered.

It is possible for a CFrame to be updated in one frame while the Touched event is triggered in a subsequent frame. The utilization of these queues ensures the overall system remains performant.

2 Likes

Thank you for the clarification.

I’ve marked this as solved. I still believe the task scheduler article should clarify this as well. My post is a result of the ambiguity in that page, which suggests that Touched will always fire immediately after the processable CFrames are updated. It makes no mention of the queue you reference, and instead states that it, “will fire after all world steps [in a given frame] are complete.”

I was under the impression that, given the 240hz physics updating, and the 240hz rendering, only one world step could be completed, and thus Touched should fire within the same frame.