extends SceneTree # Headless test: run with # godot --headless -s res://tests/test_surface_background_shared.gd # The landed surface and the setup editor drew DIFFERENT backgrounds for the # same planet. Each scene carried its own hand-written ShaderMaterial (the # game's baked-and-tiled lunar shader, the editor's per-pixel procedural one) # and only the game's side ever swapped in a profile's own shader - so a gas # giant authored in the editor showed recoloured craters while the game showed # cloud bands. An editor that lies about what the player will see is worse # than no preview. # # Both scenes now instance ONE SurfaceBackground.tscn and configure it through # ONE method. This test guards that, since nothing about the code stops # someone re-adding a local material to one scene and pulling them apart # again. # # Covers: # A. both scenes get their backdrop from the same PackedScene file # B. neither overrides the shared material - the two start out identical # C. every surface profile names its own background_shader; a null one # would mean "whatever this scene already had", which is the ambiguity # that caused the drift # D. applying each profile through the shared entry point produces the same # shader AND the same colours on both scenes, for every profile const BACKGROUND_SCENE_PATH := "res://surface/SurfaceBackground.tscn" const HOST_SCENES := { "PlanetSurface.tscn": "res://surface/PlanetSurface.tscn", "PlanetSetupEditor.tscn": "res://surface/PlanetSetupEditor.tscn", } const COLOR_PARAMETERS := ["base_color", "crater_color", "highlight_color"] var backgrounds: Dictionary = {} var hosts: Array = [] func _initialize() -> void: var surface_profiles: Script = load("res://surface/PlanetSurfaceProfiles.gd") # --- A/B: same source scene, same untouched material --------------------- for scene_name in HOST_SCENES: var host: Node = load(HOST_SCENES[scene_name]).instantiate() hosts.append(host) var layer: Node = host.get_node_or_null("BackgroundLayer") if layer == null: _fail("%s has no BackgroundLayer" % scene_name) return if layer.scene_file_path != BACKGROUND_SCENE_PATH: _fail("%s builds its own background (%s) instead of instancing %s" \ % [scene_name, layer.scene_file_path, BACKGROUND_SCENE_PATH]) return var background: Node = layer.get_node_or_null("Background") if background == null: _fail("%s's background layer has no Background node" % scene_name) return if not background.has_method("apply_profile"): _fail("%s's background has no apply_profile() - the shared entry point is gone" % scene_name) return backgrounds[scene_name] = background print("both scenes instance %s and expose apply_profile()" % BACKGROUND_SCENE_PATH) var names: Array = backgrounds.keys() var reference: ShaderMaterial = backgrounds[names[0]].material for i in range(1, names.size()): var other: ShaderMaterial = backgrounds[names[i]].material # Same configuration, SEPARATE objects. Sub-resources in a PackedScene # are shared between instances by default, which would make the whole # comparison below vacuous - it would be reading one material twice - # and would mean two live surfaces repainting each other, baked # texture included. SurfaceBackground.tscn marks the material # resource_local_to_scene for exactly this reason. if other == reference: _fail("%s and %s share one ShaderMaterial object, so applying a profile to either repaints both" \ % [names[0], names[i]]) return if other.shader != reference.shader: _fail("%s and %s start on different shaders (%s vs %s), so one of them overrides the shared material" \ % [names[0], names[i], reference.shader.resource_path, other.shader.resource_path]) return print("both start on the same shader: %s" % reference.shader.resource_path) # --- C: no profile leaves its shader unstated ---------------------------- for profile_id in surface_profiles.PROFILES: var profile: Resource = surface_profiles.get_profile(profile_id) if profile.background_shader == null: _fail("the '%s' profile does not name a background_shader, so what it draws depends on which scene happens to be hosting it" % profile_id) return print("all %d profiles name their own background shader" % surface_profiles.PROFILES.size()) # --- D: applying a profile lands identically on both --------------------- for profile_id in surface_profiles.PROFILES: var profile: Resource = surface_profiles.get_profile(profile_id) for scene_name in backgrounds: backgrounds[scene_name].apply_profile(profile) var first: ShaderMaterial = backgrounds[names[0]].material if first.shader != profile.background_shader: _fail("applying '%s' did not put its own shader on the background" % profile_id) return for i in range(1, names.size()): var other: ShaderMaterial = backgrounds[names[i]].material if other.shader != first.shader: _fail("'%s' resolved to %s in %s but %s in %s" \ % [profile_id, first.shader.resource_path, names[0], other.shader.resource_path, names[i]]) return for parameter in COLOR_PARAMETERS: var a: Color = first.get_shader_parameter(parameter) var b: Color = other.get_shader_parameter(parameter) if not a.is_equal_approx(b): _fail("'%s' set %s to %s in %s but %s in %s" \ % [profile_id, parameter, a, names[0], b, names[i]]) return # Identical to each other is only half of it - both have to match the # profile, or the shared path could be uniformly ignoring it. for parameter in COLOR_PARAMETERS: var applied: Color = first.get_shader_parameter(parameter) var declared: Color = profile.get(parameter) if not applied.is_equal_approx(declared): _fail("'%s' declares %s %s but the background ended up with %s" \ % [profile_id, parameter, declared, applied]) return print(" '%s' -> %s, identical on both" % [profile_id, first.shader.resource_path.get_file()]) for host in hosts: host.free() print("TEST_SURFACE_BACKGROUND_SHARED: PASS") quit(0) func _fail(message: String) -> void: print("TEST_SURFACE_BACKGROUND_SHARED: FAIL (%s)" % message) quit(1)