diff --git a/CLAUDE.md b/CLAUDE.md index 15a3241..a30caa2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -166,11 +166,21 @@ the legacy desktop player flags — and builds whatever scenes `EditorBuildSetti `--profile ` selects a profile from `Assets/Settings/Build Profiles` (the profile defines the target). `-l/--log-file` overrides the log path; the log streams to console unless `--no-tail`. -**No build script exists yet.** When one is added, put it at `Assets/Editor/BuildPipeline.cs` and -call `--execute-method NightclubArcadia.Editor.BuildPipeline.BuildMacOS`. The CLI forwards the -output path as `-buildOutput` and **your method is responsible for honouring it**. A build method -must call `EditorApplication.Exit(1)` on failure, or Unity exits 0 and CI reports a green build -that produced nothing. +`make build` uses the entry point below, which is the form to prefer: + +```bash +"$UNITY_CLI" build "$UNITY_PROJECT" --no-banner --target StandaloneOSX \ + --execute-method NightclubArcadia.EditorTools.PlayerBuild.BuildMacOS \ + -o "$PWD/build/NightclubArcadia.app" +``` + +`Assets/Editor/PlayerBuild.cs` reads the enabled scenes from the build settings, asserts +`Bootstrap.unity` is scene 0 (the player opens scene 0 on launch), honours the `-buildOutput` +the CLI forwards from `-o`, inspects the `BuildReport`, and calls `EditorApplication.Exit(1)` on +anything short of `Succeeded`. Without that last part Unity exits 0 on a build that produced +nothing and CI reports green. `BuildWindows` and `BuildLinux` are there too. + +Verified: a full macOS build takes about two minutes and produces a ~172MB `.app`. ### 3.4 Run an arbitrary Editor method @@ -377,11 +387,6 @@ It is a tripwire, not a proof. `Assets/Tests/PlayMode/BootstrapTests.cs`. Verifies the scene split at runtime — the thing no EditMode test can see, because none of the runtime resolution has happened yet. -Two of its checks reach their types by name through reflection. `NPCStandIn` and -`NightclubArcadia.Cinematics` live in `Assembly-CSharp`, and an asmdef test assembly cannot -reference the predefined assemblies — only the reverse. Giving the runtime code its own asmdefs -removes the need; until then the reflection is confined to those two tests. - ### 5.3 Voice-sheet sync ```bash @@ -432,9 +437,20 @@ should not be — see `docs/restructure-plan.md`. `UI.HUD`, `UI.Menu`, `Cinematics`, `Interaction`, `Player`, `Player.Math`. **Exception:** `NPCStandIn.cs` sits in the global namespace. That is a scaffold tell, not a convention to copy — new runtime types always get a namespace. -- Assembly definitions exist for `NightclubArcadia.Skills` and `NightclubArcadia.Locomotion.Math` - only. Camera, Player, UI, Dialogue, NPCs and Interaction all currently compile into the - default `Assembly-CSharp`. Widening asmdef coverage is planned, not done. +- **Assemblies.** No project code is in `Assembly-CSharp` any more: + + | Assembly | Covers | Depends on | + |---|---|---| + | `NightclubArcadia.Core` | `Scripts/Core` | nothing | + | `NightclubArcadia.Locomotion.Math` | `Scripts/Player/Math` | nothing | + | `NightclubArcadia.Skills` | `Scripts/Skills` | Yarn, TMP, uGUI | + | `NightclubArcadia.Game` | the rest of `Scripts` | Core, Skills, Math, StarterAssets, packages | + | `StarterAssets` | vendored template code | Input System | + + `Game` is one assembly rather than one per area because the areas are genuinely cyclic: + UI ↔ Dialogue, UI ↔ Player and UI ↔ Camera, via `UILayerBootstrap`, `PlayerControlLock` and + `CharacterPanelView`. Splitting further means moving those three, not just adding asmdefs. + The `.asmdef` at `Scripts/` covers everything beneath it except folders with their own. - Comments in this codebase carry *decision history* — why a flag is written at the bottom of a node rather than the top, why four voices in SC-101 breaks the three-voice cap on purpose. Match that density. Do not strip such comments as "noise"; they are the design record. @@ -465,9 +481,10 @@ should not be — see `docs/restructure-plan.md`. mesh. `ResetPath`, `isStopped` and `SetDestination` all log errors in that state. Guard on `agent.isOnNavMesh`, not just a null check. -4. **An asmdef assembly cannot reference `Assembly-CSharp`.** Only the reverse. Most runtime code - here has no asmdef, so a test assembly cannot see `NPCStandIn` or `NightclubArcadia.Cinematics` - directly — hence the reflection in `BootstrapTests`. Adding per-area asmdefs removes this. +4. **An asmdef assembly cannot reference `Assembly-CSharp`.** Only the reverse. This is why + giving the runtime code assemblies had to include `StarterAssets`, which had none and which + four runtime files use — without that, everything referencing it would have stopped compiling. + Keep it in mind before adding loose scripts outside an asmdef folder. 5. **`unity run` reserves `-batchmode`, `-nographics`, `-quit`, `-logFile`.** Passing any of them after `--` is a hard error, and its output does not reach stdout — read `Logs/Editor.log` (§3.4). diff --git a/Makefile b/Makefile index 71183ff..365b0f5 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,9 @@ test-play: lock ## Run the PlayMode suite (bootstrap + scene split) @python3 tools/ci/report_tests.py $(RESULTS)/playmode.xml build: lock ## Build a macOS player into build/ - @$(UNITY_CLI) build $(PROJECT) --no-banner --target StandaloneOSX -o $(CURDIR)/build/NightclubArcadia.app + @$(UNITY_CLI) build $(PROJECT) --no-banner --target StandaloneOSX \ + --execute-method NightclubArcadia.EditorTools.PlayerBuild.BuildMacOS \ + -o $(CURDIR)/build/NightclubArcadia.app lock: ## Fail if the Unity Editor has the project open @if pgrep -fl "Unity.app/Contents/MacOS/Unity" | grep -v AssetImportWorker | grep -q .; then \ diff --git a/NightclubArcadia/Assets/Dialogue/Characters/Bradford Kane.yarn b/NightclubArcadia/Assets/Dialogue/Characters/BradfordKane.yarn similarity index 100% rename from NightclubArcadia/Assets/Dialogue/Characters/Bradford Kane.yarn rename to NightclubArcadia/Assets/Dialogue/Characters/BradfordKane.yarn diff --git a/NightclubArcadia/Assets/Dialogue/Characters/Bradford Kane.yarn.meta b/NightclubArcadia/Assets/Dialogue/Characters/BradfordKane.yarn.meta similarity index 100% rename from NightclubArcadia/Assets/Dialogue/Characters/Bradford Kane.yarn.meta rename to NightclubArcadia/Assets/Dialogue/Characters/BradfordKane.yarn.meta diff --git a/NightclubArcadia/Assets/Dialogue/Characters/Chevalier Cassian Thal.yarn b/NightclubArcadia/Assets/Dialogue/Characters/ChevalierCassianThal.yarn similarity index 100% rename from NightclubArcadia/Assets/Dialogue/Characters/Chevalier Cassian Thal.yarn rename to NightclubArcadia/Assets/Dialogue/Characters/ChevalierCassianThal.yarn diff --git a/NightclubArcadia/Assets/Dialogue/Characters/Chevalier Cassian Thal.yarn.meta b/NightclubArcadia/Assets/Dialogue/Characters/ChevalierCassianThal.yarn.meta similarity index 100% rename from NightclubArcadia/Assets/Dialogue/Characters/Chevalier Cassian Thal.yarn.meta rename to NightclubArcadia/Assets/Dialogue/Characters/ChevalierCassianThal.yarn.meta diff --git a/NightclubArcadia/Assets/Dialogue/NightclubArcadia.yarnproject b/NightclubArcadia/Assets/Dialogue/NightclubArcadia.yarnproject index e6bbc95..d13a546 100644 --- a/NightclubArcadia/Assets/Dialogue/NightclubArcadia.yarnproject +++ b/NightclubArcadia/Assets/Dialogue/NightclubArcadia.yarnproject @@ -23,11 +23,11 @@ "x": 282, "y": 12 }, - "Bradford Kane.yarn": { + "BradfordKane.yarn": { "x": 1320, "y": 2287 }, - "Chevalier Cassian Thal.yarn": { + "ChevalierCassianThal.yarn": { "x": 1621, "y": 2260 }, diff --git a/NightclubArcadia/Assets/Editor/PlayerBuild.cs b/NightclubArcadia/Assets/Editor/PlayerBuild.cs new file mode 100644 index 0000000..5b1aeec --- /dev/null +++ b/NightclubArcadia/Assets/Editor/PlayerBuild.cs @@ -0,0 +1,123 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEditor.Build.Reporting; +using UnityEngine; + +namespace NightclubArcadia.EditorTools +{ + /// + /// Headless player builds. + /// + /// Not called "BuildPipeline" on purpose — that name collides with + /// UnityEditor.BuildPipeline, which this has to call. + /// + /// The reason to have this at all rather than let the CLI drive Unity's built-in + /// build: a flag-only build reports success even when it produced nothing useful, + /// because Unity exits 0 unless the process itself failed. CI then goes green on a + /// build that does not exist. Every entry point here inspects the BuildReport and + /// calls EditorApplication.Exit(1) on anything short of success. + /// + /// unity build NightclubArcadia --target StandaloneOSX \ + /// --execute-method NightclubArcadia.EditorTools.PlayerBuild.BuildMacOS \ + /// -o build/NightclubArcadia.app + /// + /// The CLI forwards -o as -buildOutput, and honouring it is this method's job. + /// + public static class PlayerBuild + { + public static void BuildMacOS() => Build(BuildTarget.StandaloneOSX, "build/NightclubArcadia.app"); + + public static void BuildWindows() => Build(BuildTarget.StandaloneWindows64, "build/NightclubArcadia.exe"); + + public static void BuildLinux() => Build(BuildTarget.StandaloneLinux64, "build/NightclubArcadia"); + + static void Build(BuildTarget target, string defaultOutput) + { + var output = ArgValue("-buildOutput") ?? defaultOutput; + + var scenes = EditorBuildSettings.scenes + .Where(s => s.enabled) + .Select(s => s.path) + .ToArray(); + + if (scenes.Length == 0) + { + Fail("No enabled scenes in the build settings — the player would have nothing to load."); + return; + } + + // Bootstrap loads the others by name at runtime, so it has to be index 0. + if (!scenes[0].EndsWith("/Bootstrap.unity", StringComparison.Ordinal)) + { + Fail($"Scene 0 is '{scenes[0]}', expected Assets/Scenes/Bootstrap.unity. " + + "The player opens scene 0 on launch."); + return; + } + + Debug.Log($"[PlayerBuild] {target} -> {output}"); + foreach (var scene in scenes) + { + Debug.Log($"[PlayerBuild] scene: {scene}"); + } + + var options = new BuildPlayerOptions + { + scenes = scenes, + locationPathName = output, + target = target, + options = BuildOptions.None, + }; + + BuildReport report; + try + { + report = UnityEditor.BuildPipeline.BuildPlayer(options); + } + catch (Exception e) + { + Fail($"BuildPlayer threw: {e}"); + return; + } + + var summary = report.summary; + Debug.Log($"[PlayerBuild] {summary.result} — {summary.totalSize} bytes in {summary.totalTime}, " + + $"{summary.totalErrors} error(s), {summary.totalWarnings} warning(s)"); + + if (summary.result != BuildResult.Succeeded) + { + foreach (var step in report.steps) + { + foreach (var msg in step.messages.Where(m => m.type == LogType.Error || m.type == LogType.Exception)) + { + Debug.LogError($"[PlayerBuild] {step.name}: {msg.content}"); + } + } + Fail($"Build did not succeed: {summary.result}"); + return; + } + + Debug.Log("[PlayerBuild] ok"); + } + + static void Fail(string message) + { + Debug.LogError($"[PlayerBuild] {message}"); + EditorApplication.Exit(1); + } + + /// Reads "-name value" out of the command line Unity was launched with. + static string ArgValue(string name) + { + var args = Environment.GetCommandLineArgs(); + for (var i = 0; i < args.Length - 1; i++) + { + if (string.Equals(args[i], name, StringComparison.Ordinal)) + { + return args[i + 1]; + } + } + return null; + } + } +} diff --git a/NightclubArcadia/Assets/Editor/PlayerBuild.cs.meta b/NightclubArcadia/Assets/Editor/PlayerBuild.cs.meta new file mode 100644 index 0000000..75f0d48 --- /dev/null +++ b/NightclubArcadia/Assets/Editor/PlayerBuild.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7ec643a873224cc89e1f94dfb735037d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/NightclubArcadia/Assets/Scripts/Core/NightclubArcadia.Core.asmdef b/NightclubArcadia/Assets/Scripts/Core/NightclubArcadia.Core.asmdef new file mode 100644 index 0000000..bcdc210 --- /dev/null +++ b/NightclubArcadia/Assets/Scripts/Core/NightclubArcadia.Core.asmdef @@ -0,0 +1,14 @@ +{ + "name": "NightclubArcadia.Core", + "rootNamespace": "NightclubArcadia.Core", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/NightclubArcadia/Assets/Scripts/Core/NightclubArcadia.Core.asmdef.meta b/NightclubArcadia/Assets/Scripts/Core/NightclubArcadia.Core.asmdef.meta new file mode 100644 index 0000000..9878888 --- /dev/null +++ b/NightclubArcadia/Assets/Scripts/Core/NightclubArcadia.Core.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6a610a410f5944dca5c7961fee29bdff +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/NightclubArcadia/Assets/Scripts/Dialogue/DialougueSkillComparison.cs b/NightclubArcadia/Assets/Scripts/Dialogue/SkillFunctions.cs similarity index 100% rename from NightclubArcadia/Assets/Scripts/Dialogue/DialougueSkillComparison.cs rename to NightclubArcadia/Assets/Scripts/Dialogue/SkillFunctions.cs diff --git a/NightclubArcadia/Assets/Scripts/Dialogue/DialougueSkillComparison.cs.meta b/NightclubArcadia/Assets/Scripts/Dialogue/SkillFunctions.cs.meta similarity index 100% rename from NightclubArcadia/Assets/Scripts/Dialogue/DialougueSkillComparison.cs.meta rename to NightclubArcadia/Assets/Scripts/Dialogue/SkillFunctions.cs.meta diff --git a/NightclubArcadia/Assets/Scripts/NightclubArcadia.Game.asmdef b/NightclubArcadia/Assets/Scripts/NightclubArcadia.Game.asmdef new file mode 100644 index 0000000..53a4ae4 --- /dev/null +++ b/NightclubArcadia/Assets/Scripts/NightclubArcadia.Game.asmdef @@ -0,0 +1,25 @@ +{ + "name": "NightclubArcadia.Game", + "rootNamespace": "NightclubArcadia", + "references": [ + "NightclubArcadia.Core", + "NightclubArcadia.Skills", + "NightclubArcadia.Locomotion.Math", + "StarterAssets", + "YarnSpinner.Unity", + "Unity.Cinemachine", + "Unity.InputSystem", + "Unity.AI.Navigation", + "Unity.TextMeshPro", + "UnityEngine.UI" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/NightclubArcadia/Assets/Scripts/NightclubArcadia.Game.asmdef.meta b/NightclubArcadia/Assets/Scripts/NightclubArcadia.Game.asmdef.meta new file mode 100644 index 0000000..ade6dfe --- /dev/null +++ b/NightclubArcadia/Assets/Scripts/NightclubArcadia.Game.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5d49f74ccc8b4e41aeb2d51faf439392 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/NightclubArcadia/Assets/Settings/DefaultVolumeProfile.asset b/NightclubArcadia/Assets/Settings/DefaultVolumeProfile.asset index 6fb1822..66d98de 100644 --- a/NightclubArcadia/Assets/Settings/DefaultVolumeProfile.asset +++ b/NightclubArcadia/Assets/Settings/DefaultVolumeProfile.asset @@ -342,6 +342,9 @@ MonoBehaviour: skyOcclusionIntensityMultiplier: m_OverrideState: 1 m_Value: 1 + worldOffset: + m_OverrideState: 1 + m_Value: {x: 0, y: 0, z: 0} --- !u!114 &-1216621516061285780 MonoBehaviour: m_ObjectHideFlags: 3 @@ -376,6 +379,9 @@ MonoBehaviour: highQualityFiltering: m_OverrideState: 1 m_Value: 0 + filter: + m_OverrideState: 1 + m_Value: 0 downscale: m_OverrideState: 1 m_Value: 0 @@ -462,8 +468,6 @@ MonoBehaviour: - {fileID: -6288072647309666549} - {fileID: 7518938298396184218} - {fileID: -1410297666881709256} - - {fileID: -7750755424749557576} - - {fileID: -5139089513906902183} --- !u!114 &853819529557874667 MonoBehaviour: m_ObjectHideFlags: 3 diff --git a/NightclubArcadia/Assets/Settings/PC_RPAsset.asset b/NightclubArcadia/Assets/Settings/PC_RPAsset.asset index 9fb5b0d..5707d0a 100644 --- a/NightclubArcadia/Assets/Settings/PC_RPAsset.asset +++ b/NightclubArcadia/Assets/Settings/PC_RPAsset.asset @@ -100,16 +100,16 @@ MonoBehaviour: m_Keys: [] m_Values: m_PrefilteringModeMainLightShadows: 3 - m_PrefilteringModeAdditionalLight: 4 - m_PrefilteringModeAdditionalLightShadows: 0 + m_PrefilteringModeAdditionalLight: 0 + m_PrefilteringModeAdditionalLightShadows: 2 m_PrefilterXRKeywords: 1 - m_PrefilteringModeForwardPlus: 1 + m_PrefilteringModeForwardPlus: 2 m_PrefilteringModeDeferredRendering: 0 - m_PrefilteringModeScreenSpaceOcclusion: 1 + m_PrefilteringModeScreenSpaceOcclusion: 2 m_PrefilterDebugKeywords: 1 - m_PrefilterWriteRenderingLayers: 0 + m_PrefilterWriteRenderingLayers: 1 m_PrefilterHDROutput: 1 - m_PrefilterAlphaOutput: 0 + m_PrefilterAlphaOutput: 1 m_PrefilterSSAODepthNormals: 0 m_PrefilterSSAOSourceDepthLow: 1 m_PrefilterSSAOSourceDepthMedium: 1 @@ -121,21 +121,21 @@ MonoBehaviour: m_PrefilterSSAOSampleCountHigh: 1 m_PrefilterDBufferMRT1: 1 m_PrefilterDBufferMRT2: 1 - m_PrefilterDBufferMRT3: 0 - m_PrefilterSoftShadowsQualityLow: 0 - m_PrefilterSoftShadowsQualityMedium: 0 - m_PrefilterSoftShadowsQualityHigh: 0 + m_PrefilterDBufferMRT3: 1 + m_PrefilterSoftShadowsQualityLow: 1 + m_PrefilterSoftShadowsQualityMedium: 1 + m_PrefilterSoftShadowsQualityHigh: 1 m_PrefilterSoftShadows: 0 m_PrefilterScreenCoord: 1 - m_PrefilterScreenSpaceIrradiance: 0 + m_PrefilterScreenSpaceIrradiance: 1 m_PrefilterNativeRenderPass: 1 m_PrefilterUseLegacyLightmaps: 0 - m_PrefilterBicubicLightmapSampling: 0 - m_PrefilterReflectionProbeRotation: 0 + m_PrefilterBicubicLightmapSampling: 1 + m_PrefilterReflectionProbeRotation: 1 m_PrefilterReflectionProbeBlending: 0 m_PrefilterReflectionProbeBoxProjection: 0 m_PrefilterReflectionProbeAtlas: 0 - m_PrefilterPointSamplingUpsampling: 0 + m_PrefilterPointSamplingUpsampling: 1 m_ShaderVariantLogLevel: 0 m_ShadowCascades: 0 m_Textures: diff --git a/NightclubArcadia/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset b/NightclubArcadia/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset index 1330b9f..822f51c 100644 --- a/NightclubArcadia/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset +++ b/NightclubArcadia/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset @@ -68,7 +68,22 @@ MonoBehaviour: - rid: 6097656519685046288 - rid: 6097656519685046289 m_RuntimeSettings: - m_List: [] + m_List: + - rid: 6852985685364965378 + - rid: 6852985685364965379 + - rid: 6852985685364965380 + - rid: 6852985685364965381 + - rid: 6852985685364965384 + - rid: 6852985685364965392 + - rid: 6852985685364965394 + - rid: 8712630790384254976 + - rid: 6097656519685046277 + - rid: 6097656519685046279 + - rid: 6097656519685046281 + - rid: 6097656519685046282 + - rid: 6097656519685046283 + - rid: 6097656519685046286 + - rid: 6097656519685046288 m_AssetVersion: 10 m_ObsoleteDefaultVolumeProfile: {fileID: 0} m_RenderingLayerNames: diff --git a/NightclubArcadia/Assets/StarterAssets/StarterAssets.asmdef b/NightclubArcadia/Assets/StarterAssets/StarterAssets.asmdef new file mode 100644 index 0000000..0f67fa1 --- /dev/null +++ b/NightclubArcadia/Assets/StarterAssets/StarterAssets.asmdef @@ -0,0 +1,16 @@ +{ + "name": "StarterAssets", + "rootNamespace": "StarterAssets", + "references": [ + "Unity.InputSystem" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/NightclubArcadia/Assets/StarterAssets/StarterAssets.asmdef.meta b/NightclubArcadia/Assets/StarterAssets/StarterAssets.asmdef.meta new file mode 100644 index 0000000..51c6762 --- /dev/null +++ b/NightclubArcadia/Assets/StarterAssets/StarterAssets.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: cf9c9abf848e4aceb606fcd539acb801 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/NightclubArcadia/Assets/Tests/PlayMode/BootstrapTests.cs b/NightclubArcadia/Assets/Tests/PlayMode/BootstrapTests.cs index 78c33ac..653f788 100644 --- a/NightclubArcadia/Assets/Tests/PlayMode/BootstrapTests.cs +++ b/NightclubArcadia/Assets/Tests/PlayMode/BootstrapTests.cs @@ -1,7 +1,6 @@ using System.Collections; -using System.Linq; -using System.Reflection; using NUnit.Framework; +using NightclubArcadia.Cinematics; using Unity.Cinemachine; using UnityEngine; using UnityEngine.SceneManagement; @@ -20,13 +19,6 @@ namespace NightclubArcadia.PlayMode.Tests /// none of it has run. This can. /// /// If this suite fails, the game does not start. Treat it that way. - /// - /// Two checks reach their types by name through reflection rather than referencing - /// them. NPCStandIn and NightclubArcadia.Cinematics live in Assembly-CSharp, and an - /// assembly-definition test assembly cannot reference the predefined assemblies — - /// only the other way round. Giving the runtime code its own asmdefs - /// (docs/restructure-plan.md Phase 4.1) removes the need for this; until then the - /// reflection is the accommodation, and it is confined to these two tests. /// public class BootstrapTests { @@ -84,18 +76,12 @@ namespace NightclubArcadia.PlayMode.Tests [Test] public void EveryNpc_ResolvedItsDialogueRunner() { - var npcs = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None) - .Where(m => m != null && m.GetType().Name == "NPCStandIn") - .ToArray(); + var npcs = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); Assert.IsNotEmpty(npcs, "Expected NPCs in the level scene"); foreach (var npc in npcs) { - var field = npc.GetType().GetField("dialogueRunner", - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - Assert.IsNotNull(field, "NPCStandIn no longer has a dialogueRunner field"); - - Assert.IsNotNull(field.GetValue(npc) as Object, + Assert.IsNotNull(npc.dialogueRunner, $"'{npc.name}' did not resolve a DialogueRunner. It lives in the Systems scene, " + "so the serialized reference is gone and SceneServices.Resolve has to find it."); } @@ -104,16 +90,14 @@ namespace NightclubArcadia.PlayMode.Tests [Test] public void RevealCameras_BoundToThePlayer() { - var bound = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None) - .Where(cam => cam.GetComponents() - .Any(m => m != null && m.GetType().Name == "CinemachineFollowsPlayer")) - .ToArray(); - Assert.IsNotEmpty(bound, "Expected at least one runtime-bound camera"); + var binders = Object.FindObjectsByType( + FindObjectsInactive.Include, FindObjectsSortMode.None); + Assert.IsNotEmpty(binders, "Expected at least one runtime-bound camera"); - foreach (var cam in bound) + foreach (var binder in binders) { - Assert.IsNotNull(cam.Follow, - $"'{cam.name}' never bound its Follow target to the player"); + Assert.IsNotNull(binder.GetComponent().Follow, + $"'{binder.name}' never bound its Follow target to the player"); } } diff --git a/NightclubArcadia/Assets/Tests/PlayMode/NightclubArcadia.PlayMode.Tests.asmdef b/NightclubArcadia/Assets/Tests/PlayMode/NightclubArcadia.PlayMode.Tests.asmdef index 1e78c6d..cb7e5af 100644 --- a/NightclubArcadia/Assets/Tests/PlayMode/NightclubArcadia.PlayMode.Tests.asmdef +++ b/NightclubArcadia/Assets/Tests/PlayMode/NightclubArcadia.PlayMode.Tests.asmdef @@ -2,6 +2,8 @@ "name": "NightclubArcadia.PlayMode.Tests", "rootNamespace": "NightclubArcadia.PlayMode.Tests", "references": [ + "NightclubArcadia.Game", + "NightclubArcadia.Core", "NightclubArcadia.Skills", "UnityEngine.TestRunner", "UnityEditor.TestRunner", diff --git a/NightclubArcadia/ProjectSettings/ProjectSettings.asset b/NightclubArcadia/ProjectSettings/ProjectSettings.asset index beeefff..887e2da 100644 --- a/NightclubArcadia/ProjectSettings/ProjectSettings.asset +++ b/NightclubArcadia/ProjectSettings/ProjectSettings.asset @@ -527,7 +527,10 @@ PlayerSettings: m_Height: 720 m_Kind: 1 m_SubKind: - m_BuildTargetBatching: [] + m_BuildTargetBatching: + - m_BuildTarget: Standalone + m_StaticBatching: 1 + m_DynamicBatching: 0 m_BuildTargetShaderSettings: [] m_BuildTargetGraphicsJobs: [] m_BuildTargetGraphicsJobMode: [] diff --git a/NightclubArcadia/ProjectSettings/UnityConnectSettings.asset b/NightclubArcadia/ProjectSettings/UnityConnectSettings.asset index 029ad8b..7a17e8f 100644 --- a/NightclubArcadia/ProjectSettings/UnityConnectSettings.asset +++ b/NightclubArcadia/ProjectSettings/UnityConnectSettings.asset @@ -4,7 +4,7 @@ UnityConnectSettings: m_ObjectHideFlags: 0 serializedVersion: 1 - m_Enabled: 0 + m_Enabled: 1 m_TestMode: 0 m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events m_EventUrl: https://cdp.cloud.unity3d.com/v1/events diff --git a/docs/restructure-plan.md b/docs/restructure-plan.md index 66c962f..2fd7510 100644 --- a/docs/restructure-plan.md +++ b/docs/restructure-plan.md @@ -10,7 +10,7 @@ Companion reading: `docs/skill-system-refactor-plan.md`, `docs/candidate-system- --- -## 0. Status — Phases 0, 1, 2 and 3 are complete +## 0. Status — Phases 0–4 are complete Executed 2026-08-25. The working tree is clean and tagged `pre-restructure`. @@ -19,7 +19,7 @@ the UI layer, player control setup, the dialogue-vs-menu interaction fix, and th A full pre-flight backup was taken first (`.tar.gz` of the tree excluding `Library/`, `Temp/`, `Logs/`, and build output) plus a separate copy of the scene file. -Phases 1, 2 and 3 are also done — see §4. Only Phase 4 (code structure) and Phase 5 (replacing the NPC scaffold) remain proposal. +Phases 1 through 4 are also done — see §4. Only Phase 5 (replacing the NPC scaffold) remains proposal. ### 0.1 The binary scene — RESOLVED in Phase 3 @@ -486,14 +486,54 @@ written `BootstrapTests` rather than eyeballing the scene. because an asmdef assembly cannot reference `Assembly-CSharp` where `NPCStandIn` and the `Cinematics` namespace live — Phase 4.1 (per-area asmdefs) is what removes that. -### Phase 4 — code structure +### Phase 4 — code structure ✅ DONE (2026-08-25) -| # | Step | Detail | Effort | -|---|---|---|---| -| 4.1 | Add asmdefs per area | Camera, Player, Interaction, Dialogue, UI, NPCs, Core | M | -| 4.2 | Add `Assets/Tests/PlayMode/` + asmdef | first test: bootstrap loads and reaches playable state | M | -| 4.3 | Add `Assets/Editor/BuildPipeline.cs` | `-executeMethod` entry, `EditorApplication.Exit(1)` on failure (`CLAUDE.md` §3.4) | M | -| 4.4 | Rename batch | `DialougueSkillComparison.cs` → `DialogueSkillComparison.cs`; kebab-case `.yarn` filenames. Rename **in the Unity Editor** so `.meta` GUIDs are preserved. | S | +| # | Step | Status | +|---|---|---| +| 4.1 | Assemblies per area | done — **as 3 assemblies, not 7**; see below | +| 4.2 | `Assets/Tests/PlayMode/` + asmdef | done early, in Phase 3 | +| 4.3 | `Assets/Editor/PlayerBuild.cs` | done and verified — a real 172MB `.app` | +| 4.4 | Rename batch | done — both renames differed from the plan; see below | + +Validated: EditMode **42/42**, PlayMode **5/5**, YarnCheck 9 files / 35 nodes, `make build` green. + +#### Phase 4 — what changed against the plan + +**One-asmdef-per-area is not achievable without moving code.** The plan assumed seven assemblies +(Camera, Player, Interaction, Dialogue, UI, NPCs, Core). Measuring the dependency graph first +found three cycles, all through UI: + +| Cycle | Caused by | +|---|---| +| UI ↔ Dialogue | `UI/Menu/CharacterPanelView.cs` → `NightclubArcadia.Dialogue` | +| UI ↔ Player | `UI/PlayerControlLock.cs` → `NightclubArcadia.Player` | +| UI ↔ Camera | `UI/UILayerBootstrap.cs` → `NightclubArcadia.Cinematics` | + +There are also two edges a `using`-scan cannot see, because the types are in the global +namespace: `Interaction` → `NPCStandIn` and `Dialogue` → `NPCStandIn`. + +Assemblies cannot be circular, so the split landed as **Core / Game / Skills / Locomotion.Math** +plus a new **StarterAssets** assembly. `Game` is the cyclic cluster kept whole. Splitting it +further is a code-movement task — relocate those three files — not an asmdef task, and it is +not worth doing until something needs it. + +**StarterAssets had to get an assembly too.** It had none, so it was in `Assembly-CSharp`, and an +asmdef assembly cannot reference the predefined assemblies. Four runtime files use it. Without +giving it one, moving our code into assemblies would have broken all four. + +**The payoff was immediate:** `BootstrapTests` no longer needs reflection. It references +`NPCStandIn` and `CinemachineFollowsPlayer` directly, which is what Phase 3 flagged as the reason +to do this. + +**Both renames in 4.4 were not what the plan described.** + +- `DialougueSkillComparison.cs` contains a class called `SkillFunctions`. The filename never + matched the type, so the fix was `SkillFunctions.cs`, not `DialogueSkillComparison.cs`. It is + attached to nothing in any scene and its GUID was preserved through the rename. +- The `.yarn` files were renamed to remove **spaces**, not to kebab-case: `BradfordKane.yarn`, + `ChevalierCassianThal.yarn`. Kebab-case would have made them inconsistent with `Bartender.yarn`, + `Common.yarn` and `SC101.yarn`, and spaces were the actual problem. The `.yarnproject` graph + keys and the two character sheets in `writing/` were updated to match. ### Phase 5 — replace the scaffold diff --git a/writing/characters/bradford-kane.md b/writing/characters/bradford-kane.md index 97b4176..77ae1c7 100644 --- a/writing/characters/bradford-kane.md +++ b/writing/characters/bradford-kane.md @@ -2,7 +2,7 @@ id: bradford-kane name: Bradford Kane chr: — -yarn_file: Assets/Dialogue/Characters/Bradford Kane.yarn +yarn_file: Assets/Dialogue/Characters/BradfordKane.yarn status: planned scenes: [SC101] --- diff --git a/writing/characters/chevalier-cassian-thal.md b/writing/characters/chevalier-cassian-thal.md index c496c15..03304e5 100644 --- a/writing/characters/chevalier-cassian-thal.md +++ b/writing/characters/chevalier-cassian-thal.md @@ -2,7 +2,7 @@ id: chevalier-cassian-thal name: Chevalier Cassian Thal chr: — -yarn_file: Assets/Dialogue/Characters/Chevalier Cassian Thal.yarn +yarn_file: Assets/Dialogue/Characters/ChevalierCassianThal.yarn status: drafted scenes: [SC101] ---