diff --git a/resources/levels/forest/forest_1.tres b/resources/levels/forest/forest_1.tres
index b32ddd8..00c828c 100644
--- a/resources/levels/forest/forest_1.tres
+++ b/resources/levels/forest/forest_1.tres
@@ -1,9 +1,11 @@
-[gd_resource type="Resource" script_class="LevelResource" load_steps=2 format=3 uid="uid://dsq0y2iw7tfmk"]
+[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3 uid="uid://dsq0y2iw7tfmk"]
-[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_8mmd7"]
+[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
+[ext_resource type="PackedScene" uid="uid://cq4glvuewsno5" path="res://scenes/level_forest_1.tscn" id="2_scene"]
[resource]
-script = ExtResource("1_8mmd7")
-LevelName = "LEVEL_5_NAME"
-ScenePath = "res://scenes/level_forest_5.tscn"
+script = ExtResource("1_script")
+Id = "forest_1"
+LevelName = "LEVEL_FOREST_1_NAME"
+Scene = ExtResource("2_scene")
metadata/_custom_type_script = "uid://cp68km8bykymb"
diff --git a/resources/levels/level_catalogue.tres b/resources/levels/level_catalogue.tres
new file mode 100644
index 0000000..5f4b344
--- /dev/null
+++ b/resources/levels/level_catalogue.tres
@@ -0,0 +1,13 @@
+[gd_resource type="Resource" script_class="LevelCatalogue" load_steps=8 format=3]
+
+[ext_resource type="Script" path="res://scripts/Resources/LevelCatalogue.cs" id="1_script"]
+[ext_resource type="Resource" path="res://resources/levels/tutorial_1.tres" id="2_tutorial_1"]
+[ext_resource type="Resource" uid="uid://cqtalsov2bkpo" path="res://resources/levels/village/village_1.tres" id="3_village_1"]
+[ext_resource type="Resource" uid="uid://dlwkbjw1l33uq" path="res://resources/levels/village/village_2.tres" id="4_village_2"]
+[ext_resource type="Resource" uid="uid://b63u5qfp8p7pv" path="res://resources/levels/village/village_3.tres" id="5_village_3"]
+[ext_resource type="Resource" uid="uid://cxfei28yu3buh" path="res://resources/levels/village/village_4.tres" id="6_village_4"]
+[ext_resource type="Resource" uid="uid://qld841xv5ui0" path="res://resources/levels/village/village_5.tres" id="7_village_5"]
+
+[resource]
+script = ExtResource("1_script")
+Levels = Array[Resource]([ExtResource("2_tutorial_1"), ExtResource("3_village_1"), ExtResource("4_village_2"), ExtResource("5_village_3"), ExtResource("6_village_4"), ExtResource("7_village_5")])
diff --git a/resources/levels/tutorial_1.tres b/resources/levels/tutorial_1.tres
new file mode 100644
index 0000000..c5d90b6
--- /dev/null
+++ b/resources/levels/tutorial_1.tres
@@ -0,0 +1,11 @@
+[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3]
+
+[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
+[ext_resource type="PackedScene" uid="uid://dwr3avefuwnpy" path="res://scenes/level_tutorial_1.tscn" id="2_scene"]
+
+[resource]
+script = ExtResource("1_script")
+Id = "tutorial_1"
+LevelName = "LEVEL_TUTORIAL_1_NAME"
+Scene = ExtResource("2_scene")
+metadata/_custom_type_script = "uid://cp68km8bykymb"
diff --git a/resources/levels/village/village_1.tres b/resources/levels/village/village_1.tres
index 2833437..0aaf363 100644
--- a/resources/levels/village/village_1.tres
+++ b/resources/levels/village/village_1.tres
@@ -1,10 +1,11 @@
-[gd_resource type="Resource" script_class="LevelResource" load_steps=2 format=3 uid="uid://cqtalsov2bkpo"]
+[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3 uid="uid://cqtalsov2bkpo"]
-[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_72j7f"]
+[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
+[ext_resource type="PackedScene" uid="uid://bol7g83v2accs" path="res://scenes/level_village_1.tscn" id="2_scene"]
[resource]
-resource_local_to_scene = true
-script = ExtResource("1_72j7f")
+script = ExtResource("1_script")
+Id = "village_1"
LevelName = "LEVEL_1_NAME"
-ScenePath = "res://scenes/level_village_1.tscn"
+Scene = ExtResource("2_scene")
metadata/_custom_type_script = "uid://cp68km8bykymb"
diff --git a/resources/levels/village/village_2.tres b/resources/levels/village/village_2.tres
index 6ded9e6..9eaf32d 100644
--- a/resources/levels/village/village_2.tres
+++ b/resources/levels/village/village_2.tres
@@ -1,9 +1,11 @@
-[gd_resource type="Resource" script_class="LevelResource" load_steps=2 format=3 uid="uid://dlwkbjw1l33uq"]
+[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3 uid="uid://dlwkbjw1l33uq"]
-[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_msres"]
+[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
+[ext_resource type="PackedScene" uid="uid://chqb11pfoqmeb" path="res://scenes/level_village_2.tscn" id="2_scene"]
[resource]
-script = ExtResource("1_msres")
+script = ExtResource("1_script")
+Id = "village_2"
LevelName = "LEVEL_2_NAME"
-ScenePath = "res://scenes/level_village_2.tscn"
+Scene = ExtResource("2_scene")
metadata/_custom_type_script = "uid://cp68km8bykymb"
diff --git a/resources/levels/village/village_3.tres b/resources/levels/village/village_3.tres
index 0d3176e..bc1e157 100644
--- a/resources/levels/village/village_3.tres
+++ b/resources/levels/village/village_3.tres
@@ -1,9 +1,11 @@
-[gd_resource type="Resource" script_class="LevelResource" load_steps=2 format=3 uid="uid://b63u5qfp8p7pv"]
+[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3 uid="uid://b63u5qfp8p7pv"]
-[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_6vcr8"]
+[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
+[ext_resource type="PackedScene" uid="uid://h60obxmju6mo" path="res://scenes/level_village_3.tscn" id="2_scene"]
[resource]
-script = ExtResource("1_6vcr8")
+script = ExtResource("1_script")
+Id = "village_3"
LevelName = "LEVEL_3_NAME"
-ScenePath = "res://scenes/level_village_3.tscn"
+Scene = ExtResource("2_scene")
metadata/_custom_type_script = "uid://cp68km8bykymb"
diff --git a/resources/levels/village/village_4.tres b/resources/levels/village/village_4.tres
index 6ed1da3..2cd00e4 100644
--- a/resources/levels/village/village_4.tres
+++ b/resources/levels/village/village_4.tres
@@ -1,9 +1,11 @@
-[gd_resource type="Resource" script_class="LevelResource" load_steps=2 format=3 uid="uid://cxfei28yu3buh"]
+[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3 uid="uid://cxfei28yu3buh"]
-[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_h4twv"]
+[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
+[ext_resource type="PackedScene" uid="uid://bhad760x3vvco" path="res://scenes/level_village_4.tscn" id="2_scene"]
[resource]
-script = ExtResource("1_h4twv")
+script = ExtResource("1_script")
+Id = "village_4"
LevelName = "LEVEL_4_NAME"
-ScenePath = "res://scenes/level_village_4.tscn"
+Scene = ExtResource("2_scene")
metadata/_custom_type_script = "uid://cp68km8bykymb"
diff --git a/resources/levels/village/village_5.tres b/resources/levels/village/village_5.tres
index 3c81c48..11d73cb 100644
--- a/resources/levels/village/village_5.tres
+++ b/resources/levels/village/village_5.tres
@@ -1,8 +1,11 @@
-[gd_resource type="Resource" script_class="LevelResource" load_steps=2 format=3 uid="uid://qld841xv5ui0"]
+[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3 uid="uid://qld841xv5ui0"]
-[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_chped"]
+[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
+[ext_resource type="PackedScene" uid="uid://bbwef3n2gjkt8" path="res://scenes/level_village_5.tscn" id="2_scene"]
[resource]
-script = ExtResource("1_chped")
+script = ExtResource("1_script")
+Id = "village_5"
LevelName = "LEVEL_5_NAME"
-ScenePath = "res://scenes/level_village_5.tscn"
+Scene = ExtResource("2_scene")
+metadata/_custom_type_script = "uid://cp68km8bykymb"
diff --git a/resources/levels/village/village_6.tres b/resources/levels/village/village_6.tres
new file mode 100644
index 0000000..b6e2d68
--- /dev/null
+++ b/resources/levels/village/village_6.tres
@@ -0,0 +1,11 @@
+[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3]
+
+[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
+[ext_resource type="PackedScene" uid="uid://dqekccjkwev4y" path="res://scenes/level_village_6.tscn" id="2_scene"]
+
+[resource]
+script = ExtResource("1_script")
+Id = "village_6"
+LevelName = "LEVEL_6_NAME"
+Scene = ExtResource("2_scene")
+metadata/_custom_type_script = "uid://cp68km8bykymb"
diff --git a/scenes/level_tutorial_1.tscn b/scenes/level_tutorial_1.tscn
index 00a8a36..1f5a312 100644
--- a/scenes/level_tutorial_1.tscn
+++ b/scenes/level_tutorial_1.tscn
@@ -20,7 +20,6 @@
[sub_resource type="Resource" id="Resource_asu0i"]
script = ExtResource("3_ph1yb")
LevelName = "Tutorial 1"
-ScenePath = "res://scenes/level_tutorial_1.tscn"
metadata/_custom_type_script = "uid://c2h0pqhxiqswe"
[sub_resource type="Resource" id="Resource_0nwt7"]
diff --git a/scenes/level_village_1.tscn b/scenes/level_village_1.tscn
index de5e2d1..20b49f3 100644
--- a/scenes/level_village_1.tscn
+++ b/scenes/level_village_1.tscn
@@ -54,7 +54,6 @@ color_ramp = SubResource("GradientTexture1D_f1fvy")
resource_local_to_scene = true
script = ExtResource("4_84xgs")
LevelName = "LEVEL_1_NAME"
-ScenePath = "res://scenes/level_village_1.tscn"
metadata/_custom_type_script = "uid://cp68km8bykymb"
[sub_resource type="Resource" id="Resource_0nwt7"]
diff --git a/scripts/Resources/LevelCatalogue.cs b/scripts/Resources/LevelCatalogue.cs
new file mode 100644
index 0000000..dee1023
--- /dev/null
+++ b/scripts/Resources/LevelCatalogue.cs
@@ -0,0 +1,54 @@
+using System.Collections.Generic;
+using System.Linq;
+using Godot;
+using Godot.Collections;
+using Mr.BrickAdventures.Progression;
+
+namespace Mr.BrickAdventures.scripts.Resources;
+
+///
+/// The authored list of Levels in campaign order, and the bridge from a back to
+/// the Godot scene that implements it.
+///
+///
+/// Not a domain port. Nothing in the progression domain resolves a Level to a scene — the domain only
+/// needs the ordering, which it takes as a . Scene lookup is wanted solely by
+/// the composition root, which is engine-side anyway.
+///
+[GlobalClass]
+public partial class LevelCatalogue : Resource
+{
+ ///
+ /// Levels in campaign order. A Level absent from this list is not reachable, even if its
+ /// exists.
+ ///
+ [Export]
+ public Array Levels { get; set; } = [];
+
+ ///
+ /// The ordering the domain works from. Throws if the authored data is inconsistent — a blank or
+ /// duplicated id cannot produce a , so a mistake here fails at boot rather
+ /// than corrupting a save later.
+ ///
+ public Campaign ToCampaign()
+ {
+ var ids = new List();
+
+ 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}.");
+
+ ids.Add(LevelId.Of(level.Id));
+ }
+
+ return Campaign.Of(ids);
+ }
+
+ public LevelResource Find(LevelId id) =>
+ Levels.FirstOrDefault(level => level != null && level.Id == id.Value);
+
+ public PackedScene SceneFor(LevelId id) => Find(id)?.Scene;
+}
diff --git a/scripts/Resources/LevelResource.cs b/scripts/Resources/LevelResource.cs
index 7db4219..51b003d 100644
--- a/scripts/Resources/LevelResource.cs
+++ b/scripts/Resources/LevelResource.cs
@@ -2,12 +2,33 @@ using Godot;
namespace Mr.BrickAdventures.scripts.Resources;
+///
+/// One authored Level: its stable id, its display name key, and the scene to load.
+///
+///
+///
+/// is what Playthroughs and Best Times reference, so it must never change once a
+/// build has shipped — unlike position in a list, which is what the old
+/// GameManager.LevelScenes array used and which every save depended on.
+///
+///
+/// The scene is a rather than a path string. The previous
+/// ScenePath field was never read by anything and had rotted: forest_1.tres pointed at
+/// res://scenes/level_forest_5.tscn, which does not exist. An exported reference is tracked by
+/// the editor and cannot drift.
+///
+///
[GlobalClass]
public partial class LevelResource : Resource
{
- [Export]
+ /// Stable identity. Referenced by saves — do not change after shipping.
+ [Export]
+ public string Id { get; set; } = string.Empty;
+
+ /// Translation key for the player-facing name. Labels auto-translate it.
+ [Export]
public string LevelName { get; set; } = string.Empty;
- [Export]
- public string ScenePath { get; set; } = string.Empty;
-}
\ No newline at end of file
+ [Export]
+ public PackedScene Scene { get; set; }
+}
diff --git a/translations.csv b/translations.csv
index a1ede99..9e64864 100644
--- a/translations.csv
+++ b/translations.csv
@@ -61,3 +61,6 @@ MAGNETIC_DESCRIPTION,Attract coins,Przyciąga monety w pobliżu
LEVEL_1_TOOLTIP_1,Some blocks might collapse,Niektóre bloki mogą się zapaść.
LEVEL_1_TOOLTIP_2,Jump to the other side!,Przeskocz na drugą stronę!
LEVEL_1_TOOLTIP_3,"To rescue your child, you need to find the lever.",Uratuj dziecko poprzez aktywację dźwigni.
+LEVEL_TUTORIAL_1_NAME,TUTORIAL,SAMOUCZEK
+LEVEL_6_NAME,VILLAGE – 6,WIOSKA – 6
+LEVEL_FOREST_1_NAME,FOREST – 1,LAS – 1