40 KiB
UXAssist / CheatEnabler / UniverseGenTweaks Refactor Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use
superpowers:subagent-driven-development(recommended) orsuperpowers:executing-plansto 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.UIandUXAssist.Commonpublic membersRead every file under
UXAssist/UI/andUXAssist/Common/and list everypublicclass/struct/enum/delegate/method/property/event/field that is reachable fromCheatEnablerorUniverseGenTweaks.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<T>andPatchGuidAttributeUXAssist.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.CreateCheckBoxUXAssist.UI.MySlider.CreateSliderUXAssist.UI.MyWindowManager(InitBaseObjects,Enable)
-
Step 2: Commit the inventory
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: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
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
namespace UXAssist.Common.ModFeatures; public interface IModFeature { void Init(); void Start(); void Uninit(); void OnInputUpdate(); void OnUpdate(); } -
Step 4: Implement the registry
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace UXAssist.Common.ModFeatures; public static class ModFeatureRegistry { private static readonly List<Type> _staticFeatures = []; private static readonly List<IModFeature> _instanceFeatures = []; private static readonly HashSet<Assembly> _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<T>() 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<ModFeatureAttribute>()?.Order ?? 0; } } -
Step 5: Build UXAssist
dotnet build UXAssist/UXAssist.csproj -c ReleaseExpected: 0 errors.
-
Step 6: Commit
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 mvsemantics: copy the contents of each nestedPatchImpl<T>class from the old file into a new file underUXAssist/Patches/Factory/. Keep each classinternalunless it must be public. Use namespaceUXAssist.Patches.Factory. -
Step 2: Create the coordinator
FactoryPatchMark it as a mod feature and expose the public
ConfigEntry<T>fields:using UXAssist.Common.ModFeatures; namespace UXAssist.Patches.Factory; [ModFeature("Factory", Order = 10)] public static class FactoryPatch { public static ConfigEntry<bool> 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()inUXAssist.csalready assigns everyConfigEntry; leave those assignments untouched. -
Step 3: Build UXAssist
dotnet build UXAssist/UXAssist.csproj -c Release -
Step 4: Commit
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 forwardsOnInputUpdate/OnUpdateto sub-classes. -
Step 2: Build UXAssist
dotnet build UXAssist/UXAssist.csproj -c Release -
Step 3: Commit
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. Keepinternalwhere possible. Keep publicConfigEntry<T>fields onUIFunctionsif bound inUXAssist.Awake(). -
Step 2: Update
UXAssist.Awake()export/import callsReplace
UIFunctions.ExportClusterUploadResults(w)with a coordinator call or directMilkyWayUI.Export(w). -
Step 3: Build UXAssist
dotnet build UXAssist/UXAssist.csproj -c Release -
Step 4: Commit
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:ModFeatureRegistry.Discover(Assembly.GetExecutingAssembly()); ModFeatureRegistry.InitAll(); -
Step 2: Replace lifecycle calls
In
Start():ModFeatureRegistry.StartAll();In
OnDestroy():ModFeatureRegistry.UninitAll(); -
Step 3: Replace hard-coded update calls
In
Update():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
ModsCompatscan inAwake()andStart(); compat wrappers are not mod features because they receive aHarmonyargument. -
Step 5: Centralize save participants
Add a simple registry in
UXAssist.cs:private static readonly List<Action<BinaryWriter>> _exporters = []; private static readonly List<Action<BinaryReader>> _importers = []; public static void RegisterExporter(Action<BinaryWriter> e) => _exporters.Add(e); public static void RegisterImporter(Action<BinaryReader> i) => _importers.Add(i);Update
Export/Importto iterate_exporters/_importers. Remove directFactoryPatch.Export/UIFunctions.ExportClusterUploadResultscalls; the coordinators register themselves duringInit. -
Step 6: Build UXAssist
dotnet build UXAssist/UXAssist.csproj -c Release -
Step 7: Commit
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<T>references that the UI needs:public static class FactoryConfigProvider { public static ConfigEntry<bool> NightLightEnabled => Factory.FactoryPatch.NightLightEnabled; // ... } -
Step 2: Update
UIConfigWindow.csReplace direct references like
FactoryPatch.NightLightEnabledwithFactoryConfigProvider.NightLightEnabled. -
Step 3: Build UXAssist
dotnet build UXAssist/UXAssist.csproj -c Release -
Step 4: Commit
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():ModFeatureRegistry.Discover(Assembly.GetExecutingAssembly()); ModFeatureRegistry.InitAll();In
Start():ModFeatureRegistry.StartAll();In
OnDestroy():ModFeatureRegistry.UninitAll();In
Update():if (VFInput.inputing) return; ModFeatureRegistry.OnInputUpdateAll();Remove
_patchesand the reflection-basedInit/Start/Uninitloop. -
Step 2: Build CheatEnabler
dotnet build CheatEnabler/CheatEnabler.csproj -c Release -
Step 3: Commit
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 publicConfigEntry<T>fields soCheatEnabler.Awake()continues to compile without changes. Mark coordinator[ModFeature("CheatFactory", Order = 10)]. -
Step 2: Build CheatEnabler
dotnet build CheatEnabler/CheatEnabler.csproj -c Release -
Step 3: Commit
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.csif it calls helpers -
Step 1: Extract repeated "current sphere / star" logic
[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:
public static void NotifyShellChanged(DysonSphere sphere, DysonSphereLayer layer) { layer?.RecalculateModels(); sphere?.swarm?.RecalculateModels(); } -
Step 3: Build CheatEnabler
dotnet build CheatEnabler/CheatEnabler.csproj -c Release -
Step 4: Commit
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():ModFeatureRegistry.Discover(Assembly.GetExecutingAssembly()); ModFeatureRegistry.InitAll();In
OnDestroy():ModFeatureRegistry.UninitAll();Remove explicit
MoreSettings.Init(),EpicDifficulty.Init(),BirthPlanetPatch.Init()and theirUninit()counterparts. -
Step 2: Build UniverseGenTweaks
dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release -
Step 3: Commit
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
GalaxySelectUIPatchMark it
[ModFeature("GalaxySelectUI")]. Keep slider/text creation helpers there. -
Step 2: Move transpilers to
GalaxyGenSettingsPatchMark it
[ModFeature("GalaxyGenSettings")]. Keep theInit()/Uninit()pattern and theHarmonyinstance local to this class. -
Step 3: Move combat settings to
CombatSettingsPatchMark it
[ModFeature("CombatSettings")]. Replace the seven near-identical slider-changed prefix methods with a table-driven mapper. -
Step 4: Move save serialization to
GalaxyGenSaveMark it
[ModFeature("GalaxyGenSave")]. ImplementExport(BinaryWriter)/Import(BinaryReader)and call them fromUniverseGenTweaks.Export/Import. -
Step 5: Build UniverseGenTweaks
dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release -
Step 6: Commit
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
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 ReleaseExpected: all three succeed with no compilation errors.
-
Step 2: Produce packages
dotnet build -t:ZipMod -c Release dotnet build -t:CopyToParentPackage -c ReleaseExpected:
UXAssist/package/,CheatEnabler/package/,UniverseGenTweaks/package/, andDustbin/package/patchers/(if building the full solution) are generated. -
Step 3: Public API diff check
Use
dotnet buildoutput orildasmto verify that every member listed indocs/PublicApiSurface.mdstill exists with the same signature. -
Step 4: Commit a Phase 1 checkpoint tag
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:public static class ReflectionUtil { public static Type[] GetTypesFiltered(Assembly assembly, Func<Type, bool> predicate) { ... } public static Type[] GetTypesInNamespace(Assembly assembly, string nameSpace) { ... } public static Type[] GetTypesInNamespacePrefix(Assembly assembly, string prefix) { ... } }ResourceUtil.cs:public static class ResourceUtil { public static byte[] LoadEmbeddedResource(...) { ... } public static Texture2D LoadEmbeddedTexture(...) { ... } public static Sprite LoadEmbeddedSprite(...) { ... } }PathUtil.cs:public static class PathUtil { public static string PluginFolder(Assembly assembly = null) { ... } } -
Step 2: Keep
Utilas a public forwarding facadepublic static class Util { [Obsolete("Use ReflectionUtil.GetTypesFiltered")] public static Type[] GetTypesFiltered(Assembly assembly, Func<Type, bool> 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/PathUtildirectly. -
Step 3: Build UXAssist
dotnet build UXAssist/UXAssist.csproj -c Release -
Step 4: Commit
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
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
GameLogicKeep 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
dotnet build UXAssist/UXAssist.csproj -c Release -
Step 5: Commit
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,OnInitializedsignatures exactly as they are. -
Step 2: Internal cleanup
Split the internal storage into a private
LocalizedStringrecord and a dictionary keyed by the English key. Add a public convenience overload:public static void Add(string key, string en, string zh)(this is the existing signature; keep it).
-
Step 3: Build UXAssist
dotnet build UXAssist/UXAssist.csproj -c Release -
Step 4: Commit
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), andAddElementintoLayoutHelper.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
MyWindowinstance methods as forwarding facadespublic 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
MyWindowas[Obsolete("Use LayoutHelper")]if desired, but keep them working. -
Step 3: Build UXAssist
dotnet build UXAssist/UXAssist.csproj -c Release -
Step 4: Commit
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
ConfigTabGroupsoMyConfigWindowdoes not mix tab state with window lifecycle. -
Step 2: Preserve public events and methods
Keep
OnUICreated,OnUpdateUI,CreateInstance, andDestroyInstanceonMyConfigWindowwith identical signatures. -
Step 3: Build UXAssist
dotnet build UXAssist/UXAssist.csproj -c Release -
Step 4: Commit
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
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.mdstill exists with the same signature. New[Obsolete]facades are acceptable. -
Step 3: Commit a Phase 2 checkpoint tag
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/*.csandUXAssist/Patches/Factory/BeltSignalPatch.cs, collect hard-coded item IDs:public static class ItemIds { public const int IronOre = 1001; public const int CopperOre = 1002; // ... } -
Step 2: Extract tech IDs
From
UXAssist/Patches/TechPatch.cs:public static class TechIds { public const int SorterCargoStacking = 3608; public static readonly HashSet<int> CombatTechs = [3301, 3302, ...]; } -
Step 3: Extract logistics constants
From
UXAssist/Patches/Logistics/CapacityPatch.cs:public static class LogisticsConstants { public const int DefaultLocalStorageMax = 5000; public const int DefaultRemoteStorageMax = 10000; } -
Step 4: Build UXAssist
dotnet build UXAssist/UXAssist.csproj -c Release -
Step 5: Commit
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:
if (itemId == 1001) { ... }to:
if (itemId == ItemIds.IronOre) { ... } -
Step 2: Build UXAssist
dotnet build UXAssist/UXAssist.csproj -c Release -
Step 3: Commit
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 useUXAssist.Common.GameConstants.ItemIds, etc. -
Step 2: Build both projects
dotnet build CheatEnabler/CheatEnabler.csproj -c Release dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release -
Step 3: Commit
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 withI18N.AddentriesExample:
// 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.grep -RInP '//.*[\x{4e00}-\x{9fff}]' UXAssist/ CheatEnabler/ UniverseGenTweaks/ --include='*.cs' || true -
Step 3: Build all three projects
dotnet build UXAssist/UXAssist.csproj -c Release dotnet build CheatEnabler/CheatEnabler.csproj -c Release dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release -
Step 4: Commit
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:
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 afterAwakeNeedsReset— must be cleared onGameLogic.OnGameEndCandidateForInstancing— should be owned by a per-game context class
-
Step 3: Commit
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
ResetStatemethodsExample:
internal static void ResetState() { _signalBelts = null; _someCache.Clear(); } -
Step 2: Register resets in
Initpublic static void Init() { GameLogic.OnGameEnd += ResetState; // ... } -
Step 3: Build all three projects
dotnet build UXAssist/UXAssist.csproj -c Release dotnet build CheatEnabler/CheatEnabler.csproj -c Release dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release -
Step 4: Commit
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
*Transpilermethods inUXAssist/Patches/,CheatEnabler/Patches/,UniverseGenTweaks/Patches/ -
Step 1: Add header comments
// Target game version: 0.10.34.28505 // Patches: EjectorComponent.InternalUpdate // Falls back to original IL if the pattern is not matched. private static IEnumerable<CodeInstruction> ... -
Step 2: Wrap
CodeMatcherfinalizationReplace bare
.InstructionEnumeration()with:return matcher.ReportFailure(original, Logger)?.InstructionEnumeration() ?? instructions;Implement extension:
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
dotnet build UXAssist/UXAssist.csproj -c Release dotnet build CheatEnabler/CheatEnabler.csproj -c Release dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release -
Step 4: Commit
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
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
dotnet build UXAssist/UXAssist.csproj -c Release -
Step 4: Commit
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
.csfiles (format-only changes) -
Step 1: Create
.editorconfigMinimal content:
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
dotnet format UXAssist/UXAssist.csproj dotnet format CheatEnabler/CheatEnabler.csproj dotnet format UniverseGenTweaks/UniverseGenTweaks.csproj -
Step 3: Build all three projects
dotnet build UXAssist/UXAssist.csproj -c Release dotnet build CheatEnabler/CheatEnabler.csproj -c Release dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release -
Step 4: Commit
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
dotnet build DSP_Mods.sln -c ReleaseExpected: 0 errors.
-
Step 2: Package all mods
dotnet build -t:ZipMod -c Release dotnet build -t:CopyToParentPackage -c ReleaseExpected: all
package/outputs generated. -
Step 3: Public API verification
Re-run the
docs/PublicApiSurface.mdchecklist and confirm every listed member still exists. -
Step 4: Update
AGENTS.mdIf any project conventions changed (e.g., new folder structure, naming rules,
.editorconfig), updateAGENTS.mdaccordingly. -
Step 5: Final commit / tag
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. CheatEnablerandUniverseGenTweakscompile after each phase.- Code snippets use types/methods defined in earlier tasks.