Rework the UI on Crusenho's pack; credit it in the game, not just the repo
ci / verify (push) Successful in 48s

Crusenho's Complete UI Essential Pack is CC BY 4.0 -- redistributable and
commercial-friendly, confirmed from the License.txt the pack itself ships --
so unlike the two bdragon packs a subset is committed: twelve PNGs, 48 KB,
under assets/sprites/ui/. Only what is used, because each committed PNG costs
a Godot .import sidecar and a directory nothing references is one nobody
prunes.

UiTheme builds a Theme in code from it -- button states, panels, line edits --
and every screen roots itself through UiTheme.themed_root(). The HUD's bars are
the pack's frame with a tinted fill, drawn as three horizontal slices because
Godot's nine-patch lives on nodes and the HUD is drawn rather than built from
controls. Inventory slots use the pack's slot art at exactly twice the source
size; a non-integer scale on a 1px border reads as a wobble along every edge.

The credits screen is the other half of the request and it is a licence
obligation, not a nicety: two packs are now CC BY, which asks for attribution
"in any reasonable manner", and a markdown file in a source repo is not
reasonable for someone who downloaded a build. Settings -> Credits shows every
source with its terms and a link to the licence text. test_credits.gd asserts
CREDITS.md and docs/ASSETS.md name every entry, so the three cannot drift.

Two things found by actually looking at the screen, which is the point:

  - The FIRST version of this styled nothing. A Control inherits its theme from
    Control ANCESTORS only, and the chain breaks at the first plain Node or
    CanvasLayer -- which is every screen here. get_window().theme set the
    property, changed nothing, and read as correct. check.sh, 458 tests and a
    clean smoke run all passed with the entire interface unstyled. The theme
    test now instantiates every screen and asks what its buttons resolve.
  - The settings screen showed Fire bound to the right mouse button, because
    the test suite was writing the player's real user://settings.cfg --
    rebinding calls save() and nothing had redirected the path. Settings.path
    is now redirectable, the fixture points it at a scratch file, and a test
    asserts the default is still the player's own.

tools/screenshot.tscn is what found both. It boots the client windowed and
saves the menus, the HUD, settings and credits. Manual, needs a display, and
the only thing in the project that can tell you the interface rendered.

check.sh clean, 460 tests, SMOKE PASS, all four diagnostics green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 11:12:13 +02:00
parent 872922e9e2
commit 132646f6c3
54 changed files with 1319 additions and 50 deletions
+28
View File
@@ -8,6 +8,7 @@ const SCRATCH := "user://test_settings_%d.cfg"
var _saved_bindings: Dictionary[String, Dictionary] = {}
var _saved_master: float = 0.0
var _saved_sfx: float = 0.0
var _scratch: String = ""
func before_each() -> void:
@@ -16,6 +17,12 @@ func before_each() -> void:
_saved_bindings = Settings.bindings.duplicate()
_saved_master = Settings.master_volume
_saved_sfx = Settings.sfx_volume
# And rebinding SAVES. Without redirecting the file, running the suite
# rewrote the player's own settings -- it really did, and it took a
# screenshot of the settings screen showing Fire on the right mouse button
# to notice.
_scratch = SCRATCH % randi()
Settings.path = _scratch
func after_each() -> void:
@@ -23,6 +30,8 @@ func after_each() -> void:
Settings.master_volume = _saved_master
Settings.sfx_volume = _saved_sfx
Settings.apply_input()
Settings.path = Settings.DEFAULT_PATH
DirAccess.remove_absolute(ProjectSettings.globalize_path(_scratch))
func _key_event(code: Key) -> InputEventKey:
@@ -226,3 +235,22 @@ func test_a_settings_file_missing_keys_loads_without_complaint() -> void:
# default, which is what made this log an engine error per missing action.
assert_false(loaded.has_section_key("input", "move_up"))
DirAccess.remove_absolute(ProjectSettings.globalize_path(path))
## The redirect above is a test fixture. If it were ever left in place -- or the
## default changed to something under a scratch directory -- players would
## silently stop keeping their settings.
func test_the_default_path_is_the_players_own_file() -> void:
assert_eq(Settings.DEFAULT_PATH, "user://settings.cfg")
assert_true(Settings.DEFAULT_PATH.begins_with("user://"))
## Saving has to actually reach the file the path points at, or the redirect
## above would hide a broken save rather than isolate a working one.
func test_saving_writes_to_the_configured_path() -> void:
Settings.master_volume = 0.33
Settings.save()
assert_true(FileAccess.file_exists(_scratch), "nothing was written")
var cfg := ConfigFile.new()
assert_eq(cfg.load(_scratch), OK)
assert_almost_eq(float(cfg.get_value("audio", "master")), 0.33, 0.001)