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))