From 05a507f18208574d79cd49ed9467deeacc732fac Mon Sep 17 00:00:00 2001 From: Dennis Westermann Date: Tue, 18 Aug 2026 14:11:20 +0200 Subject: [PATCH 1/4] feat(presentation): Versionsnummer permanent unten links anzeigen (#103) Ein VersionBadge auf eigenem UIDocument zeigt in jedem Zustand unten links 'v0.21.0 - '. Bewusst NICHT Teil der Match-HUD-Schicht: die wird beim Menuewechsel aus- und eingeblendet (#102), die Versionsanzeige nicht. bundleVersion steht ab jetzt auf 0.21.0 und ist die eine Versionsquelle; gelesen wird sie ueber Application.version. Der Commit kommt aus BuildInfo.Commit (D-094) und nicht aus einem zweiten Stempel: BuildCommitStamp schreibt ihn als Build-Hook vor JEDEM Player-Build, also traegt ihn auch ein Build aus der Unity-GUI. Ein zusaetzlicher Stempel aus den Packaging-Skripten waere eine zweite Quelle, die genau dann von der ersten abweicht, wenn jemand von Hand baut. Umsetzung durch den Kimi-K3-Worker. Der Worker hat den doppelten Mechanismus im Auftrag erkannt und gemeldet statt ihn umzusetzen; die Umstellung auf BuildInfo.Commit und die Ruecknahme der Packaging-Aenderungen stammen daher. Co-Authored-By: Claude Opus 5 --- Assets/Tests/PlayMode/MainMenuTests.cs | 25 ++-- Assets/Tests/PlayMode/VersionBadgeTests.cs | 130 +++++++++++++++++ .../Tests/PlayMode/VersionBadgeTests.cs.meta | 11 ++ .../Editor/BootstrapSceneGenerator.cs | 23 +++ .../Scripts/Presentation/UI/VersionBadge.cs | 131 ++++++++++++++++++ .../Presentation/UI/VersionBadge.cs.meta | 11 ++ ProjectSettings/ProjectSettings.asset | 2 +- 7 files changed, 324 insertions(+), 9 deletions(-) create mode 100644 Assets/Tests/PlayMode/VersionBadgeTests.cs create mode 100644 Assets/Tests/PlayMode/VersionBadgeTests.cs.meta create mode 100644 Assets/_Project/Scripts/Presentation/UI/VersionBadge.cs create mode 100644 Assets/_Project/Scripts/Presentation/UI/VersionBadge.cs.meta diff --git a/Assets/Tests/PlayMode/MainMenuTests.cs b/Assets/Tests/PlayMode/MainMenuTests.cs index 4c655a4..1c710b1 100644 --- a/Assets/Tests/PlayMode/MainMenuTests.cs +++ b/Assets/Tests/PlayMode/MainMenuTests.cs @@ -361,16 +361,25 @@ private static IEnumerator LoadBootstrapScene() private static UIDocument MenuDocument() { - var document = Object.FindAnyObjectByType(); - Assert.NotNull(document, - "no UIDocument in the Bootstrap scene: the MainMenu object is missing. The scene " + + // By GameObject name, not FindAnyObjectByType: the scene has TWO + // UIDocuments since the version badge (#103) — which one "any" + // returns is undefined, and the badge's document has neither a + // "menu-screen" nor an AudioSource. + UIDocument[] documents = Object.FindObjectsByType(FindObjectsInactive.Include); + foreach (UIDocument document in documents) + { + if (document.gameObject.name != "MainMenu") continue; + Assert.NotNull(document.panelSettings, + "the menu UIDocument has no PanelSettings, so it has no panel and draws nothing. " + + "MenuAssetSetup.LoadOrCreatePanelSettings creates " + + "Assets/_Project/UI/HashkriegPanelSettings.asset when the generator runs."); + return document; + } + Assert.Fail( + "no UIDocument on a 'MainMenu' object in the Bootstrap scene. The scene " + "is machine output — run Tools/Project Nova/Create Bootstrap Scene " + "(BootstrapSceneGenerator.CreateMainMenuObject)."); - Assert.NotNull(document.panelSettings, - "the menu UIDocument has no PanelSettings, so it has no panel and draws nothing. " + - "MenuAssetSetup.LoadOrCreatePanelSettings creates " + - "Assets/_Project/UI/HashkriegPanelSettings.asset when the generator runs."); - return document; + return null; } private static VisualElement MenuRoot() diff --git a/Assets/Tests/PlayMode/VersionBadgeTests.cs b/Assets/Tests/PlayMode/VersionBadgeTests.cs new file mode 100644 index 0000000..e7bf8dd --- /dev/null +++ b/Assets/Tests/PlayMode/VersionBadgeTests.cs @@ -0,0 +1,130 @@ +using System.Collections; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; +using UnityEngine.UIElements; + +namespace Nova.PlayMode.Tests +{ + /// + /// Proof that the version badge (issue #103) is always on screen with the + /// exact string the sprint pins down — in the Editor + /// "v<version> · dev (Editor)" — that it never picks, and that it + /// survives the transition the IMGUI cockpit does not (issue #102): the + /// way from the menu into the match. + /// + /// SAME ASSEMBLY-WALL PATTERN AS MainMenuTests: VersionBadge lives in + /// Nova.Presentation.UI (rank 4), which no test assembly may reference + /// (quality/scripts/run_gate_check.py:183-188). Everything below is found + /// by GameObject name and element name; a rename fails loudly here. + /// + /// + public sealed class VersionBadgeTests + { + private const string ScenePath = "Assets/_Project/Scenes/Bootstrap.unity"; + private const string MenuObjectName = "MainMenu"; + private const string BadgeObjectName = "VersionBadge"; + private const string BadgeElementName = "version-badge"; + + [UnityTest] + public IEnumerator VersionBadge_IsAlwaysThereNamesTheBuildAndNeverPicks() + { + yield return LoadBootstrapScene(); + + UIDocument badgeDocument = DocumentOn(BadgeObjectName); + UIDocument menuDocument = DocumentOn(MenuObjectName); + Label badge = BadgeLabel(badgeDocument); + + Assert.AreEqual($"v{Application.version} · dev (Editor)", badge.text, + "the badge must name the running version — Application.version, i.e. " + + "ProjectSettings' bundleVersion, the ONE source — plus the build id. In the " + + "Editor BuildInfo.Commit reports its editor sentinel (D-094), so the id is " + + "'dev' with the '(Editor)' marker the sprint specifies."); + Assert.AreEqual(PickingMode.Ignore, badge.pickingMode, + "the badge must never pick: a click in the bottom-left corner belongs to the " + + "minimap or the world behind it, not to a read-only label"); + Assert.AreNotSame(menuDocument, badgeDocument, + "the badge needs its OWN UIDocument: the menu clears and rebuilds its root on " + + "every return (MainMenuController.BuildTree), and the IMGUI cockpit is toggled " + + "across the menu/match transition (#102) — a label in either layer would " + + "vanish with it"); + Assert.Greater(badgeDocument.sortOrder, menuDocument.sortOrder, + "the badge's document must sort above the menu's: the menu paints full-screen " + + "key art plus scrim, and a badge under them is invisible exactly where the " + + "sprint wants it seen"); + + // Into the match: the menu overlay hides, the badge stays. This + // is the regression the own-document rule exists for. + Button newGame = FindButton(menuDocument.rootVisualElement, "Neues Spiel"); + using (var submit = new NavigationSubmitEvent { target = newGame }) + { + newGame.SendEvent(submit); + } + yield return null; + + Assert.AreEqual(DisplayStyle.None, + menuDocument.rootVisualElement.Q("menu-screen").style.display.value, + "sanity check: starting a match must hide the menu overlay"); + Assert.NotNull(badgeDocument.rootVisualElement.Q(BadgeElementName), + "the badge must still be there with the match running — it is not part of " + + "the layer the menu switches off"); + Assert.AreNotEqual(DisplayStyle.None, badge.style.display.value, + "nothing may hide the badge on the way into the match"); + } + + private static IEnumerator LoadBootstrapScene() + { + yield return SceneManager.LoadSceneAsync(ScenePath, LoadSceneMode.Single); + + // Awake/OnEnable run during activation, Start one frame later — + // and VersionBadge builds its label in Start. Nothing above may + // query the panel before this. + yield return null; + yield return null; + } + + private static Label BadgeLabel(UIDocument badgeDocument) + { + Assert.NotNull(badgeDocument.rootVisualElement, + "the badge UIDocument has no root visual element"); + Label badge = badgeDocument.rootVisualElement.Q