extends Node class_name MpWorldView ## Finding things in the world under test, and describing what they are doing. ## ## Nothing here caches a node. Items are consumed and respawned as the run goes ## on — the raw burger becomes a cooked burger, which becomes a hamburger, which ## is absorbed into a plate — so a held reference is a freed object waiting to ## happen. Everything is looked up at the moment it is used. var world: Node3D var report: MpReport var _despawn_timers_seen := {} func setup(p_world: Node3D, p_report: MpReport) -> void: world = p_world report = p_report ## Objects live either baked in the scene root or, once spawned at runtime, under ## WorldContent. Look in both. func find(name: String) -> Node3D: var n := world.get_node_or_null(name) if not n: n = world.get_node_or_null("WorldContent/" + name) return n as Node3D func zone_of(station_name: String) -> XRToolsSnapZone: var s := find(station_name) return s.get_node_or_null("XRToolsSnapZone") as XRToolsSnapZone if s else null ## Cooking and combining spawn their results with engine-assigned names, so the ## only stable way to find one is by the food id it carries. func find_by_food_id(id: String) -> Node3D: for child in roots_children(): var f := child.get_node_or_null("FoodItem") as FoodItem if f and f.id == id and is_instance_valid(child): return child as Node3D return null func roots_children() -> Array[Node]: var out: Array[Node] = [] for root in [world, world.get_node_or_null("WorldContent")]: if not root: continue for child in root.get_children(): out.append(child) return out func pickables() -> Array[Node]: var out: Array[Node] = [] for child in roots_children(): if child is XRToolsPickable and not child.is_queued_for_deletion(): out.append(child) return out ## Who is currently driving this object's transform. ## ## Under the high-level model the object's own multiplayer authority never ## changes — only its NetXform's does, and that is what "who is holding this" ## means now. func xform_authority(item: Node) -> int: if not is_instance_valid(item): return 0 var xform := item.get_node_or_null(NetReplication.XFORM_NAME) return xform.get_multiplayer_authority() if xform else 0 ## The test deliberately leaves objects sitting still for minutes at a time, ## which DespawningItem treats as litter and removes — it took the raw burger ## away before the client had even connected. Hold them indefinitely instead, so ## the run tests the kitchen rather than the despawn timer. ## ## Re-checked before every step rather than only at startup, because cooking and ## combining spawn new objects as the run goes on. func disable_despawn_timers() -> void: var stopped := 0 for node in world.find_children("*", "DespawningItem", true, false): if _despawn_timers_seen.has(node.get_instance_id()): continue _despawn_timers_seen[node.get_instance_id()] = true node.set_process(false) stopped += 1 if stopped > 0: report.log_line("disabled %d DespawningItem timer(s) so test objects don't vanish mid-run" % stopped) func diag(item: Node3D, hand: XRToolsFunctionPickup = null) -> String: if not is_instance_valid(item): return "item[freed]" var pc := item.get_node_or_null("PlateController") var s := "%s[pos=%s xform_authority=%d" % [item.name, item.global_position, xform_authority(item)] if item is XRToolsPickable: s += " enabled=%s can_pick_up=%s layer=%d held_by=%s" % [ item.enabled, item.can_pick_up(hand) if hand else "?", item.collision_layer, item.get_picked_up_by()] if pc: s += " dirty=%s contains=%s" % [pc.is_dirty, str(pc.contained_ids)] return s + "]" func dump_state(stations: Array, hand: XRToolsFunctionPickup) -> void: report.log_line(" stations:") for station in stations: var z := zone_of(station) if z: report.log_line(" %-12s zone holds %s (enabled=%s)" % [station, z.picked_up_object, z.enabled]) report.log_line(" items:") for child in pickables(): report.log_line(" %s" % diag(child as Node3D, hand))