extends SceneTree ## Passive observer: no selection, save/load, command or engine input injection. ## Runner launches separate owned processes with private user-data directories. var _main: Control var _elapsed := 0.0 var _last := "" var _sequence := 0 var _output := "" var _source_map: Dictionary = {} func _initialize() -> void: call_deferred("_start") func _start() -> void: _output = OS.get_environment("P24_OBSERVER_OUTPUT") var pins := OS.get_environment("P24_OBSERVER_SOURCE_MAP") if not _output.is_absolute_path() or not DirAccess.dir_exists_absolute(_output) or not pins.is_absolute_path(): push_error("P24 observer requires existing absolute output directory and source-map JSON") quit(1) return var parsed: Variant = JSON.parse_string(FileAccess.get_file_as_string(pins)) if not parsed is Dictionary or parsed.is_empty(): push_error("P24 observer source map missing") quit(1) return _source_map = parsed root.size = Vector2i(1440, 810) _main = load("res://scenes/main/main.tscn").instantiate() root.add_child(_main) print("P24_PROCESS pid=%d scene=res://scenes/main/main.tscn" % OS.get_process_id()) # Use the ordinary Main pause control through OS input when a frozen state # is needed. The observer itself never changes Runtime or Clock. func _process(delta: float) -> bool: _elapsed += delta if _elapsed >= 1.0 and is_instance_valid(_main): _elapsed = 0.0 _capture() return false func _capture() -> void: var runtime: Variant = root.get_node("SimulationRuntime") var selection: Dictionary = _main.call("_capture_interface_selection") var facility_id := StringName(selection.get("id", "")) var facility: Variant = runtime.facility_service.get_instance(facility_id) var guest_panel: GuestObservationPanel var dialog: Variant = _main.get("guest_observer") if dialog != null: guest_panel = dialog.find_child("GuestObservationPanel", true, false) as GuestObservationPanel var staff_panel: StaffManagementPanel = _main.get("staff_management_panel") var staff_model: Dictionary = {} for row: Dictionary in runtime.get_staff_management_report().get(&"staff", []): if String(row.get(&"staff_id", "")) == String(selection.get("staff_id", "")): staff_model = row.duplicate(true) var needs: Dictionary = {} if guest_panel != null: for need: StringName in guest_panel.bars: needs[need] = guest_panel.bars[need].value var observation := { "interface_selection": selection, "layer": root.get_node("GameState").get("active_layer"), "facility": {"id": String(facility_id), "model_snapshot": facility.create_snapshot() if facility != null else {}, "heading_text": _main.get("selected_heading_label").text, "detail_text": _main.get("selected_detail_label").text, "highlight_id": String(_main.get("world_preview").get("selected_entity_id"))}, "guest": {"id": selection.get("guest_id", ""), "model_report": runtime.guest_service.get_observation(StringName(selection.get("guest_id", ""))), "detail_text": guest_panel.details.text if guest_panel != null else "", "priority_text": guest_panel.priority_label.text if guest_panel != null else "", "decision_text": guest_panel.decision_label.text if guest_panel != null else "", "need_bars": needs}, "staff": {"id": selection.get("staff_id", ""), "model_report": staff_model, "detail_text": staff_panel.staff_detail_label.text, "editor_visible": staff_panel.editor_panel.is_visible_in_tree()}, } var encoded := JSON.stringify(observation, "", true) if encoded == _last: return _sequence += 1 var stem := "process-%d-observation-%d" % [OS.get_process_id(), _sequence] var report_path := _output.path_join(stem + ".json") var frame_path := _output.path_join(stem + ".png") if FileAccess.file_exists(report_path) or FileAccess.file_exists(frame_path): push_error("P24 observer refuses artifact overwrite") return var image := root.get_texture().get_image() if image.save_png(frame_path) != OK: push_error("P24 observer frame capture failed") return var file := FileAccess.open(report_path, FileAccess.WRITE) if file == null: push_error("P24 observer report write failed") return file.store_string(JSON.stringify({"pid": OS.get_process_id(), "source_map": _source_map, "scene": "res://scenes/main/main.tscn", "ticks_usec": Time.get_ticks_usec(), "raw_observation": observation, "screenshot": stem + ".png", "formal_acceptance": false}, "\t", true) + "\n") file.close() _last = encoded print("P24_RAW_OBSERVATION " + encoded)