Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore unversioniert; letztere sind unter ../backups gesichert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
3.6 KiB
GDScript
82 lines
3.6 KiB
GDScript
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)
|