extends RefCounted ## Headless tests for the procedural world, navigation mesh, and phase lighting. const WorldBuilder := preload("res://src/game/world_builder.gd") const Rules := preload("res://src/core/rules.gd") const GameManager := preload("res://src/game/game_manager.gd") func test_navmesh_built() -> bool: var root := Node3D.new() WorldBuilder.new(root).build() var region: NavigationRegion3D = root.find_child("NavRegion", true, false) as NavigationRegion3D if region == null: return TestUtils.fail("no NavRegion in world") var mesh: NavigationMesh = region.navigation_mesh if mesh == null: return TestUtils.fail("NavRegion has no navigation mesh") var a := TestUtils.check(mesh.get_polygon_count() > 0, "navmesh has polygons") var b := TestUtils.check(mesh.get_vertices().size() >= 4, "navmesh has vertices") root.free() return a and b func test_phase_lighting_config_covered() -> bool: var ok := true for phase in [Rules.Phase.EARLY, Rules.Phase.MIDDAY, Rules.Phase.EVENING, Rules.Phase.LATE]: var cfg: Variant = GameManager.PHASE_LIGHTING.get(phase, null) if not (cfg is Dictionary): ok = false continue for key in ["sun_energy", "sun_angle", "sun_color", "sky_top", "sky_horizon", "ambient", "night"]: if not (cfg as Dictionary).has(key): ok = false return TestUtils.check(ok, "all four phases have a full lighting config") func test_street_lamps_and_marquee_built() -> bool: var root := Node3D.new() var builder := WorldBuilder.new(root) builder.build() var a := TestUtils.check(builder._street_lamps.size() >= 2, "street lamps built") var b := TestUtils.check(builder.marquee_label != null, "marquee label built") # Night mode raises bulb emission; day mode drops it. builder.set_night_mode(true) var night_on := builder._lamp_bulb_mats[0].emission_energy_multiplier > 1.0 builder.set_night_mode(false) var day_off := builder._lamp_bulb_mats[0].emission_energy_multiplier <= 0.5 root.free() return a and b and TestUtils.check(night_on, "lamps brighten at night") and TestUtils.check(day_off, "lamps dim in daylight") func test_asset_textures_present_and_pow2() -> bool: var required := [ "res://assets/textures/brick_facade.png", "res://assets/textures/concrete.png", "res://assets/textures/asphalt.png", "res://assets/textures/awning.png", "res://assets/textures/skyline.png", "res://assets/textures/vignette.png", "res://assets/textures/icons/flashlight_on.png", "res://assets/textures/icons/door_locked.png", "res://assets/textures/icons/wand.png", "res://assets/textures/icons/rec_dot.png", "res://assets/textures/cards/id_card.png", "res://assets/textures/cards/wristband.png", "res://assets/textures/cards/ticket.png", "res://assets/textures/cards/credential.png", "res://assets/textures/cards/guest_list.png", ] for path in required: if not FileAccess.file_exists(path): return TestUtils.fail("missing generated asset: %s — run tools/generate_textures.py" % path) var img := Image.new() for path in required: if img.load(path) != OK: return TestUtils.fail("cannot decode asset: %s" % path) var w := img.get_width() var h := img.get_height() if w > 1024 or h > 1024: return TestUtils.fail("asset too large: %s (%dx%d)" % [path, w, h]) if (w & (w - 1)) != 0 or (h & (h - 1)) != 0: return TestUtils.fail("asset not power-of-two: %s (%dx%d)" % [path, w, h]) return true func test_steps_have_colliders() -> bool: ## Steps were visual-only and the guard walked through them (TODO). var root := Node3D.new() WorldBuilder.new(root).build() var steps := root.find_child("Steps", true, false) var a := TestUtils.check(steps != null, "Steps node exists") var b := TestUtils.check(steps is StaticBody3D, "Steps are a physics body") var shape_count := 0 if steps is StaticBody3D: var body := steps as StaticBody3D shape_count = body.get_shape_owners().size() var c := TestUtils.check(shape_count >= 5, "four treads + landing have colliders (got %d)" % shape_count) root.free() return a and b and c func test_skyline_backdrop_uses_alpha() -> bool: ## The opaque skyline texture made the plane render as a black slab (TODO). var root := Node3D.new() var builder := WorldBuilder.new(root) builder.build() var backdrop := root.find_child("SkylineBackdrop", true, false) var a := TestUtils.check(backdrop != null, "SkylineBackdrop exists") var mat: StandardMaterial3D = builder._skyline_mat var b := TestUtils.check(mat != null, "skyline material captured") var c := TestUtils.check( mat != null and mat.transparency != BaseMaterial3D.TRANSPARENCY_DISABLED, "skyline material uses alpha, not an opaque slab") root.free() return a and b and c func test_door_indicator_built() -> bool: var root := Node3D.new() var builder := WorldBuilder.new(root) builder.build() var a := TestUtils.check(builder.door_indicator != null, "door indicator mesh exists") if builder.door_indicator != null: builder.set_door_locked(true) var mat: StandardMaterial3D = builder.door_indicator.material_override as StandardMaterial3D var b := TestUtils.check(mat != null and mat.albedo_color.r > 0.5, "indicator turns red when locked") builder.set_door_locked(false) var c := TestUtils.check(mat != null and mat.albedo_color.g > 0.5, "indicator turns green when open") root.free() return a and b and c root.free() return a