mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 19:13:43 +00:00
6320b982a8
Godot's ConfigFile writer does not round-trip comments in project.godot. An observed rewrite deleted both `;` blocks outright and spliced the three-line `#` block above run/main_scene.dedicated_server onto the setting's own line, leaving it commented out — which would send dedicated builds to the interactive main menu instead of server_boot.tscn, with nothing failing until someone noticed a server process rendering a menu. Move the explanations into the code that owns the settings (server_boot.gd for the dedicated-server override, video_settings.gd for stretch mode and vsync) so they cannot be destroyed by a rewrite, and leave project.godot holding only assignments plus Godot's own regenerated header. Add tests/cases/test_project_settings.gd as the backstop: the feature-override assertions read project.godot as text and reject a line that has been folded into a comment, since ProjectSettings resolves `key.<feature>` overrides at load time and never exposes the suffixed key. Verified by reproducing the exact corruption, which fails the test, and it also covers the Jolt physics engine, the required autoloads, and that no test-hook autoload is ever shipped registered.
91 lines
3.9 KiB
GDScript
91 lines
3.9 KiB
GDScript
extends "res://tests/test_case.gd"
|
|
|
|
# Guards the project settings that are load-bearing but easy to destroy
|
|
# silently. Godot's ConfigFile writer does not round-trip comments in
|
|
# project.godot: it drops `;` blocks outright, and a `#` block sitting directly
|
|
# above a setting can be spliced onto that setting's own line on rewrite, which
|
|
# comments the setting out. A dedicated build would then boot the interactive
|
|
# main menu instead of the server, and nothing would fail until someone noticed
|
|
# a server process rendering a menu.
|
|
#
|
|
# The explanations that used to live as comments beside these settings are now
|
|
# in the code that owns them — server_boot.gd and video_settings.gd.
|
|
#
|
|
# The feature-override assertions read project.godot as TEXT rather than through
|
|
# ProjectSettings. Godot resolves `key.<feature>` overrides at load time against
|
|
# the running build's own feature tags and does not expose the suffixed key, so
|
|
# get_setting("run/main_scene.dedicated_server") returns "" in a normal editor/
|
|
# headless run even when the line is perfectly intact. Reading the file also
|
|
# matches the actual threat, which is textual corruption of the file.
|
|
|
|
|
|
func _project_godot_lines() -> PackedStringArray:
|
|
var file := FileAccess.open("res://project.godot", FileAccess.READ)
|
|
if file == null:
|
|
return PackedStringArray()
|
|
return file.get_as_text().split("\n")
|
|
|
|
|
|
# True only if `key="value"` appears as a real, uncommented assignment. A line
|
|
# that got spliced into a `#`/`;` comment is deliberately NOT a match — that is
|
|
# precisely the corruption being guarded against.
|
|
func _has_setting_line(key: String, value: String) -> bool:
|
|
var wanted := "%s=\"%s\"" % [key, value]
|
|
for raw_line in _project_godot_lines():
|
|
var line := raw_line.strip_edges()
|
|
if line.begins_with("#") or line.begins_with(";"):
|
|
continue
|
|
if line == wanted:
|
|
return true
|
|
return false
|
|
|
|
|
|
func test_project_godot_is_readable() -> void:
|
|
# Everything below is vacuously true if the file could not be opened.
|
|
assert_true(not _project_godot_lines().is_empty(), "project.godot readable and non-empty")
|
|
|
|
|
|
func test_dedicated_server_feature_override_is_set() -> void:
|
|
# Consumed by dedicated exports; see server_boot.gd.
|
|
assert_true(
|
|
_has_setting_line("run/main_scene.dedicated_server", "res://scenes/server_boot.tscn"),
|
|
"run/main_scene.dedicated_server present and uncommented"
|
|
)
|
|
|
|
|
|
func test_training_feature_override_is_set() -> void:
|
|
assert_true(
|
|
_has_setting_line("run/main_scene.training", "res://scenes/training.tscn"),
|
|
"run/main_scene.training present and uncommented"
|
|
)
|
|
|
|
|
|
func test_physics_engine_is_jolt() -> void:
|
|
# The whole flight model and every trained policy assume Jolt. Silently
|
|
# reverting to Godot Physics would change ship/ball behaviour under bots
|
|
# trained against Jolt, without any other test failing on its own.
|
|
var engine: String = ProjectSettings.get_setting("physics/3d/physics_engine", "")
|
|
assert_eq(engine, "Jolt Physics", "3D physics engine")
|
|
|
|
|
|
func test_required_autoloads_are_registered() -> void:
|
|
# NetworkManager in particular is reached by name from many scripts; losing
|
|
# it from [autoload] fails only at the point of use, deep in a smoke test.
|
|
for autoload_name in ["GameSettings", "VideoSettings", "NetworkManager", "MatchNet", "MatchSim"]:
|
|
assert_true(
|
|
ProjectSettings.has_setting("autoload/" + autoload_name),
|
|
"autoload/%s registered" % autoload_name
|
|
)
|
|
|
|
|
|
func test_test_hook_autoloads_are_not_shipped() -> void:
|
|
# main_menu_test_hooks / lobby_test_hooks are added to [autoload] by hand
|
|
# when running those scene-level smoke tests, and must be removed again —
|
|
# see CLAUDE.md. Shipping one registered would run test code in the real
|
|
# game, so fail here rather than discovering it in a build.
|
|
for hook_name in ["MainMenuTestHooks", "LobbyTestHooks", "NetworkedMatchTestHooks"]:
|
|
assert_true(
|
|
not ProjectSettings.has_setting("autoload/" + hook_name),
|
|
"test hook autoload/%s must not be registered" % hook_name
|
|
)
|