extends Node ## Render actual presentation materials from both sides; headless cannot prove visibility. var checks: int = 0 var failures: Array[String] = [] func check(ok: bool, label: String) -> void: checks += 1 if not ok: failures.append(label) printerr("MATERIAL_FAIL: ", label) func _ready() -> void: call_deferred("run") func run() -> void: if DisplayServer.get_name() == "headless": printerr("This material test requires a rendering display.") await Soundscape.quit_game(1) return Soundscape.enabled = false var view := SubViewport.new() view.size = Vector2i(512, 256) view.own_world_3d = true view.render_target_update_mode = SubViewport.UPDATE_ALWAYS add_child(view) var world := WorldEnvironment.new() world.environment = Environment.new() world.environment.background_mode = Environment.BG_COLOR world.environment.background_color = Color(.01, .01, .01) world.environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR world.environment.ambient_light_color = Color.WHITE world.environment.ambient_light_energy = 1.0 world.environment.reflected_light_source = Environment.REFLECTION_SOURCE_DISABLED view.add_child(world) var camera := Camera3D.new() camera.projection = Camera3D.PROJECTION_ORTHOGONAL camera.size = 2.4 camera.position = Vector3(0, 0, 3) view.add_child(camera) var front := MeshInstance3D.new() front.mesh = QuadMesh.new() front.position.x = -.6 view.add_child(front) var back := MeshInstance3D.new() back.mesh = front.mesh back.position.x = .6 back.rotation.y = PI view.add_child(back) var source: Node3D = load("res://assets/characters/quellgeflecht.glb").instantiate() var surfaces: Dictionary = {} for node: Node in source.find_children("*", "MeshInstance3D", true, false): for index: int in range(node.mesh.get_surface_count()): var material: Material = node.get_active_material(index) if material.resource_name in ["QG_Leaf", "QG_Moss", "QG_Petal"]: surfaces[material.resource_name] = material check(surfaces.size() == 3, "all three imported foliage materials present") var measurements: Dictionary = {} for key: String in surfaces: var original: StandardMaterial3D = surfaces[key] check(original.cull_mode == BaseMaterial3D.CULL_DISABLED, key + " authored double-sided") var material: Material = AssetLibrary.presentation_material(original) front.material_override = material back.material_override = material for frame in range(5): await get_tree().process_frame await RenderingServer.frame_post_draw var pixels := view.get_texture().get_image() var front_pixel := camera.unproject_position(front.position) var back_pixel := camera.unproject_position(back.position) var front_light := pixels.get_pixelv(Vector2i(front_pixel)).get_luminance() var back_light := pixels.get_pixelv(Vector2i(back_pixel)).get_luminance() check(front_light > .15, key + " front is visible") check(back_light > .15, key + " reverse side is visible") check(absf(front_light - back_light) < .08, key + " sides have consistent ambient lighting") measurements[key] = {"front_luminance": front_light, "back_luminance": back_light} pixels.save_png("res://docs/screenshots/phase17/" + key.to_lower() + "-sides.png") source.free() var report := FileAccess.open("res://docs/phase17-material-verification.json", FileAccess.WRITE) report.store_string(JSON.stringify({"checks": checks, "failures": failures, "renderer": RenderingServer.get_current_rendering_method(), "materials": measurements}, "\t")) report.close() print("MATERIAL_PASS: ", checks, " checks; ", failures.size(), " failures") await Soundscape.quit_game(0 if failures.is_empty() else 1)