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]