extends SceneTree ## Adaptive preview selection, placement/commit behavior, and per-finish CPU costs. const RENDERER := preload("res://scripts/stage/scenery/stage_surface_renderer.gd") const CONTROLLER := preload("res://scripts/editors/stage/stage_scenery_controller.gd") class TimedWall extends StageWallPanel: var draw_times_usec: Array[int] = [] func _draw() -> void: var started: int = Time.get_ticks_usec() super._draw() draw_times_usec.append(Time.get_ticks_usec() - started) class TimedGround extends StageBackgroundGround: var draw_times_usec: Array[int] = [] func _draw() -> void: var started: int = Time.get_ticks_usec() super._draw() draw_times_usec.append(Time.get_ticks_usec() - started) class SlowWall extends StageWallPanel: var inject_delay: bool = true func get_surface_draw_origin_world() -> Vector2: if inject_delay: OS.delay_usec(PREVIEW_DRAW_BUDGET_USEC + 1000) return super.get_surface_draw_origin_world() var _failures: Array[String] = [] func _initialize() -> void: call_deferred("_run") func _run() -> void: root.size = Vector2i(2560, 1440) for finish: StringName in RENDERER.WALL_FINISHES + RENDERER.BACKGROUND_FINISHES: _expect(not RENDERER.needs_light_preview(finish, Vector2(16.0, 16.0)), "tiny %s keeps its detail" % finish) _expect(not RENDERER.needs_light_preview(finish, Vector2.ZERO), "invisible %s has no preview work" % finish) _test_placement(finish) await _benchmark_finish(finish) for finish: StringName in [&"finish_wall_brick", &"finish_wall_wood", &"finish_background_grass", &"finish_background_carpet"]: _expect(RENDERER.needs_light_preview(finish, Vector2(2400.0, 1320.0)), "large %s reduces detail automatically" % finish) _expect(not RENDERER.needs_light_preview(&"finish_wall_paint", Vector2(100000.0, 100000.0)), "paint stays detailed at any size") _expect(not RENDERER.needs_light_preview(&"finish_background_concrete", Vector2(2400.0, 1320.0)), "ordinary concrete slab patterns stay detailed") _expect(RENDERER.needs_light_preview(&"finish_background_concrete", Vector2(24000.0, 13200.0)), "very large concrete patterns can also simplify") _expect(RENDERER.needs_light_preview(&"finish_wall_wood", Vector2(240.0, 132.0), Vector2(10.0, 10.0)), "work estimate accounts for motif scale") await _test_runtime_fallback() if _failures.is_empty(): print("PASS: adaptive scenery surface previews") else: for failure: String in _failures: push_error("FAIL: " + failure) quit(0 if _failures.is_empty() else 1) func _test_placement(finish: StringName) -> void: var world := Node2D.new() var ghosts := Node2D.new() var controller := CONTROLLER.new() as StageSceneryController root.add_child(world) root.add_child(ghosts) root.add_child(controller) controller.configure(world, ghosts, null) var wall: bool = RENDERER.is_wall_finish(finish) var tint: Color = RENDERER.default_tint(finish) var start := Vector2(-300.0, -150.0) if wall: controller.begin_wall(finish, start, tint) else: controller.begin_background_ground(finish, start, tint) var ghost: StageRectSurface = ghosts.get_child(0) as StageRectSurface _expect(ghost.interactive_preview, "%s placement opts into adaptive rendering" % finish) var press := InputEventMouseButton.new() press.button_index = MOUSE_BUTTON_LEFT press.pressed = true controller.handle_mouse_button(press, start, false) controller.handle_mouse_motion(Vector2(2100.0, 1170.0)) var bounds: Rect2 = ghost.global_transform * ghost.get_local_bounds() var release := InputEventMouseButton.new() release.button_index = MOUSE_BUTTON_LEFT controller.handle_mouse_button(release, Vector2(2100.0, 1170.0), false) _expect(world.get_child_count() == 1, "%s commits one surface" % finish) var placed: StageRectSurface = world.get_child(0) as StageRectSurface _expect(not placed.interactive_preview, "%s commit uses full detail" % finish) _expect(placed.finish_id == finish and placed.tint == tint, "%s preserves finish and tint" % finish) _expect(bounds.is_equal_approx(placed.global_transform * placed.get_local_bounds()), "%s preserves preview bounds" % finish) controller.exit_tool() world.free() ghosts.free() controller.free() func _benchmark_finish(finish: StringName) -> void: var sample: StageRectSurface = TimedWall.new() if RENDERER.is_wall_finish(finish) else TimedGround.new() sample.position = Vector2(1280.0, 720.0) sample.interactive_preview = true sample.configure(Vector2(2400.0, 1320.0), finish, RENDERER.default_tint(finish)) root.add_child(sample) await process_frame await process_frame var preview_usec: float = await _measure_redraws(sample) sample.interactive_preview = false await process_frame await process_frame var full_usec: float = await _measure_redraws(sample) print("%s CPU redraw: adaptive=%.2f ms, full=%.2f ms" % [finish, preview_usec / 1000.0, full_usec / 1000.0]) sample.free() func _measure_redraws(sample: StageRectSurface) -> float: var timings: Array[int] = sample.get("draw_times_usec") timings.clear() for _iteration: int in 3: sample.queue_redraw() await process_frame await process_frame _expect(not timings.is_empty(), "surface draws during benchmark") var total: float = 0.0 for elapsed: int in timings: total += float(elapsed) return total / maxf(1.0, float(timings.size())) func _test_runtime_fallback() -> void: var sample := SlowWall.new() sample.position = Vector2(100.0, 100.0) sample.interactive_preview = true sample.configure(Vector2(16.0, 16.0), &"finish_wall_wood", Color.WHITE) root.add_child(sample) await process_frame await process_frame _expect(sample.get("_preview_over_budget"), "measured slow redraw enables the fallback even for a small panel") sample.inject_delay = false sample.configure(Vector2(20.0, 20.0), sample.finish_id, sample.tint) await process_frame await process_frame _expect(sample.get("_preview_over_budget"), "fallback stays stable during the current placement session") sample.interactive_preview = false _expect(not sample.get("_preview_over_budget"), "leaving preview resets the timing fallback") sample.free() func _expect(condition: bool, message: String) -> void: if not condition: _failures.append(message)