extends SceneTree ## Deterministic set-piece for judging the LOOK, not the level. ## ## visual_capture.gd shoots the real game, which is the right tool for "does ## this still work" and the wrong one for "did that shader change help": the ## player spawns at a random spawn point, weapons idle-sway, and characters ## animate, so two runs of the same build produce different frames. Measuring ## those with tools/levels.py gives numbers that bounce by 15 and hide every ## delta smaller than the noise. ## ## This builds a fixed scene instead — known primitives on a known ground plane, ## a fixed sun angle, a fixed camera — so the ONLY thing that can move a ## statistic is the change under test. The shapes are chosen to exercise each ## part of the cel look: ## ## sphere / cylinder curved terminator (the band-edge anti-aliasing case) ## box flat faces meeting at hard angles (crisp break case) ## the low slab casts across the ground plane (cast-shadow case) ## the overhang deep occlusion right above a surface (contact/AO case) ## the stack object-on-object contact, the "does it float" case ## ## Run: ## godot --path . --windowed --resolution 1280x720 \ ## -s res://debug/fidelity_probe.gd -- ## ## Writes probe_key.png (three-quarter hero angle) and probe_graze.png (low ## raking angle, where terminator crawl and outline weight show worst). var _frames := 0 var _out_dir := "." var _root: Node3D var _cam: Camera3D func _initialize() -> void: var args := OS.get_cmdline_user_args() if args.size() > 0: _out_dir = args[0] _build() func _build() -> void: _root = Node3D.new() _root.name = "FidelityProbe" root.add_child(_root) LevelEnvironment.add_to(_root, "day") # Pin the sun rather than take the shared default, so a change to the # shared default cannot silently move the probe's baseline. Raking enough # that everything throws a long, legible shadow across the plane. var sun: DirectionalLight3D = _root.get_node("Sun") sun.rotation_degrees = Vector3(-34, 42, 0) sun.light_energy = 2.2 sun.directional_shadow_max_distance = 80.0 # Ground: one large plane, deliberately untextured flat cel colour. This is # the surface the `detail` statistic is measured against — if a surface # detail law is doing anything, it shows up here first. var ground := MeshInstance3D.new() var plane := PlaneMesh.new() plane.size = Vector2(60, 60) ground.mesh = plane ground.material_override = LevelMaterials.flat(Color(0.52, 0.50, 0.58), "ground") _root.add_child(ground) var wall_col := Color(0.62, 0.58, 0.66) var prop_col := Color(0.80, 0.78, 0.84) # Back wall — a big flat expanse, the "dead%" case. _box(Vector3(0, 4, -9), Vector3(30, 8, 0.6), wall_col, "wall") # Low slab: throws a long shadow across open ground. _box(Vector3(-6, 0.5, 2), Vector3(4, 1, 1.2), prop_col, "trim") # Overhang: a lid on two legs, deep occlusion directly above the ground. _box(Vector3(5, 2.6, 0), Vector3(5, 0.4, 4), prop_col, "panel") _box(Vector3(3.2, 1.3, -1.4), Vector3(0.4, 2.2, 0.4), prop_col) _box(Vector3(6.8, 1.3, 1.4), Vector3(0.4, 2.2, 0.4), prop_col) # Stack: object resting on object, the classic "does it float" read. _box(Vector3(0, 0.6, 3.5), Vector3(2.4, 1.2, 2.4), prop_col, "panel") _box(Vector3(0, 1.6, 3.5), Vector3(1.2, 0.8, 1.2), Color(0.86, 0.52, 0.34)) # Curved surfaces: the terminator that crawls if the band edge is not # anti-aliased in screen space. _shape(SphereMesh.new(), Vector3(-2.5, 1.2, 0.5), prop_col, 1.2) var cyl := CylinderMesh.new() cyl.top_radius = 0.5 cyl.bottom_radius = 0.5 cyl.height = 3.4 _shape(cyl, Vector3(-4.5, 1.7, -3.0), Color(0.42, 0.62, 0.72), 1.0) _cam = Camera3D.new() _root.add_child(_cam) _cam.current = true _aim_key() func _box(pos: Vector3, size: Vector3, col: Color, law: String = "") -> void: var mi := MeshInstance3D.new() var m := BoxMesh.new() m.size = size mi.mesh = m mi.position = pos mi.material_override = LevelMaterials.flat(col, law) _root.add_child(mi) func _shape(mesh: Mesh, pos: Vector3, col: Color, scale: float) -> void: var mi := MeshInstance3D.new() if mesh is SphereMesh: mesh.radius = 1.0 mesh.height = 2.0 mi.mesh = mesh mi.position = pos mi.scale = Vector3.ONE * scale mi.material_override = LevelMaterials.flat(col) _root.add_child(mi) ## look_at_from_position, not position-then-look_at: _initialize() runs before ## the node is actually in the tree, and look_at needs a global transform. func _aim_key() -> void: _cam.look_at_from_position(Vector3(7.5, 5.2, 12.0), Vector3(0.0, 1.0, 0.5), Vector3.UP) func _aim_graze() -> void: _cam.look_at_from_position(Vector3(-9.0, 1.05, 7.5), Vector3(2.0, 1.4, -2.0), Vector3.UP) func _process(_delta: float) -> bool: _frames += 1 # 90 frames of settle: shaders compile on first use and the first frames # render with the fallback material, which would show up as a huge and # entirely fake improvement in every statistic. if _frames == 90: _shot("key") _aim_graze() elif _frames == 120: _shot("graze") return true return false func _shot(tag: String) -> void: var img := root.get_viewport().get_texture().get_image() var path := _out_dir + "/probe_" + tag + ".png" img.save_png(path) print("fidelity_probe: saved ", path)