To cut a very long story into just a long one, the default Task Cancellation Policy (TCP) is being applied.
Say Motion Sensor 3 has just gone inactive. The piston will see it needs to wait for 30 seconds and will do so by scheduling a new task and exiting.
Now supposing Motion Sensor 3 goes active again during those 30 seconds. The piston will run from the top again, see ‘Motion Sensor 3 changes to inactive’ is no longer true and assume you no longer need the scheduled task, which is usually exactly what you want - if you are back in the room you want the lights to stay on.
Now supposing it was Motion Sensor 5 that went active. The same thing still applies. Motion Sensor 3 hasn’t changed to inactive so the scheduled task gets cancelled and the lights stay on.
You can change the TCP in the settings for each ‘with’ or ‘wait’ but that doesn’t help you as you need that behaviour when you return to a room.
You can change ‘changes to’ to ‘is’ so the comparisons work on what the motion state actually is, not what event just happened. However you might find that the timers get restarted (that is called the Task Scheduling Policy).
Using ‘stays inactive’ works a bit like the above as it uses the ‘is’ comparison. I’m not sure if the timers restart though.
There is another ‘gotcha’. If two motion sensors change state at almost the same time, whatever event happens first will cause the piston to run and the second run will be delayed by ten seconds. That can be disruptive. That issue could perhaps be dealt with be enabling parallelism in the piston settings.
Wasteful as it might seem, you might actually be best served by having a separate piston for each room. There is a lot to be said for only having any one piston triggered by changes to one device attribute.