OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_settings_panel.gd

207 lines · gd ref:
extends SceneTree
## Gate: the Settings panel is usable (settings page audit, docs/settings-page-audit.md).
##
## Measured before the redesign: a fixed VBox 1175 px tall in an 873 px window, top at
## y = -151, no scroll — Apply/Cancel were OFF SCREEN. This holds the redesign to:
##   1. the panel and Apply/Cancel fit a 1280x720 window (the shipped size);
##   2. every offered SCHEMA row exists, on a tab, with the widget its kind calls for;
##   3. option values name the real setting (no Low/High on Cloud Quality);
##   4. advanced groups start collapsed and open on click;
##   5. RTX quality rows are disabled while Path Tracing is off;
##   6. the preset row says Custom when no preset matches;
##   7. focusing a row puts its help (with measured cost) in the help line;
##   8. widgets write Settings.
## The menu is taken out of game.tscn on its own (as gate_purge_confirm does). No
## Apply is pressed, so user://settings.json is never written; overrides are restored.
##   HYPERTPS_DAEMON_TCP=127.0.0.1:1 "$HYPER_TPS_GODOT" --headless --resolution 1280x720 --path . --script res://scenes/test/gate_settings_panel.gd

var ok := true
var checks := 0
var _frame := 0
var _menu: Node
var _settings: Node
var _saved: Dictionary


func _p(c: bool, m: String, got: Variant = "") -> void:
	checks += 1
	print(("ok   " if c else "FAIL: ") + m + ("" if str(got) == "" else "  (" + str(got) + ")"))
	if not c:
		ok = false


func _inside(r: Rect2, view: Rect2) -> bool:
	return view.grow(0.5).encloses(r)


func _process(_d: float) -> bool:
	_frame += 1
	match _frame:
		1:
			# Headless ignores --resolution (the root stays 64x64): size it here.
			root.size = Vector2i(1280, 720)
			_settings = root.get_node("/root/Settings")
			_saved = _settings._overrides.duplicate()
			_settings._overrides.clear()
			# A WorldEnvironment so the RTX rows are offered (the fork's Environment
			# has pathtracing_enabled; on stock Godot they stay hidden and 5 is skipped).
			var we := WorldEnvironment.new()
			we.environment = Environment.new()
			root.add_child(we)
			var game: Node = (load("res://scenes/game/game.tscn") as PackedScene).instantiate()
			_menu = game.get_node("MenuUI/MenuMain")
			_menu.get_parent().remove_child(_menu)
			game.free()
			root.add_child(_menu)
		5:
			_menu.call("_show_settings")
		15:
			_run()
			_settings._overrides = _saved
			if checks < 20:
				_p(false, "only %d checks ran" % checks)
			print("GATE settings_panel: %s (%d checks)" % ["PASS" if ok else "FAIL", checks])
			quit(0 if ok else 1)
			return true
	return false


func _run() -> void:
	var view := Rect2(Vector2.ZERO, Vector2(root.size))
	print("-- window %s" % view.size)
	var panel: Control = _menu.get("settings_panel")
	_p(_inside(panel.get_global_rect(), view), "1. the panel fits the window", panel.get_global_rect())
	for bn: String in ["ApplyButton", "CancelButton"]:
		var b: Control = panel.find_child(bn, true, false)
		_p(b != null and b.is_visible_in_tree() and _inside(b.get_global_rect(), view),
			"1. %s is visible and on screen" % bn, b.get_global_rect() if b != null else "missing")

	print("-- rows")
	var missing: Array = []
	for o: Dictionary in _settings.SCHEMA:
		var k: String = o["key"]
		if _settings.is_available(k) and (_menu.call("settings_row", k) as Dictionary).is_empty():
			missing.append(k)
	_p(missing.is_empty(), "2. every offered SCHEMA row exists", missing)
	var tabs: PackedStringArray = _menu.call("settings_tabs")
	_p(tabs.size() >= 2 and tabs[0] == "Graphics", "2. tabs, Graphics first", tabs)
	var kinds := {"clouds": "OptionButton", "render_scale": "HSlider",
		"terrain_material": "segmented", "interest_management": "CheckButton"}
	for k: String in kinds:
		var r: Dictionary = _menu.call("settings_row", k)
		var got: String = String(r.get("widget", ""))
		_p(got == kinds[k], "2. %s is a %s" % [k, kinds[k]], got)

	var cl: Dictionary = _menu.call("settings_row", "clouds")
	var ob: OptionButton = cl.get("control") as OptionButton
	var labels: Array = []
	if ob != null:
		for i in ob.item_count:
			labels.append(ob.get_item_text(i))
	_p(labels == ["Off", "Quarter res", "Half res", "Full res"],
		"3. Cloud Quality names the real setting", labels)
	for o: Dictionary in _settings.SCHEMA:
		if String(o["key"]) == "preset":
			continue
		var ch: Array = o.get("choices", [])
		_p(not (ch.has("Low") or ch.has("High") or ch.has("Ultra")),
			"3. %s has no Low/High/Ultra values" % o["key"], ch)

	print("-- groups")
	var aa: Dictionary = _menu.call("settings_row", "aa")
	var aa_ctl: Control = aa.get("control")
	_p(aa_ctl != null and not aa_ctl.is_visible_in_tree(), "4. MSAA starts in a collapsed group")
	var toggle: BaseButton = aa.get("group_button")
	var last_vis: Control = (_menu.call("settings_row", "clouds") as Dictionary).get("label")
	_p(toggle != null and last_vis != null and toggle.get_global_rect().position.y > last_vis.get_global_rect().position.y,
		"4. collapsed groups sit BELOW the visible rows")
	if toggle != null:
		toggle.pressed.emit()
	_p(aa_ctl != null and aa_ctl.is_visible_in_tree(), "4. clicking the group header shows it")

	print("-- dependencies")
	if _settings.is_available("pathtracing"):
		_settings.set_value("pathtracing", false)
		_menu.call("_highlight_settings_buttons")
		var ps: Dictionary = _menu.call("settings_row", "pt_samples")
		_p(bool(ps.get("disabled", false)), "5. PT samples disabled while Path Tracing is off")
		_settings.set_value("pathtracing", true)
		_menu.call("_highlight_settings_buttons")
		ps = _menu.call("settings_row", "pt_samples")
		_p(not bool(ps.get("disabled", true)), "5. and enabled when it is on")
		_settings.set_value("pathtracing", false)
	else:
		print("skip 5: no pathtracing_enabled on this engine")

	print("-- preset")
	_settings.set_value("preset", 1)
	_menu.call("_highlight_settings_buttons")
	_p(String(_menu.call("settings_preset_state")) == "Medium", "6. preset row reads Medium",
		_menu.call("settings_preset_state"))
	_settings.set_value("aa", 3)
	_menu.call("_highlight_settings_buttons")
	_p(String(_menu.call("settings_preset_state")) == "Custom", "6. a manual change reads Custom",
		_menu.call("settings_preset_state"))

	print("-- help")
	if ob != null:
		ob.focus_entered.emit()
	var help: String = _menu.call("settings_help_text")
	_p(help.contains("cloud") and help.contains("ms"), "7. focusing Cloud Quality shows its help and cost", help)

	print("-- widgets write Settings")
	if ob != null:
		ob.select(1)
		ob.item_selected.emit(1)
	_p(int(_settings.get_value("clouds")) == 1, "8. dropdown writes clouds", _settings.get_value("clouds"))
	var rs: HSlider = (_menu.call("settings_row", "render_scale") as Dictionary).get("control") as HSlider
	if rs != null:
		rs.value = 1
	_p(int(_settings.get_value("render_scale")) == 1, "8. slider writes render_scale", _settings.get_value("render_scale"))

	print("-- 9. Display / Controls / Audio tabs")
	tabs = _menu.call("settings_tabs")
	_p("Display" in tabs and "Controls" in tabs and "Audio" in tabs, "9. Display, Controls and Audio tabs exist", tabs)
	for key: String in ["window_mode", "resolution", "vsync", "max_fps", "fov", "mouse_sensitivity", "invert_y", "master_volume"]:
		_p(not (_menu.call("settings_row", key) as Dictionary).is_empty(), "9. row %s" % key)

	print("-- 10. continuous sliders")
	var sens: Dictionary = _menu.call("settings_row", "mouse_sensitivity")
	var ss: HSlider = sens.get("control") as HSlider
	_p(ss != null and not is_equal_approx(ss.step, 1.0), "10. sensitivity is a continuous slider", ss.step if ss != null else "")
	if ss != null:
		ss.value = 0.55
	_p(is_equal_approx(float(_settings.get_value("mouse_sensitivity")), 0.55), "10. slider writes a float", _settings.get_value("mouse_sensitivity"))
	_settings.set_value("invert_y", true)
	var ls: Vector2 = _settings.mouse_look_scale()
	_p(is_equal_approx(ls.x, 0.55) and is_equal_approx(ls.y, -0.55), "10. mouse_look_scale = sensitivity, Y inverted", ls)

	print("-- 11. apply reaches the engine")
	var fps0 := Engine.max_fps
	var vol0 := AudioServer.get_bus_volume_db(0)
	_settings._apply_max_fps(60)
	_p(Engine.max_fps == 60, "11. max FPS -> Engine.max_fps", Engine.max_fps)
	_settings._apply_master_volume(50.0)
	_p(absf(AudioServer.get_bus_volume_db(0) - linear_to_db(0.5)) < 0.01, "11. 50 % volume -> Master bus dB", AudioServer.get_bus_volume_db(0))
	Engine.max_fps = fps0
	AudioServer.set_bus_volume_db(0, vol0)

	print("-- 12. keep / revert display changes")
	var sp: String = _settings.SAVE_PATH
	var had := FileAccess.file_exists(sp)
	var keep := FileAccess.get_file_as_string(sp) if had else ""
	_settings.set_value("window_mode", 0)
	_settings.set_value("window_mode", 1)
	var dlg: Node = _menu.call("_ask_keep_display", {"window_mode": 0, "resolution": int(_settings.get_value("resolution"))})
	_p(dlg is ConfirmationDialog and (dlg as ConfirmationDialog).visible, "12. a keep / revert dialog opens")
	if dlg is ConfirmationDialog:
		_p(String((dlg as ConfirmationDialog).dialog_text).contains("15"), "12. it says it reverts in 15 s", (dlg as ConfirmationDialog).dialog_text)
		(dlg as ConfirmationDialog).canceled.emit()
	_p(int(_settings.get_value("window_mode")) == 0, "12. revert restores the previous window mode", _settings.get_value("window_mode"))
	if had:
		var f := FileAccess.open(sp, FileAccess.WRITE)
		f.store_string(keep)
		f.close()
	elif FileAccess.file_exists(sp):
		DirAccess.remove_absolute(ProjectSettings.globalize_path(sp))