fix(levels): ScenePath must be a string, not a PackedScene

slice 7 regression. level_village_2..5.tscn each reference their own
LevelResource for the death screen label, so pointing the resource back
at the scene forms a cycle Godot cannot resolve - "Parse Error: Busy"
and the level fails to load entirely.

ScenePath was a string for exactly this reason. Reverted, and the rot it
allowed (forest_1 aimed at a nonexistent scene) is now caught by
LevelCatalogue.Validate checking every path resolves, plus a repo-level
structural check - fixed by validation rather than by typing.

project.godot: restores the C# feature flag and dialogue_manager
uses_dotnet, which `godot --headless --import` strips on every run.
This commit is contained in:
2026-07-28 19:40:02 +02:00
parent 7a69292066
commit f85c16c641
12 changed files with 74 additions and 43 deletions

View File

@@ -27,8 +27,8 @@ public partial class LevelCatalogue : Resource
/// <summary>
/// The ordering the domain works from. Throws if the authored data is inconsistent — a blank or
/// duplicated id cannot produce a <see cref="Campaign"/>, so a mistake here fails at boot rather
/// than corrupting a save later.
/// duplicated id cannot produce a <see cref="Campaign"/>, so a mistake fails at boot rather than
/// corrupting a save later.
/// </summary>
public Campaign ToCampaign()
{
@@ -36,19 +36,48 @@ public partial class LevelCatalogue : Resource
for (var i = 0; i < Levels.Count; i++)
{
var level = Levels[i];
if (level == null)
throw new System.InvalidOperationException(
$"LevelCatalogue has an empty slot at index {i}.");
if (Levels[i] == null)
throw new System.InvalidOperationException($"LevelCatalogue has an empty slot at index {i}.");
ids.Add(LevelId.Of(level.Id));
ids.Add(LevelId.Of(Levels[i].Id));
}
return Campaign.Of(ids);
}
/// <summary>
/// Checks every catalogued Level names a scene that actually exists, returning the problems found.
/// </summary>
/// <remarks>
/// This is what keeps <see cref="LevelResource.ScenePath"/> honest. The path cannot be a typed
/// <c>PackedScene</c> — several level scenes reference their own LevelResource, so a reference back
/// to the scene is a cycle Godot reports as "Parse Error: Busy" and the level fails to load. A
/// string plus this check gets the safety without the cycle.
/// </remarks>
public IReadOnlyList<string> Validate()
{
var problems = new List<string>();
foreach (var level in Levels.Where(l => l != null))
{
if (string.IsNullOrWhiteSpace(level.ScenePath))
problems.Add($"level '{level.Id}' has no scene path");
else if (!ResourceLoader.Exists(level.ScenePath))
problems.Add($"level '{level.Id}' points at '{level.ScenePath}', which does not exist");
}
return problems;
}
public LevelResource Find(LevelId id) =>
Levels.FirstOrDefault(level => level != null && level.Id == id.Value);
public PackedScene SceneFor(LevelId id) => Find(id)?.Scene;
/// <summary>Loads a Level's scene on demand. Null when the Level is not catalogued.</summary>
public PackedScene SceneFor(LevelId id)
{
var path = Find(id)?.ScenePath;
return string.IsNullOrWhiteSpace(path) ? null : ResourceLoader.Load<PackedScene>(path);
}
public string DisplayNameKeyFor(LevelId id) => Find(id)?.LevelName ?? string.Empty;
}