Revision control
++ // Copyright (C) 2026 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ // Takes the scene away from the world from a handler on the velocity a sync
++ // writes. Like the position in tst_scene_changed_mid_sync.qml, the velocities
++ // are written while the walk over the bodies is on the stack, so moving the
++ // world from there deregisters every body, the one being synced included, and
++ // that body has nothing left to finish its sync with.
++ //
++ // Reaching the end of both of the frame's walks is the assertion, asked for as
++ // two more finished frames after the move.
++
++ import QtQuick
++ import QtTest
++ import QtQuick3D
++ import QtQuick3D.Physics
++ import QtQuick3D.Physics.TestUtils
++
++ Item {
++ id: root
++ width: 64
++ height: 64
++
++ // The frame the world was moved on, set by whichever body the walk reached
++ // first.
++ property int frameAtMove: -1
++
++ PhysicsWorld {
++ id: world
++ gravity: Qt.vector3d(0, -981, 0)
++ running: true
++ // On, so the frame that loses its scene mid-walk goes on to the second
++ // walk it makes over the same backends, the one debug drawing does.
++ forceDebugDraw: true
++ typicalLength: 100
++ typicalSpeed: 1000
++ minimumTimestep: 15
++ maximumTimestep: 15
++ scene: fallingScene
++
++ property int frameCount: 0
++ onFrameDone: world.frameCount++
++ }
++
++ Node {
++ id: fallingScene
++
++ // Falling freely, so every sync has a new velocity to write.
++ Repeater3D {
++ model: 3
++ DynamicRigidBody {
++ required property int index
++ position: Qt.vector3d(index * 120 - 120, 200, 0)
++ collisionShapes: BoxShape {}
++ onLinearVelocityChanged: {
++ if (world.frameCount > 5 && world.scene === fallingScene) {
++ root.frameAtMove = world.frameCount;
++ world.scene = elsewhere;
++ }
++ }
++ }
++ }
++ }
++
++ // What the world is moved to. It holds no bodies, so the move leaves every
++ // body without a world rather than handing it to a new one.
++ Node {
++ id: elsewhere
++ }
++
++ PhysicsTestCase {
++ name: "moving the world while it updates a body's velocity"
++ goalReached: root.frameAtMove >= 0 && world.frameCount > root.frameAtMove + 1
++ }
++ }