extends Control var connection: Node var busy := false var status: Label var history: RichTextLabel var prepare: Button var start: Button var reset: Button var message: Label var confirm: ConfirmationDialog func _ready() -> void: connection = preload("res://core/network/room_connection.gd").new() add_child(connection) connection.configure("operator") var center := CenterContainer.new() center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) add_child(center) var column := VBoxContainer.new() column.custom_minimum_size = Vector2(1050, 0) column.add_theme_constant_override("separation", 18) center.add_child(column) var title := Label.new() title.text = "ARCADE ROOM CONTROL" title.add_theme_font_size_override("font_size", 42) column.add_child(title) status = Label.new() status.add_theme_font_size_override("font_size", 25) column.add_child(status) var row := HBoxContainer.new() column.add_child(row) prepare = add_button(row, "Prepare Pac-Man", func(): command("prepare")) start = add_button(row, "Start Game", func(): command("start")) reset = add_button(row, "Reset Cabinet…", func(): confirm.popup_centered()) confirm = ConfirmationDialog.new() confirm.dialog_text = "Unload the current game and reset this cabinet's room session?" confirm.confirmed.connect(func(): command("reset")) add_child(confirm) message = Label.new() message.text = "Cabinet 01 quick controls • Open the controller URL in a browser for all cabinets" column.add_child(message) history = RichTextLabel.new() history.custom_minimum_size = Vector2(1050, 380) history.add_theme_font_size_override("normal_font_size", 20) column.add_child(history) var hint := Label.new() hint.text = "Accepted GPIO output does not confirm physical coin delivery.\nClosing this window leaves the room controller running. Escape closes this app." column.add_child(hint) set_buttons(false, false, false) var timer := Timer.new() timer.wait_time = 1.0 timer.timeout.connect(refresh) add_child(timer) timer.start() refresh() func add_button(parent: Node, text: String, callback: Callable) -> Button: var button := Button.new() button.text = text button.add_theme_font_size_override("font_size", 24) button.pressed.connect(callback) parent.add_child(button) return button func set_buttons(can_prepare: bool, can_start: bool, can_reset: bool) -> void: prepare.disabled = not can_prepare start.disabled = not can_start reset.disabled = not can_reset func display(state: Dictionary) -> void: if state.has("error"): status.text = "CONTROLLER UNAVAILABLE\n" + str(state.error) set_buttons(false, false, false) return var pending := false for item in state.commands: if item.status == "queued": pending = true var available: bool = state.online and not pending set_buttons(available and state.phase in ["idle", "won", "lost", "needs_prepare"], available and state.phase == "ready", available) status.text = "Cabinet 01: %s | %s\nGame: %s | GPIO mode: %s | Agent: %s\nReward: %s" % ["ONLINE" if state.online else "OFFLINE", str(state.phase).to_upper(), state.get("game", "pacman"), state.gpio, state.version, state.reward] history.text = "EVENT HISTORY\n" for item in state.history: history.text += "%s %s\n" % [Time.get_datetime_string_from_unix_time(int(item.time)).replace("T", " ") + " UTC", item.message] history.scroll_to_line(history.get_line_count()) func refresh() -> void: if busy: return busy = true var state: Dictionary = await connection.post("/operator/state") busy = false display(state) func command(action: String) -> void: if busy: return busy = true set_buttons(false, false, false) var payload := {"action": action, "cabinet": "cabinet_01"} if action == "prepare": payload = {"action": "prepare_scenario", "scenario": "proof_of_concept"} var state: Dictionary = await connection.post("/operator/command", payload) busy = false if state.has("error"): message.text = str(state.error) else: message.text = "Command queued; waiting for cabinet acknowledgement" display(state)