No project code is in Assembly-CSharp any more. Five assemblies: Core (no dependencies), Locomotion.Math, Skills, Game (everything else under Scripts), and StarterAssets. Game is one assembly rather than one per area, which is what the plan assumed. Measuring the dependency graph first found three cycles, all through UI — CharacterPanelView reaches into Dialogue, PlayerControlLock into Player, and UILayerBootstrap into Cinematics — plus two edges a using-scan cannot see, because NPCStandIn is in the global namespace. Assemblies cannot be circular, so splitting further means relocating those three files. That is a code-movement task, not an asmdef task, and nothing needs it yet. StarterAssets had to get an assembly of its own. It had none, so it lived in Assembly-CSharp, and an asmdef assembly cannot reference the predefined assemblies — four runtime files use it, and all four would have stopped compiling. The payoff is immediate: BootstrapTests no longer needs reflection to reach NPCStandIn and CinemachineFollowsPlayer, which is exactly why Phase 3 wanted this. Assets/Editor/PlayerBuild.cs is the headless build entry point. It reads the enabled scenes, asserts Bootstrap is scene 0 because the player opens scene 0 on launch, honours the -buildOutput the CLI forwards from -o, and calls EditorApplication.Exit(1) on anything short of Succeeded — without which Unity exits 0 on a build that produced nothing and CI goes green on it. Verified end to end: a 172MB .app in about two minutes. Two renames, both different from what the plan described. DialougueSkillComparison.cs contains a class called SkillFunctions — the filename never matched the type, so it is now SkillFunctions.cs. The two .yarn files with spaces in their names lost the spaces rather than becoming kebab-case, which would have made them inconsistent with Bartender.yarn and SC101.yarn; spaces were the actual problem. Both renames preserved their .meta GUIDs, and the yarnproject graph keys and the two character sheets were updated. EditMode 42/42, PlayMode 5/5, YarnCheck 9 files / 35 nodes, make build green. Co-Authored-By: Claude Opus 5 <[email protected]>
106 lines
3.8 KiB
C#
106 lines
3.8 KiB
C#
using System.Collections;
|
|
using NUnit.Framework;
|
|
using NightclubArcadia.Cinematics;
|
|
using Unity.Cinemachine;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.TestTools;
|
|
using Yarn.Unity;
|
|
|
|
namespace NightclubArcadia.PlayMode.Tests
|
|
{
|
|
/// <summary>
|
|
/// Proves the scene split actually comes up.
|
|
///
|
|
/// Splitting Systems out of the level nulled seven serialized references that
|
|
/// Unity cannot carry across a scene boundary — the two NPCs' dialogue runner, UI
|
|
/// and player fields, and the reveal camera's tracking target. Each is supposed to
|
|
/// be resolved at runtime instead. An EditMode test cannot see any of that, because
|
|
/// none of it has run. This can.
|
|
///
|
|
/// If this suite fails, the game does not start. Treat it that way.
|
|
/// </summary>
|
|
public class BootstrapTests
|
|
{
|
|
const string Bootstrap = "Bootstrap";
|
|
const string Systems = "Systems";
|
|
const string Level = "SC101_ConferenceHall";
|
|
|
|
[UnitySetUp]
|
|
public IEnumerator LoadFromBootstrap()
|
|
{
|
|
yield return SceneManager.LoadSceneAsync(Bootstrap, LoadSceneMode.Single);
|
|
|
|
// GameBootstrap loads the other two additively over a few frames.
|
|
var deadline = Time.realtimeSinceStartup + 20f;
|
|
while (Time.realtimeSinceStartup < deadline)
|
|
{
|
|
if (SceneManager.GetSceneByName(Systems).isLoaded &&
|
|
SceneManager.GetSceneByName(Level).isLoaded)
|
|
{
|
|
break;
|
|
}
|
|
yield return null;
|
|
}
|
|
|
|
// let Awake/Start run on everything that just loaded
|
|
for (var i = 0; i < 5; i++)
|
|
{
|
|
yield return null;
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Bootstrap_LoadsSystemsAndLevel()
|
|
{
|
|
Assert.IsTrue(SceneManager.GetSceneByName(Systems).isLoaded, "Systems scene did not load");
|
|
Assert.IsTrue(SceneManager.GetSceneByName(Level).isLoaded, "Level scene did not load");
|
|
Assert.AreEqual(Level, SceneManager.GetActiveScene().name,
|
|
"The level should be the active scene, so new objects and lighting land there");
|
|
}
|
|
|
|
[Test]
|
|
public void ThePlayerExists_AndIsTagged()
|
|
{
|
|
var player = GameObject.FindGameObjectWithTag("Player");
|
|
Assert.IsNotNull(player, "No object tagged Player — the reveal camera and the NPCs both need it");
|
|
}
|
|
|
|
[Test]
|
|
public void TheDialogueRunnerExists()
|
|
{
|
|
Assert.IsNotNull(Object.FindFirstObjectByType<DialogueRunner>(),
|
|
"No DialogueRunner in any loaded scene");
|
|
}
|
|
|
|
[Test]
|
|
public void EveryNpc_ResolvedItsDialogueRunner()
|
|
{
|
|
var npcs = Object.FindObjectsByType<NPCStandIn>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
Assert.IsNotEmpty(npcs, "Expected NPCs in the level scene");
|
|
|
|
foreach (var npc in npcs)
|
|
{
|
|
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.");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void RevealCameras_BoundToThePlayer()
|
|
{
|
|
var binders = Object.FindObjectsByType<CinemachineFollowsPlayer>(
|
|
FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
Assert.IsNotEmpty(binders, "Expected at least one runtime-bound camera");
|
|
|
|
foreach (var binder in binders)
|
|
{
|
|
Assert.IsNotNull(binder.GetComponent<CinemachineCamera>().Follow,
|
|
$"'{binder.name}' never bound its Follow target to the player");
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|