## Does changing each node's parameters change the baked mesh at all?
##
## Bakes the graph, perturbs one input default, re-bakes, reports how far
## vertices moved. A node whose every parameter moves nothing is not
## contributing to the result.
##
## TWO instrument bugs were found by its own controls before it said anything
## true. First it freed each host after baking, every bake after the second
## returned an empty mesh, and the size sentinel read `inf` everywhere -- which
## looks exactly like "nothing has any effect". Then, with that fixed, every
## perturbation ACCUMULATED: ResourceLoader.load returns the CACHED resource, so
## "a fresh load" was the same object each time and node 0's vertex-count change
## leaked into every later row. Hence CACHE_MODE_IGNORE_DEEP below.
##
## FIRST LINE IS A CONTROL: re-bake with NO perturbation. It must read 0.
## The first version of this script freed each host after baking, every bake
## after the second returned an empty mesh, and the size-mismatch sentinel
## printed `inf` for nearly every socket -- which reads exactly like "nothing
## has any effect". Without the control I would have reported my own broken
## instrument as the finding.
##
## ⚠ RUNNING GODOT DIRECTLY REWRITES project.godot. The editor strips every
## comment and drops any setting that equals its default -- it ate the 30-line
## Jolt block and `screen_space_roughness_limiter/enabled` during this script's
## own development. scripts/dev-editor.sh restores it; a bare `godot --headless
## --path .` does not. Check `git status project.godot` after running this
## locally. CI is unaffected: it runs in a throwaway git-archive extraction.
extends SceneTree
var _hosts := []
func _bake(g: ProcNodeGraph) -> PackedVector3Array:
var host := ProcGeometryNode.new()
host.use_gpu = false
host.set_graph(g)
get_root().add_child(host)
_hosts.append(host) # deliberately kept alive; short-lived process
var m: ArrayMesh = host.bake_mesh()
if m == null or m.get_surface_count() == 0:
return PackedVector3Array()
return m.surface_get_arrays(0)[Mesh.ARRAY_VERTEX]
func _diff(a: PackedVector3Array, b: PackedVector3Array) -> String:
if a.is_empty() or b.is_empty():
return "BAKE FAILED"
if a.size() != b.size():
return "count %d -> %d" % [a.size(), b.size()]
var d := 0.0
for i in range(a.size()):
d = max(d, (a[i] - b[i]).length())
return "%12.6f%s" % [d, "" if d > 1e-6 else " <-- NO EFFECT"]
func _init() -> void:
var a := OS.get_cmdline_user_args()
var path: String = a[0]
var ref := _bake(ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_IGNORE_DEEP))
print("baseline: ", ref.size(), " vertices")
print("CONTROL (no perturbation): ", _diff(ref, _bake(ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_IGNORE_DEEP))))
var base: ProcNodeGraph = ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_IGNORE_DEEP)
print("\n%-5s %-15s %-14s %s" % ["node", "type", "socket", "max vertex move"])
for ni in range(base.get_node_count()):
var node: ProcNode = base.get_node(ni)
if node == null:
continue
for si in range(node.get_input_count()):
var cur = node.get_input_default(si)
var t := typeof(cur)
if t != TYPE_FLOAT and t != TYPE_INT:
continue
var nv = (float(cur) * 1.7 + 0.9) if t == TYPE_FLOAT else int(cur) + 1
var p: ProcNodeGraph = ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_IGNORE_DEEP)
p.get_node(ni).set_input_default(si, nv)
print("%-5d %-15s %-14s %s" % [ni, node.get_node_type(),
node.get_input_name(si), _diff(ref, _bake(p))])
quit()