Duplicating an instantiated node restores removed children

Godot Version

v4.7.2

Question

I feel I don’t get how the DUPLICATE_USE_INSTANTIATION flag works, or rather how Godot’s memory scope works, because I don’t understand what is happening here.

A minimal example code in the scene’s _ready:

var brick: Node3D = preload("res://Brick.blend").instantiate() # Just the default Blender cube with a name and with light and camera removed.
brick.print_tree()
# Prints:
# .
# Brick    (this is MeshInstance3D)
# Brick/StaticBody3D
# Brick/StaticBody3D/CollisionShape3D

for child in brick.get_children():
	brick.remove_child(child)
	child.free()
	# I've tried other ways of clearing children as well.

brick.print_tree()
# Prints:
# .
brick = brick.duplicate(7)
brick.print_tree()
# Prints:
# .
brick = brick.duplicate()
brick.print_tree()
# Prints:
# .
# Brick
# Brick/StaticBody3D
# Brick/StaticBody3D/CollisionShape3D

The ‘brick’ variable is a Node3D, so I don’t expect some kind of shenanigans that I’m not aware of. I’ve removed all children from the node, and the console tells me that the variable has no children. When I duplicate a node with no children, all children magically reappear.

When I create a hierarchy manually with new empty nodes without instantiating, the problem doesn’t occur.

The Docs for DUPLICATE_USE_INSTANTIATION (is on by default) say:

Duplicate using PackedScene.instantiate(). If the node comes from a scene saved on disk, reuses PackedScene.instantiate() as the base for the duplicated node and its children.

Does that mean that Node3D internally stores its state in memory after being instantiated and that state is used for duplication instead of the current state? Is it a bug?

Should I turn off the flag every time I want to actually clone a node?

It’s probably based on the node’s scene_file_path property, that will be empty for nodes that don’t come from instantiation.

It does seem like strange behavior for a default, maybe worth raising an issue on github. I’d bet it is more performant to duplicate based on the packed scene than it is to duplicate loose nodes and their children.

Not a bug — it’s what the flag is documented to do. With DUPLICATE_USE_INSTANTIATION (on by default), duplicating a node that came from a PackedScene doesn’t deep-copy the current runtime state; it calls PackedScene.instantiate() again and reapplies the recorded scene overrides. Removing a child at runtime is not a recorded override (overrides are property changes and added nodes), so the fresh instance comes back with the full original hierarchy.

If you want an actual clone of the current state, duplicate without the flag:

var copy = original.duplicate(DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS)

The tradeoff: the copy is a plain node tree with no link back to the .tscn, so future edits to the scene file won’t propagate to it. Rule of thumb: keep the flag when you want “another instance of the scene”, drop it when you want “a snapshot of this exact object right now”.