diff --git a/Assets/Tests/PlayMode/FieldReservePickTests.cs b/Assets/Tests/PlayMode/FieldReservePickTests.cs index 1841222..f6b394c 100644 --- a/Assets/Tests/PlayMode/FieldReservePickTests.cs +++ b/Assets/Tests/PlayMode/FieldReservePickTests.cs @@ -33,13 +33,36 @@ public IEnumerator ClickOnStartField_SelectsTheField() bootstrap.StartGrayboxMatch(); Assert.IsTrue(bootstrap.IsMatchReady, "match did not start"); - // Two frames: let every Awake/Start and the first model rebuilds settle. + // Two frames: let every Awake/Start and the first model rebuilds + // settle. MainMenuController.Start is among them, and it is the + // one that matters below. yield return null; yield return null; MonoBehaviour input = FindByTypeName("RtsDeviceInput"); Assert.NotNull(input, "scene contains no RtsDeviceInput"); + // THE COCKPIT HAS TO BE SWITCHED ON, and this test has to do it + // itself. Since package 21.8 the whole HUD root is inactive while + // the main menu owns the screen (#102), and the thing that turns + // it back on is MainMenuController.StartMatch — which this test + // deliberately bypasses by driving MatchBootstrap directly. Left + // off, RtsDeviceInput never runs a single Update, never binds its + // dispatcher, and every pick path below dereferences null. + // + // The OFF assertion keeps this line honest: it must run AFTER the + // two frames above, because the switch-off happens in + // MainMenuController.Start, not during scene activation — asserted + // one frame earlier it reads the scene file's default and passes + // for the wrong reason. + Assert.IsFalse(input.gameObject.activeInHierarchy, + "the HUD root is expected to be off while the main menu owns the screen — if this " + + "fails, the menu/match switch moved and the SetActive below is papering over it"); + input.gameObject.SetActive(true); + + // One more frame so the freshly enabled input binds its dispatcher. + yield return null; + // The serialized scene predates the 21.2 field: a missing YAML // entry must materialise the C# default 2f — pin that assumption, // it is exactly the kind of silent zero a scene upgrade swallows. diff --git a/Assets/Tests/PlayMode/MainMenuTests.cs b/Assets/Tests/PlayMode/MainMenuTests.cs index 1c710b1..c957e7a 100644 --- a/Assets/Tests/PlayMode/MainMenuTests.cs +++ b/Assets/Tests/PlayMode/MainMenuTests.cs @@ -26,10 +26,12 @@ namespace Nova.PlayMode.Tests /// rank-4 assemblies, and quality/scripts/run_gate_check.py:183-188 forbids /// any test assembly from referencing one — Nova.Presentation.Tests was /// dissolved for exactly that reason in 5cdb0ce. The menu's contract with - /// those three components (it switches two of them off while it is up and - /// back on when the match starts) is still worth a test, so they are found - /// by type name. A name that no longer resolves fails loudly here; the - /// alternative was not testing the behaviour at all. + /// those components (it switches the camera rig off while it is up, and + /// the whole gameplay HUD ROOT with it — the one GameObject every + /// in-match HUD component, DebugHud included, lives on) is still worth a + /// test, so they are found by type name. A name that no longer resolves + /// fails loudly here; the alternative was not testing the behaviour at + /// all. /// /// /// Run headless-with-graphics, never with -quit (same lane as @@ -135,9 +137,14 @@ public IEnumerator BootstrapScene_OpensInTheMainMenuWithNoMatchRunning() "this scene with no 'no match yet' guard: its LateUpdate reads scroll wheel, MMB, " + "Z/X and the screen-edge pan every frame, so a pointer resting near an edge would " + "pan the camera away from the HQ before the match even starts."); - Assert.IsFalse(DebugHudBehaviour().enabled, - "the debug HUD must be silent while the menu is up: its always-on status bar draws " + - "BEFORE its own F3 visibility check, so it would sit on top of the key art"); + Behaviour debugHud = DebugHudBehaviour(); + Assert.IsFalse(debugHud.gameObject.activeInHierarchy, + "the gameplay HUD root must be OFF while the menu is up: the menu switches the one " + + "GameObject every in-match HUD component lives on (MainMenuController.SetGameplayLayerActive), " + + "so the whole cockpit — including the debug HUD's always-on status bar, which draws " + + "BEFORE its own F3 visibility check — stays silent instead of sitting on the key art. " + + "The check goes through activeInHierarchy because the switch is the root's SetActive, " + + "not a per-component enabled toggle."); } // ------------------------------------------------------------------ @@ -213,11 +220,12 @@ public IEnumerator NewGame_StartsTheMatchHidesTheOverlayAndWakesTheCockpit() Assert.IsTrue(CameraRig().enabled, "the RTS camera must take over as the match starts; it was switched off only for " + "as long as the menu was up"); - Assert.IsTrue(DebugHudBehaviour().enabled, - "the status bar belongs to the cockpit and must come back with the match"); + Assert.IsTrue(DebugHudBehaviour().gameObject.activeInHierarchy, + "the gameplay HUD root must come back with the match — the status bar and the whole " + + "cockpit ride on the one root object the menu switches"); Assert.IsFalse(MenuController().enabled, - "the menu switches itself off after starting: this sprint has no pause menu and no " + - "restart, so there is deliberately no way back into it"); + "the menu switches itself off after starting; the way back in is a match-end or " + + "pause-menu button (ReturnToMenu re-enables it), not the start click itself"); uint tickAtStart = runner.Session.CurrentTick; yield return new WaitForSeconds(2f); diff --git a/Assets/Tests/PlayMode/PauseMenuTests.cs b/Assets/Tests/PlayMode/PauseMenuTests.cs new file mode 100644 index 0000000..5b95b72 --- /dev/null +++ b/Assets/Tests/PlayMode/PauseMenuTests.cs @@ -0,0 +1,96 @@ +using System.Collections; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; +using Nova.Gameplay; + +namespace Nova.PlayMode.Tests +{ + /// + /// The two traps of package 21.8, pinned where they actually bite. + /// + /// WHY THIS FILE NAMES THE TYPE AS A STRING: Nova.PlayMode.Tests + /// references Nova.Gameplay but NOT Nova.Presentation.UI, so + /// PauseMenuHud cannot be named as a type here — the same reason + /// MainMenuTests reaches for DebugHud that way. + /// ModalSurfaceLink IS reachable: it lives in Nova.Gameplay + /// precisely so both Presentation assemblies and this one can see it. + /// + /// + public sealed class PauseMenuTests + { + private const string ScenePath = "Assets/_Project/Scenes/Bootstrap.unity"; + private const string PauseMenuTypeName = "PauseMenuHud"; + private const string DebugHudTypeName = "DebugHud"; + + /// + /// The pause menu is IN THE SCENE, on the switched root, and the modal + /// channel is clean while the main menu owns the screen. + /// + /// THE SCENE IS THE POINT OF THIS TEST. Bootstrap.unity is machine + /// output that is committed, and it went stale once already: it sat + /// unchanged from 2026-08-08 while the generator moved three times, so + /// components wired only in the generator were simply absent from the + /// running game — code merged, CI green, nothing on screen. Without + /// this assertion the whole of 21.8 can ship and do nothing. + /// + /// + /// THE CLEAN CHANNEL IS THE SECOND POINT. ModalSurfaceLink is a + /// per-frame verdict published by the pause menu, and the way to the + /// main menu switches the HUD root — writer included — OFF. A writer + /// that stopped publishing while its last word was true would + /// leave every world gesture suspended for the rest of the session: + /// no selection, no orders, no camera edge-pan, and no component still + /// running that could ever clear it. That is why the writer resets in + /// OnDisable, and this is the assertion that keeps it there. + /// + /// + [UnityTest] + public IEnumerator PauseMenu_IsWiredOntoTheHudRootAndLeavesNoStaleModalFlag() + { + yield return SceneManager.LoadSceneAsync(ScenePath, LoadSceneMode.Single); + yield return null; + yield return null; + + Behaviour pauseMenu = RequireBehaviour(PauseMenuTypeName, + "the UI object carries no pause menu, so ESC does nothing and a player cannot leave " + + "a running match (#105). The scene is machine output — run " + + "Tools/Project Nova/Create Bootstrap Scene after any change to " + + "BootstrapSceneGenerator.CreateUiObject."); + + Behaviour debugHud = RequireBehaviour(DebugHudTypeName, + "the UI object carries no debug HUD"); + + Assert.AreSame(debugHud.gameObject, pauseMenu.gameObject, + "the pause menu must live on the same GameObject as the rest of the cockpit: that " + + "root IS the menu/match switch (MainMenuController.SetGameplayLayerActive). A pause " + + "menu beside it would keep drawing over the main menu — which is exactly the defect " + + "(#102) this package exists to end."); + + Assert.IsFalse(pauseMenu.gameObject.activeInHierarchy, + "the HUD root must be OFF while the main menu owns the screen"); + + Assert.IsFalse(ModalSurfaceLink.Open, + "no modal may be claimed while the main menu is up. This is the deadlock guard: the " + + "channel is a per-frame verdict whose only writer sits on the root that was just " + + "switched off, so a last word of 'true' would suspend every world gesture for the " + + "rest of the session with nothing left running to clear it."); + } + + private static Behaviour RequireBehaviour(string typeName, string message) + { + // FindObjectsInactive.Include is load-bearing: the menu switches + // the HUD root off, and everything on it counts as inactive. + Behaviour[] all = Object.FindObjectsByType(FindObjectsInactive.Include); + for (int i = 0; i < all.Length; i++) + { + if (all[i].GetType().Name == typeName) return all[i]; + } + + Assert.Fail($"{message} (looked for a component named '{typeName}'). If it was renamed, " + + "this file has to follow — it cannot reference the type."); + return null; + } + } +} diff --git a/Assets/Tests/PlayMode/PauseMenuTests.cs.meta b/Assets/Tests/PlayMode/PauseMenuTests.cs.meta new file mode 100644 index 0000000..127e9d8 --- /dev/null +++ b/Assets/Tests/PlayMode/PauseMenuTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f1b0f5732f82436683fa1006703b5a46 \ No newline at end of file diff --git a/Assets/_Project/Editor/BootstrapSceneGenerator.cs b/Assets/_Project/Editor/BootstrapSceneGenerator.cs index d153788..2a6a961 100644 --- a/Assets/_Project/Editor/BootstrapSceneGenerator.cs +++ b/Assets/_Project/Editor/BootstrapSceneGenerator.cs @@ -307,11 +307,17 @@ private static void EnsureGlutrinneMapAsset() /// /// Input sampling, the world-space HUD markers (selection, rally - /// flags, placement ghost), the build bar, the command card and the + /// flags, placement ghost), the build bar, the command card, the + /// match frame (result/network panels), the pause menu and the /// read-only debug overlay. The build bar and the command card are /// additionally wired INTO the input component: clicks landing on a /// HUD rect belong to the HUD and must not start a world selection - /// drag, place a building or resolve an order pick behind it. + /// drag, place a building or resolve an order pick behind it. The + /// pause menu is wired into the input's counterpart direction: it + /// reads the gesture-cancel stamp so ESC peels one layer at a time. + /// Everything lands on ONE GameObject so the main menu can silence + /// the whole cockpit with a single root switch — a component added + /// here is covered by that switch from the day it lands. /// private static GameObject CreateUiObject(MatchRunner runner, Camera camera) { @@ -375,6 +381,13 @@ private static GameObject CreateUiObject(MatchRunner runner, Camera camera) // _menu is wired in CreateMainMenuObject — the menu object does // not exist yet at this point in the generation order. + PauseMenuHud pauseMenu = uiObject.AddComponent(); + WireReference(pauseMenu, "_runner", runner); + WireReference(pauseMenu, "_matchFrame", frame); + WireReference(pauseMenu, "_input", input); + // _menu is wired in CreateMainMenuObject — same ordering reason + // as the match frame above. + DebugHud hud = uiObject.AddComponent(); WireReference(hud, "_runner", runner); WireReference(hud, "_input", input); @@ -389,15 +402,17 @@ private static GameObject CreateUiObject(MatchRunner runner, Camera camera) /// CreateMatchObject) and MainMenuController starts the match from /// "Neues Spiel". /// - /// The camera rig and the debug HUD are wired INTO the menu because - /// they are the only two components in this scene without a "no match - /// yet" guard: the rig would edge-pan and zoom while the player moves - /// the pointer over the menu, and the HUD's always-on status bar draws - /// before its own visibility check. The menu switches both off while - /// it is up. On the receiving side the rig is typed as Behaviour - /// (Nova.Presentation.UI may not reference Nova.Presentation), which - /// WireReference handles — it assigns object references, not typed - /// fields. + /// TWO things are wired INTO the menu because they are the scene's + /// only parts without a "no match yet" guard: the camera rig (it + /// would edge-pan and zoom while the player moves the pointer over + /// the menu) and the gameplay HUD ROOT — this one GameObject carries + /// every in-match HUD component, so the menu silences the whole + /// cockpit (including the debug HUD's always-on status bar, which + /// draws before its own visibility check) with a single root switch + /// that can never rot the way a component catalogue would. On the + /// receiving side the rig is typed as Behaviour (Nova.Presentation.UI + /// may not reference Nova.Presentation), which WireReference handles + /// — it assigns object references, not typed fields. /// /// private static void CreateMainMenuObject( @@ -430,7 +445,7 @@ private static void CreateMainMenuObject( WireReference(menu, "_bootstrap", runner.GetComponent()); WireReference(menu, "_music", music); WireReference(menu, "_cameraRig", camera.GetComponent()); - WireReference(menu, "_debugHud", uiObject.GetComponent()); + WireReference(menu, "_gameplayHudRoot", uiObject); WireReference(menu, "_keyArt", MenuAssetSetup.LoadRequired(MenuAssetSetup.KeyArtPath)); WireReference(menu, "_titleFont", @@ -438,12 +453,18 @@ private static void CreateMainMenuObject( WireReference(menu, "_bodyFont", MenuAssetSetup.LoadRequired(MenuAssetSetup.BodyFontPath)); - // The match frame's "Hauptmenü" button calls back into this menu. + // The match frame's "Hauptmenü" and the pause menu's "Zum + // Hauptmenü"/"Spiel beenden" call back into this menu. MatchFrameHud frame = uiObject.GetComponent(); if (frame != null) { WireReference(frame, "_menu", menu); } + PauseMenuHud pauseMenu = uiObject.GetComponent(); + if (pauseMenu != null) + { + WireReference(pauseMenu, "_menu", menu); + } CreateIngameMusicObject(runner, menu, musicGroup); } diff --git a/Assets/_Project/Scenes/Bootstrap.unity b/Assets/_Project/Scenes/Bootstrap.unity index 761ae59..0806275 100644 --- a/Assets/_Project/Scenes/Bootstrap.unity +++ b/Assets/_Project/Scenes/Bootstrap.unity @@ -119,7 +119,7 @@ NavMeshSettings: debug: m_Flags: 0 m_NavMeshData: {fileID: 0} ---- !u!1 &318030897 +--- !u!1 &128460811 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -127,101 +127,120 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 318030902} - - component: {fileID: 318030899} - - component: {fileID: 318030898} - - component: {fileID: 318030901} - - component: {fileID: 318030900} + - component: {fileID: 128460815} + - component: {fileID: 128460813} + - component: {fileID: 128460812} + - component: {fileID: 128460814} m_Layer: 0 - m_Name: Match - m_TagString: Untagged + m_Name: Main Camera + m_TagString: MainCamera m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!114 &318030898 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 318030897} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 0543a6c6c2d8cbd976ba484a58c2ddc5, type: 3} - m_Name: - m_EditorClassIdentifier: Nova.Gameplay::Nova.Gameplay.Match.MatchBootstrap - AutoStart: 0 - _seed: 16032251723485610609 - _mapWidth: 128 - _mapHeight: 128 - _entityCapacity: 1024 - UseDefinitionStats: 1 ---- !u!114 &318030899 +--- !u!114 &128460812 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 318030897} + m_GameObject: {fileID: 128460811} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 3b9301a799fe64f85afe9906dc15bc39, type: 3} + m_Script: {fileID: 11500000, guid: 7e207447d841f8c67cd26bb722acdaf5, type: 3} m_Name: - m_EditorClassIdentifier: Nova.Gameplay::Nova.Gameplay.Match.MatchRunner - _seed: 12345 + m_EditorClassIdentifier: Nova.Presentation::Nova.Presentation.RtsCameraController _mapWidth: 128 _mapHeight: 128 - _maxUnits: 2048 ---- !u!114 &318030900 -MonoBehaviour: + _panSpeed: 18 + _panSpeedHeightScale: 3 + _edgePanEnabled: 1 + _edgePanMargin: 12 + _zoomSpeed: 60 + _minHeight: 12 + _maxHeight: 90 + _pitchDegrees: 55 + _rotationSpeed: 90 + _middleDragDegreesPerPixel: 0.35 + _startFocusX: 8 + _startFocusZ: 6 + _startHeight: 42 + _startYawDegrees: 45 +--- !u!20 &128460813 +Camera: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 318030897} + m_GameObject: {fileID: 128460811} m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: bad3b0760ec204c94bb15e3918bbc921, type: 3} - m_Name: - m_EditorClassIdentifier: Nova.Gameplay::Nova.Gameplay.Match.UnitViewManager - _matchRunner: {fileID: 318030899} - _unitPrefab: {fileID: 0} - _combatEffects: {fileID: 318030901} - _assetMappings: {fileID: 11400000, guid: 54dfdf235acc54eecbef61df13a6b356, type: 2} - _interpolationSpeed: 25 - _viewerTeamOverride: -1 - _unknownPlayerColor: {r: 0.62, g: 0.62, b: 0.62, a: 1} - _damagedColor: {r: 0.42, g: 0.05, b: 0.05, a: 1} - _healthTintFloor: 0.25 ---- !u!114 &318030901 -MonoBehaviour: + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!81 &128460814 +AudioListener: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 318030897} + m_GameObject: {fileID: 128460811} m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: f4cba4e364582443899c3490f303f72f, type: 3} - m_Name: - m_EditorClassIdentifier: Nova.Gameplay::Nova.Gameplay.CombatFeedback.CombatEffectController ---- !u!4 &318030902 +--- !u!4 &128460815 Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 318030897} + m_GameObject: {fileID: 128460811} serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalRotation: {x: 0.5, y: 0, z: 0, w: 0.8660254} + m_LocalPosition: {x: 64, y: 60, z: -20} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &595700764 +--- !u!1 &214746646 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -229,217 +248,315 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 595700767} - - component: {fileID: 595700766} - - component: {fileID: 595700765} + - component: {fileID: 214746661} + - component: {fileID: 214746660} + - component: {fileID: 214746659} + - component: {fileID: 214746658} + - component: {fileID: 214746657} + - component: {fileID: 214746656} + - component: {fileID: 214746655} + - component: {fileID: 214746654} + - component: {fileID: 214746653} + - component: {fileID: 214746652} + - component: {fileID: 214746651} + - component: {fileID: 214746650} + - component: {fileID: 214746649} + - component: {fileID: 214746648} + - component: {fileID: 214746647} m_Layer: 0 - m_Name: VersionBadge + m_Name: UI m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!114 &595700765 +--- !u!114 &214746647 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 595700764} + m_GameObject: {fileID: 214746646} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: b5291f1c2dd449aea7a5874cd8d818bb, type: 3} + m_Script: {fileID: 11500000, guid: 743caecd4c5e6831d67dc968613cbfc6, type: 3} m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.VersionBadge - _document: {fileID: 595700766} - _font: {fileID: 12800000, guid: f028379ddea954641b86d6e9d5be7473, type: 3} - _fontSize: 12 - _marginLeft: 10 - _marginBottom: 8 - _textColor: {r: 0.88, g: 0.91, b: 0.95, a: 1} - _opacity: 0.55 ---- !u!114 &595700766 + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.DebugHud + _runner: {fileID: 2042920973} + _input: {fileID: 214746660} + _visible: 0 + _uiScale: 1.5 + _fontSize: 13 +--- !u!114 &214746648 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 595700764} + m_GameObject: {fileID: 214746646} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 19102, guid: 0000000000000000e000000000000000, type: 0} + m_Script: {fileID: 11500000, guid: 4839e6caa28240839b51760036ea05f0, type: 3} m_Name: - m_EditorClassIdentifier: UnityEngine.dll::UnityEngine.UIElements.UIDocument - m_PanelSettings: {fileID: 11400000, guid: db0593cf705514e1aa6af7b41cca6743, type: 2} - m_ParentUI: {fileID: 0} - sourceAsset: {fileID: 0} - m_SortingOrder: 0 - m_Position: 0 - m_WorldSpaceSizeMode: 1 - m_WorldSpaceWidth: 1920 - m_WorldSpaceHeight: 1080 - m_PivotReferenceSize: 0 - m_Pivot: 0 - m_WorldSpaceCollider: {fileID: 0} ---- !u!4 &595700767 -Transform: + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.PauseMenuHud + _runner: {fileID: 2042920973} + _menu: {fileID: 1629940937} + _matchFrame: {fileID: 214746649} + _input: {fileID: 214746660} + _uiScale: 1.5 + _panelWidth: 340 +--- !u!114 &214746649 +MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 595700764} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &712913255 -GameObject: + m_GameObject: {fileID: 214746646} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9a8f5ae5c96d7491494f6d2974f32f3b, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.MatchFrameHud + _runner: {fileID: 2042920973} + _bootstrap: {fileID: 2042920972} + _menu: {fileID: 1629940937} + _views: {fileID: 2042920974} + _uiScale: 1.5 + _panelWidth: 340 +--- !u!114 &214746650 +MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 712913258} - - component: {fileID: 712913257} - - component: {fileID: 712913256} - m_Layer: 0 - m_Name: IngameMusic - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &712913256 + m_GameObject: {fileID: 214746646} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3fbdd2dfe068b4dc0a6f71442dc18c31, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.HealthBarHud + _runner: {fileID: 2042920973} + _input: {fileID: 214746660} + _uiScale: 1.5 + _barWidth: 26 + _barHeight: 4 + _unitHeightOffset: 1.6 + _buildingHeightOffset: 3.4 +--- !u!114 &214746651 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 712913255} + m_GameObject: {fileID: 214746646} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: cdcf36c4668804ae6a8c2889ba4c9d33, type: 3} + m_Script: {fileID: 11500000, guid: 885e2e9fac8724df08b326c82e983510, type: 3} m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.MusicDirector - _source: {fileID: 712913257} - _runner: {fileID: 318030899} - _bootstrap: {fileID: 318030898} - _menu: {fileID: 766640420} - _playlist: - - {fileID: 8300000, guid: 3c291cb887cd427f8fe213d0cc97e7ba, type: 3} - - {fileID: 8300000, guid: 159885d03b3d4674a15ae77a1275fd85, type: 3} - - {fileID: 8300000, guid: 8605bd0c749044cbaaa4a64ec990b5b5, type: 3} - _fadeSeconds: 1.25 - _trackGapSeconds: 2 ---- !u!82 &712913257 -AudioSource: + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.MinimapHud + _runner: {fileID: 2042920973} + _buildMenu: {fileID: 214746654} + _uiScale: 1.5 + _mapSize: 168 + _margin: 8 + _terrainColor: {r: 0.72, g: 0.6, b: 0.42, a: 1} + _exploredBrightness: 0.45 + _unexploredBrightness: 0.15 + _unitDotSize: 3 + _buildingDotSize: 4.5 + _radarPingColor: {r: 1, g: 0.55, b: 0.1, a: 1} +--- !u!114 &214746652 +MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 712913255} + m_GameObject: {fileID: 214746646} m_Enabled: 1 - serializedVersion: 4 - OutputAudioMixerGroup: {fileID: -161172014317909320, guid: 7831b040d10e94eb9bddf12783bcee5e, type: 2} - m_audioClip: {fileID: 0} - m_Resource: {fileID: 0} - m_PlayOnAwake: 1 - m_Volume: 1 - m_Pitch: 1 - Loop: 0 - Mute: 0 - Spatialize: 0 - SpatializePostEffects: 0 - Priority: 128 - DopplerLevel: 1 - MinDistance: 1 - MaxDistance: 500 - Pan2D: 0 - rolloffMode: 0 - BypassEffects: 0 - BypassListenerEffects: 0 - BypassReverbZones: 0 - rolloffCustomCurve: - serializedVersion: 2 - m_Curve: - - serializedVersion: 3 - time: 0 - value: 1 - inSlope: 0 - outSlope: 0 - tangentMode: 0 - weightedMode: 0 - inWeight: 0.33333334 - outWeight: 0.33333334 - - serializedVersion: 3 - time: 1 - value: 0 - inSlope: 0 - outSlope: 0 - tangentMode: 0 - weightedMode: 0 - inWeight: 0.33333334 - outWeight: 0.33333334 - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 - panLevelCustomCurve: - serializedVersion: 2 - m_Curve: - - serializedVersion: 3 - time: 0 - value: 0 - inSlope: 0 - outSlope: 0 - tangentMode: 0 - weightedMode: 0 - inWeight: 0.33333334 - outWeight: 0.33333334 - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 - spreadCustomCurve: - serializedVersion: 2 - m_Curve: - - serializedVersion: 3 - time: 0 - value: 0 - inSlope: 0 - outSlope: 0 - tangentMode: 0 - weightedMode: 0 - inWeight: 0.33333334 - outWeight: 0.33333334 - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 - reverbZoneMixCustomCurve: + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 956e9df8ef76b491abc04e94045ee0d7, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.FogOfWarOverlayView + _runner: {fileID: 2042920973} + _unexploredAlpha: 0.85 + _exploredAlpha: 0.45 + _overlayHeight: 0.04 +--- !u!114 &214746653 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 214746646} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 79cf751f533e746e993958254d4d427e, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.CommandCardHud + _runner: {fileID: 2042920973} + _input: {fileID: 214746660} + _buildMenu: {fileID: 214746654} + _bootstrap: {fileID: 2042920972} + _uiScale: 1.5 + _panelWidth: 236 + _panelMargin: 8 +--- !u!114 &214746654 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 214746646} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 22628296e159b4d5a8e9c701b125cee0, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.BuildMenuHud + _runner: {fileID: 2042920973} + _input: {fileID: 214746660} + _uiScale: 1.5 + _buttonWidth: 108 + _buttonHeight: 62 + _barMargin: 8 + _hintMinSeconds: 6 + _hintMaxSeconds: 90 +--- !u!114 &214746655 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 214746646} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a2b97178f25d64f78897be61bbbb70dd, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.ConstructionSiteMarkerView + _runner: {fileID: 2042920973} + _markerHeight: 0.07 + _buildingColor: {r: 0.35, g: 0.9, b: 0.55, a: 0.55} + _pausedColor: {r: 1, g: 0.62, b: 0.15, a: 1} + _pulseSpeed: 4 +--- !u!114 &214746656 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 214746646} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 60b14ad0059d4ecbb3bb0f16e8f28aaf, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.BuildZoneOverlayView + _runner: {fileID: 2042920973} + _input: {fileID: 214746660} + _buildableColor: serializedVersion: 2 - m_Curve: - - serializedVersion: 3 - time: 0 - value: 1 - inSlope: 0 - outSlope: 0 - tangentMode: 0 - weightedMode: 0 - inWeight: 0.33333334 - outWeight: 0.33333334 - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 ---- !u!4 &712913258 + rgba: 442164806 + _spacingBlockedColor: + serializedVersion: 2 + rgba: 740857599 + _overlayHeight: 0.05 + _repaintIntervalSeconds: 0.25 +--- !u!114 &214746657 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 214746646} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2cef43f34213c4155a30156e42d58fa4, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.PlacementGhostView + _input: {fileID: 214746660} + _ghostHeight: 0.09 + _validColor: {r: 0.3, g: 1, b: 0.4, a: 1} + _invalidColor: {r: 1, g: 0.3, b: 0.25, a: 1} +--- !u!114 &214746658 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 214746646} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f602abebcfdbc4649af799cbeddc3fcc, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.RallyFlagView + _runner: {fileID: 2042920973} + _input: {fileID: 214746660} + _markerHeight: 0.07 + _flagSize: 1 + _lineWidth: 0.18 + _rallyColor: {r: 1, g: 0.82, b: 0.25, a: 1} +--- !u!114 &214746659 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 214746646} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bc78ab4760d9d417bb2865e8edf60d3d, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.SelectionMarkerView + _runner: {fileID: 2042920973} + _input: {fileID: 214746660} + _views: {fileID: 2042920974} + _markerHeight: 0.06 + _unitMarkerSize: 2.4 + _buildingMarkerMargin: 0.4 + _selectionColor: {r: 0.35, g: 1, b: 0.4, a: 1} +--- !u!114 &214746660 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 214746646} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 39a0624d641e60a06323d84ab3edc209, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.RtsDeviceInput + _runner: {fileID: 2042920973} + _camera: {fileID: 128460813} + _buildMenu: {fileID: 214746654} + _commandCard: {fileID: 214746653} + _minimap: {fileID: 0} + _menu: {fileID: 0} + _groundPlaneY: 0 + _dragThresholdPixels: 8 + _pickRadiusWorld: 1.5 + _fieldPickRadiusWorld: 2 + _buildingDefId: 5 + _altBuildingDefId: 7 + _unitDefId: 2 + _altUnitDefId: 12 + _storageDefId: 6 + _vehicleFactoryDefId: 8 + _researchLabDefId: 9 + _radarDefId: 10 + _defensePlatformDefId: 11 + _refineryDefId: 4 + _builderDefId: 1 + _antiArmorDefId: 13 + _scoutDefId: 14 + _lightTankDefId: 15 + _battleTankDefId: 16 + _artilleryDefId: 17 +--- !u!4 &214746661 Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 712913255} + m_GameObject: {fileID: 214746646} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} @@ -448,7 +565,7 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &766640419 +--- !u!1 &526112586 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -456,83 +573,45 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 766640424} - - component: {fileID: 766640423} - - component: {fileID: 766640422} - - component: {fileID: 766640421} - - component: {fileID: 766640420} + - component: {fileID: 526112589} + - component: {fileID: 526112588} + - component: {fileID: 526112587} m_Layer: 0 - m_Name: MainMenu + m_Name: IngameMusic m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!114 &766640420 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 766640419} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: e77b731ae61b8471192a9ee5a7727fdb, type: 3} - m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.MainMenuController - _document: {fileID: 766640423} - _bootstrap: {fileID: 318030898} - _music: {fileID: 766640421} - _cameraRig: {fileID: 1208129614} - _debugHud: {fileID: 1729466583} - _keyArt: {fileID: 2800000, guid: 0f0988a5c327e451a990f97269d3ceee, type: 3} - _titleFont: {fileID: 12800000, guid: b95db3c2c8b9045b683866f321683fad, type: 3} - _bodyFont: {fileID: 12800000, guid: f028379ddea954641b86d6e9d5be7473, type: 3} - _title: HASHKRIEG - _titleFontSize: 92 - _titleLetterSpacing: 14 - _titleRuleWidth: 420 - _contentPadding: 120 - _buttonFontSize: 30 - _buttonWidth: 340 - _buttonHeight: 56 - _fieldFontSize: 17 - _fieldLabelWidth: 190 - _settingsWidth: 680 - _settingsScrollHeight: 480 - _titleColor: {r: 0.92, g: 0.96, b: 1, a: 1} - _accentColor: {r: 0.35, g: 0.78, b: 0.92, a: 1} - _bodyColor: {r: 0.88, g: 0.91, b: 0.95, a: 1} - _panelFill: {r: 0.05, g: 0.06, b: 0.08, a: 0.82} - _panelEdge: {r: 0.55, g: 0.62, b: 0.72, a: 0.5} - _buttonFill: {r: 0.08, g: 0.1, b: 0.13, a: 0.85} - _buttonHoverFill: {r: 0.16, g: 0.26, b: 0.32, a: 0.92} - _scrimColor: {r: 0.02, g: 0.03, b: 0.05, a: 0.42} - _disabledTextColor: {r: 0.52, g: 0.55, b: 0.6, a: 1} - _disabledEdgeColor: {r: 0.34, g: 0.38, b: 0.44, a: 0.45} - _saveIntervalSeconds: 0.35 ---- !u!114 &766640421 +--- !u!114 &526112587 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 766640419} + m_GameObject: {fileID: 526112586} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: bccc1231bdd82465fa987e6ad5bb61e2, type: 3} + m_Script: {fileID: 11500000, guid: cdcf36c4668804ae6a8c2889ba4c9d33, type: 3} m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.MenuMusicPlayer - _source: {fileID: 766640422} - _clip: {fileID: 8300000, guid: 85a05f77cb9f94a3684e85a7325e0184, type: 3} - _fadeOutSeconds: 1.25 ---- !u!82 &766640422 + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.MusicDirector + _source: {fileID: 526112588} + _runner: {fileID: 2042920973} + _bootstrap: {fileID: 2042920972} + _menu: {fileID: 1629940937} + _playlist: + - {fileID: 8300000, guid: 3c291cb887cd427f8fe213d0cc97e7ba, type: 3} + - {fileID: 8300000, guid: 159885d03b3d4674a15ae77a1275fd85, type: 3} + - {fileID: 8300000, guid: 8605bd0c749044cbaaa4a64ec990b5b5, type: 3} + _fadeSeconds: 1.25 + _trackGapSeconds: 2 +--- !u!82 &526112588 AudioSource: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 766640419} + m_GameObject: {fileID: 526112586} m_Enabled: 1 serializedVersion: 4 OutputAudioMixerGroup: {fileID: -161172014317909320, guid: 7831b040d10e94eb9bddf12783bcee5e, type: 2} @@ -623,36 +702,13 @@ AudioSource: m_PreInfinity: 2 m_PostInfinity: 2 m_RotationOrder: 4 ---- !u!114 &766640423 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 766640419} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 19102, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: UnityEngine.dll::UnityEngine.UIElements.UIDocument - m_PanelSettings: {fileID: 11400000, guid: db0593cf705514e1aa6af7b41cca6743, type: 2} - m_ParentUI: {fileID: 0} - sourceAsset: {fileID: 0} - m_SortingOrder: 0 - m_Position: 0 - m_WorldSpaceSizeMode: 1 - m_WorldSpaceWidth: 1920 - m_WorldSpaceHeight: 1080 - m_PivotReferenceSize: 0 - m_Pivot: 0 - m_WorldSpaceCollider: {fileID: 0} ---- !u!4 &766640424 +--- !u!4 &526112589 Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 766640419} + m_GameObject: {fileID: 526112586} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} @@ -661,7 +717,7 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &796025941 +--- !u!1 &802321239 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -669,9 +725,9 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 796025944} - - component: {fileID: 796025943} - - component: {fileID: 796025942} + - component: {fileID: 802321242} + - component: {fileID: 802321241} + - component: {fileID: 802321240} m_Layer: 0 m_Name: Audio m_TagString: Untagged @@ -679,25 +735,25 @@ GameObject: m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!114 &796025942 +--- !u!114 &802321240 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 796025941} + m_GameObject: {fileID: 802321239} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 209209a86486d4b3a8bed38f9ae33c50, type: 3} m_Name: m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.SfxSettingsBridge ---- !u!114 &796025943 +--- !u!114 &802321241 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 796025941} + m_GameObject: {fileID: 802321239} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5dc2508a98b444101ad6cef0e77cec67, type: 3} @@ -721,13 +777,13 @@ MonoBehaviour: _weaponsGroup: {fileID: -2573851237862643720, guid: 7831b040d10e94eb9bddf12783bcee5e, type: 2} _unitsGroup: {fileID: -3201287591632689540, guid: 7831b040d10e94eb9bddf12783bcee5e, type: 2} _uiGroup: {fileID: 2761139959417956413, guid: 7831b040d10e94eb9bddf12783bcee5e, type: 2} ---- !u!4 &796025944 +--- !u!4 &802321242 Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 796025941} + m_GameObject: {fileID: 802321239} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} @@ -736,7 +792,7 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &1208129612 +--- !u!1 &954524122 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -744,162 +800,41 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 1208129616} - - component: {fileID: 1208129613} - - component: {fileID: 1208129614} - - component: {fileID: 1208129615} + - component: {fileID: 954524124} + - component: {fileID: 954524123} m_Layer: 0 - m_Name: Main Camera - m_TagString: MainCamera + m_Name: Map + m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!20 &1208129613 -Camera: +--- !u!114 &954524123 +MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1208129612} + m_GameObject: {fileID: 954524122} m_Enabled: 1 - serializedVersion: 2 - m_ClearFlags: 2 - m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} - m_projectionMatrixMode: 1 - m_GateFitMode: 2 - m_FOVAxisMode: 0 - m_Iso: 200 - m_ShutterSpeed: 0.005 - m_Aperture: 16 - m_FocusDistance: 10 - m_FocalLength: 50 - m_BladeCount: 5 - m_Curvature: {x: 2, y: 11} - m_BarrelClipping: 0.25 - m_Anamorphism: 0 - m_SensorSize: {x: 36, y: 24} - m_LensShift: {x: 0, y: 0} - m_NormalizedViewPortRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 1 - height: 1 - near clip plane: 0.3 - far clip plane: 1000 - field of view: 60 - orthographic: 0 - orthographic size: 5 - m_Depth: 0 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingPath: -1 - m_TargetTexture: {fileID: 0} - m_TargetDisplay: 0 - m_TargetEye: 3 - m_HDR: 1 - m_AllowMSAA: 1 - m_AllowDynamicResolution: 0 - m_ForceIntoRT: 0 - m_OcclusionCulling: 1 - m_StereoConvergence: 10 - m_StereoSeparation: 0.022 ---- !u!114 &1208129614 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1208129612} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 7e207447d841f8c67cd26bb722acdaf5, type: 3} - m_Name: - m_EditorClassIdentifier: Nova.Presentation::Nova.Presentation.RtsCameraController - _mapWidth: 128 - _mapHeight: 128 - _panSpeed: 18 - _panSpeedHeightScale: 3 - _edgePanEnabled: 1 - _edgePanMargin: 12 - _zoomSpeed: 60 - _minHeight: 12 - _maxHeight: 90 - _pitchDegrees: 55 - _rotationSpeed: 90 - _middleDragDegreesPerPixel: 0.35 - _startFocusX: 8 - _startFocusZ: 6 - _startHeight: 42 - _startYawDegrees: 45 ---- !u!81 &1208129615 -AudioListener: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1208129612} - m_Enabled: 1 ---- !u!4 &1208129616 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1208129612} - serializedVersion: 2 - m_LocalRotation: {x: 0.5, y: 0, z: 0, w: 0.8660254} - m_LocalPosition: {x: 64, y: 60, z: -20} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &1239032164 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1239032166} - - component: {fileID: 1239032165} - m_Layer: 0 - m_Name: Map - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &1239032165 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1239032164} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 9b36a2ce3f98b46249dd0de03a577164, type: 3} - m_Name: - m_EditorClassIdentifier: Nova.Presentation::Nova.Presentation.Maps.GlutrinneBlockoutView - _bootstrap: {fileID: 318030898} - _groundRenderer: {fileID: 1688916558} - _sandColor: {r: 0.72, g: 0.6, b: 0.42, a: 1} - _crystalColor: {r: 0.2, g: 0.85, b: 0.9, a: 1} - _edgeColor: {r: 0.28, g: 0.24, b: 0.2, a: 1} - _rockColor: {r: 0.36, g: 0.31, b: 0.26, a: 1} - _groundTextureTiling: {x: 32, y: 32} ---- !u!4 &1239032166 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1239032164} + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9b36a2ce3f98b46249dd0de03a577164, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Presentation::Nova.Presentation.Maps.GlutrinneBlockoutView + _bootstrap: {fileID: 2042920972} + _groundRenderer: {fileID: 1162144974} + _sandColor: {r: 0.72, g: 0.6, b: 0.42, a: 1} + _crystalColor: {r: 0.2, g: 0.85, b: 0.9, a: 1} + _edgeColor: {r: 0.28, g: 0.24, b: 0.2, a: 1} + _rockColor: {r: 0.36, g: 0.31, b: 0.26, a: 1} + _groundTextureTiling: {x: 32, y: 32} +--- !u!4 &954524124 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 954524122} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} @@ -908,7 +843,7 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &1688916557 +--- !u!1 &1162144973 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -916,10 +851,10 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 1688916561} - - component: {fileID: 1688916560} - - component: {fileID: 1688916559} - - component: {fileID: 1688916558} + - component: {fileID: 1162144977} + - component: {fileID: 1162144976} + - component: {fileID: 1162144975} + - component: {fileID: 1162144974} m_Layer: 0 m_Name: Ground m_TagString: Untagged @@ -927,13 +862,13 @@ GameObject: m_NavMeshLayer: 0 m_StaticEditorFlags: 2147483647 m_IsActive: 1 ---- !u!23 &1688916558 +--- !u!23 &1162144974 MeshRenderer: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1688916557} + m_GameObject: {fileID: 1162144973} m_Enabled: 1 m_CastShadows: 1 m_ReceiveShadows: 1 @@ -976,13 +911,13 @@ MeshRenderer: m_SortingOrder: 0 m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} ---- !u!64 &1688916559 +--- !u!64 &1162144975 MeshCollider: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1688916557} + m_GameObject: {fileID: 1162144973} m_Material: {fileID: 0} m_IncludeLayers: serializedVersion: 2 @@ -998,21 +933,21 @@ MeshCollider: m_Convex: 0 m_CookingOptions: 30 m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0} ---- !u!33 &1688916560 +--- !u!33 &1162144976 MeshFilter: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1688916557} + m_GameObject: {fileID: 1162144973} m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0} ---- !u!4 &1688916561 +--- !u!4 &1162144977 Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1688916557} + m_GameObject: {fileID: 1162144973} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 64, y: 0, z: 64} @@ -1021,7 +956,7 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &1729466582 +--- !u!1 &1309622895 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -1029,271 +964,375 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 1729466595} - - component: {fileID: 1729466584} - - component: {fileID: 1729466594} - - component: {fileID: 1729466593} - - component: {fileID: 1729466592} - - component: {fileID: 1729466591} - - component: {fileID: 1729466586} - - component: {fileID: 1729466585} - - component: {fileID: 1729466590} - - component: {fileID: 1729466589} - - component: {fileID: 1729466588} - - component: {fileID: 1729466587} - - component: {fileID: 1729466583} + - component: {fileID: 1309622898} + - component: {fileID: 1309622897} + - component: {fileID: 1309622896} m_Layer: 0 - m_Name: UI + m_Name: VersionBadge m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!114 &1729466583 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1729466582} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 743caecd4c5e6831d67dc968613cbfc6, type: 3} - m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.DebugHud - _runner: {fileID: 318030899} - _input: {fileID: 1729466584} - _visible: 0 - _uiScale: 1.5 - _fontSize: 13 ---- !u!114 &1729466584 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1729466582} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 39a0624d641e60a06323d84ab3edc209, type: 3} - m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.RtsDeviceInput - _runner: {fileID: 318030899} - _camera: {fileID: 1208129613} - _buildMenu: {fileID: 1729466586} - _commandCard: {fileID: 1729466585} - _minimap: {fileID: 0} - _menu: {fileID: 0} - _groundPlaneY: 0 - _dragThresholdPixels: 8 - _pickRadiusWorld: 1.5 - _buildingDefId: 5 - _altBuildingDefId: 7 - _unitDefId: 2 - _altUnitDefId: 12 - _storageDefId: 6 - _vehicleFactoryDefId: 8 - _researchLabDefId: 9 - _radarDefId: 10 - _defensePlatformDefId: 11 - _refineryDefId: 4 - _builderDefId: 1 - _antiArmorDefId: 13 - _scoutDefId: 14 - _lightTankDefId: 15 - _battleTankDefId: 16 - _artilleryDefId: 17 ---- !u!114 &1729466585 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1729466582} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 79cf751f533e746e993958254d4d427e, type: 3} - m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.CommandCardHud - _runner: {fileID: 318030899} - _input: {fileID: 1729466584} - _buildMenu: {fileID: 1729466586} - _uiScale: 1.5 - _panelWidth: 236 - _panelMargin: 8 ---- !u!114 &1729466586 +--- !u!114 &1309622896 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1729466582} + m_GameObject: {fileID: 1309622895} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 22628296e159b4d5a8e9c701b125cee0, type: 3} + m_Script: {fileID: 11500000, guid: b5291f1c2dd449aea7a5874cd8d818bb, type: 3} m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.BuildMenuHud - _runner: {fileID: 318030899} - _input: {fileID: 1729466584} - _uiScale: 1.5 - _buttonWidth: 108 - _buttonHeight: 62 - _barMargin: 8 - _hintMinSeconds: 6 - _hintMaxSeconds: 90 ---- !u!114 &1729466587 + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.VersionBadge + _document: {fileID: 1309622897} + _font: {fileID: 12800000, guid: f028379ddea954641b86d6e9d5be7473, type: 3} + _fontSize: 12 + _marginLeft: 10 + _marginBottom: 8 + _textColor: {r: 0.88, g: 0.91, b: 0.95, a: 1} + _opacity: 0.55 +--- !u!114 &1309622897 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1729466582} + m_GameObject: {fileID: 1309622895} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 9a8f5ae5c96d7491494f6d2974f32f3b, type: 3} + m_Script: {fileID: 19102, guid: 0000000000000000e000000000000000, type: 0} m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.MatchFrameHud - _runner: {fileID: 318030899} - _bootstrap: {fileID: 318030898} - _menu: {fileID: 766640420} - _views: {fileID: 318030900} - _uiScale: 1.5 - _panelWidth: 340 ---- !u!114 &1729466588 -MonoBehaviour: + m_EditorClassIdentifier: UnityEngine.dll::UnityEngine.UIElements.UIDocument + m_PanelSettings: {fileID: 11400000, guid: db0593cf705514e1aa6af7b41cca6743, type: 2} + m_ParentUI: {fileID: 0} + sourceAsset: {fileID: 0} + m_SortingOrder: 0 + m_Position: 0 + m_WorldSpaceSizeMode: 1 + m_WorldSpaceWidth: 1920 + m_WorldSpaceHeight: 1080 + m_PivotReferenceSize: 0 + m_Pivot: 0 + m_WorldSpaceCollider: {fileID: 0} +--- !u!4 &1309622898 +Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1729466582} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 3fbdd2dfe068b4dc0a6f71442dc18c31, type: 3} - m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.HealthBarHud - _runner: {fileID: 318030899} - _input: {fileID: 1729466584} - _uiScale: 1.5 - _barWidth: 26 - _barHeight: 4 - _unitHeightOffset: 1.6 - _buildingHeightOffset: 3.4 ---- !u!114 &1729466589 -MonoBehaviour: + m_GameObject: {fileID: 1309622895} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1521561486 +GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1729466582} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 885e2e9fac8724df08b326c82e983510, type: 3} - m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.MinimapHud - _runner: {fileID: 318030899} - _buildMenu: {fileID: 1729466586} - _uiScale: 1.5 - _mapSize: 168 - _margin: 8 - _terrainColor: {r: 0.72, g: 0.6, b: 0.42, a: 1} - _exploredBrightness: 0.45 - _unexploredBrightness: 0.15 - _unitDotSize: 3 - _buildingDotSize: 4.5 - _radarPingColor: {r: 1, g: 0.55, b: 0.1, a: 1} ---- !u!114 &1729466590 -MonoBehaviour: + serializedVersion: 6 + m_Component: + - component: {fileID: 1521561488} + - component: {fileID: 1521561487} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &1521561487 +Light: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1729466582} + m_GameObject: {fileID: 1521561486} m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 956e9df8ef76b491abc04e94045ee0d7, type: 3} - m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.FogOfWarOverlayView - _runner: {fileID: 318030899} - _unexploredAlpha: 0.85 - _exploredAlpha: 0.45 - _overlayHeight: 0.04 ---- !u!114 &1729466591 + serializedVersion: 13 + m_Type: 1 + m_Color: {r: 1, g: 0.87, b: 0.7, a: 1} + m_Intensity: 1.15 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.802082 + m_CookieSize2D: {x: 0.5, y: 0.5} + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 0.85 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ForceVisible: 0 + m_ShapeRadius: 0.025 + m_ShadowAngle: 0 + m_LightUnit: 1 + m_LuxAtDistance: 1 + m_EnableSpotReflector: 1 +--- !u!4 &1521561488 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1521561486} + serializedVersion: 2 + m_LocalRotation: {x: 0.12450414, y: -0.8617815, z: 0.25527132, w: 0.42031893} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1629940936 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1629940941} + - component: {fileID: 1629940940} + - component: {fileID: 1629940939} + - component: {fileID: 1629940938} + - component: {fileID: 1629940937} + m_Layer: 0 + m_Name: MainMenu + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1629940937 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1729466582} + m_GameObject: {fileID: 1629940936} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: a2b97178f25d64f78897be61bbbb70dd, type: 3} + m_Script: {fileID: 11500000, guid: e77b731ae61b8471192a9ee5a7727fdb, type: 3} m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.ConstructionSiteMarkerView - _runner: {fileID: 318030899} - _markerHeight: 0.07 - _buildingColor: {r: 0.35, g: 0.9, b: 0.55, a: 0.55} - _pausedColor: {r: 1, g: 0.62, b: 0.15, a: 1} - _pulseSpeed: 4 ---- !u!114 &1729466592 + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.MainMenuController + _document: {fileID: 1629940940} + _bootstrap: {fileID: 2042920972} + _music: {fileID: 1629940938} + _cameraRig: {fileID: 128460812} + _gameplayHudRoot: {fileID: 214746646} + _keyArt: {fileID: 2800000, guid: 0f0988a5c327e451a990f97269d3ceee, type: 3} + _titleFont: {fileID: 12800000, guid: b95db3c2c8b9045b683866f321683fad, type: 3} + _bodyFont: {fileID: 12800000, guid: f028379ddea954641b86d6e9d5be7473, type: 3} + _title: HASHKRIEG + _titleFontSize: 92 + _titleLetterSpacing: 14 + _titleRuleWidth: 420 + _contentPadding: 120 + _buttonFontSize: 30 + _buttonWidth: 340 + _buttonHeight: 56 + _fieldFontSize: 17 + _fieldLabelWidth: 190 + _settingsWidth: 680 + _settingsScrollHeight: 480 + _titleColor: {r: 0.92, g: 0.96, b: 1, a: 1} + _accentColor: {r: 0.35, g: 0.78, b: 0.92, a: 1} + _bodyColor: {r: 0.88, g: 0.91, b: 0.95, a: 1} + _panelFill: {r: 0.05, g: 0.06, b: 0.08, a: 0.82} + _panelEdge: {r: 0.55, g: 0.62, b: 0.72, a: 0.5} + _buttonFill: {r: 0.08, g: 0.1, b: 0.13, a: 0.85} + _buttonHoverFill: {r: 0.16, g: 0.26, b: 0.32, a: 0.92} + _scrimColor: {r: 0.02, g: 0.03, b: 0.05, a: 0.42} + _disabledTextColor: {r: 0.52, g: 0.55, b: 0.6, a: 1} + _disabledEdgeColor: {r: 0.34, g: 0.38, b: 0.44, a: 0.45} + _saveIntervalSeconds: 0.35 +--- !u!114 &1629940938 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1729466582} + m_GameObject: {fileID: 1629940936} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 2cef43f34213c4155a30156e42d58fa4, type: 3} + m_Script: {fileID: 11500000, guid: bccc1231bdd82465fa987e6ad5bb61e2, type: 3} m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.PlacementGhostView - _input: {fileID: 1729466584} - _ghostHeight: 0.09 - _validColor: {r: 0.3, g: 1, b: 0.4, a: 1} - _invalidColor: {r: 1, g: 0.3, b: 0.25, a: 1} ---- !u!114 &1729466593 -MonoBehaviour: + m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.MenuMusicPlayer + _source: {fileID: 1629940939} + _clip: {fileID: 8300000, guid: 85a05f77cb9f94a3684e85a7325e0184, type: 3} + _fadeOutSeconds: 1.25 +--- !u!82 &1629940939 +AudioSource: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1729466582} + m_GameObject: {fileID: 1629940936} m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: f602abebcfdbc4649af799cbeddc3fcc, type: 3} - m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.RallyFlagView - _runner: {fileID: 318030899} - _input: {fileID: 1729466584} - _markerHeight: 0.07 - _flagSize: 1 - _lineWidth: 0.18 - _rallyColor: {r: 1, g: 0.82, b: 0.25, a: 1} ---- !u!114 &1729466594 + serializedVersion: 4 + OutputAudioMixerGroup: {fileID: -161172014317909320, guid: 7831b040d10e94eb9bddf12783bcee5e, type: 2} + m_audioClip: {fileID: 0} + m_Resource: {fileID: 0} + m_PlayOnAwake: 1 + m_Volume: 1 + m_Pitch: 1 + Loop: 0 + Mute: 0 + Spatialize: 0 + SpatializePostEffects: 0 + Priority: 128 + DopplerLevel: 1 + MinDistance: 1 + MaxDistance: 500 + Pan2D: 0 + rolloffMode: 0 + BypassEffects: 0 + BypassListenerEffects: 0 + BypassReverbZones: 0 + rolloffCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 1 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + panLevelCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + spreadCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + reverbZoneMixCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!114 &1629940940 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1729466582} + m_GameObject: {fileID: 1629940936} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: bc78ab4760d9d417bb2865e8edf60d3d, type: 3} + m_Script: {fileID: 19102, guid: 0000000000000000e000000000000000, type: 0} m_Name: - m_EditorClassIdentifier: Nova.Presentation.UI::Nova.Presentation.UI.SelectionMarkerView - _runner: {fileID: 318030899} - _input: {fileID: 1729466584} - _views: {fileID: 318030900} - _markerHeight: 0.06 - _unitMarkerSize: 2.4 - _buildingMarkerMargin: 0.4 - _selectionColor: {r: 0.35, g: 1, b: 0.4, a: 1} ---- !u!4 &1729466595 + m_EditorClassIdentifier: UnityEngine.dll::UnityEngine.UIElements.UIDocument + m_PanelSettings: {fileID: 11400000, guid: db0593cf705514e1aa6af7b41cca6743, type: 2} + m_ParentUI: {fileID: 0} + sourceAsset: {fileID: 0} + m_SortingOrder: 0 + m_Position: 0 + m_WorldSpaceSizeMode: 1 + m_WorldSpaceWidth: 1920 + m_WorldSpaceHeight: 1080 + m_PivotReferenceSize: 0 + m_Pivot: 0 + m_WorldSpaceCollider: {fileID: 0} +--- !u!4 &1629940941 Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1729466582} + m_GameObject: {fileID: 1629940936} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} @@ -1302,7 +1341,7 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &2086866621 +--- !u!1 &2042920971 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -1310,89 +1349,94 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 2086866623} - - component: {fileID: 2086866622} + - component: {fileID: 2042920976} + - component: {fileID: 2042920973} + - component: {fileID: 2042920972} + - component: {fileID: 2042920975} + - component: {fileID: 2042920974} m_Layer: 0 - m_Name: Directional Light + m_Name: Match m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!108 &2086866622 -Light: +--- !u!114 &2042920972 +MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2086866621} + m_GameObject: {fileID: 2042920971} m_Enabled: 1 - serializedVersion: 13 - m_Type: 1 - m_Color: {r: 1, g: 0.87, b: 0.7, a: 1} - m_Intensity: 1.15 - m_Range: 10 - m_SpotAngle: 30 - m_InnerSpotAngle: 21.802082 - m_CookieSize2D: {x: 0.5, y: 0.5} - m_Shadows: - m_Type: 2 - m_Resolution: -1 - m_CustomResolution: -1 - m_Strength: 0.85 - m_Bias: 0.05 - m_NormalBias: 0.4 - m_NearPlane: 0.2 - m_CullingMatrixOverride: - e00: 1 - e01: 0 - e02: 0 - e03: 0 - e10: 0 - e11: 1 - e12: 0 - e13: 0 - e20: 0 - e21: 0 - e22: 1 - e23: 0 - e30: 0 - e31: 0 - e32: 0 - e33: 1 - m_UseCullingMatrixOverride: 0 - m_Cookie: {fileID: 0} - m_DrawHalo: 0 - m_Flare: {fileID: 0} - m_RenderMode: 0 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingLayerMask: 1 - m_Lightmapping: 4 - m_LightShadowCasterMode: 0 - m_AreaSize: {x: 1, y: 1} - m_BounceIntensity: 1 - m_ColorTemperature: 6570 - m_UseColorTemperature: 0 - m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} - m_UseBoundingSphereOverride: 0 - m_UseViewFrustumForShadowCasterCull: 1 - m_ForceVisible: 0 - m_ShapeRadius: 0.025 - m_ShadowAngle: 0 - m_LightUnit: 1 - m_LuxAtDistance: 1 - m_EnableSpotReflector: 1 ---- !u!4 &2086866623 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0543a6c6c2d8cbd976ba484a58c2ddc5, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Gameplay::Nova.Gameplay.Match.MatchBootstrap + AutoStart: 0 + _seed: 16032251723485610609 + _mapWidth: 128 + _mapHeight: 128 + _entityCapacity: 1024 + UseDefinitionStats: 1 +--- !u!114 &2042920973 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2042920971} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3b9301a799fe64f85afe9906dc15bc39, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Gameplay::Nova.Gameplay.Match.MatchRunner + _seed: 12345 + _mapWidth: 128 + _mapHeight: 128 + _maxUnits: 2048 +--- !u!114 &2042920974 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2042920971} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bad3b0760ec204c94bb15e3918bbc921, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Gameplay::Nova.Gameplay.Match.UnitViewManager + _matchRunner: {fileID: 2042920973} + _unitPrefab: {fileID: 0} + _combatEffects: {fileID: 2042920975} + _assetMappings: {fileID: 11400000, guid: 54dfdf235acc54eecbef61df13a6b356, type: 2} + _interpolationSpeed: 25 + _viewerTeamOverride: -1 + _unknownPlayerColor: {r: 0.62, g: 0.62, b: 0.62, a: 1} + _damagedColor: {r: 0.42, g: 0.05, b: 0.05, a: 1} + _healthTintFloor: 0.25 +--- !u!114 &2042920975 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2042920971} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4cba4e364582443899c3490f303f72f, type: 3} + m_Name: + m_EditorClassIdentifier: Nova.Gameplay::Nova.Gameplay.CombatFeedback.CombatEffectController +--- !u!4 &2042920976 Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2086866621} + m_GameObject: {fileID: 2042920971} serializedVersion: 2 - m_LocalRotation: {x: 0.12450414, y: -0.8617815, z: 0.25527132, w: 0.42031893} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 @@ -1403,13 +1447,13 @@ Transform: SceneRoots: m_ObjectHideFlags: 0 m_Roots: - - {fileID: 1208129616} - - {fileID: 2086866623} - - {fileID: 1688916561} - - {fileID: 796025944} - - {fileID: 318030902} - - {fileID: 1239032166} - - {fileID: 1729466595} - - {fileID: 766640424} - - {fileID: 712913258} - - {fileID: 595700767} + - {fileID: 128460815} + - {fileID: 1521561488} + - {fileID: 1162144977} + - {fileID: 802321242} + - {fileID: 2042920976} + - {fileID: 954524124} + - {fileID: 214746661} + - {fileID: 1629940941} + - {fileID: 526112589} + - {fileID: 1309622898} diff --git a/Assets/_Project/Scripts/Gameplay/UI/ModalSurfaceLink.cs b/Assets/_Project/Scripts/Gameplay/UI/ModalSurfaceLink.cs new file mode 100644 index 0000000..39f1c87 --- /dev/null +++ b/Assets/_Project/Scripts/Gameplay/UI/ModalSurfaceLink.cs @@ -0,0 +1,46 @@ +namespace Nova.Gameplay +{ + /// + /// Read-only modal-surface channel between the HUD layer + /// (Nova.Presentation.UI) and anyone who must know whether a modal panel + /// currently owns the player's input. Same pattern as + /// : a static in Nova.Gameplay both + /// Presentation assemblies can see, no new assembly edge. + /// + /// The semantics are exactly "a modal surface owns the input this frame" + /// — the pause menu, the match-result screen, a network status panel — + /// and deliberately NOT "the match is not running": a paused match and a + /// match without any modal are different states, and consumers that need + /// the clock read MatchRunner.IsRunning instead. + /// + /// + /// ONE writer: the component that draws the modal panels publishes once + /// per frame (today PauseMenuHud, which aggregates MatchFrameHud's + /// result/network panels). The value is a per-frame verdict, never a + /// latch with memory — a stuck true would deadlock every world + /// gesture for the rest of the session, so the writer's OnDisable calls + /// (the UI root switch-off on the way to the main + /// menu is the case that must never leave a stale true behind). + /// The reader is RtsDeviceInput, which suspends world gestures and + /// re-publishes through so the camera's + /// edge-pan strip stops too without gaining a reader of its own. + /// + /// + public static class ModalSurfaceLink + { + /// The last published verdict: a modal surface owns the input this frame. + public static bool Open { get; private set; } + + /// Publishes this frame's verdict. Called by the modal-drawing component every frame. + public static void Publish(bool open) + { + Open = open; + } + + /// Clears the verdict (writer disabled, play-mode transitions, domain-reload-off safety). + public static void Reset() + { + Open = false; + } + } +} diff --git a/Assets/_Project/Scripts/Gameplay/UI/ModalSurfaceLink.cs.meta b/Assets/_Project/Scripts/Gameplay/UI/ModalSurfaceLink.cs.meta new file mode 100644 index 0000000..d4e98b7 --- /dev/null +++ b/Assets/_Project/Scripts/Gameplay/UI/ModalSurfaceLink.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 593d941e459240a1ac57d96f8b4168ed \ No newline at end of file diff --git a/Assets/_Project/Scripts/Presentation/UI/MainMenuController.cs b/Assets/_Project/Scripts/Presentation/UI/MainMenuController.cs index 404486f..f290a69 100644 --- a/Assets/_Project/Scripts/Presentation/UI/MainMenuController.cs +++ b/Assets/_Project/Scripts/Presentation/UI/MainMenuController.cs @@ -17,9 +17,11 @@ namespace Nova.Presentation.UI /// scene-flow layer — bought for nothing. /// is off (the scene generator /// writes it), so the scene loads into an idle host: the kernel is not - /// running, every HUD component checks for a running match and draws - /// nothing, and "Neues Spiel" calls the idempotent - /// and hides this overlay. + /// running, and Start() switches the whole gameplay HUD root off in one + /// call before the first frame + /// draws. "Neues Spiel" calls the idempotent + /// , switches the root back + /// on and hides this overlay. /// /// /// UI TOOLKIT, NOT uGUI, and built in C# rather than from UXML. uGUI would @@ -65,8 +67,11 @@ public sealed partial class MainMenuController : MonoBehaviour "The scene generator wires the concrete component.")] [SerializeField] private Behaviour _cameraRig; - [Tooltip("Debug HUD. Its status bar draws before its own visibility check, so it would sit on the key art.")] - [SerializeField] private DebugHud _debugHud; + [Tooltip("The UI root GameObject the scene generator puts EVERY in-match HUD component on " + + "(input, markers, build bar, command card, fog, minimap, health bars, match frame, " + + "pause menu, debug HUD). Switched as ONE object, so a HUD added to the root later is " + + "covered without ever being named here — a component catalogue would rot with the next one.")] + [SerializeField] private GameObject _gameplayHudRoot; [Header("Content (scene generator)")] [SerializeField] private Texture2D _keyArt; @@ -131,9 +136,10 @@ public sealed partial class MainMenuController : MonoBehaviour /// /// True while the menu overlay owns the screen (initial state, and - /// after MatchFrameHud's "Hauptmenü"). Read by the input component to - /// suspend world gestures while the menu is up — a click on a menu - /// button must never also select or order in the world behind it. + /// after the match frame's "Hauptmenü" or the pause menu's "Zum + /// Hauptmenü"). Read by the input component to suspend world gestures + /// while the menu is up — a click on a menu button must never also + /// select or order in the world behind it. /// public bool IsMenuVisible { get; private set; } @@ -143,6 +149,14 @@ private void Awake() if (_music == null) _music = GetComponent(); if (_bootstrap == null) _bootstrap = FindAnyObjectByType(); if (_views == null) _views = FindAnyObjectByType(); + if (_gameplayHudRoot == null) + { + // Self-heal for scenes generated before the root switch + // existed: the root is by definition the GameObject the input + // component lives on (BootstrapSceneGenerator.CreateUiObject). + RtsDeviceInput input = FindAnyObjectByType(); + if (input != null) _gameplayHudRoot = input.gameObject; + } } private void Start() @@ -199,11 +213,12 @@ private void LogMissingWiring() "[MainMenuController] No camera rig wired — the RTS camera keeps reading edge pan and " + "scroll wheel while the menu is up, so the match will open on a drifted camera."); } - if (_debugHud == null) + if (_gameplayHudRoot == null) { Debug.LogError( - "[MainMenuController] No DebugHud wired — its always-on status bar will draw on top of " + - "the main menu."); + "[MainMenuController] No gameplay HUD root wired (and none found via RtsDeviceInput) — " + + "the cockpit will keep drawing over the menu. Re-run " + + "Tools/Project Nova/Create Bootstrap Scene."); } } @@ -714,22 +729,30 @@ private void StartMatch() if (_music != null) _music.FadeOutAndStop(); if (_screen != null) _screen.style.display = DisplayStyle.None; - // Nothing left to drive — until a match ends: MatchFrameHud's - // "Hauptmenü" button re-enters through ReturnToMenu below. + // Nothing left to drive — until a match ends or is abandoned: + // MatchFrameHud's "Hauptmenü" and the pause menu's "Zum + // Hauptmenü" re-enter through ReturnToMenu below. enabled = false; } /// - /// The way back from a finished match (MatchFrameHud's "Hauptmenü" - /// button): pause the host, hide the cockpit layer, bring the menu - /// screen and its music back. The match state itself is left for the - /// NEXT "Neues Spiel", which restarts it via - /// — the menu does not - /// demolish what it may never need again. + /// The way back from a finished or abandoned match (MatchFrameHud's + /// "Hauptmenü" button, the pause menu's "Zum Hauptmenü"): pause the + /// host, hide the cockpit layer, bring the menu screen and its music + /// back. The match state itself is left for the NEXT "Neues Spiel", + /// which restarts it via — + /// the menu does not demolish what it may never need again. /// public void ReturnToMenu() { - if (_bootstrap != null && _bootstrap.Runner != null && _bootstrap.Runner.IsRunning) + // A relay kernel cannot pause independently of its peer + // (PauseMatch logs an error and refuses) — and with the pause + // menu, "leave a RUNNING relay match" is now a reachable path, + // so the refusal must not be triggered on purpose. The relay + // match keeps ticking behind the menu until the next start + // restarts it, exactly as a locally paused match does. + if (_bootstrap != null && _bootstrap.Runner != null + && _bootstrap.Runner.IsRunning && !_bootstrap.Runner.IsRelayMatch) { _bootstrap.Runner.PauseMatch(); } @@ -753,7 +776,12 @@ public void ReturnToMenu() enabled = true; } - private void Quit() + /// + /// Quits the application after flushing any unsaved settings. Public + /// so the pause menu's "Spiel beenden" runs the exact same exit as + /// this menu's "Beenden" — one quit path, no copy. + /// + public void Quit() { FlushSettings(); #if UNITY_EDITOR @@ -766,24 +794,35 @@ private void Quit() } /// - /// Silences (or restores) the two scene components that do NOT guard - /// themselves against "no match yet". The list is short on purpose: - /// every other HUD component already returns early without a running - /// match, and a catalogue of every HUD in the scene would rot with - /// the next one added. + /// Silences (or restores) everything that must not run while the menu + /// owns the screen. TWO switches, by construction: /// - /// The camera rig has no guard at all — its LateUpdate reads - /// scroll wheel, MMB, Z/X, arrow keys and the screen-edge pan every - /// frame. Awake has already applied the start framing, so a disabled - /// rig sits exactly where the match wants it. - /// The debug HUD draws its always-on status bar BEFORE its own - /// visibility check, so its text would sit on top of the key art. + /// The camera rig — a plain component toggle. It lives on "Main + /// Camera", not on the HUD root, and has no "no match yet" guard: its + /// LateUpdate reads scroll wheel, MMB, Z/X, arrow keys and the + /// screen-edge pan every frame. Awake has already applied the start + /// framing, so a disabled rig sits exactly where the match wants + /// it. + /// The gameplay HUD ROOT — the one GameObject the scene + /// generator puts every in-match HUD component on. Switching the root + /// instead of a component list is what keeps this method correct + /// forever: a catalogue would rot with the next HUD added, while + /// whatever the generator places on the root is covered from the day + /// it lands, without ever being named here. Deactivation IS the + /// gate — OnGUI, Update and the hit-tests of every child die with + /// it, including the debug HUD whose always-on status bar would + /// otherwise sit on the key art (behaviour identical to the old + /// per-component toggle; the F3 state survives SetActive). /// + /// The in-match PAUSE menu is a different case and is deliberately + /// NOT this switch: it keeps the root on (the world stays visible + /// under the panel) and gates only the input, through + /// ModalSurfaceLink — see PauseMenuHud. /// private void SetGameplayLayerActive(bool active) { if (_cameraRig != null) _cameraRig.enabled = active; - if (_debugHud != null) _debugHud.enabled = active; + if (_gameplayHudRoot != null) _gameplayHudRoot.SetActive(active); } // --- settings persistence ---------------------------------------- diff --git a/Assets/_Project/Scripts/Presentation/UI/MatchFrameHud.cs b/Assets/_Project/Scripts/Presentation/UI/MatchFrameHud.cs index 9e06c5c..ea4afbc 100644 --- a/Assets/_Project/Scripts/Presentation/UI/MatchFrameHud.cs +++ b/Assets/_Project/Scripts/Presentation/UI/MatchFrameHud.cs @@ -9,22 +9,32 @@ namespace Nova.Presentation.UI { /// - /// The match frame (sprint 09 §6): the two modal panels that close a - /// round — the RESULT screen once latches an - /// outcome ("Sieg" / "Niederlage" / "Unentschieden" with the decided-tick - /// timestamp), and the PAUSE overlay while the kernel clock is stopped - /// (a paused match and a broken one used to look identical). Both offer - /// "Neue Runde" and/or "Hauptmenü", so quitting the application is no - /// longer the only way out of a finished round. + /// The match frame (sprint 09 §6): the modal panels that close a round — + /// the RESULT screen once latches an outcome + /// ("Sieg" / "Niederlage" / "Unentschieden" with the decided-tick + /// timestamp), and the NETWORK status panels for a relay match that + /// ended or stalls. Both offer a way out ("Neue Runde" and/or + /// "Hauptmenü"), so quitting the application is no longer the only exit + /// from a finished round. The PAUSE surface no longer lives here: since + /// sprint 21.8 it is (ESC/P), which also owns + /// the kernel clock. /// /// "Neue Runde" runs (the sim /// side is rebuilt wholesale) and then resets the presentation side: /// views via , the camera through /// — the rig owns /// the reset itself because Nova.Presentation.UI may not reference - /// Nova.Presentation (same rank). The selection clears itself on the - /// ingress rebind (RtsDeviceInput.EnsureDispatcher), and the fog overlay - /// plus minimap self-heal through their fog-instance guards. + /// Nova.Presentation (same rank). The selection and any armed gesture + /// clear themselves on the ingress rebind (RtsDeviceInput.EnsureDispatcher), + /// and the fog overlay plus minimap self-heal through their fog-instance + /// guards. + /// + /// + /// reports once per frame whether one of these + /// panels is up; PauseMenuHud aggregates it into + /// , the channel that tells the input + /// component "a modal owns every click" — a click on "Hauptmenü" must + /// never fall through the panel and select a unit behind it. /// /// /// READ-ONLY toward the simulation: this panel polls the victory system's @@ -48,6 +58,21 @@ public sealed class MatchFrameHud : MonoBehaviour private GUIStyle _bodyStyle; private GUIStyle _buttonStyle; + // The panel state, derived once per frame in Update: OnGUI draws from + // it (possibly several passes per frame) and PauseMenuHud reads + // ModalOpen for the ModalSurfaceLink publish — one derivation, no + // drift between "is a panel up" and "which panel is drawn". + private VictorySystem _panelVictory; + private string _panelNetworkReason; + private int _panelStalledSlot; + + /// + /// True while one of this component's modal panels (result or + /// network status) is up. Computed once per frame in Update; + /// PauseMenuHud aggregates it into . + /// + public bool ModalOpen { get; private set; } + private void Awake() { if (_runner == null) _runner = FindAnyObjectByType(); @@ -56,20 +81,42 @@ private void Awake() if (_views == null) _views = FindAnyObjectByType(); } - private void OnGUI() + private void Update() { - if (_runner == null || _bootstrap == null) return; - if (_menu != null && _menu.IsMenuVisible) return; + ModalOpen = ComputeModalState(); + } + + /// + /// The modal verdict and the panel payload behind it. "Modal" means + /// the decided-result screen, a relay end reason or a stalled relay + /// handshake — the paused-with-victory-pending state is deliberately + /// absent: it belongs to the pause menu now. + /// + private bool ComputeModalState() + { + _panelVictory = null; + _panelNetworkReason = null; + _panelStalledSlot = -1; + if (_runner == null || _bootstrap == null) return false; + if (_menu != null && _menu.IsMenuVisible) return false; bool isMatchReady = _bootstrap.IsMatchReady; - VictorySystem victory = isMatchReady ? _runner.Victory : null; - string networkEndReason = isMatchReady + _panelVictory = isMatchReady ? _runner.Victory : null; + _panelNetworkReason = isMatchReady ? _runner.RelayEndReason : _bootstrap.NetworkStatusReason; - int stalledOnSlot = isMatchReady && _runner.IsRelayMatch && _runner.RelayCommandsAllowed + _panelStalledSlot = isMatchReady && _runner.IsRelayMatch && _runner.RelayCommandsAllowed ? _bootstrap.NetworkStalledOnSlot : -1; - if (victory == null && string.IsNullOrEmpty(networkEndReason) && stalledOnSlot < 0) return; + + return (_panelVictory != null && _panelVictory.IsDecided) + || !string.IsNullOrEmpty(_panelNetworkReason) + || _panelStalledSlot >= 0; + } + + private void OnGUI() + { + if (!ModalOpen) return; EnsureStyles(); @@ -77,23 +124,19 @@ private void OnGUI() Matrix4x4 previousMatrix = GUI.matrix; GUI.matrix = Matrix4x4.Scale(new Vector3(scale, scale, 1f)); - if (victory != null && victory.IsDecided) + if (_panelVictory != null && _panelVictory.IsDecided) { - DrawResultPanel(victory); + DrawResultPanel(_panelVictory); } - else if (!string.IsNullOrEmpty(networkEndReason)) + else if (!string.IsNullOrEmpty(_panelNetworkReason)) { - DrawNetworkStatusPanel("NETZWERKFEHLER", networkEndReason); + DrawNetworkStatusPanel("NETZWERKFEHLER", _panelNetworkReason); } - else if (stalledOnSlot >= 0) + else if (_panelStalledSlot >= 0) { DrawNetworkStatusPanel( "VERBINDUNG", - $"Warte auf Spieler {stalledOnSlot + 1} … {_bootstrap.NetworkStallSeconds:0.0}s"); - } - else if (victory != null && !_runner.IsRunning) - { - DrawPausePanel(); + $"Warte auf Spieler {_panelStalledSlot + 1} … {_bootstrap.NetworkStallSeconds:0.0}s"); } GUI.matrix = previousMatrix; @@ -154,28 +197,6 @@ private void DrawResultPanel(VictorySystem victory) GUILayout.EndArea(); } - private void DrawPausePanel() - { - Rect rect = CenteredRect(150f); - GUI.Box(rect, GUIContent.none, HudChrome.OpaquePanelStyle); - GUILayout.BeginArea(rect); - GUILayout.Space(HudChrome.OpaquePanelStyle.padding.top); - GUILayout.Label("PAUSE", _headlineStyle); - GUILayout.Label("Die Simulation steht — P oder der Knopf setzt fort.", _bodyStyle); - GUILayout.Space(10f); - if (GUILayout.Button("Fortsetzen (P)", _buttonStyle, GUILayout.Height(30f))) - { - AudioServiceLocator.Play2D(SoundEventId.UI_Click); - _runner.StartMatch(); - } - if (GUILayout.Button("Hauptmenü", _buttonStyle, GUILayout.Height(26f))) - { - AudioServiceLocator.Play2D(SoundEventId.UI_Click); - ReturnToMenu(); - } - GUILayout.EndArea(); - } - /// /// The full round reset: sim side via the bootstrap, view side via /// the view manager, camera via the link channel (see the class diff --git a/Assets/_Project/Scripts/Presentation/UI/PauseMenuHud.cs b/Assets/_Project/Scripts/Presentation/UI/PauseMenuHud.cs new file mode 100644 index 0000000..4a40537 --- /dev/null +++ b/Assets/_Project/Scripts/Presentation/UI/PauseMenuHud.cs @@ -0,0 +1,258 @@ +// Graybox throwaway. Legacy Input + OnGUI. Does NOT satisfy G2/G4 UI criteria +// (see docs/production/MVPRecoveryPlan.md). Replaced when the new Input System and the real UI land. +using UnityEngine; +using Nova.Gameplay; +using Nova.Gameplay.Audio; +using Nova.Gameplay.Match; + +namespace Nova.Presentation.UI +{ + /// + /// The in-match pause menu (#105): ESC or P opens it OVER the live world + /// — the HUD root stays on, so the frozen match remains visible behind + /// the panel — and stops the kernel clock via + /// . "Fortsetzen" (or ESC/P again) + /// resumes what the menu itself paused, "Zum Hauptmenü" leaves through + /// , "Spiel beenden" through + /// . Relay matches cannot pause + /// (the runner refuses it), so there the menu opens without stopping the + /// clock and says so. + /// + /// ESC PEELS ONE LAYER PER PRESS: an armed placement ghost or order pick + /// owns the key first (RtsDeviceInput runs at -200 and stamps + /// when it cancels + /// one); the menu opens only on the next press. P is not a cancel key and + /// opens the menu directly — the input component disarms any armed + /// gesture the moment the modal gate engages. + /// + /// + /// SINGLE WRITER of : every frame this + /// component publishes whether ANY modal surface owns the input — this + /// menu or 's terminal panels (result / + /// network), which outrank the menu: the menu refuses to open over them + /// and closes itself if one appears underneath. RtsDeviceInput reads the + /// verdict and suspends world gestures while it is up. The publish is + /// per-frame, never latched, and resets the + /// channel — the UI root switch-off on the way to the main menu must + /// never leave a stale "open" behind, or the next match would deadlock + /// every click. + /// + /// + /// READ-ONLY toward the simulation: the only state this component + /// touches is the kernel clock (pause/resume); it never submits a + /// command. Settings are deliberately NOT a second mask here — they live + /// in the main menu, and the panel says so instead of drifting a copy. + /// + /// + [DisallowMultipleComponent] + public sealed class PauseMenuHud : MonoBehaviour + { + [Header("Wiring (scene generator)")] + [SerializeField] private MatchRunner _runner; + [SerializeField] private MainMenuController _menu; + [Tooltip("Its result/network panels are the terminal modals: the pause menu never stacks on top of them.")] + [SerializeField] private MatchFrameHud _matchFrame; + [Tooltip("Read for the ESC layer-peel: an armed gesture cancels first, the menu opens on the next press.")] + [SerializeField] private RtsDeviceInput _input; + + [Header("Presentation")] + [SerializeField] private float _uiScale = 1.5f; + [SerializeField] private float _panelWidth = 340f; + + private bool _menuOpen; + private bool _pausedByMenu; + + private GUIStyle _headlineStyle; + private GUIStyle _bodyStyle; + private GUIStyle _buttonStyle; + + /// True while the pause menu panel is up (read-only, for observers and tests). + public bool IsPauseMenuOpen => _menuOpen; + + private void Awake() + { + if (_runner == null) _runner = FindAnyObjectByType(); + if (_menu == null) _menu = FindAnyObjectByType(); + if (_matchFrame == null) _matchFrame = FindAnyObjectByType(); + if (_input == null) _input = FindAnyObjectByType(); + } + + private void OnDisable() + { + // The channel must never outlive its writer: this component sits + // on the UI root, and the root is switched OFF on the way to the + // main menu — exactly the transition where a latched "open" would + // deadlock the next match's input. Drop the local state with it, + // so a re-enabled component (next match start) starts clean. + _menuOpen = false; + _pausedByMenu = false; + ModalSurfaceLink.Reset(); + } + + private void Update() + { + if (_menu != null && _menu.IsMenuVisible) + { + // The main menu owns the screen and the UI root should + // already be off (this Update would not run). Hand-built + // scenes without the root switch still get a clean state: + // ReturnToMenu keeps the match paused, so nothing resumes here. + _menuOpen = false; + _pausedByMenu = false; + } + else + { + HandleToggleInput(); + if (_menuOpen && _matchFrame != null && _matchFrame.ModalOpen) + { + // A terminal panel (result / network) appeared under the + // open menu — it outranks the pause menu and takes over. + CloseMenu(playClick: false); + } + } + + ModalSurfaceLink.Publish(_menuOpen || (_matchFrame != null && _matchFrame.ModalOpen)); + } + + private void HandleToggleInput() + { + bool escape = Input.GetKeyDown(KeyCode.Escape); + bool pause = Input.GetKeyDown(KeyCode.P); + if (!escape && !pause) return; + + if (_menuOpen) + { + CloseMenu(playClick: true); + return; + } + + // ESC peels one layer at a time: RtsDeviceInput ran earlier this + // frame (execution order -200) and stamped the press if it + // cancelled an armed placement ghost or order pick — that press + // belongs to the gesture, not to the menu. P never cancels a + // gesture, so it needs no such check. + if (escape && !pause + && _input != null && _input.LastGestureCancelFrame == Time.frameCount) + { + return; + } + + // The terminal modals (result / network panels) own the screen; + // the pause menu does not stack on top of them. + if (_matchFrame != null && _matchFrame.ModalOpen) return; + + OpenMenu(); + } + + private void OpenMenu() + { + _menuOpen = true; + _pausedByMenu = false; + // Only a running LOCAL match is paused (a relay kernel cannot + // pause independently of its peer — PauseMatch would log an + // error, so it is not even called). The menu itself stays fully + // usable in a relay match; it just does not stop the clock. + if (_runner != null && !_runner.IsRelayMatch && _runner.IsRunning) + { + _pausedByMenu = _runner.PauseMatch(); + } + AudioServiceLocator.Play2D(SoundEventId.UI_Click); + } + + /// Closes the menu and resumes the clock if — and only if — this menu paused it. + private void CloseMenu(bool playClick) + { + _menuOpen = false; + if (_pausedByMenu) + { + _pausedByMenu = false; + if (_runner != null && !_runner.IsRunning) + { + _runner.StartMatch(); + } + } + if (playClick) AudioServiceLocator.Play2D(SoundEventId.UI_Click); + } + + private void OnGUI() + { + if (!_menuOpen) return; + + EnsureStyles(); + + float scale = Mathf.Max(1f, _uiScale); + Matrix4x4 previousMatrix = GUI.matrix; + GUI.matrix = Matrix4x4.Scale(new Vector3(scale, scale, 1f)); + + Rect rect = CenteredRect(250f); + GUI.Box(rect, GUIContent.none, HudChrome.OpaquePanelStyle); + GUILayout.BeginArea(rect); + GUILayout.Space(HudChrome.OpaquePanelStyle.padding.top); + GUILayout.Label("PAUSE", _headlineStyle); + GUILayout.Label( + _runner != null && _runner.IsRelayMatch + ? "Netzpartie — die Simulation läuft im Hintergrund weiter." + : "Die Simulation steht — ESC setzt fort.", + _bodyStyle); + GUILayout.Space(10f); + if (GUILayout.Button("Fortsetzen (ESC)", _buttonStyle, GUILayout.Height(34f))) + { + CloseMenu(playClick: true); + } + if (GUILayout.Button("Zum Hauptmenü", _buttonStyle, GUILayout.Height(30f))) + { + AudioServiceLocator.Play2D(SoundEventId.UI_Click); + // No resume: the menu leaves the match for good, and + // ReturnToMenu keeps it paused for the next "Neues Spiel". + _menuOpen = false; + _pausedByMenu = false; + if (_menu != null) _menu.ReturnToMenu(); + } + if (GUILayout.Button("Spiel beenden", _buttonStyle, GUILayout.Height(30f))) + { + AudioServiceLocator.Play2D(SoundEventId.UI_Click); + if (_menu != null) _menu.Quit(); + } + GUILayout.Space(6f); + GUILayout.Label("Einstellungen (Ton, Bild, Auflösung) gibt es im Hauptmenü.", _bodyStyle); + GUILayout.EndArea(); + + GUI.matrix = previousMatrix; + } + + private Rect CenteredRect(float height) + { + float scale = Mathf.Max(1f, _uiScale); + float x = (Screen.width / scale - _panelWidth) * 0.5f; + float y = (Screen.height / scale - height) * 0.4f; + return new Rect(x, y, _panelWidth, height); + } + + private void EnsureStyles() + { + if (_headlineStyle == null) + { + _headlineStyle = new GUIStyle(GUI.skin.label) + { + fontSize = 22, + fontStyle = FontStyle.Bold, + alignment = TextAnchor.MiddleCenter, + wordWrap = true + }; + } + if (_bodyStyle == null) + { + _bodyStyle = new GUIStyle(GUI.skin.label) + { + fontSize = 12, + alignment = TextAnchor.MiddleCenter, + wordWrap = true + }; + } + if (_buttonStyle == null) + { + _buttonStyle = new GUIStyle(GUI.skin.button) { fontSize = 14 }; + } + } + } +} diff --git a/Assets/_Project/Scripts/Presentation/UI/PauseMenuHud.cs.meta b/Assets/_Project/Scripts/Presentation/UI/PauseMenuHud.cs.meta new file mode 100644 index 0000000..3f498eb --- /dev/null +++ b/Assets/_Project/Scripts/Presentation/UI/PauseMenuHud.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4839e6caa28240839b51760036ea05f0 \ No newline at end of file diff --git a/Assets/_Project/Scripts/Presentation/UI/RtsDeviceInput.cs b/Assets/_Project/Scripts/Presentation/UI/RtsDeviceInput.cs index 51b9443..dbb3642 100644 --- a/Assets/_Project/Scripts/Presentation/UI/RtsDeviceInput.cs +++ b/Assets/_Project/Scripts/Presentation/UI/RtsDeviceInput.cs @@ -69,6 +69,21 @@ namespace Nova.Presentation.UI /// the Builder actually walks into reach. /// /// + /// MODAL GATE (sprint 21.8): while + /// — published once per frame by PauseMenuHud, which aggregates every + /// modal surface (pause menu, result and network panels) — this + /// component's Update suspends ALL world gestures: no selection, no + /// orders, no hotkeys, no harvester escort into a stopped kernel, and no + /// click falling through a panel onto the unit behind it. The camera's + /// edge-pan strip is covered by re-publishing "pointer over HUD" + /// () instead of growing a second reader. + /// Any armed gesture (placement ghost, order pick) is disarmed when the + /// gate engages, so a modal never inherits one — and the ingress rebind + /// clears them too, so an armed ghost never survives into the next + /// match (#102). Pause/resume itself moved out of here: ESC/P and the + /// pause menu live in . + /// + /// /// World mapping (identical to UnitViewManager / FlowFieldDebugView / /// RtsCameraController): sim X -> Unity x, sim Y -> Unity z, ground /// plane at y = (0). Cell (gx, gy) covers the @@ -228,6 +243,16 @@ private enum PendingOrder /// public bool BuildZoneOverlayPinned => _buildZoneOverlayPinned; + /// + /// The frame () ESC last cancelled an + /// armed gesture here (placement ghost or order pick). PauseMenuHud + /// reads it to peel exactly one layer per press: this component runs + /// at -200, so its consumption is already stamped when the menu's + /// Update looks at the same ESC press — the gesture cancels first, + /// the menu opens on the NEXT press. + /// + public int LastGestureCancelFrame { get; private set; } = -1; + /// Definition the armed placement ghost currently carries. public ushort PlacementDefId => _placementDefId; @@ -419,7 +444,7 @@ private void Awake() _legend = "LMB click/drag select | RMB move — with an own producer building selected: set its rally point | S stop | " + "A attack enemy under cursor (else plain move; armed units auto-acquire visible in-range enemies, D-087) | " + - "H harvest nearest field | R return cargo | P pause/resume (local match only) | " + + "H harvest nearest field | R return cargo | ESC/P pause menu (clock stops in local matches only)\n" + "O build zone overlay on/off (shows on its own while a placement ghost is armed)\n" + "Build (build bar below or hotkey — a ghost follows the cursor; LMB place | RMB/ESC cancel): " + $"B {_buildingDefId} | Shift+B {_altBuildingDefId} | C {_storageDefId} | V {_vehicleFactoryDefId} | " + @@ -437,6 +462,26 @@ private void Update() if (!EnsureDispatcher()) return; if (_menu != null && _menu.IsMenuVisible) return; // the overlay owns every click + if (ModalSurfaceLink.Open) + { + // A modal surface (pause menu, result or network panel) owns + // the input: no selection, no orders, no hotkeys, no escort + // moves into a stopped kernel — and no click falling through + // the panel onto the world behind it. "Pointer over HUD" is + // published so the camera's edge-pan strip stops too, without + // a second reader. An in-flight drag or armed gesture dies + // here instead of resolving behind the panel. + HudPointerLink.Publish(true); + _dragActive = false; + if (_placementMode || _pendingOrder != PendingOrder.None) + { + _placementMode = false; + _pendingOrder = PendingOrder.None; + _lastCommandStatus = "Gesture cancelled — a modal surface owns the input"; + } + return; + } + Vector2 mouse = Input.mousePosition; UpdatePlacementHover(mouse); HandleBuildZoneOverlayToggle(); @@ -481,6 +526,13 @@ private bool EnsureDispatcher() // ClearSelection also drops the selected field (21.2): a rebinding // means a fresh match, and its ids belong to the old one. _selection.ClearSelection(); + // Armed gestures are input state tied to the OLD match's world: + // an armed placement ghost or order pick must never survive the + // ingress rebind into the next round (#102 — the ghost used to + // stay armed through "Hauptmenü" → "Neues Spiel"). + _placementMode = false; + _pendingOrder = PendingOrder.None; + _dragActive = false; return true; } @@ -541,8 +593,12 @@ private void HandleBuildZoneOverlayToggle() /// private void HandlePlacementModeInput(Vector2 mouse, bool shift) { - if (Input.GetKeyDown(KeyCode.Escape) || Input.GetMouseButtonDown(1)) + bool escape = Input.GetKeyDown(KeyCode.Escape); + if (escape || Input.GetMouseButtonDown(1)) { + // Stamp the ESC consumption: the pause menu peels one layer + // per press and must not open on this same press. + if (escape) LastGestureCancelFrame = Time.frameCount; _placementMode = false; _lastCommandStatus = "Placement cancelled"; return; @@ -935,26 +991,11 @@ private void HandleOrders(Vector2 mouse) if (Input.GetKeyDown(KeyCode.R)) OrderReturnCargo(); - // Pause/resume toggles the kernel clock only — simulation state is - // untouched, so this needs no command. StartMatch after PauseMatch - // simply restarts the tick pump. - if (Input.GetKeyDown(KeyCode.P)) - { - if (_runner.IsRelayMatch) - { - _lastCommandStatus = "Pause/resume is unavailable in a relay match"; - } - else if (_runner.IsRunning) - { - _runner.PauseMatch(); - _lastCommandStatus = "Match paused (P resumes)"; - } - else - { - _runner.StartMatch(); - _lastCommandStatus = "Match resumed"; - } - } + // Pause/resume no longer lives here: ESC/P and the pause menu + // belong to PauseMenuHud, which pauses the kernel clock directly + // (no command involved). While its modal is up, the gate in + // Update keeps every hotkey above from firing into the stopped + // kernel. // Control groups 1-9 (sprint 09 §7): Ctrl/Cmd+Digit stores the // current selection, the bare digit recalls it. @@ -1150,8 +1191,11 @@ private bool IsPointerOverHud(Vector2 mouse) /// private void HandleOrderPickInput(Vector2 mouse) { - if (Input.GetKeyDown(KeyCode.Escape) || Input.GetMouseButtonDown(1)) + bool escape = Input.GetKeyDown(KeyCode.Escape); + if (escape || Input.GetMouseButtonDown(1)) { + // Same one-layer-per-press contract as the placement cancel. + if (escape) LastGestureCancelFrame = Time.frameCount; _pendingOrder = PendingOrder.None; _lastCommandStatus = "Order pick cancelled"; return; diff --git a/reports/v8.6.0/sprint-21/01-kimi-hud-state-audit.md b/reports/v8.6.0/sprint-21/01-kimi-hud-state-audit.md new file mode 100644 index 0000000..90f69f3 --- /dev/null +++ b/reports/v8.6.0/sprint-21/01-kimi-hud-state-audit.md @@ -0,0 +1,54 @@ +--- +Bericht: HUD-Zustandsaudit (Paket 21.8, #102 / #105 / #103) +Worker: Kimi K3 (cc-Backend, read-only), 2026-08-18 +Auftraggeber: Orchestrator +--- + +> **Hinweis zur Vollständigkeit:** Die Abschnitte 1 (Inventar) und 2 (Klassen- +> einteilung) fehlen — sie wurden beim Einsammeln der Worker-Ausgabe +> abgeschnitten (`| tail -30` beim Dispatch, Fehler des Orchestrators, nicht des +> Workers). Erhalten sind die Abschnitte 3 bis 5 samt Verdikt; sie tragen die +> Befunde, auf denen Paket 21.8 gebaut wird. + + +## 3. Klasse (b): Warum geht es heute gut, und wann kippt es? + +- **`PlacementGhostView`** — Heute gut, weil der Welt-Space-Ghost hinter dem opaken Menü-Hintergrund unsichtbar ist. Kippt zweifach: (i) ein Pausemenü, das die Welt (abgedunkelt) sichtbar lässt, zeigt den scharfen Ghost mitten im Modalzustand — der Armiert-Zustand `_placementMode` (RtsDeviceInput.cs:164) überlebt `ReturnToMenu` ungelöscht, weil nur die *Auswahl* beim Ingress-Rebind geräumt wird (RtsDeviceInput.cs:468); (ii) derselbe Überlebensweg trägt einen armierten Ghost sogar in die **nächste** Runde („Neues Spiel" → `RestartMatch`). Das ist die #102-Annahme in Reinform: der Riegel ist Eingabezustand, nicht Oberflächenzustand. +- **`ConstructionSiteMarkerView`** — Heute gut, weil Welt-Space (verdeckt) und der Puls (`Time.time`, :69) unsichtbar vor sich hin läuft. Kippt beim selben Szenario wie der Ghost (sichtbare Welt unter Pausemenü); bis dahin nur vergeudete Reads. Kein `IsRunning`-Riegel — unter P-Pause pulsiert es weiter, was heute harmlos ist. +- **`FogOfWarOverlayView`** — Heute gut, weil verdeckt und der Repaint an `fog.LastRecomputeTick` hängt: pausierter Kernel → kein neuer Tick → null Arbeit. Die Fog-Instanz-Wache (:96) heilt den Restart selbst. Kippt nur bei transparentem/ Welt zeigendem Overlay — und selbst dann wäre stehende Fog-Karte kosmetisch korrekt. +- **`CombatEffectController`** — Heute gut, weil Effekte unscaled ≤ 1,5 s leben und ohne Ticks keine neuen Events kommen. Kippt praktisch nicht; maximal spielt ein Todes-Sound in die Menü-Musik hinein, wenn `ReturnToMenu` exakt auf einen Kill fällt. +- **`GlutrinneBlockoutView`** — Heute gut, weil verdeckt und 0,5-s-Kadenz billig. Sein Docstring (:139-141) nennt das Menü sogar — deckt aber nur „noch keine Economy" ab, nicht „Economy überlebt pausiert": es stagt die Scherben hinter dem Menü weiter. Kippt nicht sichtbar; es ist die erste Komponente, deren Docstring die falsche Annahme bereits dokumentiert. +- **`FlowFieldDebugView`** — Heute gut, weil Gizmos nur in der Editor-Scene-View existieren. Kippt nie im Build. +- **`RtsDeviceInput` (OnGUI + Panel-Klicks)** — Heute gut, weil `Update` bei `IsMenuVisible` aussteigt und das Drag-Rechteck einen aktiven Drag braucht. Kippt an zwei Stellen: (i) Die modalen Panels von `MatchFrameHud` (Resultat, Pause) stehen **nicht** in `IsPointerOverHud` (RtsDeviceInput.cs:1102-1107 kennt nur Baukarte, Karte, Minimap) — ein Klick auf „Hauptmenü" läuft in `Update` zuerst durch `HandleSelection`/`SelectSingle` (:819, :1354) und **verändert die Auswahl hinter dem Panel**, bevor `OnGUI` den Button feuert. Der Ausstiegsklick selbst kann also die Auswahl erzeugen, die `CommandCardHud` dann über dem Menü zeichnet — #102 armiert sich teilweise selbst. (ii) **P-Pause hat gar keinen Riegel**: `IsMenuVisible` ist false, also laufen Selektion, sämtliche Hotkeys (S/A/H/R/Q/B/U…) und die Harvester-Eskorte (:634) in den Ingress des gestoppten Kernels weiter — Befehle stauen sich bis zum Resume. Genau hier bricht das geplante Pausemenü mit „derselben Anforderung" ein: für den Menü-Zustand existiert ein Signal, für den Pause-Zustand keines. + +## 4. Zustandssignale — was ist heute abfragbar? + +| Signal | Ort | Bedeutung | Lesbar ohne neue `MainMenuController`-Abhängigkeit? | +|---|---|---|---| +| `MainMenuController.IsMenuVisible` | MainMenuController.cs:138 | Menü-Oberfläche besitzt den Schirm | Nur innerhalb Nova.Presentation.UI (drei Komponenten verdrahtet bereits: RtsDeviceInput.cs:92, MatchFrameHud.cs:40, MusicDirector.cs:42). Für Nova.Presentation **unmöglich** — Rank-Gate, darum ist `_cameraRig` als `Behaviour` deklariert (:63-66). Sagt nichts über Pause. | +| `MatchBootstrap.IsMatchReady` | MatchBootstrap.cs:213 | „Match-Welt existiert" (Kernel + Systeme gebaut) | **Ja** — `MatchBootstrap` wird bereits aus Presentation.UI und Presentation.Maps referenziert. Bleibt bei Pause **true** — trennt „kein Match" von „pausiert", nicht „läuft" von „pausiert". | +| `MatchRunner.IsRunning` | MatchRunner.cs:157 (`Kernel != null && Kernel.IsRunning`) | Simulationsuhr läuft | **Ja** — bereits der häufigste Riegel. „Pausiert" und „kein Match" lesen beide false; die Unterscheidung liefert erst die Kombination mit `IsMatchReady` (bzw. `Entities != null`). | +| `Runner.PauseMatch()` / `StartMatch()` | MatchRunner.cs:314 / :296 | **Aktionen**, keine Signale | — | +| `_runner.Victory.IsDecided` | via MatchRunner | Matchende gelatcht | **Ja** (MatchFrameHud, MusicDirector, DebugHud lesen es bereits). | +| Abgeleitet: `IsMatchReady && !IsRunning` | MatchFrameHud.cs:94 macht genau das (via `victory != null`) | **„pausiert"** | **Ja** — der dritte Zustand ist heute schon ohne neues Signal ableitbar. | +| `HudPointerLink.PointerOverHud` | HudPointerLink.cs:24 | Zeiger über HUD (pro Frame publiziert) | **Ja** — statischer Kanal in Nova.Gameplay, von beiden Presentation-Assemblies lesbar, **der etablierte Präzedenzfall** für Signale ohne neue Assembly-Kante (mit `MinimapCameraLink`, MinimapCameraLink.cs:26). | +| `_runner.Entities/Economy/FogOfWar != null` | überall | „Welt existiert" | Ja — aber das ist exakt die gebrochene #102-Annahme: **„Welt existiert" ≠ „Match ist aktive Oberfläche"**. | + +Fazit: „kein Match" vs. „pausiert" ist über `IsMatchReady` + `IsRunning` sauber trennbar; „Oberfläche gehört einem Modal" (Menü, Pause, Resultat) existiert als Signal nur fürs Hauptmenü (`IsMenuVisible`) und ist rank-bedignt nicht überall lesbar. + +## 5. Empfehlung: ein einziger Riegel — und ja, die Wurzel statt des Flags + +**Die gemeinsame UI-Wurzel existiert bereits** und ist der bessere Riegel: der Szenengenerator legt **alle** In-Match-HUD-Komponenten auf **ein** GameObject „UI" (BootstrapSceneGenerator.cs:317: RtsDeviceInput, SelectionMarkerView, RallyFlagView, PlacementGhostView, ConstructionSiteMarkerView, BuildMenuHud, CommandCardHud, FogOfWarOverlayView, MinimapHud, HealthBarHud, MatchFrameHud, DebugHud). Damit: + +1. **Sichtbarkeit: `SetGameplayLayerActive` (MainMenuController.cs:783) schaltet die Wurzel** (`_gameplayHudRoot.SetActive(active)`) statt einer Komponentenliste. Setzer: weiterhin allein `MainMenuController`. Leser: **niemand** — die Deaktivierung *ist* der Riegel; OnGUI, LateUpdate und Hit-Tests der Kinder sterben mit. Das ist die einzige Lösung, die die berechtigte Sorge des Docstrings (:770-773) strukturell beantwortet: ein Katalog verrottet, eine Wurzel nicht — eine neue HUD-Komponente landet ohnehin im Generator auf diesem Objekt und ist ab dann abgedeckt, **ohne je namentlich genannt zu werden**. Der Kamera-Rig bleibt separater Toggle (er liegt auf „Main Camera", :101, nicht auf der Wurzel). Musik braucht nichts (`MusicDirector` ist bereits Klasse (a)), `UnityAudioService` bleibt passiv — Restklänge verklingen, das ist korrekt so. +2. **Eingabe im dritten Zustand (Pausemenü):** Die Wurzel deckt Menü und Matchende-mit-Menü ab; das Pausemenü will die Wurzel aber typischerweise **an** lassen (Welt/HUD darunter sichtbar) und nur die Weltgesten sperren. Dafür **ein** Flag mit der Semantik „eine modale Oberfläche besitzt die Eingabe" — *nicht* „Match läuft nicht". Konkret nach dem HudPointerLink-Vorbild: ein statischer Kanal in Nova.Gameplay (z. B. `ModalSurfaceLink.Open`), der **pro Frame publiziert** wird (kein Latch mit Gedächtnis — ein hängengebliebenes `true` wäre ein Eingabe-Deadlock). Setzer: genau eine Stelle — die Komponente, die modale Panels zeichnet, heute `MatchFrameHud` (Resultat + Pause), morgen das Pausemenü; sie muss außerhalb der UI-Wurzel leben, damit sie im Menü-Zustand überhaupt publizieren kann (im Menü ist die Wurzel ohnehin aus — niemand liest). Leser: `RtsDeviceInput.Update` (ergänzt `IsMenuVisible` an :424 und behandelt Klicks wie HUD-Klicks — damit sterben auch die Klickdurchfälle auf „Hauptmenü"/„Fortsetzen") und **gratis** die Kamera: `RtsDeviceInput` publiziert bei offenem Modal einfach `HudPointerLink.Publish(true)` — der bestehende Kanal deckt den Rand-Pan ohne neuen Leser ab. Pause und „kein Match" bleiben dabei sauber getrennt: das Flag sagt „Modal offen", die Match-Zustände liest weiterhin, wer sie braucht, aus `IsMatchReady`/`IsRunning`. +3. **DebugHud — Absprache nötig (Einheitenstrang):** `DebugHud` liegt auf derselben Wurzel (:370). `DebugHud.cs` selbst muss **nicht** geändert werden — `_visible` und F3-Zustand überleben `SetActive`, das Verhalten ist identisch zum heutigen `_debugHud.enabled`-Toggle. Aber der Kontrollfluss über die fremde Komponente ändert sich (benannter Einzel-Toggle → Wurzel), und der Wurzel-Ansatz nimmt die Option, DebugHud künftig *über* dem Menü zu zeigen (Diagnose am Menü), ohne die Komponente umzuhängen. Das gehört in die Absprache, bevor `SetGameplayLayerActive` umgestellt wird. + +Bewusst **nicht** empfohlen: jeder Komponente einen `IsMenuVisible`-/`IsRunning`-Check zu verpassen. Das ist der Katalog, der mit der nächsten Komponente verrottet — 21.2 hat gerade zwei neue (FieldCard, Scherben-Staging) geliefert. + +STATUS: DONE +- Klasse (c) = #102-Mechanismus: BuildMenuHud, CommandCardHud, MinimapHud prüfen nur „Welt existiert" (`Entities`/`Economy`/`FogOfWar != null`); ReturnToMenu pausiert nur, also zeichnen und **klicken** sie über dem Menü (IMGUI liegt immer über UI Toolkit); Minimap-Klicks stellen zusätzlich einen Kamera-Sprung ein, der beim Matchstart ausgeführt wird (MinimapCameraLink.cs:103). +- Klasse (b)-Kritikalste: RtsDeviceInput — die modalen Panels (Resultat/Pause) stehen nicht in `IsPointerOverHud` (RtsDeviceInput.cs:1102), der Klick auf „Hauptmenü" selektiert hinter dem Panel und armiert die Karte über dem Menü selbst; P-Pause hat gar keinen Riegel, alle Hotkeys und die Harvester-Eskorte feuern in den gestoppten Kernel. +- Empfehlung: die bereits existierende UI-Wurzel (BootstrapSceneGenerator.cs:317) als einziger Sichtbarkeits-Riegel statt Komponentenkatalog, plus **ein** pro Frame publiziertes „Modal offen"-Flag nach HudPointerLink-Vorbild für Eingaben im Pause-Zustand; DebugHud liegt auf derselben Wurzel — Verhalten bleibt gleich, aber der Kontrollfluss über die Einheitenstrang-Komponente ändert sich: Absprache erforderlich. + +[exited with code 0] diff --git a/reports/v8.6.0/sprint-21/02-kimi-version-hud.md b/reports/v8.6.0/sprint-21/02-kimi-version-hud.md new file mode 100644 index 0000000..c1b9e85 --- /dev/null +++ b/reports/v8.6.0/sprint-21/02-kimi-version-hud.md @@ -0,0 +1,42 @@ +--- +Bericht: 02-kimi-version-hud (Sprint 21) +Worker: Kimi K3 (cc-Backend, --mode rw), 2026-08-18 +Auftraggeber: Orchestrator +--- + +> Hinweis: Der Anfang der Worker-Ausgabe wurde beim Einsammeln abgeschnitten +> (`| tail -30` beim Dispatch, Fehler des Orchestrators). Erhalten sind die +> Entscheidungen, das Testergebnis und das Verdikt. + +| `Assets/Tests/PlayMode/MainMenuTests.cs` | **Reparatur nötig:** `MenuDocument()` nutzte `FindAnyObjectByType()` — mit zwei UIDocuments in der Szene wäre das mehrdeutig; findet jetzt per GameObject-Name „MainMenu" | + +### Getroffene Entscheidungen + +1. **Eigenes UIDocument auf eigenem GameObject** statt Einklinken ins Menü: `MainMenuController.BuildTree()` ruft `root.Clear()` auf und würde das Badge löschen; die IMGUI-Schicht fällt unter #102. Das Badge teilt sich nur die PanelSettings mit dem Menü. +2. **`sortOrder = 1` in `Awake`** (Menü bleibt 0) — zwingend, sonst läge das Badge *unter* dem Vollbild-Key-Art und wäre im Menü unsichtbar. +3. **Editor zeigt immer `dev (Editor)`**, auch wenn ein Stempel von einem früheren Packaging-Lauf im Arbeitsbaum liegt (`Application.isEditor`-Zweig): Eine Editor-Session ist nicht dieser Commit. +4. **Dateiinhalt ist `$COMMIT`** aus den Skripten — exakt `git rev-parse --short HEAD`, plus `-dirty` bei schmutzigem Baum, konsistent mit Info.plist- und Archiv-Stempel derselben Skripte. +5. **Fehlende Datei → `dev`, nie ein Log** (try/catch wie das bestehende `BuildInfo.Load`). +6. **Optik** am Menü orientiert: Rajdhani-Regular, Body-Farbe `(0.88, 0.91, 0.95)`, Opazität 0.55, 12 px, 10/8 px Rand; Werte als `[SerializeField]`-Defaults auf der Komponente („the generator wires, it does not tune"). + +### Nicht prüfbar (kein Unity hier) + +- **Kompilieren:** Alle benutzten Symbole gegen Bestandscode gegengeprüft (`Label(string)`-Ctor + Objekt-Initializer wie `MainMenuController.cs:312`, `FontDefinition.FromFont`, `style.display.value`, `FindObjectsByType(FindObjectsInactive)`-Overload wie `MainMenuTests.cs:152`). `UIDocument.sortOrder` (get/set) ist aus der Unity-Doku, nicht gegen Projektnutzung verifiziert, weil bisher niemand es benutzt. +- **Szene nicht regeneriert:** `Bootstrap.unity` enthält das Badge erst nach „Tools/Project Nova/Create Bootstrap Scene". Bis dahin schlägt der neue PlayMode-Test fehl — mit lauter Anleitung, wie es die bestehenden Tests bei veralteter Szene auch tun. +- **Packaging-Läufe** nicht ausgeführt (nur `bash -n`). +- **Rajdhani-Glyphe `·` (U+00B7)** nicht verifiziert — bei Fehlen greift der Theme-Font; Zeichen steht exakt so in der Vorgabe. +- **Visuelle Überlappung** mit Minimap/Build-Bar nicht gesehen (10/8 px Rand, gedämpft). + +### Befund außerhalb des Auftrags + +Es existiert bereits ein paralleler Mechanismus: `BuildCommitStamp` (Nova.Editor, `IPreprocessBuildWithReport`) stempelt `NovaBuildCommit.txt` bei **jedem** Player-Build, `BuildInfo.Commit` (Nova.Gameplay.Match) liest ihn (Lobby, D-094). Der Auftrag verlangte ausdrücklich `BuildInfo.txt` aus den Packaging-Skripten — so gebaut, Bestand unangetastet. Konsequenz, die ihr kennen solltet: Ein Build über die Unity-GUI (ohne Skripte) zeigt `dev`, obwohl `NovaBuildCommit.txt` frisch gestempelt wäre; und ein nach einem Packaging-Lauf im Arbeitsbaum liegender `BuildInfo.txt`-Stempel würde in einem späteren GUI-Build dessen alten Hash zeigen. Alternative wäre gewesen, das Badge auf `BuildInfo.Commit` lesen zu lassen — nicht der Auftrag, daher nur als Hinweis. + +STATUS: DONE +- VersionBadge als eigenes UIDocument/GameObject umgesetzt: immer sichtbar (Menü + Match), unten links, gedämpft, `PickingMode.Ignore`, String einmalig in `Start()` — Format Editor `v0.21.0 · dev (Editor)`, Build `v0.21.0 · ` aus `Resources/BuildInfo.txt`, fehlend → `dev` ohne Log. +- `bundleVersion` auf `0.21.0` gesetzt; beide Packaging-Skripte stempeln `BuildInfo.txt` vor dem Unity-Aufruf (`$COMMIT` inkl. `-dirty`); Datei + Meta sind git-ignoriert. +- `MainMenuTests.MenuDocument()` repariert (zwei UIDocuments machten `FindAnyObjectByType` mehrdeutig); neuer `VersionBadgeTests`-PlayMode-Test pinnt Format, Nicht-Klickbarkeit, Dokument-Unabhängigkeit und Sortierung. +- Nicht verifizierbar ohne Unity: Kompilieren, Szenen-Regeneration (Badge fehlt in `Bootstrap.unity`, bis der Generator läuft — neuer Test schlägt bis dahin bewusst laut fehl), `UIDocument.sortOrder`-Setter zur Laufzeit, Rajdhani-Glyphe `·`, optisches Ergebnis. +- Nebenbefund: Mit `BuildCommitStamp`→`NovaBuildCommit.txt`→`BuildInfo.Commit` (D-094) existiert ein zweiter, build-hook-basierter Stempelmechanismus; GUI-Builds ohne Skripte zeigen im Badge `dev`. +/Users/denniswestermann/.claude/skills/kimi/scripts/kimi-agent.sh: line 270: hen: command not found + +[exited with code 0] diff --git a/reports/v8.6.0/sprint-21/03-kimi-buildzone-overlay.md b/reports/v8.6.0/sprint-21/03-kimi-buildzone-overlay.md new file mode 100644 index 0000000..57826b7 --- /dev/null +++ b/reports/v8.6.0/sprint-21/03-kimi-buildzone-overlay.md @@ -0,0 +1,42 @@ +--- +Bericht: 03-kimi-buildzone-overlay (Sprint 21) +Worker: Kimi K3 (cc-Backend, --mode rw), 2026-08-18 +Auftraggeber: Orchestrator +--- + +> Hinweis: Der Anfang der Worker-Ausgabe wurde beim Einsammeln abgeschnitten +> (`| tail -30` beim Dispatch, Fehler des Orchestrators). Erhalten sind die +> Entscheidungen, das Testergebnis und das Verdikt. + +| `Assets/_Project/Scripts/Simulation/Construction/ConstructionSystem.cs` | **Minimal:** `IsInsideBuildInfluence` und `HasMinimumBuildingSpacing` von `private` auf `public`, plus Docstrings. Keine Verhaltensänderung — die Methodenkörper sind Byte-identisch. | +| `Assets/_Project/Scripts/Presentation/UI/RtsDeviceInput.cs` | O-Taste als Toggle (`BuildZoneOverlayPinned`, reiner View-State), Handler in `Update()` (greift auch während Platzierung/Pick-Modus), Hilfetext-Legende ergänzt. | +| `Assets/_Project/Editor/BootstrapSceneGenerator.cs` | Ein Block (7 Zeilen) in `CreateUiObject`, direkt hinter der `PlacementGhostView`-Verdrahtung — rebase-freundlich. | +| `Assets/Tests/EditMode/Simulation/BuildZoneOverlayQueryTests.cs` (+`.meta`) | **Neu.** Zwei Tests, die den Overlay-Vertrag pinnen. | + +### Getroffene Entscheidungen + +1. **Die Regel wird abgefragt, nicht nachgebaut.** Jeder Texel ist die Antwort von `ConstructionSystem.IsInsideBuildInfluence` bzw. `HasMinimumBuildingSpacing` für einen 3×3-Footprint-Ursprung auf dieser Zelle — exakt die beiden Reads, die `ValidatePlacement` selbst konsumiert. Kein Radius, keine Ankerliste im Overlay. Wenn D-108 die Ankerliste öffnet, folgt das Bild von allein. +2. **Drei Zustände:** ausserhalb der Zone = kein Tint; innerhalb + Abstand gewahrt = Grün (Alpha 26); innerhalb + Mindestabstand verletzt = Orange (Alpha 44). Genau die Unterscheidung „innerhalb und trotzdem gesperrt“, die der Testbericht vermisste. Terrain-Begehbarkeit und Feldabstände werden bewusst **nicht** gemalt (Feldabstand ist rollenabhängig — die Refinery invertiert ihn; der Ghost liefert das Vollverdikt am Cursor). +3. **Sichtbarkeit:** automatisch während `PlacementModeActive`, zusätzlich per **O** umschaltbar (O war frei: B/C/V/T/G/F/Y Gebäude, Q/U/N/E/D Einheiten, S/A/H/R/P Befehle, Z/X/Leertaste Kamera, F3–F5 Debug). Der Toggle sitzt in `Update()`, nicht in `HandleOrders`, damit er auch im Platzierungsmodus lebt. +4. **Performance:** Repaint-Kadenz 4 Hz nur solange sichtbar (Bauzustand ändert sich nur auf Tick-Events), Sofort-Repaint beim Einblenden; `FilterMode.Point`, damit Grün/Orange nicht zu einem bedeutungslosen Drittton verschwimmen. Höhe 0.05: über Fog (0.04), unter den Markern (0.06+). +5. **Kartenrand:** Ursprungszellen, deren Footprint die Karte verlassen würde, bleiben leer (Kartengeometrie über die öffentlichen Konstanten, nicht die Zonenregel). +6. **Teststrategie:** Der EditMode-Test pinnt nur die *Relation* (beide Reads sind notwendig für `Applied`; das Paar unterscheidet „draussen“ von „drin, aber gesperrt“) — nie Radiuswerte oder Ankerrollen, damit er D-108 unverändert übersteht. Verifiziert habe ich ihn über eine **transiente** Kopie in der .NET-Spiegel-Lane (kompiliert, 2/2 grün, danach entfernt — `tools/` ist im Enddiff unberührt). + +### Testergebnis + +**725/725 grün** (`dotnet test tools/Nova.SimRunner.Tests`), davon 0 Fehler, 0 übersprungen. Die „723“ aus dem Auftrag ist veraltet: die Baseline auf HEAD (9281341) zählt bereits **725** — die zwei Kapazitätstests aus 21.1/#92 kamen mit Merge #100 dazu. Gegenprobe auf jungfräulichem HEAD in einem temporären Worktree bestätigt: 725 vorher = 725 nachher, keine Regression. Der mitlaufende `PresentationSourceBoundaryTests`-Scan akzeptiert die neue View-Datei. + +### Nicht prüfbar + +- **Unity-Kompilierung** der Präsentations- und Editor-Dateien sowie der EditMode-Testdatei (kein Unity hier). Abgemildert: sämtliche verwendeten Symbole/Signatures sind aus funktionierenden Geschwisterdateien gespiegelt (`FogOfWarOverlayView`, `PlacementGhostView`, `ConstructionSystemTests`), asmdef-Referenzen reichen aus, und der Test wurde in der identischen .NET-Quell-Lane tatsächlich kompiliert und ausgeführt. +- **Optik** (Farbwerte, Höhe, Lesbarkeit im laufenden Spiel) — Werte sind SerializeField-Defaults und im Inspector nachjustierbar. +- `./.dotnet/dotnet` existiert im Worktree nicht (untracked SDK); verwendet wurde das gepinnte SDK **8.0.318** aus dem Haupt-Checkout — exakt die Version, die `global.json` verlangt. + +STATUS: DONE +- Overlay fragt ausschliesslich die beiden Sim-Reads ab (kein Regel-Nachbau); D-108-Ankeränderung erbt es gratis; ConstructionSystem-Diff ist reine Sichtbarkeit + Docstrings, Körper unverändert. +- Testgate grün: 725/725, Baseline-Vergleich auf sauberem HEAD bestätigt keine Regression („723“ im Auftrag war um die zwei 21.1-Tests veraltet). +- Zwei Zustände unterscheidbar gemalt (Grün = baubar, Orange = in der Zone, aber Mindestabstand verletzt); Sichtbarkeit: Platzierungsmodus + O-Toggle, Legende ergänzt, Szene mit einem 7-Zeilen-Block verdrahtet. +- Nicht verifizierbar blieben Unity-Kompilierung der UI-/EditMode-Dateien und das visuelle Ergebnis — beides oben transparent gemacht. +/Users/denniswestermann/.claude/skills/kimi/scripts/kimi-agent.sh: line 270: hen: command not found + +[exited with code 0] diff --git a/reports/v8.6.0/sprint-21/04-kimi-anchor-rule.md b/reports/v8.6.0/sprint-21/04-kimi-anchor-rule.md new file mode 100644 index 0000000..f8d8468 --- /dev/null +++ b/reports/v8.6.0/sprint-21/04-kimi-anchor-rule.md @@ -0,0 +1,42 @@ +--- +Bericht: 04-kimi-anchor-rule (Sprint 21) +Worker: Kimi K3 (cc-Backend, --mode rw), 2026-08-18 +Auftraggeber: Orchestrator +--- + +> Hinweis: Der Anfang der Worker-Ausgabe wurde beim Einsammeln abgeschnitten +> (`| tail -30` beim Dispatch, Fehler des Orchestrators). Erhalten sind die +> Entscheidungen, das Testergebnis und das Verdikt. + +2. **`Assets/Tests/EditMode/Simulation/ConstructionSystemTests.cs`** — neuer Test `ValidatePlacement_EveryCompletedBuildingExtendsTheZone_ItsSiteDoesNot`. +3. **`tools/Nova.SimRunner.Tests/ConstructionSystemTests.cs`** — derselbe neue Test (gespiegelte .NET-Lane). + +**Neue Regel in einem Satz:** Jedes eigene, lebende, fertiggestellte Gebäude ist ein Bauanker und schiebt die Bauzone um seinen eigenen Radius (Footprint-Distanz ≤ 8) weiter — Baustellen niemals. + +## Testergebnisse + +- **Vor der Änderung (Baseline, via `git stash` hergestellt):** 725/725 grün. +- **Nach der Änderung:** **726/726 grün** — keine roten Tests, die Liste ist leer. Es gab also nichts einzuordnen (keine Baseline-Bewegung, kein echter Fehler). +- **Diskriminierungsnachweis:** Der neue Test wurde einmal gegen die *alte* Produktionsregel laufen gelassen (Produktionsdatei allein gestasht, Tests drin gelassen) und schlägt dort exakt fehl: `Expected: Applied / But was: RejectedInvalidTarget` — er pinnt also genau die Regeländerung, nichts Beliebiges. + +## Einordnung einzelner erwarteter, aber ausgebliebener Rottöne + +- **`CanonicalAiOutcomeTests`** (pinnt Tick 2726 / `0x10B83E94F86F2E55`): **blieb grün.** Die kanonische KI-Partie versucht nie eine Platzierung, deren Validierungsausgang unter der neuen Regel kippt — der gepinnte Ausgang bewegt sich nicht. Die im Auftrag erwartete Bewegung trat nicht ein; ein Baseline-Neusetzungs-PR ist nach dieser Suite nicht nötig. +- **`RulesHash64`: hat sich NICHT bewegt.** Der Hash wird in `MatchFingerprint.ComputeRulesHash64` ausschließlich aus numerischen Regelkonstanten gebaut (u. a. `BuildInfluenceRadiusCells`); die Anker-Rollenliste war nie ein gehashtes Feld. Da `Simulation/Replays/` für mich eingefroren ist, habe ich keine Revision angefasst. Falls die Regelidentität die Ankerregel künftig abbilden soll, braucht es eine `RulesRevisionV4` mit neuem Hash-Feld — das ist eine Entscheidung außerhalb meines Schreibbereichs und ich melde sie hiermit zurück. +- **`BuildZoneCapacityTests`** (pinnt 15 Kraftwerke): grün — der Scan ist auf die HQ-Startzone begrenzt und Power war bereits Ankerrolle; identische Platzierungsmenge. +- **Bestehende Tests angepasst:** keiner. Kein bestehender Test schrieb die alte Dreierliste fest (der Einfluss-Test `..._AtDistanceEight` pinnt nur own/living/completed/Distanz-8 sowie Feind/Tot/Baustelle — alles weiterhin wahr). Die Dreierliste stand nur in den Docstrings. Neu hinzugekommen ist ein Verhaltenstest (Kategorie „echte Verhaltensannahme", kein Hash-Pin). + +## Was ich nicht prüfen konnte + +- Die Unity-EditMode-Lane läuft in dieser Umgebung nicht (kein Unity); die gespiegelte Datei wurde textidentisch synchronisiert — Unity-CI muss sie bestätigen. +- Der `IsActiveSite`-Guard in `IsInsideBuildInfluence` ist strukturell defensiv: `_buildings` enthält im Normalfluss nur fertige Placements (`CompleteSite` deaktiviert die Baustelle vor dem Anlegen des Placements), die Prüfung kann derzeit nicht feuern — sie dokumentiert die Regel explizit, wie beauftragt. +- Ob der Auftraggeber `RulesHash64` trotz unveränderter Konstanten bewegt haben will (s. o.) — das erfordert einen Eingriff in eingefrorenen Bereich. + +STATUS: DONE +- Regel umgesetzt: jedes eigene, lebende, fertiggestellte Gebäude ist Bauanker; Baustellen explizit ausgeschlossen (`IsActiveSite`); Docstrings auf den geltenden Stand. +- 726/726 Tests grün (Baseline vorher 725/725); null rote Tests — die erwartete Baseline-/Pin-Bewegung trat nicht ein (KI-Ausgang und RulesHash64 unverändert, da konstantenbasiert gehasht). +- Neuer Test `ValidatePlacement_EveryCompletedBuildingExtendsTheZone_ItsSiteDoesNot` in beiden Lanes; gegen die alte Regel nachweislich rot (Applied vs. RejectedInvalidTarget). +- Keine Baseline-, Pin-, CHANGELOG-, docs- oder Frozen-Area-Datei angefasst; Rückfrage an den Orchestrator: RulesHash64-Revision (V4) für die Ankerregel gewünscht? +/Users/denniswestermann/.claude/skills/kimi/scripts/kimi-agent.sh: line 270: hen: command not found + +[exited with code 0] diff --git a/reports/v8.6.0/sprint-21/05-kimi-pause-and-gate.md b/reports/v8.6.0/sprint-21/05-kimi-pause-and-gate.md new file mode 100644 index 0000000..bb0b95a --- /dev/null +++ b/reports/v8.6.0/sprint-21/05-kimi-pause-and-gate.md @@ -0,0 +1,58 @@ +--- +Bericht: 05-kimi-pause-and-gate (Sprint 21) +Worker: Kimi K3 (cc-Backend, --mode rw), 2026-08-18 +Auftraggeber: Orchestrator +--- + +> Hinweis: Der Anfang der Worker-Ausgabe wurde beim Einsammeln abgeschnitten +> (`| tail -30` beim Dispatch, Fehler des Orchestrators). Erhalten sind die +> Entscheidungen, das Testergebnis und das Verdikt. + +# Bericht — Paket 21.8: Pausemenü (#105) und der Eingabe-Riegel (#102) + +## Dateiliste + +| Datei | Änderung | +|---|---| +| `Assets/_Project/Scripts/Gameplay/UI/ModalSurfaceLink.cs` (+ `.meta`) | **Neu.** Statischer Kanal nach `HudPointerLink`-Vorbild: `Open` / `Publish(bool)` / `Reset()`. Semantik: „eine modale Oberfläche besitzt die Eingabe" — ausdrücklich nicht „Match läuft nicht". Pro Frame publiziert, kein Latch. | +| `Assets/_Project/Scripts/Presentation/UI/PauseMenuHud.cs` (+ `.meta`) | **Neu.** Das Pausemenü: ESC/P öffnet und schließt, hält lokale Matches an (`MatchRunner.PauseMatch`, Relay wird gar nicht erst aufgerufen) und setzt nur fort, was es selbst angehalten hat. Einträge: **Fortsetzen**, **Zum Hauptmenü** (`MainMenuController.ReturnToMenu`), **Spiel beenden** (`MainMenuController.Quit`, jetzt `public`). **Einziger Setzer** von `ModalSurfaceLink` (aggregiert eigenen Zustand + `MatchFrameHud.ModalOpen`); `OnDisable` resettet den Kanal — die Wurzel-Abschaltung auf dem Weg ins Menü kann so nie ein hängendes `true` hinterlassen. Liegt auf der UI-Wurzel (Generator), die Welt bleibt darunter sichtbar. | +| `RtsDeviceInput.cs` | Modal-Gate in `Update` direkt hinter dem `IsMenuVisible`-Ausstieg; Gesture-Cleanup beim Ingress-Rebind; P-Block aus `HandleOrders` entfernt (Pause gehört jetzt PauseMenuHud); `LastGestureCancelFrame` für die ESC-Schichtung (Geste zuerst, Menü beim nächsten Druck — RtsDeviceInput läuft auf -200 zuerst); Legende aktualisiert. | +| `MatchFrameHud.cs` | `DrawPausePanel` entfernt (Pause-Oberfläche ist jetzt das Pausemenü; alle gestoppten-Kernel-Zustände haben weiterhin eine Oberfläche: Resultat, Netzwerk, Pausemenü). Neu: `ModalOpen`-Property, einmal pro Frame in `Update` via `ComputeModalState()` abgeleitet — exakt die bisherige OnGUI-Bedingung, Resultat + Netzwerk-Panels. OnGUI zeichnet aus dem einmal abgeleiteten Zustand. | +| `MainMenuController.cs` | `_debugHud`-Feld durch `_gameplayHudRoot` (GameObject) ersetzt; `SetGameplayLayerActive` schaltet jetzt **die Wurzel** plus separat das Kamera-Rig (liegt auf „Main Camera"). Awake-Fallback findet die Wurzel über `RtsDeviceInput` für Szenen aus before-root-Generationen. `Quit()` public. `ReturnToMenu` ruft `PauseMatch` nicht mehr bei Relay-Matches auf (der Weg „laufende Netzpartie verlassen" ist jetzt erreichbar; die Verweigerung würde sonst absichtlich einen Error loggen — Match tickt hinter dem Menü weiter bis zum nächsten Start, wie ein lokal pausiertes). | +| `BootstrapSceneGenerator.cs` | `CreateUiObject` legt `PauseMenuHud` auf die Wurzel und verdrahtet `_runner`/`_matchFrame`/`_input`; `CreateMainMenuObject` verdrahtet `_gameplayHudRoot` statt `_debugHud` und `pauseMenu._menu`. | +| `Assets/Tests/PlayMode/MainMenuTests.cs` | DebugHud-Assertions von `.enabled` auf `gameObject.activeInHierarchy` umgestellt (der Schalter ist jetzt die Wurzel, nicht die Komponente — alter Assert würde sonst fehlschlagen); veraltete „no pause menu"-Meldung korrigiert. | + +## Die drei Fehler aus Punkt 3 + +1. **Ausstiegsklick armiert #102 selbst:** Behoben durch das Gate, nicht durch eine Rect-Liste: `ModalSurfaceLink.Open` → `RtsDeviceInput.Update` steigt aus, bevor `HandleSelection`/`SelectSingle` laufen. Klicks auf „Hauptmenü"/„Fortsetzen" (Resultat-, Netzwerk-, Pause-Panel) selektieren nichts mehr hinter dem Panel; der Drag-State wird fallen gelassen. Der Kamera-Randscroll fällt gratis mit ab, weil das Gate `HudPointerLink.Publish(true)` publiziert. +2. **`_placementMode` überlebt den Menüwechsel:** Doppelt geräumt — (a) der Ingress-Rebind (`EnsureDispatcher`) löscht jetzt `_placementMode`, `_pendingOrder` und `_dragActive` zusammen mit der Auswahl, damit wandert kein Ghost in die nächste Runde; (b) das Gate disarmt armierte Gesten in dem Frame, in dem ein Modal aufgeht (mit Statusmeldung), damit kein Ghost armiert unter einem Panel schwebt. +3. **P-Taste ohne Riegel:** P/ESC sind aus `RtsDeviceInput` heraus in `PauseMenuHud` umgezogen. Pausiert heißt jetzt: Pausemenü offen → Kanal `true` → Selektion, sämtliche Hotkeys (S/A/H/R/Q/B/U…) und die Harvester-Eskorte feuern nicht mehr in den gestoppten Kernel. + +Dazu der strukturelle #102-Fix: `SetGameplayLayerActive(false)` schaltet die ganze Wurzel — BuildMenuHud, CommandCardHud und MinimapHud (Audit-Klasse (c)) können über dem Menü weder zeichnen noch klicken, ohne je namentlich katalogisiert worden zu sein. + +## „Einstellungen" — bewusst kein zweiter Dialog + +Die Einstellungsmaske des Hauptmenüs ist fest in dessen `_screen`-Baum gebaut (`ShowSettings` toggelt gegen das Haupt-Panel mit „Neues Spiel"; „Zurück" führt dorthin). Sie mitten im Match zu zeigen hieße: ganzer Menü-Screen inkl. Key Art über dem Spiel, `IsMenuVisible`-Semantik gebrochen — die Verrenkung, vor der der Auftrag warnt. Stattdessen: drei funktionierende Einträge plus ehrliche Hinweiszeile im Panel („Einstellungen (Ton, Bild, Auflösung) gibt es im Hauptmenü."). Ein Eintrag weniger, keine driftende Zweitmaske. + +## Testergebnis + +`dotnet test tools/Nova.SimRunner.Tests` (dotnet aus dem Haupt-Checkout; der Worktree hat kein `.dotnet`): **725/725 grün, 0 Fehler.** Der Auftrag nannte 723 — der Branch-Stand (9281341) trägt bereits zwei Tests mehr; meine Änderungen kompilieren ohnehin nicht in dieses Projekt hinein (es referenziert nur Core/Simulation/AI/Networking), die Oberfläche hat nichts bewegt. + +## Nicht prüfbar gewesen (ausdrücklich) + +- **Kein Unity-Lauf:** Keine Unity-Assembly kompiliert. Symbolnamen/Signaturen sind doppelt gegen die Bestandsdateien geprüft (`PauseMatch()`/`StartMatch()` liefern `bool` — wird genutzt bzw. ignoriert wie im Bestand; `HudChrome.OpaquePanelStyle` ist assembly-intern erreichbar), aber ein Tippfehler fiele hier nicht auf. +- **`Bootstrap.unity` ist NICHT regeneriert** (Maschinenausgabe, Unity nötig). Bis jemand `Tools/Project Nova/Create Bootstrap Scene` laufen lässt, enthält die committete Szene kein `PauseMenuHud` → kein Pausemenü und kein publizierter Riegel (Resultat-Panels zeichnen weiterhin, das Klickdurchfall-Problem bleibt in der alten Szene bestehen). Der Wurzel-Schalter heilt die alte Szene dagegen selbst (Awake-Fallback über `RtsDeviceInput`), sodass die angepassten `MainMenuTests` auch gegen die alte Szene korrekt sind. **Es gibt bewusst keinen neuen PlayMode-Test fürs Pausemenü** — er hinge an der regenerierten Szene und liefe hier auf ein falsches Rot. +- **PlayMode-Tests nicht ausgeführt** (brauchen Unity headless-with-graphics) — die Anpassung in `MainMenuTests.cs` ist nur am Mechanismus gespiegelt, nicht gelaufen. +- **Editor-Assembly** (`BootstrapSceneGenerator.cs`) ebenfalls unkompiliert geprüft. +- Reihenfolge-Annahme verifiziert am Bestand: `RtsDeviceInput` trägt `DefaultExecutionOrder(-200)`, läuft also vor `PauseMenuHud.Update` — die ESC-Schichtung baut darauf. Kanal-Latenz ist konstruktionsbedingt 1 Frame (wie HudPointerLink). +- `DebugHud.cs` unangetastet. Nachlese-Befund: nur `Awake`/`Update`/`OnGUI`, kein `Start`/`OnEnable`/`OnDisable` mit Seiteneffekten; `_visible` (F3) überlebt `SetActive` — der Wurzel-Schalter ist exakt verhaltensgleich zum alten `enabled`-Toggle. Nichts an den anderen Beitragenden zu melden. +- Kamera während des Pausemenüs: Pfeiltasten/Zoom/Rotation bleiben absichtlich bedienbar (Kernel steht, Karteninspektion im Pausezustand ist üblich); nur der Randscroll-Unfall ist gegaten. Falls unerwünscht, wäre das ein Folgepunkt. + +STATUS: DONE +- Eingabe-Riegel steht: `ModalSurfaceLink` (pro Frame, ein Setzer, OnDisable-Reset) gatet `RtsDeviceInput.Update` komplett — Selektion, Hotkeys und Harvester-Eskorte feuern nicht mehr in den gestoppten Kernel, und Klicks auf modale Panels selektieren nichts mehr dahinter (#102-Selbstarmierung tot). +- `_placementMode`/`_pendingOrder` überleben weder Modal-Öffnung noch Ingress-Rebind — der Bau-Ghost wandert nicht mehr ins Menü oder in die nächste Runde. +- Pausemenü (ESC/P) mit Fortsetzen / Zum Hauptmenü / Spiel beenden steht; „Einstellungen" ehrlich als Hinweis statt driftender Zweitmaske; `SetGameplayLayerActive` schaltet jetzt die UI-Wurzel (plus Kamera-Rig separat) — Cockpit liegt nicht mehr über dem Hauptmenü. +- Nicht prüfbar ohne Unity: Kompilierung der Unity-/Editor-Assemblies, PlayMode-Tests, und `Bootstrap.unity` muss regeneriert werden, sonst existieren Pausemenü und Riegel in der Szene schlicht nicht (Wurzel-Schalter heilt die alte Szene per Awake-Fallback). SimRunner: 725/725 grün. +/Users/denniswestermann/.claude/skills/kimi/scripts/kimi-agent.sh: line 270: hen: command not found + +[exited with code 127]