disable the pre-split scene builders instead of retargeting them

Fixes the "no Yarn Project has been configured" error coming back after running
the setup menus.

Last commit fixed the wrong layer. Retargeting YarnDemoSetup at a sandbox
stopped it destroying the level, but left the deeper problem: all of these
builders assume the single-scene layout, and after the split there is no target
path that makes them correct.

Two failures, both observed rather than reasoned about. Running the Yarn setup
left a generated scene open whose DialogueRunner had no project and auto-started
— that is the error the user kept seeing, coming from Dev/YarnSandbox.unity, not
from Systems. And retargeting the other three at Systems made PlayerControlSetup
pull navigation in with it; the baked NavMeshData is embedded rather than an
asset, so Systems.unity silently turned binary, 61KB text to 109KB binary. That
is trap 1 recurring on a scene that had just been fixed.

So the three scene builders now return early through LegacySceneSetup.Blocked,
with a message saying what they would have done and what to do instead. They are
not deleted: UILayerSetup alone is 818 lines that still record how the UI layer
is wired, and that record matters until prefabs replace it in Phase 5.

Set Up Skill System keeps its useful half. It regenerates the SkillDefinition
assets from JSON, which touches no scene; only the WireWorkingLevelScene call is
dropped, because those objects already exist in Systems.unity. Set Up Candidate
System was always asset-only and is untouched.

While diagnosing, YarnDemoSetup's own bug was found and fixed even though the
menu is now blocked: it set yarnProject through a SerializedObject on a prefab
instance without recording the override, so the assignment was dropped on save.
It now records the modification and refuses to save a scene whose runner has no
project.

Verified by running all five menus headlessly: the two asset generators run and
produce no diff, the three builders refuse, and no scene or asset changes.
Systems.unity restored to text, the level scene intact at 5 roots.

EditMode 42/42, PlayMode 6/6, YarnCheck 9 files / 35 nodes.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-25 21:24:24 +02:00
co-authored by Claude Opus 5
parent 46160d86dc
commit 4f48d258b5
7 changed files with 111 additions and 16 deletions
+9 -7
View File
@@ -486,13 +486,15 @@ should not be — see `docs/restructure-plan.md`.
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. **`Tools ▸ Nightclub Arcadia ▸ Set Up Yarn Sandbox Scene` overwrites its target scene
wholesale.** It builds a scene from scratch and calls `SaveScene` over `ScenePath`. It is
pinned to `Assets/Scenes/Dev/` and guarded by `ScenePaths.IsDisposable`, because it was once
briefly retargeted at `SC101_ConferenceHall` and destroyed it — ten of fourteen roots, the
player and the lighting included. **Never point a scene-generating menu at a real scene.**
The other three setup menus (`PlayerControlSetup`, `UILayerSetup`, `SkillSystemSetup`) open
and modify their target, which is safe.
5. **The one-shot scene builders under `Assets/Editor` are disabled, on purpose.**
`Set Up UI Layer`, `Set Up Player Control & Camera` and `Set Up Yarn Sandbox Scene` were
written for the single-scene layout. After the split they do damage rather than work: pointed
at a level they rebuild systems content that already exists in `Systems.unity`, and pointed at
Systems, `PlayerControlSetup` drags navigation in and silently turns that scene binary (trap 1
again). Both were observed. They now return early via `LegacySceneSetup.Blocked` and are kept
only as a record of how the wiring works until prefabs replace them (Phase 5).
`Set Up Skill System` and `Set Up Candidate System` still work — they regenerate assets from
JSON and touch no scene. **To run the game, open `Bootstrap.unity` and press Play.**
6. **`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).
@@ -0,0 +1,48 @@
using UnityEditor;
using UnityEngine;
namespace NightclubArcadia.EditorTools
{
/// <summary>
/// Gate for the one-shot scene builders under Assets/Editor.
///
/// Those menus were written when the whole game lived in a single scene. They find
/// or create the dialogue system, the UI canvases, the player and the camera rig,
/// and save the result into one scene. After the split into Bootstrap / Systems /
/// Level, that assumption is false, and running them does damage rather than work:
///
/// - Pointed at a level, they rebuild systems content that already exists in
/// Systems.unity, leaving duplicate UIStateControllers and dialogue runners.
/// - Pointed at Systems, PlayerControlSetup drags navigation in with it. The
/// baked NavMeshData is embedded rather than an asset, and NavMeshData prefers
/// binary serialization, so the whole scene silently turns binary — the exact
/// problem that took a bisection to find the first time (CLAUDE.md trap 1).
///
/// Both were observed, not theorised. So the builders are blocked rather than
/// retargeted, and deliberately not deleted: UILayerSetup alone is 818 lines that
/// still record how the UI layer is wired, and that record is worth keeping until
/// the prefabs replace it (docs/restructure-plan.md Phase 5).
///
/// Asset generators are unaffected. Regenerating SkillDefinitions or the candidate
/// registry from JSON touches no scene and stays available.
/// </summary>
public static class LegacySceneSetup
{
/// <summary>
/// Call at the top of any menu that builds scene content. Returns true if the
/// caller should stop, which is currently always.
/// </summary>
public static bool Blocked(string menuName, string whatItWouldDo)
{
Debug.LogError(
$"'{menuName}' is disabled.\n\n" +
$"It was written for the single-scene layout and would {whatItWouldDo}. " +
"The game is now split across Bootstrap, Systems and a level scene, and the " +
"objects it builds already exist in Assets/Scenes/Systems/Systems.unity.\n\n" +
"To run the game: open Assets/Scenes/Bootstrap.unity and press Play.\n" +
"To verify it comes up: make test-play\n" +
"Rewriting these for the split is docs/restructure-plan.md Phase 5.");
return true;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d28eed724b04f52af81ac74d57f3f26
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -10,18 +10,22 @@ using UnityEngine;
using UnityEngine.AI;
/// <summary>
/// Wires player control, navigation, and camera systems into SC101_ConferenceHall.unity.
/// Wires player control, navigation, and camera systems into Systems.unity.
/// Run via Tools → Nightclub Arcadia → Set Up Player Control &amp; Camera.
/// </summary>
public static class PlayerControlSetup
{
const string ScenePath = NightclubArcadia.EditorTools.ScenePaths.WorkingLevel;
const string ScenePath = NightclubArcadia.EditorTools.ScenePaths.Systems;
const string PlayerPrefabPath = "Assets/StarterAssets/ThirdPersonController/Prefabs/PlayerArmature.prefab";
const string SettingsFolder = "Assets/Settings";
[MenuItem("Tools/Nightclub Arcadia/Set Up Player Control & Camera")]
public static void SetUp()
{
if (NightclubArcadia.EditorTools.LegacySceneSetup.Blocked("Set Up Player Control & Camera",
"rebuild the player and camera rig and pull navigation into Systems.unity, "
+ "which turns that scene binary")) return;
EnsurePlayerPrefab();
WireScene();
AssetDatabase.SaveAssets();
@@ -23,7 +23,7 @@ public static class SkillSystemSetup
const string SkillsFolder = "Assets/Skills";
const string BiblePath = "Assets/Skills/skill_bible.json";
const string DatabasePath = "Assets/Skills/SkillDatabase.asset";
const string ScenePath = NightclubArcadia.EditorTools.ScenePaths.WorkingLevel;
const string ScenePath = NightclubArcadia.EditorTools.ScenePaths.Systems;
const string AutoSetupSessionKey = "NightclubArcadia.SkillSystem.AutoSetupDone";
[DidReloadScripts]
@@ -55,7 +55,7 @@ public static class SkillSystemSetup
/// <summary>
/// Creates/updates ScriptableObject assets without opening a scene.
/// Safe to run on domain reload. Scene wiring stays on the menu item /
/// the checked-in SC101_ConferenceHall.unity Skill System objects.
/// the checked-in Systems.unity Skill System objects.
/// </summary>
static void EnsureSkillAssetsOnly()
{
@@ -122,9 +122,12 @@ public static class SkillSystemSetup
EditorUtility.SetDirty(database);
AssetDatabase.SaveAssets();
WireWorkingLevelScene(database);
// Scene wiring deliberately skipped: the Skill System objects live in
// Systems.unity and are already wired there. Regenerating the assets is
// still useful and touches no scene. See LegacySceneSetup.
AssetDatabase.SaveAssets();
Debug.Log($"Skill system setup complete. Database at {DatabasePath}; scene wired at {ScenePath}.");
Debug.Log($"Skill assets regenerated. Database at {DatabasePath}. Scene wiring skipped — "
+ "the Skill System already exists in Systems.unity.");
}
static bool TryLoadRoster(out SkillBibleEntry[] roster)
@@ -243,7 +246,7 @@ public static class SkillSystemSetup
var runner = UnityEngine.Object.FindAnyObjectByType<DialogueRunner>();
if (runner == null)
{
Debug.LogError("No DialogueRunner in SC101_ConferenceHall.unity.");
Debug.LogError("No DialogueRunner in Systems.unity.");
return;
}
@@ -16,12 +16,12 @@ using UnityEngine.UI;
using Yarn.Unity;
/// <summary>
/// Builds UI prefabs and wires them into SC101_ConferenceHall.unity.
/// Builds UI prefabs and wires them into Systems.unity.
/// Run via Tools → Nightclub Arcadia → Set Up UI Layer.
/// </summary>
public static class UILayerSetup
{
const string ScenePath = NightclubArcadia.EditorTools.ScenePaths.WorkingLevel;
const string ScenePath = NightclubArcadia.EditorTools.ScenePaths.Systems;
const string PrefabFolder = "Assets/Prefabs/UI";
const string ArtFolder = "Assets/Art/UI/Placeholder";
@@ -40,6 +40,9 @@ public static class UILayerSetup
[MenuItem("Tools/Nightclub Arcadia/Set Up UI Layer")]
public static void SetUp()
{
if (NightclubArcadia.EditorTools.LegacySceneSetup.Blocked("Set Up UI Layer",
"rebuild the UI canvases that already live in Systems.unity, leaving duplicates")) return;
EnsureFolders();
CreatePortraitPlaceholder();
@@ -14,6 +14,9 @@ public static class YarnDemoSetup
[MenuItem("Tools/Nightclub Arcadia/Set Up Yarn Sandbox Scene")]
public static void SetUpYarnDialogueTest()
{
if (NightclubArcadia.EditorTools.LegacySceneSetup.Blocked("Set Up Yarn Sandbox Scene",
"generate a throwaway scene whose dialogue runner duplicates the real one")) return;
// This menu BUILDS A SCENE FROM SCRATCH and saves over ScenePath. Pointed at a
// real level it silently destroys it, which is exactly what happened when it
// was briefly retargeted at SC101_ConferenceHall. The guard is the fix; do not
@@ -67,9 +70,30 @@ public static class YarnDemoSetup
so.FindProperty("yarnProject").objectReferenceValue = yarnProject;
so.ApplyModifiedPropertiesWithoutUndo();
// CreateDialogueRunner returns a component on a PREFAB INSTANCE of the package's
// Dialogue System prefab. Applying a SerializedProperty on one of those does not
// always record the override, and an unrecorded override is dropped on save —
// which produced a sandbox whose runner had no project, auto-started, and
// failed with "Can't start dialogue: no Yarn Project has been configured."
if (PrefabUtility.IsPartOfPrefabInstance(runner))
{
PrefabUtility.RecordPrefabInstancePropertyModifications(runner);
}
runner.autoStart = true;
runner.startNode = "Start";
EditorUtility.SetDirty(runner);
EditorSceneManager.MarkSceneDirty(scene);
// Postcondition. This menu exists to produce a runnable scene; a scene whose
// runner has no project is worse than no scene, because it fails at Play time
// with an error that points nowhere near here.
if (new SerializedObject(runner).FindProperty("yarnProject").objectReferenceValue == null)
{
Debug.LogError("Yarn sandbox setup failed: the DialogueRunner has no Yarn Project. " +
"The generated scene will error on Play. Not saving.");
return;
}
#if UNITY_2023_1_OR_NEWER
var eventSystems = Object.FindObjectsByType<EventSystem>(FindObjectsInactive.Include);