# UXAssist / CheatEnabler / UniverseGenTweaks Refactor Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Improve readability and maintainability of `UXAssist`, `CheatEnabler`, and `UniverseGenTweaks` by splitting oversized files, centralizing constants/localization, cleaning static mutable state, and hardening transpilers. Only `UXAssist.UI` and `UXAssist.Common` public APIs are treated as a stable contract; everything else may be refactored freely, and downstream projects are updated to consume the new structure. **Architecture:** Keep the existing BepInEx + Harmony stack. Introduce a reusable, public mod-feature lifecycle abstraction in `UXAssist.Common.ModFeatures` that replaces the current namespace-based reflection on static `Functions`/`Patches` classes. Each static feature class declares `[ModFeature]` and exposes `Init`/`Start`/`Uninit`/`OnInputUpdate`/`OnUpdate` as needed; `ModFeatureRegistry.Discover` finds and registers them, then drives their lifecycle. `CheatEnabler` and `UniverseGenTweaks` adopt the same abstraction, so they benefit from the same architecture rather than using `InternalsVisibleTo`. Split monolithic `Patches/*.cs` and `Functions/*.cs` files into focused classes grouped by subsystem (e.g., `UXAssist.Patches.Factory.*`). Refactor the internals of `UXAssist.UI` and `UXAssist.Common` into smaller helpers while keeping the existing public surface intact (old members become thin forwarding facades or are marked `[Obsolete]`). Introduce `ConfigProvider` helpers and `GameConstants` classes to decouple UI from patch internals. Clean static state through explicit `ResetState` callbacks registered on `GameLogic.OnGameEnd`. Add version/fallback annotations to transpilers to survive game updates. **Tech Stack:** C# (`net472`), BepInEx 5.x, HarmonyLib, SDK-style MSBuild, PowerShell `Compress-Archive`. --- ## Phase 1 — Structural Split + Public Lifecycle Abstraction ### Task 1: Document the public API surface that must stay stable **Files:** - Create: `docs/PublicApiSurface.md` - [ ] **Step 1: Inventory `UXAssist.UI` and `UXAssist.Common` public members** Read every file under `UXAssist/UI/` and `UXAssist/Common/` and list every `public` class/struct/enum/delegate/method/property/event/field that is reachable from `CheatEnabler` or `UniverseGenTweaks`. At minimum, the list must include: - `UXAssist.Common.I18N` (`Add`, `Apply`, `Translate`, `Init`, `OnInitialized`) - `UXAssist.Common.GameLogic` (`Enable`, `OnDataLoaded`, `OnGameBegin`, `OnGameEnd`, `OnFactoryFrameBegin`) - `UXAssist.Common.PatchImpl` and `PatchGuidAttribute` - `UXAssist.Common.Util` (`GetTypesFiltered`, `GetTypesInNamespace`, `LoadEmbeddedResource`, `LoadEmbeddedTexture`, `LoadEmbeddedSprite`, `PluginFolder`) - `UXAssist.UI.MyConfigWindow` (`OnUICreated`, `OnUpdateUI`, `CreateInstance`, `DestroyInstance`) - `UXAssist.UI.MyWindow` (`InitBaseObject`, `Create`, `AddText`, `AddText2`, `AddButton`, `AddTipsButton`, `AddTipsButton2`, `Open`, `Close`, `TryClose`, `AutoFitWindowSize`) - `UXAssist.UI.MyWindowWithTabs` (`AddTabGroup`, `AddTab`) - `UXAssist.UI.MyCheckBox.CreateCheckBox` - `UXAssist.UI.MySlider.CreateSlider` - `UXAssist.UI.MyWindowManager` (`InitBaseObjects`, `Enable`) - [ ] **Step 2: Commit the inventory** ```bash git add docs/PublicApiSurface.md git commit -m "docs: inventory UXAssist.UI/Common public API surface" ``` --- ### Task 2: Introduce public `ModFeature` lifecycle abstraction **Files:** - Create: `UXAssist/Common/ModFeatures/ModFeatureAttribute.cs` - Create: `UXAssist/Common/ModFeatures/IModFeature.cs` - Create: `UXAssist/Common/ModFeatures/ModFeatureRegistry.cs` - Modify: `UXAssist/Common/Util.cs` - [ ] **Step 1: Add namespace-prefix reflection helper** In `UXAssist/Common/Util.cs`: ```csharp public static Type[] GetTypesInNamespacePrefix(Assembly assembly, string prefix) { return GetTypesFiltered(assembly, t => t.Namespace != null && t.Namespace.StartsWith(prefix, StringComparison.Ordinal)); } ``` - [ ] **Step 2: Define the attribute** ```csharp namespace UXAssist.Common.ModFeatures; [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class ModFeatureAttribute : Attribute { public string Name { get; } public int Order { get; set; } public ModFeatureAttribute(string name = null) { Name = name; } } ``` - [ ] **Step 3: Define the optional instance interface** ```csharp namespace UXAssist.Common.ModFeatures; public interface IModFeature { void Init(); void Start(); void Uninit(); void OnInputUpdate(); void OnUpdate(); } ``` - [ ] **Step 4: Implement the registry** ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace UXAssist.Common.ModFeatures; public static class ModFeatureRegistry { private static readonly List _staticFeatures = []; private static readonly List _instanceFeatures = []; private static readonly HashSet _discoveredAssemblies = []; public static void Discover(Assembly assembly) { if (!_discoveredAssemblies.Add(assembly)) return; var staticTypes = Util.GetTypesFiltered(assembly, t => t.IsClass && t.IsAbstract && t.IsSealed && Attribute.IsDefined(t, typeof(ModFeatureAttribute))); foreach (var type in staticTypes.OrderBy(GetOrder)) { if (!_staticFeatures.Contains(type)) _staticFeatures.Add(type); } } public static void Register() where T : class, IModFeature, new() { var instance = new T(); _instanceFeatures.Add(instance); } public static void InitAll() { ForEachStatic("Init"); foreach (var f in _instanceFeatures) f.Init(); } public static void StartAll() { ForEachStatic("Start"); foreach (var f in _instanceFeatures) f.Start(); } public static void UninitAll() { ForEachStatic("Uninit"); foreach (var f in _instanceFeatures) f.Uninit(); } public static void OnInputUpdateAll() { ForEachStatic("OnInputUpdate"); foreach (var f in _instanceFeatures) f.OnInputUpdate(); } public static void OnUpdateAll() { ForEachStatic("OnUpdate"); foreach (var f in _instanceFeatures) f.OnUpdate(); } private static void ForEachStatic(string methodName) { foreach (var type in _staticFeatures) { var method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); method?.Invoke(null, null); } } private static int GetOrder(Type type) { return type.GetCustomAttribute()?.Order ?? 0; } } ``` - [ ] **Step 5: Build UXAssist** ```bash dotnet build UXAssist/UXAssist.csproj -c Release ``` Expected: 0 errors. - [ ] **Step 6: Commit** ```bash git add UXAssist/Common/ModFeatures/ UXAssist/Common/Util.cs git commit -m "feat(UXAssist): public ModFeature lifecycle registry" ``` --- ### Task 3: Split `UXAssist/Patches/FactoryPatch.cs` and mark features **Files:** - Create: `UXAssist/Patches/Factory/FactoryPatch.cs` (config coordinator) - Create: `UXAssist/Patches/Factory/ImmediateBuildPatch.cs` - Create: `UXAssist/Patches/Factory/ArchitectModePatch.cs` - Create: `UXAssist/Patches/Factory/BuildToolPatch.cs` - Create: `UXAssist/Patches/Factory/BeltSignalPatch.cs` - Create: `UXAssist/Patches/Factory/BuildingBufferPatch.cs` - Create: `UXAssist/Patches/Factory/VeinProtectionPatch.cs` - Create: `UXAssist/Patches/Factory/PowerGenerationPatch.cs` - Create: `UXAssist/Patches/Factory/RenderingPatch.cs` - Delete: `UXAssist/Patches/FactoryPatch.cs` - [ ] **Step 1: Move nested patch classes to new files** Use `git mv` semantics: copy the contents of each nested `PatchImpl` class from the old file into a new file under `UXAssist/Patches/Factory/`. Keep each class `internal` unless it must be public. Use namespace `UXAssist.Patches.Factory`. - [ ] **Step 2: Create the coordinator `FactoryPatch`** Mark it as a mod feature and expose the public `ConfigEntry` fields: ```csharp using UXAssist.Common.ModFeatures; namespace UXAssist.Patches.Factory; [ModFeature("Factory", Order = 10)] public static class FactoryPatch { public static ConfigEntry UnlimitInteractiveEnabled { get; internal set; } // ... all other public ConfigEntry fields from the original file public static void Init() { ImmediateBuildPatch.Init(); ArchitectModePatch.Init(); // ... } public static void Start() { ImmediateBuildPatch.Start(); // ... } public static void Uninit() { ImmediateBuildPatch.Uninit(); // ... } public static void OnInputUpdate() => BeltSignalPatch.OnInputUpdate(); public static void Export(BinaryWriter w) => BeltSignalPatch.Export(w); public static void Import(BinaryReader r) => BeltSignalPatch.Import(r); } ``` `Awake()` in `UXAssist.cs` already assigns every `ConfigEntry`; leave those assignments untouched. - [ ] **Step 3: Build UXAssist** ```bash dotnet build UXAssist/UXAssist.csproj -c Release ``` - [ ] **Step 4: Commit** ```bash git add UXAssist/Patches/Factory/ git rm UXAssist/Patches/FactoryPatch.cs git commit -m "refactor(UXAssist): split FactoryPatch into focused classes" ``` --- ### Task 4: Split `UXAssist/Patches/LogisticsPatch.cs` and mark features **Files:** - Create: `UXAssist/Patches/Logistics/LogisticsPatch.cs` - Create: `UXAssist/Patches/Logistics/AutoConfigPatch.cs` - Create: `UXAssist/Patches/Logistics/CapacityPatch.cs` - Create: `UXAssist/Patches/Logistics/OverflowPatch.cs` - Create: `UXAssist/Patches/Logistics/RealtimeInfoPanelPatch.cs` - Delete: `UXAssist/Patches/LogisticsPatch.cs` - [ ] **Step 1: Move nested classes and create coordinator** Same pattern as Task 3. Coordinator is marked `[ModFeature("Logistics", Order = 11)]` and forwards `OnInputUpdate` / `OnUpdate` to sub-classes. - [ ] **Step 2: Build UXAssist** ```bash dotnet build UXAssist/UXAssist.csproj -c Release ``` - [ ] **Step 3: Commit** ```bash git add UXAssist/Patches/Logistics/ git rm UXAssist/Patches/LogisticsPatch.cs git commit -m "refactor(UXAssist): split LogisticsPatch into focused classes" ``` --- ### Task 5: Split `UXAssist/Functions/UIFunctions.cs` and mark features **Files:** - Create: `UXAssist/Functions/UI/StarmapFilterUI.cs` - Create: `UXAssist/Functions/UI/MilkyWayUI.cs` - Create: `UXAssist/Functions/UI/AutoCruiseUI.cs` - Create: `UXAssist/Functions/UI/MenuButtonUI.cs` - Modify: `UXAssist/Functions/UIFunctions.cs` (coordinator, marked `[ModFeature("UI", Order = 12)]`) - Modify: `UXAssist/UXAssist.cs` - [ ] **Step 1: Move features to sub-files** Namespace `UXAssist.Functions.UI`. Keep `internal` where possible. Keep public `ConfigEntry` fields on `UIFunctions` if bound in `UXAssist.Awake()`. - [ ] **Step 2: Update `UXAssist.Awake()` export/import calls** Replace `UIFunctions.ExportClusterUploadResults(w)` with a coordinator call or direct `MilkyWayUI.Export(w)`. - [ ] **Step 3: Build UXAssist** ```bash dotnet build UXAssist/UXAssist.csproj -c Release ``` - [ ] **Step 4: Commit** ```bash git add UXAssist/Functions/UI/ UXAssist/Functions/UIFunctions.cs UXAssist/UXAssist.cs git commit -m "refactor(UXAssist): split UIFunctions into UI sub-features" ``` --- ### Task 6: Update `UXAssist.cs` to drive features through `ModFeatureRegistry` **Files:** - Modify: `UXAssist/UXAssist.cs` - [ ] **Step 1: Discover features from UXAssist assembly** In `Awake()`, after config binding: ```csharp ModFeatureRegistry.Discover(Assembly.GetExecutingAssembly()); ModFeatureRegistry.InitAll(); ``` - [ ] **Step 2: Replace lifecycle calls** In `Start()`: ```csharp ModFeatureRegistry.StartAll(); ``` In `OnDestroy()`: ```csharp ModFeatureRegistry.UninitAll(); ``` - [ ] **Step 3: Replace hard-coded update calls** In `Update()`: ```csharp if (VFInput.inputing) return; if (DSPGame.IsMenuDemo) { ModFeatureRegistry.OnInputUpdateAll(); return; } ModFeatureRegistry.OnInputUpdateAll(); ModFeatureRegistry.OnUpdateAll(); ``` Remove the explicit per-class calls (`LogisticsPatch.OnInputUpdate()`, `UIFunctions.OnInputUpdate()`, `GamePatch.OnInputUpdate()`, `FactoryPatch.OnInputUpdate()`, `PlayerPatch.OnInputUpdate()`, `LogisticsPatch.OnUpdate()`). - [ ] **Step 4: Keep compat initialization separate** Keep the `ModsCompat` scan in `Awake()` and `Start()`; compat wrappers are not mod features because they receive a `Harmony` argument. - [ ] **Step 5: Centralize save participants** Add a simple registry in `UXAssist.cs`: ```csharp private static readonly List> _exporters = []; private static readonly List> _importers = []; public static void RegisterExporter(Action e) => _exporters.Add(e); public static void RegisterImporter(Action i) => _importers.Add(i); ``` Update `Export`/`Import` to iterate `_exporters`/`_importers`. Remove direct `FactoryPatch.Export` / `UIFunctions.ExportClusterUploadResults` calls; the coordinators register themselves during `Init`. - [ ] **Step 6: Build UXAssist** ```bash dotnet build UXAssist/UXAssist.csproj -c Release ``` - [ ] **Step 7: Commit** ```bash git add UXAssist/UXAssist.cs git commit -m "refactor(UXAssist): drive lifecycle via ModFeatureRegistry" ``` --- ### Task 7: Decouple `UXAssist.UIConfigWindow` from patch internals **Files:** - Create: `UXAssist/Common/Config/FactoryConfigProvider.cs` - Create: `UXAssist/Common/Config/LogisticsConfigProvider.cs` - Modify: `UXAssist/UIConfigWindow.cs` - [ ] **Step 1: Create config providers** Each provider exposes only the `ConfigEntry` references that the UI needs: ```csharp public static class FactoryConfigProvider { public static ConfigEntry NightLightEnabled => Factory.FactoryPatch.NightLightEnabled; // ... } ``` - [ ] **Step 2: Update `UIConfigWindow.cs`** Replace direct references like `FactoryPatch.NightLightEnabled` with `FactoryConfigProvider.NightLightEnabled`. - [ ] **Step 3: Build UXAssist** ```bash dotnet build UXAssist/UXAssist.csproj -c Release ``` - [ ] **Step 4: Commit** ```bash git add UXAssist/Common/Config/ UXAssist/UIConfigWindow.cs git commit -m "refactor(UXAssist): introduce config providers between UI and patches" ``` --- ### Task 8: Adopt `ModFeatureRegistry` in `CheatEnabler` **Files:** - Modify: `CheatEnabler/CheatEnabler.cs` - [ ] **Step 1: Replace exact-namespace reflection with feature discovery** In `Awake()`: ```csharp ModFeatureRegistry.Discover(Assembly.GetExecutingAssembly()); ModFeatureRegistry.InitAll(); ``` In `Start()`: ```csharp ModFeatureRegistry.StartAll(); ``` In `OnDestroy()`: ```csharp ModFeatureRegistry.UninitAll(); ``` In `Update()`: ```csharp if (VFInput.inputing) return; ModFeatureRegistry.OnInputUpdateAll(); ``` Remove `_patches` and the reflection-based `Init`/`Start`/`Uninit` loop. - [ ] **Step 2: Build CheatEnabler** ```bash dotnet build CheatEnabler/CheatEnabler.csproj -c Release ``` - [ ] **Step 3: Commit** ```bash git add CheatEnabler/CheatEnabler.cs git commit -m "refactor(CheatEnabler): adopt ModFeatureRegistry from UXAssist" ``` --- ### Task 9: Split `CheatEnabler/Patches/FactoryPatch.cs` **Files:** - Create: `CheatEnabler/Patches/Factory/FactoryPatch.cs` - Create: `CheatEnabler/Patches/Factory/ImmediateBuildPatch.cs` - Create: `CheatEnabler/Patches/Factory/ArchitectModePatch.cs` - Create: `CheatEnabler/Patches/Factory/BeltSignalPatch.cs` - Create: `CheatEnabler/Patches/Factory/PowerBoostPatch.cs` - Create: `CheatEnabler/Patches/Factory/LogisticsControlPatch.cs` - Delete: `CheatEnabler/Patches/FactoryPatch.cs` - [ ] **Step 1: Apply the coordinator pattern and `[ModFeature]`** Namespace `CheatEnabler.Patches.Factory`. Coordinator exposes the public `ConfigEntry` fields so `CheatEnabler.Awake()` continues to compile without changes. Mark coordinator `[ModFeature("CheatFactory", Order = 10)]`. - [ ] **Step 2: Build CheatEnabler** ```bash dotnet build CheatEnabler/CheatEnabler.csproj -c Release ``` - [ ] **Step 3: Commit** ```bash git add CheatEnabler/Patches/Factory/ git rm CheatEnabler/Patches/FactoryPatch.cs git commit -m "refactor(CheatEnabler): split FactoryPatch into focused classes" ``` --- ### Task 10: Split `CheatEnabler/Functions/DysonSphereFunctions.cs` **Files:** - Create: `CheatEnabler/Functions/DysonSphere/DysonSphereResolver.cs` - Create: `CheatEnabler/Functions/DysonSphere/ShellCompletionFunctions.cs` - Create: `CheatEnabler/Functions/DysonSphere/FrameRemovalFunctions.cs` - Create: `CheatEnabler/Functions/DysonSphere/IllegalShellFunctions.cs` - Create: `CheatEnabler/Functions/DysonSphere/GeometryHelpers.cs` - Modify: `CheatEnabler/Functions/DysonSphereFunctions.cs` (coordinator, or delete if empty) - Modify: `CheatEnabler/Patches/DysonSpherePatch.cs` if it calls helpers - [ ] **Step 1: Extract repeated "current sphere / star" logic** ```csharp [ModFeature("DysonSphereResolver")] public static class DysonSphereResolver { public static (DysonSphere sphere, StarData star)? ResolveCurrent() { var star = GameMain.localStar; if (star == null) return null; var sphere = GameMain.data.dysonSpheres[star.index]; if (sphere == null) return null; return (sphere, star); } } ``` - [ ] **Step 2: Move shell actions to dedicated files** Replace duplicated cleanup blocks with a shared helper: ```csharp public static void NotifyShellChanged(DysonSphere sphere, DysonSphereLayer layer) { layer?.RecalculateModels(); sphere?.swarm?.RecalculateModels(); } ``` - [ ] **Step 3: Build CheatEnabler** ```bash dotnet build CheatEnabler/CheatEnabler.csproj -c Release ``` - [ ] **Step 4: Commit** ```bash git add CheatEnabler/Functions/DysonSphere/ git rm CheatEnabler/Functions/DysonSphereFunctions.cs git commit -m "refactor(CheatEnabler): split DysonSphereFunctions into focused helpers" ``` --- ### Task 11: Adopt `ModFeatureRegistry` in `UniverseGenTweaks` **Files:** - Modify: `UniverseGenTweaks/UniverseGenTweaks.cs` - [ ] **Step 1: Replace explicit Init/Uninit with feature discovery** In `Awake()`: ```csharp ModFeatureRegistry.Discover(Assembly.GetExecutingAssembly()); ModFeatureRegistry.InitAll(); ``` In `OnDestroy()`: ```csharp ModFeatureRegistry.UninitAll(); ``` Remove explicit `MoreSettings.Init()`, `EpicDifficulty.Init()`, `BirthPlanetPatch.Init()` and their `Uninit()` counterparts. - [ ] **Step 2: Build UniverseGenTweaks** ```bash dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release ``` - [ ] **Step 3: Commit** ```bash git add UniverseGenTweaks/UniverseGenTweaks.cs git commit -m "refactor(UniverseGenTweaks): adopt ModFeatureRegistry from UXAssist" ``` --- ### Task 12: Split `UniverseGenTweaks/MoreSettings.cs` **Files:** - Create: `UniverseGenTweaks/Patches/GalaxyGenSettingsPatch.cs` - Create: `UniverseGenTweaks/Patches/GalaxySelectUIPatch.cs` - Create: `UniverseGenTweaks/Patches/CombatSettingsPatch.cs` - Create: `UniverseGenTweaks/Functions/GalaxyGenSave.cs` - Modify: `UniverseGenTweaks/MoreSettings.cs` (coordinator, or delete) - [ ] **Step 1: Move UI construction to `GalaxySelectUIPatch`** Mark it `[ModFeature("GalaxySelectUI")]`. Keep slider/text creation helpers there. - [ ] **Step 2: Move transpilers to `GalaxyGenSettingsPatch`** Mark it `[ModFeature("GalaxyGenSettings")]`. Keep the `Init()`/`Uninit()` pattern and the `Harmony` instance local to this class. - [ ] **Step 3: Move combat settings to `CombatSettingsPatch`** Mark it `[ModFeature("CombatSettings")]`. Replace the seven near-identical slider-changed prefix methods with a table-driven mapper. - [ ] **Step 4: Move save serialization to `GalaxyGenSave`** Mark it `[ModFeature("GalaxyGenSave")]`. Implement `Export(BinaryWriter)` / `Import(BinaryReader)` and call them from `UniverseGenTweaks.Export`/`Import`. - [ ] **Step 5: Build UniverseGenTweaks** ```bash dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release ``` - [ ] **Step 6: Commit** ```bash git add UniverseGenTweaks/Patches/ UniverseGenTweaks/Functions/ git rm UniverseGenTweaks/MoreSettings.cs git commit -m "refactor(UniverseGenTweaks): split MoreSettings into focused classes" ``` --- ### Task 13: Phase 1 cross-project build verification - [ ] **Step 1: Clean and build all three projects** ```bash dotnet clean UXAssist/UXAssist.csproj -c Release dotnet clean CheatEnabler/CheatEnabler.csproj -c Release dotnet clean UniverseGenTweaks/UniverseGenTweaks.csproj -c Release dotnet build UXAssist/UXAssist.csproj -c Release dotnet build CheatEnabler/CheatEnabler.csproj -c Release dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release ``` Expected: all three succeed with no compilation errors. - [ ] **Step 2: Produce packages** ```bash dotnet build -t:ZipMod -c Release dotnet build -t:CopyToParentPackage -c Release ``` Expected: `UXAssist/package/`, `CheatEnabler/package/`, `UniverseGenTweaks/package/`, and `Dustbin/package/patchers/` (if building the full solution) are generated. - [ ] **Step 3: Public API diff check** Use `dotnet build` output or `ildasm` to verify that every member listed in `docs/PublicApiSurface.md` still exists with the same signature. - [ ] **Step 4: Commit a Phase 1 checkpoint tag** ```bash git tag refactor-phase1 ``` --- ## Phase 2 — UI / Common Internal Refactoring (public surface preserved) ### Task 14: Split `UXAssist/Common/Util.cs` into focused helpers **Files:** - Create: `UXAssist/Common/Util/ReflectionUtil.cs` - Create: `UXAssist/Common/Util/ResourceUtil.cs` - Create: `UXAssist/Common/Util/PathUtil.cs` - Modify: `UXAssist/Common/Util.cs` - [ ] **Step 1: Move implementations to focused helpers** `ReflectionUtil.cs`: ```csharp public static class ReflectionUtil { public static Type[] GetTypesFiltered(Assembly assembly, Func predicate) { ... } public static Type[] GetTypesInNamespace(Assembly assembly, string nameSpace) { ... } public static Type[] GetTypesInNamespacePrefix(Assembly assembly, string prefix) { ... } } ``` `ResourceUtil.cs`: ```csharp public static class ResourceUtil { public static byte[] LoadEmbeddedResource(...) { ... } public static Texture2D LoadEmbeddedTexture(...) { ... } public static Sprite LoadEmbeddedSprite(...) { ... } } ``` `PathUtil.cs`: ```csharp public static class PathUtil { public static string PluginFolder(Assembly assembly = null) { ... } } ``` - [ ] **Step 2: Keep `Util` as a public forwarding facade** ```csharp public static class Util { [Obsolete("Use ReflectionUtil.GetTypesFiltered")] public static Type[] GetTypesFiltered(Assembly assembly, Func predicate) => ReflectionUtil.GetTypesFiltered(assembly, predicate); [Obsolete("Use ReflectionUtil.GetTypesInNamespace")] public static Type[] GetTypesInNamespace(Assembly assembly, string nameSpace) => ReflectionUtil.GetTypesInNamespace(assembly, nameSpace); [Obsolete("Use ResourceUtil.LoadEmbeddedResource")] public static byte[] LoadEmbeddedResource(string path, Assembly assembly = null) => ResourceUtil.LoadEmbeddedResource(path, assembly); // ... forward all other existing methods } ``` New code inside UXAssist may call `ReflectionUtil`/`ResourceUtil`/`PathUtil` directly. - [ ] **Step 3: Build UXAssist** ```bash dotnet build UXAssist/UXAssist.csproj -c Release ``` - [ ] **Step 4: Commit** ```bash git add UXAssist/Common/Util/ git commit -m "refactor(UXAssist): split Util into focused helpers with forwarding facade" ``` --- ### Task 15: Refactor `UXAssist/Common/GameLogic.cs` event invocation **Files:** - Create: `UXAssist/Common/GameEvent.cs` - Modify: `UXAssist/Common/GameLogic.cs` - [ ] **Step 1: Introduce a small safe-event wrapper** ```csharp public static class GameEvent { public static void InvokeSafe(this Action action, ManualLogSource logger, string name) { if (action == null) return; foreach (var d in action.GetInvocationList()) { try { d.DynamicInvoke(); } catch (Exception ex) { logger?.LogWarning($"GameEvent '{name}' handler failed: {ex}"); } } } } ``` - [ ] **Step 2: Use the wrapper inside `GameLogic`** Keep the public event fields unchanged. Replace manual invocation loops with `OnDataLoaded.InvokeSafe(UXAssist.Logger, nameof(OnDataLoaded));`. - [ ] **Step 3: Add XML documentation to all public members** - [ ] **Step 4: Build UXAssist** ```bash dotnet build UXAssist/UXAssist.csproj -c Release ``` - [ ] **Step 5: Commit** ```bash git add UXAssist/Common/GameEvent.cs UXAssist/Common/GameLogic.cs git commit -m "refactor(UXAssist): safe GameEvent wrapper and XML docs" ``` --- ### Task 16: Refactor `UXAssist/Common/I18N.cs` internals **Files:** - Modify: `UXAssist/Common/I18N.cs` - [ ] **Step 1: Preserve public API** Keep `Add`, `Apply`, `Translate`, `Init`, `OnInitialized` signatures exactly as they are. - [ ] **Step 2: Internal cleanup** Split the internal storage into a private `LocalizedString` record and a dictionary keyed by the English key. Add a public convenience overload: ```csharp public static void Add(string key, string en, string zh) ``` (this is the existing signature; keep it). - [ ] **Step 3: Build UXAssist** ```bash dotnet build UXAssist/UXAssist.csproj -c Release ``` - [ ] **Step 4: Commit** ```bash git add UXAssist/Common/I18N.cs git commit -m "refactor(UXAssist): clean up I18N internals without changing public API" ``` --- ### Task 17: Extract layout helpers from `UXAssist/UI/MyWindow.cs` **Files:** - Create: `UXAssist/UI/LayoutHelper.cs` - Modify: `UXAssist/UI/MyWindow.cs` - [ ] **Step 1: Move static layout helpers** Move `AddText`, `AddTipsButton`, `AddButton` (static overloads), and `AddElement` into `LayoutHelper`. ```csharp public static class LayoutHelper { public static Text AddText(float x, float y, RectTransform parent, string label, int fontSize = 14, string objName = "label") { ... } public static UIButton AddTipsButton(...) { ... } public static UIButton AddButton(...) { ... } } ``` - [ ] **Step 2: Keep `MyWindow` instance methods as forwarding facades** ```csharp public Text AddText2(float x, float y, RectTransform parent, string label, int fontSize = 14, string objName = "label") { var text = LayoutHelper.AddText(x, y, parent, label, fontSize, objName); _maxX = Math.Max(_maxX, x + text.rectTransform.sizeDelta.x); MaxY = Math.Max(MaxY, y + text.rectTransform.sizeDelta.y); return text; } ``` Mark the public static helpers on `MyWindow` as `[Obsolete("Use LayoutHelper")]` if desired, but keep them working. - [ ] **Step 3: Build UXAssist** ```bash dotnet build UXAssist/UXAssist.csproj -c Release ``` - [ ] **Step 4: Commit** ```bash git add UXAssist/UI/LayoutHelper.cs UXAssist/UI/MyWindow.cs git commit -m "refactor(UXAssist): extract UI layout helpers from MyWindow" ``` --- ### Task 18: Refactor `UXAssist/UI/MyConfigWindow.cs` tab management **Files:** - Create: `UXAssist/UI/ConfigTabGroup.cs` - Modify: `UXAssist/UI/MyConfigWindow.cs` - Modify: `UXAssist/UI/MyWindowWithTabs.cs` - [ ] **Step 1: Extract tab group logic** Move the data structure that tracks tab groups into `ConfigTabGroup` so `MyConfigWindow` does not mix tab state with window lifecycle. - [ ] **Step 2: Preserve public events and methods** Keep `OnUICreated`, `OnUpdateUI`, `CreateInstance`, and `DestroyInstance` on `MyConfigWindow` with identical signatures. - [ ] **Step 3: Build UXAssist** ```bash dotnet build UXAssist/UXAssist.csproj -c Release ``` - [ ] **Step 4: Commit** ```bash git add UXAssist/UI/ConfigTabGroup.cs UXAssist/UI/MyConfigWindow.cs UXAssist/UI/MyWindowWithTabs.cs git commit -m "refactor(UXAssist): split tab management out of MyConfigWindow" ``` --- ### Task 19: Phase 2 cross-project build verification - [ ] **Step 1: Build all three projects** ```bash dotnet build UXAssist/UXAssist.csproj -c Release dotnet build CheatEnabler/CheatEnabler.csproj -c Release dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release ``` - [ ] **Step 2: Public API diff check** Confirm every member in `docs/PublicApiSurface.md` still exists with the same signature. New `[Obsolete]` facades are acceptable. - [ ] **Step 3: Commit a Phase 2 checkpoint tag** ```bash git tag refactor-phase2 ``` --- ## Phase 3 — Constants & Localization ### Task 20: Create centralized constants files **Files:** - Create: `UXAssist/Common/GameConstants/ItemIds.cs` - Create: `UXAssist/Common/GameConstants/TechIds.cs` - Create: `UXAssist/Common/GameConstants/LogisticsConstants.cs` - Create: `UXAssist/Common/GameConstants/DysonSphereConstants.cs` - Create: `UXAssist/Common/GameConstants/UniverseGenConstants.cs` - [ ] **Step 1: Extract item IDs** From `CheatEnabler/Patches/Factory/*.cs` and `UXAssist/Patches/Factory/BeltSignalPatch.cs`, collect hard-coded item IDs: ```csharp public static class ItemIds { public const int IronOre = 1001; public const int CopperOre = 1002; // ... } ``` - [ ] **Step 2: Extract tech IDs** From `UXAssist/Patches/TechPatch.cs`: ```csharp public static class TechIds { public const int SorterCargoStacking = 3608; public static readonly HashSet CombatTechs = [3301, 3302, ...]; } ``` - [ ] **Step 3: Extract logistics constants** From `UXAssist/Patches/Logistics/CapacityPatch.cs`: ```csharp public static class LogisticsConstants { public const int DefaultLocalStorageMax = 5000; public const int DefaultRemoteStorageMax = 10000; } ``` - [ ] **Step 4: Build UXAssist** ```bash dotnet build UXAssist/UXAssist.csproj -c Release ``` - [ ] **Step 5: Commit** ```bash git add UXAssist/Common/GameConstants/ git commit -m "refactor: centralize game constants" ``` --- ### Task 21: Replace magic numbers in UXAssist **Files:** - Modify: `UXAssist/Patches/Factory/*.cs` - Modify: `UXAssist/Patches/Logistics/*.cs` - Modify: `UXAssist/Patches/TechPatch.cs` - Modify: `UXAssist/Patches/DysonSpherePatch.cs` - [ ] **Step 1: Replace literal IDs and capacities with constants** For example, change: ```csharp if (itemId == 1001) { ... } ``` to: ```csharp if (itemId == ItemIds.IronOre) { ... } ``` - [ ] **Step 2: Build UXAssist** ```bash dotnet build UXAssist/UXAssist.csproj -c Release ``` - [ ] **Step 3: Commit** ```bash git add UXAssist/Patches/ git commit -m "refactor(UXAssist): replace magic numbers with constants" ``` --- ### Task 22: Replace magic numbers in CheatEnabler and UniverseGenTweaks **Files:** - Modify: `CheatEnabler/Patches/Factory/*.cs` - Modify: `CheatEnabler/Functions/DysonSphere/*.cs` - Modify: `CheatEnabler/Functions/PlanetFunctions.cs` - Modify: `UniverseGenTweaks/Patches/GalaxyGenSettingsPatch.cs` - Modify: `UniverseGenTweaks/Patches/CombatSettingsPatch.cs` - Modify: `UniverseGenTweaks/BirthPlanetPatch.cs` - [ ] **Step 1: Reference UXAssist constants where appropriate** CheatEnabler/UniverseGenTweaks already reference `UXAssist.csproj`, so they can use `UXAssist.Common.GameConstants.ItemIds`, etc. - [ ] **Step 2: Build both projects** ```bash dotnet build CheatEnabler/CheatEnabler.csproj -c Release dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release ``` - [ ] **Step 3: Commit** ```bash git add CheatEnabler/ UniverseGenTweaks/ git commit -m "refactor(CheatEnabler/UniverseGen): consume centralized constants" ``` --- ### Task 23: Localization key governance **Files:** - Modify: `UXAssist/UIConfigWindow.cs` - Modify: `CheatEnabler/UIConfigWindow.cs` - Modify: `UniverseGenTweaks/UIConfigWindow.cs` - Modify: `CheatEnabler/Functions/PlayerFunctions.cs` - Modify: `UXAssist/Patches/Factory/BeltSignalPatch.cs` - [ ] **Step 1: Replace hard-coded Chinese `.Translate()` keys with `I18N.Add` entries** Example: ```csharp // Before var btn = MyWindow.AddButton(..., "确定", ...); // After I18N.Add("OK", "OK", "确定"); var btn = MyWindow.AddButton(..., "OK", ...); ``` - [ ] **Step 2: Translate Chinese comments to English** Run a search for `// ` followed by CJK characters and translate or delete stale comments. ```bash grep -RInP '//.*[\x{4e00}-\x{9fff}]' UXAssist/ CheatEnabler/ UniverseGenTweaks/ --include='*.cs' || true ``` - [ ] **Step 3: Build all three projects** ```bash dotnet build UXAssist/UXAssist.csproj -c Release dotnet build CheatEnabler/CheatEnabler.csproj -c Release dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release ``` - [ ] **Step 4: Commit** ```bash git add UXAssist/ CheatEnabler/ UniverseGenTweaks/ git commit -m "refactor: localize hard-coded strings and translate comments" ``` --- ## Phase 4 — Static State & Lifecycle Cleanup ### Task 24: Inventory static mutable state **Files:** - Create: `docs/StaticStateInventory.md` - [ ] **Step 1: List all static mutable fields in the three projects** Search: ```bash grep -RInP 'private static (?!readonly)\S+' UXAssist/Patches/ UXAssist/Functions/ CheatEnabler/Patches/ CheatEnabler/Functions/ UniverseGenTweaks/ --include='*.cs' > docs/StaticStateInventory.md grep -RInP 'public static (?!readonly)\S+' UXAssist/Patches/ UXAssist/Functions/ CheatEnabler/Patches/ CheatEnabler/Functions/ UniverseGenTweaks/ --include='*.cs' >> docs/StaticStateInventory.md ``` - [ ] **Step 2: Classify each field by subsystem** Mark each as: - `LifecycleSafe` — read-only after `Awake` - `NeedsReset` — must be cleared on `GameLogic.OnGameEnd` - `CandidateForInstancing` — should be owned by a per-game context class - [ ] **Step 3: Commit** ```bash git add docs/StaticStateInventory.md git commit -m "docs: inventory static mutable state" ``` --- ### Task 25: Add lifecycle reset callbacks **Files:** - Modify: `UXAssist/Patches/Factory/BeltSignalPatch.cs` - Modify: `UXAssist/Patches/Factory/VeinProtectionPatch.cs` - Modify: `UXAssist/Patches/DysonSpherePatch.cs` - Modify: `UXAssist/Functions/UI/MilkyWayUI.cs` - Modify: `CheatEnabler/Patches/Factory/BeltSignalPatch.cs` - Modify: `UniverseGenTweaks/Patches/GalaxySelectUIPatch.cs` - [ ] **Step 1: Add `ResetState` methods** Example: ```csharp internal static void ResetState() { _signalBelts = null; _someCache.Clear(); } ``` - [ ] **Step 2: Register resets in `Init`** ```csharp public static void Init() { GameLogic.OnGameEnd += ResetState; // ... } ``` - [ ] **Step 3: Build all three projects** ```bash dotnet build UXAssist/UXAssist.csproj -c Release dotnet build CheatEnabler/CheatEnabler.csproj -c Release dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release ``` - [ ] **Step 4: Commit** ```bash git add UXAssist/ CheatEnabler/ UniverseGenTweaks/ git commit -m "refactor: register static-state resets on game end" ``` --- ## Phase 5 — Transpiler Robustness & Quality Gates ### Task 26: Annotate transpilers with target version and fallback **Files:** - Modify: all `*Transpiler` methods in `UXAssist/Patches/`, `CheatEnabler/Patches/`, `UniverseGenTweaks/Patches/` - [ ] **Step 1: Add header comments** ```csharp // Target game version: 0.10.34.28505 // Patches: EjectorComponent.InternalUpdate // Falls back to original IL if the pattern is not matched. private static IEnumerable ... ``` - [ ] **Step 2: Wrap `CodeMatcher` finalization** Replace bare `.InstructionEnumeration()` with: ```csharp return matcher.ReportFailure(original, Logger)?.InstructionEnumeration() ?? instructions; ``` Implement extension: ```csharp public static CodeMatcher ReportFailure(this CodeMatcher matcher, MethodBase original, ManualLogSource logger) { if (matcher.IsValid) return matcher; logger?.LogWarning($"Transpiler failed for {original.DeclaringType?.Name}.{original.Name}"); return null; } ``` - [ ] **Step 3: Build all three projects** ```bash dotnet build UXAssist/UXAssist.csproj -c Release dotnet build CheatEnabler/CheatEnabler.csproj -c Release dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release ``` - [ ] **Step 4: Commit** ```bash git add UXAssist/ CheatEnabler/ UniverseGenTweaks/ git commit -m "refactor: add transpiler version/fallback annotations" ``` --- ### Task 27: Centralize third-party compat reflection targets **Files:** - Create: `UXAssist/ModsCompat/CompatTargets.cs` - Modify: `UXAssist/ModsCompat/AuxilaryfunctionWrapper.cs` - Modify: `UXAssist/ModsCompat/BulletTimeWrapper.cs` - Modify: `UXAssist/ModsCompat/BlueprintTweaks.cs` - [ ] **Step 1: Extract magic strings** ```csharp internal static class CompatTargets { public const string Auxilaryfunction = "auxilaryfunction.Auxilaryfunction"; public const string SpeedUpPatch = "Auxilaryfunction.SpeedUpPatch"; // ... } ``` - [ ] **Step 2: Replace literal strings with constants** - [ ] **Step 3: Build UXAssist** ```bash dotnet build UXAssist/UXAssist.csproj -c Release ``` - [ ] **Step 4: Commit** ```bash git add UXAssist/ModsCompat/ git commit -m "refactor(UXAssist): centralize compat reflection targets" ``` --- ### Task 28: Add `.editorconfig` and run `dotnet format` **Files:** - Create: `.editorconfig` - Modify: all touched `.cs` files (format-only changes) - [ ] **Step 1: Create `.editorconfig`** Minimal content: ```ini root = true [*.cs] indent_style = space indent_size = 4 charset = utf-8-bom end_of_line = crlf insert_final_newline = true dotnet_sort_system_directives_first = true dotnet_separate_import_directive_groups = false ``` - [ ] **Step 2: Run format** ```bash dotnet format UXAssist/UXAssist.csproj dotnet format CheatEnabler/CheatEnabler.csproj dotnet format UniverseGenTweaks/UniverseGenTweaks.csproj ``` - [ ] **Step 3: Build all three projects** ```bash dotnet build UXAssist/UXAssist.csproj -c Release dotnet build CheatEnabler/CheatEnabler.csproj -c Release dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release ``` - [ ] **Step 4: Commit** ```bash git add .editorconfig git add -u UXAssist/ CheatEnabler/ UniverseGenTweaks/ git commit -m "style: add editorconfig and run dotnet format" ``` --- ## Final Verification - [ ] **Step 1: Full solution build** ```bash dotnet build DSP_Mods.sln -c Release ``` Expected: 0 errors. - [ ] **Step 2: Package all mods** ```bash dotnet build -t:ZipMod -c Release dotnet build -t:CopyToParentPackage -c Release ``` Expected: all `package/` outputs generated. - [ ] **Step 3: Public API verification** Re-run the `docs/PublicApiSurface.md` checklist and confirm every listed member still exists. - [ ] **Step 4: Update `AGENTS.md`** If any project conventions changed (e.g., new folder structure, naming rules, `.editorconfig`), update `AGENTS.md` accordingly. - [ ] **Step 5: Final commit / tag** ```bash git tag refactor-complete ``` --- ## Self-Review Checklist - [ ] Every task references exact file paths. - [ ] No `TODO`, `TBD`, or placeholder steps remain. - [ ] Public API contract (`UXAssist.UI` + `UXAssist.Common`) is preserved throughout. - [ ] `CheatEnabler` and `UniverseGenTweaks` compile after each phase. - [ ] Code snippets use types/methods defined in earlier tasks.