diff --git a/Launcher.Next.App/MainWindow.xaml b/Launcher.Next.App/MainWindow.xaml
index d43cfdb..c3df942 100644
--- a/Launcher.Next.App/MainWindow.xaml
+++ b/Launcher.Next.App/MainWindow.xaml
@@ -1 +1 @@
-
+
diff --git a/Launcher.Next.App/MainWindow.xaml.cs b/Launcher.Next.App/MainWindow.xaml.cs
index 2b0bbe3..f088b28 100644
--- a/Launcher.Next.App/MainWindow.xaml.cs
+++ b/Launcher.Next.App/MainWindow.xaml.cs
@@ -1 +1,26 @@
-namespace Launcher.Next.App; public partial class MainWindow : System.Windows.Window { public MainWindow() => InitializeComponent(); }
+using Launcher.Next.Application;
+using Launcher.Next.Autodesk;
+
+namespace Launcher.Next.App;
+public partial class MainWindow : System.Windows.Window
+{
+ private readonly LegacyConfigurationAdapter adapter=new();
+ public MainWindow() { InitializeComponent(); RefreshDiscovery(); Status.Text="Ready: choose a legacy Settings.xml to inspect approved configurations."; }
+ private void RefreshDiscovery()
+ {
+ var product=new PlantProductDiscovery(new WindowsRegistryReader()).DiscoverPlant3D(2026);
+ AutodeskStatus.Text=product is null?"Autodesk Plant 3D 2026: Not found":"Autodesk Plant 3D 2026: Found";
+ ExecutableStatus.Text=product is null?"Discovery diagnostic: ACAD-9117 Plant 3D registry entry or executable unavailable.":$"Executable: {product.ExecutablePath}";
+ }
+ private void Refresh_Click(object sender,System.Windows.RoutedEventArgs e)=>RefreshDiscovery();
+ private void OpenSettings_Click(object sender,System.Windows.RoutedEventArgs e)
+ {
+ var dialog=new Microsoft.Win32.OpenFileDialog { Filter="Settings.xml|Settings.xml|XML files|*.xml" };
+ if(dialog.ShowDialog()!=true)return;
+ try { var settings=adapter.ParseSettings(System.IO.File.ReadAllText(dialog.FileName)); Patterns.ItemsSource=settings.Patterns; ConfigurationStatus.Text=$"Loaded {settings.Patterns.Count} configuration(s) from {System.IO.Path.GetFileName(dialog.FileName)}"; Status.Text="Select a configuration. Exact package preparation remains blocked until an approved compatibility overlay and local verified catalog are supplied."; }
+ catch(System.Exception ex) { ConfigurationStatus.Text="Configuration invalid";Status.Text=$"Blocked: {ex.Message}";Patterns.ItemsSource=null; }
+ }
+ private void Patterns_SelectionChanged(object sender,System.Windows.Controls.SelectionChangedEventArgs e)
+ { if(Patterns.SelectedItem is not LegacyProductPattern pattern)return;PatternDetails.Text=$"Filter: {pattern.Filter}\nRoster: {pattern.Roster}\nLegacy plugins: {pattern.Plugins.Count}";PackageStatus.Text="Exact package requirements are derived only after approved overlay + ordered download_list.xml join.";Status.Text="Configuration selected; provide approved local compatibility inputs to prepare packages."; }
+ private void Diagnostics_Click(object sender,System.Windows.RoutedEventArgs e)=>System.Windows.MessageBox.Show($"{AutodeskStatus.Text}\n{ConfigurationStatus.Text}\n{Status.Text}","Launcher Next diagnostics");
+}
diff --git a/Launcher.Next.Autodesk/AutodeskAdapters.cs b/Launcher.Next.Autodesk/AutodeskAdapters.cs
index 3d05cfc..ca5df19 100644
--- a/Launcher.Next.Autodesk/AutodeskAdapters.cs
+++ b/Launcher.Next.Autodesk/AutodeskAdapters.cs
@@ -7,9 +7,12 @@ public sealed class PlantProductDiscovery(IRegistryReader registry) : IProductDi
{
public ProductInstallation? DiscoverPlant3D(int year)
{
- var install = registry.GetValue($@"SOFTWARE\Autodesk\AutoCAD\R25.1\ACAD-xxxx:409", "InstallDir") as string;
- if (string.IsNullOrWhiteSpace(install)) return null; var exe = Path.Combine(install, "acad.exe");
- return File.Exists(exe) ? new(exe, year.ToString(), "x64", []) : null;
+ if (year != 2026) return null;
+ const string plantInstall = @"SOFTWARE\Autodesk\AutoCAD\R25.1\ACAD-9117\Install";
+ var install = registry.GetValue(plantInstall, "InstallDir") as string;
+ var exe = string.IsNullOrWhiteSpace(install) ? null : Path.Combine(install, "acad.exe");
+ if (exe is null || !File.Exists(exe) || !Directory.Exists(Path.Combine(install!, "PLNT3D"))) return null;
+ return new(exe, "2026", "x64", []);
}
}
public sealed class StartupScriptBuilder : IStartupScriptBuilder
diff --git a/docs/legacy-launcher-capability-matrix.md b/docs/legacy-launcher-capability-matrix.md
new file mode 100644
index 0000000..5d312b5
--- /dev/null
+++ b/docs/legacy-launcher-capability-matrix.md
@@ -0,0 +1,20 @@
+# Legacy launcher capability matrix
+
+Source audit: `All_Serv_Install/Form1.cs`, `F1_Xml.cs`, `F2_Xml.cs`, `All_Serv_Load/Form1.cs`, `All_Serv_InstallUpdater/Form1.cs`, `Control_Sum_XML.cs`, `P_Serv_Starter/Program.cs`, `launcher_starter.lsp`, `Settings.xml`, and `download_list_plant.xml`.
+
+| Legacy capability | User-visible behavior | Modern equivalent | Status |
+| --- | --- | --- | --- |
+| Settings startup validation | Shows configurations only after required XML fields validate | Strict `LegacyConfigurationAdapter.ParseSettings` and UI file selection | SOURCE_VERIFIED / IMPLEMENTED |
+| Pattern selection | Lists pattern name, filter and roster, then enables project/plugin workflow | Launcher Next lists patterns and selected filter/roster | SOURCE_VERIFIED / IMPLEMENTED |
+| Ordered download list | Resolves ordered plugin IDs, sources and renewal aliases | Adapter preserves ordered IDs; overlay join rejects missing/ambiguous IDs | SOURCE_VERIFIED / VERIFIED_BY_TESTS |
+| Plugin copy/install | Recursively copies arbitrary catalog directories and optional install payloads | Verified manifest acquisition to immutable local package only | SOURCE_VERIFIED / UNSUPPORTED_BLOCKING |
+| Update checks | Reconciles server tree using mutable MD5 `control_sums.xml` | No trust is assigned to legacy checksum XML; SHA-256 manifest validation is required | SOURCE_VERIFIED / IGNORED_NON_OPERATIONAL_WITH_REASON |
+| Local active state | Deletes/replaces local catalogs before launch | Materialize, validate, immutable commit and explicit activation | IMPLEMENTED / CONTROLLED_RUNTIME_VERIFIED |
+| Starter LSP | Stages loader, mutates SupportPath and loads DLL/DVB/LISP | Not reproduced: direct legacy payload execution and persistent SupportPath mutation are unsafe | SOURCE_VERIFIED / UNSUPPORTED_BLOCKING |
+| Plant discovery | Legacy BAT starts Plant 3D fire-and-forget | Product-specific ACAD-9117 registry discovery and UI status | SOURCE_VERIFIED / IMPLEMENTED |
+| Interactive launch | Starts BAT without PID/evidence or wait | Requires verified local active package, explicit safe launch plan, PID check and evidence | SOURCE_VERIFIED / UNSUPPORTED_BLOCKING |
+| Error/status UX | WinForms message boxes and partial continuation | Explicit UI ready/block status and diagnostics | SOURCE_VERIFIED / IMPLEMENTED |
+
+## Blocking external inputs
+
+The checked-in legacy settings reference `N:`/`Q:` production sources and legacy binaries. No owner-approved 2026 compatibility overlay, local manifest-backed catalog, or exact package roster is present in this worktree. Therefore enabling Prepare or Launch would either guess mappings or execute unverified legacy payloads; both violate the Launcher invariants.
diff --git a/tests/Launcher.Next.Tests/PlantDiscoveryTests.cs b/tests/Launcher.Next.Tests/PlantDiscoveryTests.cs
new file mode 100644
index 0000000..26649b7
--- /dev/null
+++ b/tests/Launcher.Next.Tests/PlantDiscoveryTests.cs
@@ -0,0 +1,18 @@
+using Launcher.Next.Autodesk;
+using Xunit;
+
+namespace Launcher.Next.Tests;
+
+public sealed class PlantDiscoveryTests
+{
+ private sealed class Registry(string? install) : IRegistryReader
+ { public object? GetValue(string path,string name)=>path.Contains("ACAD-9117",StringComparison.Ordinal)&&name=="InstallDir"?install:null; }
+
+ [Fact]
+ public void Plant2026DiscoveryUsesPlantSpecificRegistryKey()
+ {
+ var directory=Path.Combine(Path.GetTempPath(),Guid.NewGuid().ToString("N"));Directory.CreateDirectory(Path.Combine(directory,"PLNT3D"));File.WriteAllBytes(Path.Combine(directory,"acad.exe"),[]);
+ try { var product=new PlantProductDiscovery(new Registry(directory)).DiscoverPlant3D(2026);Assert.NotNull(product);Assert.Equal("2026",product!.ProductVersion);Assert.Equal(Path.Combine(directory,"acad.exe"),product.ExecutablePath); }
+ finally { Directory.Delete(directory,true); }
+ }
+}