## Reference in-process Atlas agent driver (T-971, D-226 item 4) — the ## SANCTIONED way to drive the Atlas headlessly via AtlasAgentInterface, ## replacing the five hand-rolled scratch eyeball drivers the T-1183/T-1157 ## harness rounds produced ad hoc. Every real navigation call below goes ## through AtlasAgentInterface.act()/observe() — this file does not touch ## `_gui_input`, does not synthesize pixel events, and does not reach into ## any screen's private fields. ## ## Folds in two of the T-1157 harness-techniques inventory patterns ## (pql ticket show T-1157): ## 1. InputSwallower — a root-level Node, added first, low process_priority, ## whose _input()/_unhandled_input() both call ## get_viewport().set_input_as_handled() unconditionally. Neutralizes ## real desktop input reaching this `-s` SceneTree driver's window ## (X11/XWayland can deliver pointer events to an unfocused window under ## the cursor on a shared desktop) — this driver runs real production ## code via method calls, but the WINDOW still exists and can still ## receive stray input, so the guard applies here too. ## 4. Full-run-restart-on-anomaly (documented, not code-enforced — see ## _run() header note below): any assertion failure here should discard ## the whole run rather than retrying mid-sequence, matching the ## inventory's "partial retries silently corrupt the state the ## comparison depends on" finding. This driver's own job list is short ## and idempotent per invocation (fresh process each run), so the ## discipline is "don't loop-retry a failed step in place" rather than a ## literal restart mechanism. ## ## Technique 5 (fixed-center revisit) is what `jump_to_center` (this file's ## own `_run_job("jump_to_center", ...)` dispatch) exists to exercise — ## AtlasAgentInterface's production seam for it, not reimplemented here. ## ## Usage (JSON job list on stdin path, matching visual_capture.gd's own ## `-- --flag value` CLI convention): ## godot --rendering-driver opengl3 --path client \ ## -s res://tests/atlas_agent_driver.gd -- \ ## --jobs res://tests/fixtures/atlas_agent_smoke_jobs.json \ ## --output /abs/path/observe_log.json ## ## Each job is {"intent": "...", "params": {...}}; "observe" is a ## pseudo-intent this driver special-cases to call ## AtlasAgentInterface.observe() instead of act() (observe takes no params). ## The full sequence of act()/observe() results is written to --output as a ## JSON array — the smoke test (test_atlas_agent_driver_smoke.gd) reads this ## back to assert the reference driver actually completes a real session. extends SceneTree var _jobs_path: String = "" var _output_path: String = "" var _results: Array = [] func _init(): _run.call_deferred() func _run() -> void: _parse_args() if _jobs_path.is_empty(): push_error("atlas_agent_driver: --jobs PATH is required") quit(1) return if _output_path.is_empty(): push_error("atlas_agent_driver: --output PATH is required") quit(1) return var jobs: Array = _load_jobs(_jobs_path) if jobs.is_empty(): push_error("atlas_agent_driver: no jobs loaded from %s" % _jobs_path) quit(1) return var main_scene = load("res://scenes/main.tscn") var main_node = main_scene.instantiate() # Technique 1 (T-1157 inventory) — InputSwallower, added FIRST so its low # process_priority still runs before anything that might otherwise react # to stray desktop input reaching this unfocused/off-screen window. var swallower := _InputSwallower.new() swallower.process_priority = -1000 root.add_child(swallower) root.add_child(main_node) # Settle frames — camera smoothing, fog uniform init, UI layout (same # rationale as visual_capture.gd's own settle-frame pass). for i in range(10): await process_frame var atlas_agent_interface := load("res://ui/implant/apps/atlas/atlas_agent_interface.gd") var atlas_agent_bridge: Node = root.get_node("/root/AtlasAgentBridge") for job: Dictionary in jobs: var intent: String = str(job.get("intent", "")) var params: Dictionary = job.get("params", {}) var result: Dictionary if intent == "observe": result = atlas_agent_interface.observe(atlas_agent_bridge.current_app) else: result = atlas_agent_interface.act(atlas_agent_bridge.current_app, intent, params) # Settle-until-ready (T-1157 inventory item 2's underlying discipline, # applied generically here rather than per-navigation-intent): a few # frames after every mutating call so the resulting screen/viewer # state (panel rebuilds, queue_redraw()) has actually applied before # the NEXT job's observe() reads it. for i in range(4): await process_frame _results.append({"intent": intent, "params": params, "result": result}) _write_output(_output_path, _results) quit() func _parse_args() -> void: var args := OS.get_cmdline_user_args() var i := 0 while i < args.size(): match args[i]: "--jobs": i += 1 if i < args.size(): _jobs_path = args[i] "--output": i += 1 if i < args.size(): _output_path = args[i] i += 1 static func _load_jobs(path: String) -> Array: var abs_path: String = path if path.begins_with("res://"): abs_path = ProjectSettings.globalize_path(path) var f := FileAccess.open(abs_path, FileAccess.READ) if f == null: return [] var text := f.get_as_text() f.close() var parsed: Variant = JSON.parse_string(text) return parsed if parsed is Array else [] static func _write_output(path: String, results: Array) -> void: var f := FileAccess.open(path, FileAccess.WRITE) if f == null: push_error("atlas_agent_driver: cannot open output path %s" % path) return f.store_string(JSON.stringify(results, " ")) f.close() ## T-1157 inventory item 1 — see this file's own header doc for the full ## rationale. A tiny inner class rather than a separate file: this pattern ## has exactly one consumer (this driver) today: promote to a shared ## `client/tests/` helper the moment a second `-s` driver needs it. class _InputSwallower: extends Node func _input(_event: InputEvent) -> void: get_viewport().set_input_as_handled() func _unhandled_input(_event: InputEvent) -> void: get_viewport().set_input_as_handled()