@tool class_name WorldRenderFacade extends Node ## Stable main-thread boundary between runtime/tool callers and the monolithic ## streaming renderer. The facade owns no queues, caches, nodes or RIDs. const GODOT_WORLD_POSITION_SCRIPT := preload("res://src/domain/coordinates/godot_world_position.gd") const STREAMING_FOCUS_SCRIPT := preload("res://src/domain/streaming/streaming_focus.gd") const RENDERED_GROUND_SAMPLE_SCRIPT := preload("res://src/render/terrain/rendered_ground_sample.gd") const WORLD_ENVIRONMENT_SNAPSHOT_SCRIPT := preload("res://src/render/environment/world_environment_snapshot.gd") const ENTITY_PRESENTATION_SNAPSHOT_SCRIPT := preload("res://src/render/entities/entity_presentation_snapshot.gd") const ENTITY_ID_SCRIPT := preload("res://src/domain/identity/entity_id.gd") ## Internal renderer implementation resolved relative to this node. ## The referenced node remains owned by its scene parent. @export var streaming_world_loader_path: NodePath = NodePath("..") ## Scene-owned outdoor environment implementation resolved relative to this node. ## The facade delegates snapshots but owns neither the controller nor Environment. @export var world_environment_controller_path: NodePath = NodePath("../WowSkyController") ## Scene-owned service that creates and owns session-local world entity visuals. @export var world_entity_presenter_path: NodePath = NodePath("../WorldEntityPresenter") ## Optional explicit Node3D source sampled in Godot world coordinates. ## Runtime scenes use the player; capture and probe tools replace it with their camera. @export var streaming_focus_source_path: NodePath var _streaming_world_loader: Node var _world_environment_controller: Node var _world_entity_presenter: Node var _missing_loader_reported := false var _missing_environment_controller_reported := false var _missing_entity_presenter_reported := false var _missing_focus_source_reported := false func _ready() -> void: _resolve_streaming_world_loader() set_process(streaming_focus_source_path != NodePath()) if streaming_focus_source_path != NodePath(): # Child nodes become ready before their parent StreamingWorldLoader. Only # publish the initial focus here; the loader applies it after initialization. _capture_streaming_focus_from_source() func _process(_delta_seconds: float) -> void: _capture_streaming_focus_from_source() ## Replaces the renderer focus. The immutable value remains caller-owned; the ## internal streamer retains the reference for its next throttled refresh. func set_streaming_focus(streaming_focus: StreamingFocus) -> void: var loader := _resolve_streaming_world_loader() if loader == null: return loader.call("set_streaming_focus", streaming_focus) ## Samples the configured source and requests target refresh from the internal ## streamer. Returns false when either dependency or a valid focus is unavailable. func refresh_streaming_focus(force: bool = false) -> bool: _capture_streaming_focus_from_source() var loader := _resolve_streaming_world_loader() if loader == null: return false return bool(loader.call("refresh_streaming_focus", force)) ## Returns a detached read-only metrics snapshot for diagnostics and baselines. ## Mutating the returned Dictionary cannot mutate renderer-owned state. func renderer_metrics_snapshot() -> Dictionary: var loader := _resolve_streaming_world_loader() if loader == null: return {} var metrics_variant = loader.call("render_baseline_snapshot") if not (metrics_variant is Dictionary): push_error("WorldRenderFacade received a non-Dictionary renderer metrics snapshot.") return {} return (metrics_variant as Dictionary).duplicate(true) ## Samples the currently loaded render terrain at a typed Godot world position. ## This read-only renderer view is diagnostic and does not replace authoritative ## gameplay collision or the independently composed [TerrainQuery]. func sample_ground_height(godot_world_position: GodotWorldPosition) -> RefCounted: var loader := _resolve_streaming_world_loader() if loader == null: return RENDERED_GROUND_SAMPLE_SCRIPT.unavailable(&"render_facade_unavailable") var sample_variant = loader.call("sample_rendered_ground_height", godot_world_position) if sample_variant is RefCounted and sample_variant.get_script() == RENDERED_GROUND_SAMPLE_SCRIPT: return sample_variant push_error("WorldRenderFacade received an invalid rendered ground sample.") return RENDERED_GROUND_SAMPLE_SCRIPT.unavailable(&"render_ground_sample_invalid") ## Returns a detached diagnostic snapshot for one rendered-ground query. ## Mutating the result cannot mutate streamer queues, tile state or resources. func renderer_ground_query_snapshot(godot_world_position: GodotWorldPosition) -> Dictionary: var loader := _resolve_streaming_world_loader() if loader == null: return {} var snapshot_variant = loader.call("render_ground_query_snapshot", godot_world_position) if not (snapshot_variant is Dictionary): push_error("WorldRenderFacade received a non-Dictionary ground query snapshot.") return {} return (snapshot_variant as Dictionary).duplicate(true) ## Applies an immutable environment snapshot through the renderer-owned sky path. ## Returns false for an invalid value or unavailable/incompatible controller. func apply_environment_snapshot(world_environment_snapshot: RefCounted) -> bool: if ( world_environment_snapshot == null or world_environment_snapshot.get_script() != WORLD_ENVIRONMENT_SNAPSHOT_SCRIPT or not bool(world_environment_snapshot.get("is_valid")) ): return false var controller := _resolve_world_environment_controller() if controller == null: return false return bool(controller.call("apply_environment_snapshot", world_environment_snapshot)) ## Idempotently creates or updates one world entity visual through the renderer- ## owned presenter. The immutable full snapshot remains caller-owned. func present_entity(entity_presentation_snapshot: RefCounted) -> bool: if ( entity_presentation_snapshot == null or entity_presentation_snapshot.get_script() != ENTITY_PRESENTATION_SNAPSHOT_SCRIPT or not bool(entity_presentation_snapshot.get("is_valid")) ): return false var presenter := _resolve_world_entity_presenter() if presenter == null: return false return bool(presenter.call("present_entity", entity_presentation_snapshot)) ## Removes one session-local entity visual without changing authoritative state. func remove_entity(entity_id: RefCounted) -> bool: if entity_id == null or entity_id.get_script() != ENTITY_ID_SCRIPT or not bool(entity_id.call("is_valid")): return false var presenter := _resolve_world_entity_presenter() if presenter == null: return false return bool(presenter.call("remove_entity", entity_id)) ## Returns detached entity-presentation diagnostics with no Node references. func entity_presentation_snapshot() -> Dictionary: var presenter := _resolve_world_entity_presenter() if presenter == null: return {} var snapshot_variant = presenter.call("entity_presentation_snapshot") if not (snapshot_variant is Dictionary): push_error("WorldRenderFacade received a non-Dictionary entity presentation snapshot.") return {} return (snapshot_variant as Dictionary).duplicate(true) func _capture_streaming_focus_from_source() -> void: if streaming_focus_source_path == NodePath(): return var focus_source := get_node_or_null(streaming_focus_source_path) as Node3D if focus_source == null: if not _missing_focus_source_reported: push_warning("WorldRenderFacade focus source is missing or is not Node3D: %s" % streaming_focus_source_path) _missing_focus_source_reported = true return _missing_focus_source_reported = false var godot_position: Vector3 = focus_source.global_position var typed_world_position = GODOT_WORLD_POSITION_SCRIPT.new( godot_position.x, godot_position.y, godot_position.z ) set_streaming_focus(STREAMING_FOCUS_SCRIPT.new(typed_world_position)) func _resolve_streaming_world_loader() -> Node: if is_instance_valid(_streaming_world_loader): return _streaming_world_loader var candidate := get_node_or_null(streaming_world_loader_path) if candidate == null: if not _missing_loader_reported: push_error("WorldRenderFacade cannot resolve StreamingWorldLoader at %s" % streaming_world_loader_path) _missing_loader_reported = true return null for required_method in [ &"set_streaming_focus", &"refresh_streaming_focus", &"render_baseline_snapshot", &"sample_rendered_ground_height", &"render_ground_query_snapshot", ]: if not candidate.has_method(required_method): if not _missing_loader_reported: push_error("WorldRenderFacade renderer implementation lacks %s" % required_method) _missing_loader_reported = true return null _streaming_world_loader = candidate _missing_loader_reported = false return _streaming_world_loader func _resolve_world_environment_controller() -> Node: if is_instance_valid(_world_environment_controller): return _world_environment_controller var candidate := get_node_or_null(world_environment_controller_path) if candidate == null or not candidate.has_method(&"apply_environment_snapshot"): if not _missing_environment_controller_reported: push_error( "WorldRenderFacade cannot resolve a compatible environment controller at %s" % world_environment_controller_path ) _missing_environment_controller_reported = true return null _world_environment_controller = candidate _missing_environment_controller_reported = false return _world_environment_controller func _resolve_world_entity_presenter() -> Node: if is_instance_valid(_world_entity_presenter): return _world_entity_presenter var candidate := get_node_or_null(world_entity_presenter_path) if candidate == null: if not _missing_entity_presenter_reported: push_error("WorldRenderFacade cannot resolve entity presenter at %s" % world_entity_presenter_path) _missing_entity_presenter_reported = true return null for required_method in [&"present_entity", &"remove_entity", &"entity_presentation_snapshot"]: if not candidate.has_method(required_method): if not _missing_entity_presenter_reported: push_error("WorldRenderFacade entity presenter lacks %s" % required_method) _missing_entity_presenter_reported = true return null _world_entity_presenter = candidate _missing_entity_presenter_reported = false return _world_entity_presenter