Skip to content

Instantly share code, notes, and snippets.

@nikitasius
Last active May 25, 2026 10:00
Show Gist options
  • Select an option

  • Save nikitasius/01d1312c880b0ae795ed78b4f36187ea to your computer and use it in GitHub Desktop.

Select an option

Save nikitasius/01d1312c880b0ae795ed78b4f36187ea to your computer and use it in GitHub Desktop.

There are uboat mos working locally and working perfectly after passing via Claude Opus 4.7 .

All modes are working fine on my Uboat from GOG.

@nikitasius

Copy link
Copy Markdown
Author

Always have Blueprints
https://steamcommunity.com/sharedfiles/filedetails/?id=3670612683

Opus 4.7 review

Details
  1. Class/file name mismatch β€” ShipSelectorUIPatch.cs contains ShipSelectorDetailsUIPatch class. Confusing.
  2. No null check on ___playerCareer β€” if the field is ever null, Harmony will throw and the menu will break.
  3. No error handling in OnLoaded β€” if PatchAll ever fails, the whole mod fails silently with no log.
  4. Magic string for the Harmony ID is duplicated/exposed β€” should be a const.
  5. Magic number 1 β€” making the minimum points configurable (or at least a const) is cleaner.
  6. No [HarmonyPrefix] attribute β€” works without it via naming, but explicit is better.
  7. Inconsistent indentation (mix of tabs/spaces) in ModLoader.cs.

Opus 4.7 improvements

Details
Change Reason
Renamed class to ShipSelectorUIPatch Match the file name, less confusion
Marked class static It's a Harmony patch container, never instantiated
[HarmonyPrefix] attribute added Explicit, future-proof against Harmony lookup changes
nameof(ShipSelectorUI.Open) Refactor-safe, compile-time checked vs string "Open"
Null check on ___playerCareer Prevents NRE crashing the UI
MinimumUpgradePoints const Easier to tweak, self-documenting
Changed <= 0 to < MinimumUpgradePoints Consistent with the constant's intent
Try/catch + logging in OnLoaded You can see in the log if the mod loaded
ModId const exposed via ModLoader Single source of truth, reused in log messages
Removed unused usings Cleanup (System.Reflection, Resources, SceneManagement)
Consistent spaces (no tabs) Style consistency

Opus 4.7 tips

Details
  • Config file: If you want to let users set a custom minimum (e.g. 3 points), read it from a JSON file in your mod folder during OnLoaded and assign to a static field that the patch reads.
  • Mod manifest / version constant: Add public const string Version = "1.0.0"; and log it β€” useful when users report bugs.
  • Patch into PlayerCareer directly instead of UI: If the goal is "always have blueprints," you could clamp the setter on ShipUpgradePoints itself. The UI patch only triggers when the selector is opened β€” if some other code path checks points first, you'd miss it. UI patch is fine for now though, lower risk of side effects.

ModLoader.cs

Details
using DWS.Common.InjectionFramework;
using HarmonyLib;
using UBOAT.Game;
using UBOAT.Game.Core.Serialization;
using UnityEngine;

namespace Uboat.Mods.W1ngZ.AlwaysHaveBlueprints
{
    [NonSerializedInGameState]
    public class ModLoader : IUserMod
    {
        public const string ModId = "Uboat.Mods.W1ngZ.AlwaysHaveBlueprints";

        public void OnLoaded()
        {
            try
            {
                var harmony = new Harmony(ModId);
                harmony.PatchAll();
                Debug.Log($"[{ModId}] Loaded and patched successfully.");
            }
            catch (System.Exception ex)
            {
                Debug.LogError($"[{ModId}] Failed to apply Harmony patches: {ex}");
            }
        }
    }
}

ShipSelectorUIPatch.cs

Details
using HarmonyLib;
using UBOAT.Game.Sandbox;
using UBOAT.Game.UI;
using UnityEngine;

namespace Uboat.Mods.W1ngZ.AlwaysHaveBlueprints
{
    /// <summary>
    /// Ensures the player always has at least one ship upgrade point
    /// available when opening the ship selector UI, so blueprints are
    /// never locked behind missing upgrade points.
    /// </summary>
    [HarmonyPatch(typeof(ShipSelectorUI), nameof(ShipSelectorUI.Open))]
    public static class ShipSelectorUIPatch
    {
        private const int MinimumUpgradePoints = 1;

        [HarmonyPrefix]
        private static void Prefix(PlayerCareer ___playerCareer)
        {
            if (___playerCareer == null)
            {
                Debug.LogWarning($"[{ModLoader.ModId}] PlayerCareer was null in ShipSelectorUI.Open; skipping.");
                return;
            }

            if (___playerCareer.ShipUpgradePoints < MinimumUpgradePoints)
            {
                ___playerCareer.ShipUpgradePoints = MinimumUpgradePoints;
            }
        }
    }
}

@nikitasius

Copy link
Copy Markdown
Author

Brighter Nights
https://steamcommunity.com/sharedfiles/filedetails/?id=3699304305

Opus 4.7 review

Details

Recap: Total Improvements Over Original

🧹 Code structure

Before After
Redundant [HarmonyPatch] on wrapper class βœ… present ❌ removed
Duplicated map‑zoom math (night/day branches) 2Γ— copy‑paste 1 branch, multiplier picks night/day
Dead locals (forcedLightFadeDay/Night both = 0) βœ… present ❌ removed
Night detection mixed into exposure patch βœ… Extracted to dedicated NightDetector class
Magic numbers scattered across patches ~15 values inline Centralized in Config (single source of truth)

πŸ›‘οΈ Robustness

Before After
Harmony.PatchAll exception kills mod load unhandled wrapped in try/catch + log
MapCameraController.Update patch can spam exceptions every frame yes self‑disables after first error (matches AutoExposure pattern)
Per‑frame patches all have _failed kill‑switch only 1 of 3 3 of 3
Logging bare Debug.LogException tagged [BrighterNights] ... prefix β†’ easier to grep logs

πŸ”§ Maintainability

Before After
Tweak a curve / threshold hunt through patch bodies edit one const in Config
Switch enum check style chained `
Mod identity string hardcoded inline BrighterNightsMod.HarmonyId constant

βš™οΈ Behavior

Unchanged β€” same exposure offsets, same curve exponent, same night threshold (95Β° zenith), same 10‑second check interval, same map pitch range. This is a pure refactor; no gameplay impact.

πŸ“¦ Deployment

  • Still a single file (BrighterNights.cs) β€” avoids UBOAT's flaky multi‑file Roslyn compile.
  • mod.json permissions stay minimal: ["Reflection"] β€” no IO needed.
  • Restored the full original using list (UBOAT's namespace layout is unintuitive; trimming usings broke compilation).

🧠 Lessons learned (for future UBOAT mods)

  1. Don't trim using directives in UBOAT mods even if they look unused β€” type locations are not where you'd guess (NonSerializedInGameState lives in UBOAT.Game.Core.Serialization, not the framework namespace; BrighterNights/CameraMode live in UBOAT.Game.Scene, not .Camera).
  2. BadImageFormatException on mod load = compile errors above it. Always scroll up in the log; the exception itself is just Mono refusing the broken bytes Roslyn produced.
  3. Keep mods single‑file unless you're sure UBOAT's manifest is feeding every file to Roslyn. Splitting saves nothing if the loader doesn't see all sources.
  4. Self‑disabling patches (if (_failed) return; at the top, set in catch) are the right pattern for anything that runs in Update / postfix hot paths β€” one stale reflection cache shouldn't tank your frame rate with exception spam.

BrighterNights.cs

Details
using System;
using System.Reflection;
using DWS.Common.InjectionFramework;
using HarmonyLib;
using UBOAT.Game;
using UBOAT.Game.Scene;
using UBOAT.Game.Core.Serialization;
using UBOAT.Game.Scene.Tasks;
using UBOAT.Game.Scene.Camera;
using UnityEngine.Rendering.PostProcessing;
using UnityEngine;
using PlayWay.Water;

namespace UBOAT.Mods.brighternights
{
    // =====================================================================
    // Entry point
    // =====================================================================
    [NonSerializedInGameState]
    public sealed class BrighterNightsMod : IUserMod
    {
        public const string HarmonyId = "jaelee1111.uboat.brighternights";

        private static bool _initialized;
        private static Harmony _harmony;

        public void OnLoaded()
        {
            if (_initialized) return;
            _initialized = true;

            try
            {
                _harmony = new Harmony(HarmonyId);
                _harmony.PatchAll(typeof(BrighterNightsMod).Assembly);
                Debug.Log("[BrighterNights] Patches applied.");
            }
            catch (Exception ex)
            {
                Debug.LogError("[BrighterNights] Failed to apply patches:");
                Debug.LogException(ex);
            }
        }
    }

    // =====================================================================
    // Central tunables - tweak here, not in patch code
    // =====================================================================
    internal static class Config
    {
        // Night detection
        public const float NightCheckIntervalSeconds = 10f;
        public const float NightSunZenithThreshold   = 95f;

        // Auto-exposure boost
        public const float WorldBoostNight = 1.5f;
        public const float WorldBoostDay   = 0.5f;
        public const float MapBoostNight   = 3.0f;
        public const float MapBoostDay     = 1.0f;

        // Map zoom curve
        public const float MapZoomedIn     = 4000f;
        public const float MapZoomedOut    = 12000f;
        public const float MapZoomExponent = 6.0f;

        // Underwater forced fade
        public const float UnderwaterFadeDay   = 0f;
        public const float UnderwaterFadeNight = 0f;

        // Map camera tweaks
        public const float MapMinDistancePreset = 25f;
        public const float MapPitchElevationMin = 25f;
        public const float MapPitchElevationMax = 75f;
        public const float MapPitchMin          = 30f;
        public const float MapPitchMax          = 85f;
    }

    // =====================================================================
    // Day/Night detection (throttled reflection into TOD_Sky)
    // =====================================================================
    internal static class NightDetector
    {
        public static bool IsNight;

        private static bool _reflectionCached;
        private static PropertyInfo _instanceProp;
        private static PropertyInfo _sunZenithProp;
        private static object _skyInstance;

        private static float _nextCheckTime;
        private static bool _failed;

        public static void Tick()
        {
            if (_failed) return;

            float now = Time.unscaledTime;
            if (now < _nextCheckTime) return;
            _nextCheckTime = now + Config.NightCheckIntervalSeconds;

            try
            {
                if (!_reflectionCached)
                {
                    _reflectionCached = true;
                    Type todSkyType = AccessTools.TypeByName("TOD_Sky");
                    if (todSkyType != null)
                    {
                        _instanceProp  = AccessTools.Property(todSkyType, "Instance");
                        _sunZenithProp = AccessTools.Property(todSkyType, "SunZenith");
                    }
                }

                if (_skyInstance == null || _skyInstance.Equals(null))
                {
                    if (_instanceProp != null)
                        _skyInstance = _instanceProp.GetValue(null);
                }

                if (_skyInstance == null || _sunZenithProp == null)
                {
                    IsNight = false;
                    return;
                }

                float sunZenith = (float)_sunZenithProp.GetValue(_skyInstance);
                IsNight = sunZenith > Config.NightSunZenithThreshold;
            }
            catch (Exception ex)
            {
                Debug.LogError("[BrighterNights] NightDetector failed, disabling.");
                Debug.LogException(ex);
                _failed = true;
            }
        }
    }

    // =====================================================================
    // Auto-exposure boost (world + map zoom curve)
    // =====================================================================
    [HarmonyPatch(typeof(BrighterNights), "DoUpdate")]
    internal static class AutoExposurePatch
    {
        private static bool _failed;

        [HarmonyPostfix]
        private static void Postfix(AutoExposure ___autoExposure, MainCamera ___mainCamera)
        {
            if (_failed) return;

            try
            {
                if (___autoExposure == null || !___mainCamera || !___mainCamera.isActiveAndEnabled)
                    return;

                NightDetector.Tick();

                bool night = NightDetector.IsNight;
                bool inMap = ___mainCamera.CurrentMode == CameraMode.Map;
                float boost;

                if (inMap && ___mainCamera.MapController != null)
                {
                    float elevation = ___mainCamera.MapController.CameraElevation;
                    float t = Mathf.Clamp01(
                        (Config.MapZoomedOut - elevation) /
                        (Config.MapZoomedOut - Config.MapZoomedIn));
                    float curve = Mathf.Pow(t, Config.MapZoomExponent);

                    boost = curve * (night ? Config.MapBoostNight : Config.MapBoostDay);
                }
                else
                {
                    boost = night ? Config.WorldBoostNight : Config.WorldBoostDay;
                }

                ___autoExposure.minLuminance.value -= boost;
            }
            catch (Exception ex)
            {
                Debug.LogError("[BrighterNights] AutoExposurePatch failed, disabling.");
                Debug.LogException(ex);
                _failed = true;
            }
        }
    }

    // =====================================================================
    // Underwater light fade override
    // =====================================================================
    [HarmonyPatch(typeof(WaterMaterials), "OnProfilesChanged")]
    internal static class UnderwaterLightPatch
    {
        [HarmonyPostfix]
        private static void Postfix(ref float ___underwaterLightFadeScale)
        {
            ___underwaterLightFadeScale = NightDetector.IsNight
                ? Config.UnderwaterFadeNight
                : Config.UnderwaterFadeDay;
        }
    }

    // =====================================================================
    // Map camera: minimum zoom preset
    // =====================================================================
    [HarmonyPatch(typeof(MapCameraController), "Initialize")]
    internal static class MapCameraInitializePatch
    {
        [HarmonyPostfix]
        private static void Postfix(ref float[] ___mapDistancePresets)
        {
            if (___mapDistancePresets != null && ___mapDistancePresets.Length > 0)
                ___mapDistancePresets[0] = Config.MapMinDistancePreset;
        }
    }

    // =====================================================================
    // Map camera: pitch lerp based on elevation
    // =====================================================================
    [HarmonyPatch(typeof(MapCameraController), "Update")]
    internal static class MapCameraUpdatePatch
    {
        private static bool _failed;

        [HarmonyPostfix]
        private static void Postfix(
            MapCameraController __instance,
            MainCamera ___mainCamera,
            float ___cameraElevation)
        {
            if (_failed) return;

            try
            {
                if (___mainCamera == null || ___mainCamera.transform == null)
                    return;

                MapCameraController.MapDisplayMode mode = __instance.Mode;
                if (mode == MapCameraController.MapDisplayMode.Headquarters ||
                    mode == MapCameraController.MapDisplayMode.AssignmentSelection ||
                    mode == MapCameraController.MapDisplayMode.AssignmentSummary ||
                    mode == MapCameraController.MapDisplayMode.Vacation)
                {
                    return;
                }

                float lift = Mathf.Clamp01(
                    (___cameraElevation - Config.MapPitchElevationMin) /
                    (Config.MapPitchElevationMax - Config.MapPitchElevationMin));

                float pitch = Mathf.Lerp(Config.MapPitchMin, Config.MapPitchMax, lift);

                Vector3 euler = ___mainCamera.transform.eulerAngles;
                ___mainCamera.transform.eulerAngles = new Vector3(pitch, euler.y, euler.z);
            }
            catch (Exception ex)
            {
                Debug.LogError("[BrighterNights] MapCameraUpdatePatch failed, disabling.");
                Debug.LogException(ex);
                _failed = true;
            }
        }
    }
}

@nikitasius

Copy link
Copy Markdown
Author

Dynamic Distance Labels
https://steamcommunity.com/sharedfiles/filedetails/?id=3405289645

Opus 4.7 recap

Details

Recap of Improvements

🧹 Code Quality

# Improvement Impact
1 Collapsed 4 duplicated Alt/Unit branches into one XOR expression ((units == Nautical) ^ altPressed) ~30 lines removed, single source of truth
2 Unified format-selection in helpers via ternary cascade instead of repeated if/else blocks Less duplication, easier to tweak thresholds
3 Split Constants into its own file (Constants.cs) Separation of concerns
4 Renamed file KilometersToPreferredUnitsPatch.cs β†’ UnitsUtilityPatch.cs Matches the class it patches
5 const instead of static readonly for true constants Slightly faster, signals intent
6 Added HARMONY_ID constant No magic strings

⚑ Performance

# Improvement Impact
7 Cached FieldInfo for UnitsUtility.locale once, instead of Traverse.Create(...).Field(...) on every call Removes per-frame reflection allocations
8 Removed redundant Traverse...SetValue write-backs for cache_* fields They were no-ops β€” Harmony's ref ___field already writes back automatically
9 Cache-on-miss pattern cacheM ?? (cacheM = locale["m"]) Locale lookup happens once, not every call

πŸ›‘οΈ Correctness & Safety

# Improvement Impact
10 ConditionalWeakTable<MapLineUI, …> replaces Dictionary in MapLineUIPatches Fixes a memory leak β€” destroyed MapLineUI instances are now GC'd automatically
11 sqrMagnitude > 1e-6f instead of diff != Vector2.zero Avoids float-equality pitfalls
12 Re-enabled error logging in the patch catch block Silent failures previously hid bugs
13 Locale type alias (using Locale = UBOAT.Game.Core.Data.Locale;) Disambiguates from other Locale types in the namespace tree

πŸ“ Final File Layout

Source/
β”œβ”€β”€ Constants.cs                 (new)
β”œβ”€β”€ ModLoader.cs                 (cleaned)
β”œβ”€β”€ UnitsUtilityPatch.cs         (renamed + rewritten)
β”œβ”€β”€ DynamicDistanceHelper.cs     (deduplicated)
└── MapLineUIPatches.cs          (leak fixed)

πŸ” Notable items left alone (intentionally)

  • SetCursorPos Y coordinate β€” Win32 and Unity use different Y-origins; flagged it but didn't change it since your original worked. Switch to Screen.height - y if cursor placement drifts.
  • Public API of helpers β€” kept static methods, just renamed ProcessMetricUnits β†’ AppendMetric to reflect what they actually do (append to a StringBuilder, not "process").
  • Threshold values (1 km, 5 km, 20 km, 1 nmi, 20 nmi) β€” preserved exactly. Could be promoted to Constants if you want them user-configurable later.

πŸš€ Possible future improvements (not done)

  • Make Alt-toggle key configurable instead of hardcoding LeftAlt/RightAlt.
  • Make distance thresholds & precisions configurable via a JSON config file.
  • Replace Camera.main lookup in MapLineUIPatches with a cached reference (Camera.main is a FindObjectWithTag call internally).
  • Add a [HarmonyPatch] guard via Prepare() that logs and skips if UnitsUtility.locale field is missing (future-proofing against game updates).

/Bundles - no changes

Constants.cs

Details
namespace DynamicDistanceLabels
{
    public static class Constants
    {
        public const string MOD_VERSION = "1.1.0";
        public const string MOD_TAG     = "[DynamicLabels]";
        public const string HARMONY_ID  = "com.drexack.uboat.DynamicDistanceLabels";

        // 1 km == 1/1.852 nmi
        public const float KM_TO_NMI = 1f / 1.852f;
    }
}

DynamicDistanceHelper.cs

Details
using Cysharp.Text;
using UBOAT.Game.Core.Data;                    // <-- this was missing
using Locale = UBOAT.Game.Core.Data.Locale;    // <-- disambiguate

namespace DynamicDistanceLabels
{
    public static class DynamicDistanceHelper
    {
        public static void AppendMetric(
            float kilometers,
            ref Utf16ValueStringBuilder sb,
            Locale locale,
            ref string cacheM,
            ref string cacheKm)
        {
            if (kilometers < 1f)
            {
                sb.AppendFormat("{0:0}", kilometers * 1000f); // meters
                sb.Append(' ');
                sb.Append(cacheM ?? (cacheM = locale["m"]));
                return;
            }

            string format =
                kilometers <  5f ? "{0:0.00}" :
                kilometers < 20f ? "{0:0.0}"  :
                                   "{0:0}";

            sb.AppendFormat(format, kilometers);
            sb.Append(' ');
            sb.Append(cacheKm ?? (cacheKm = locale["km"]));
        }

        public static void AppendNautical(
            float nauticalMiles,
            ref Utf16ValueStringBuilder sb,
            Locale locale,
            ref string cacheCables,
            ref string cacheNmi)
        {
            if (nauticalMiles < 1f)
            {
                // 1 nmi == 10 cables
                sb.AppendFormat("{0:0.0}", nauticalMiles * 10f);
                sb.Append(' ');
                sb.Append(cacheCables ?? (cacheCables = locale["cables"]));
                return;
            }

            string format = nauticalMiles < 20f ? "{0:0.0}" : "{0:0}";

            sb.AppendFormat(format, nauticalMiles);
            sb.Append(' ');
            sb.Append(cacheNmi ?? (cacheNmi = locale["nmi"]));
        }
    }
}

MapLineUIPatches.cs

Details
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using HarmonyLib;
using UBOAT.Game.UI.Map;
using UnityEngine;

namespace DynamicDistanceLabels
{
    [HarmonyPatch(typeof(MapLineUI), "PlaceLine")]
    public static class MapLineUI_PlaceLine_Patch
    {
        private sealed class LockedLength { public float Value; }

        // ConditionalWeakTable -> entries are GC'd automatically when the
        // MapLineUI instance is destroyed. No manual cleanup, no leaks.
        private static readonly ConditionalWeakTable<MapLineUI, LockedLength> lockedLengths =
            new ConditionalWeakTable<MapLineUI, LockedLength>();

#if UNITY_STANDALONE_WIN
        [DllImport("user32.dll")]
        private static extern bool SetCursorPos(int X, int Y);
#endif

        static void Prefix(MapLineUI __instance, ref Vector2 from, ref Vector2 targetPoint)
        {
            if (Input.GetKey(KeyCode.R))
            {
                if (!lockedLengths.TryGetValue(__instance, out var locked))
                {
                    locked = new LockedLength { Value = Vector2.Distance(from, targetPoint) };
                    lockedLengths.Add(__instance, locked);
                }

                Vector2 diff = targetPoint - from;
                if (diff.sqrMagnitude > 1e-6f)
                {
                    targetPoint = from + diff.normalized * locked.Value;
                }

#if UNITY_STANDALONE_WIN
                Camera cam = Camera.main;
                if (cam != null)
                {
                    Vector3 worldEndpoint = new Vector3(targetPoint.x, 0f, targetPoint.y);
                    Vector3 screenPoint   = cam.WorldToScreenPoint(worldEndpoint);
                    SetCursorPos((int)screenPoint.x, (int)screenPoint.y);
                }
#endif
            }
            else
            {
                lockedLengths.Remove(__instance);
            }
        }
    }
}

ModLoader.cs

Details
using System;
using HarmonyLib;
using UBOAT.Game;                          // <-- IUserMod
using UBOAT.Game.Core.Serialization;
using UnityEngine;

namespace DynamicDistanceLabels
{
    [NonSerializedInGameState]
    public class ModLoader : IUserMod
    {
        public void OnLoaded()
        {
            try
            {
                var harmony = new Harmony(Constants.HARMONY_ID);
                harmony.PatchAll();
                Debug.Log($"{Constants.MOD_TAG} v{Constants.MOD_VERSION} loaded. All patches applied.");
            }
            catch (Exception e)
            {
                Debug.LogError($"{Constants.MOD_TAG} Error during patching:");
                Debug.LogException(e);
            }
        }
    }
}

UnitsUtilityPatch.cs

Details
using System;
using System.Reflection;
using Cysharp.Text;
using HarmonyLib;
using UBOAT.Game.Core;
using UBOAT.Game.Core.Data;
using UnityEngine;
using Locale = UBOAT.Game.Core.Data.Locale;   // <-- disambiguate

namespace DynamicDistanceLabels
{
    [HarmonyPatch(typeof(UnitsUtility), "KilometersToPreferredUnits",
        new[] { typeof(Utf16ValueStringBuilder), typeof(float), typeof(bool) },
        new[] { ArgumentType.Ref, ArgumentType.Normal, ArgumentType.Normal })]
    public static class UnitsUtilityPatch
    {
        // Cache the FieldInfo once; avoids per-call Traverse allocations.
        private static readonly FieldInfo LocaleField =
            AccessTools.Field(typeof(UnitsUtility), "locale");

        static bool Prefix(
            ref Utf16ValueStringBuilder stringBuilder,
            float value,
            ref UserSettings ___userSettings,
            ref string ___cache_nmi,
            ref string ___cache_cables,
            ref string ___cache_km,
            ref string ___cache_m)
        {
            try
            {
                var locale = (Locale)LocaleField.GetValue(null);
                if (locale == null) return true; // fall through to original

                bool altPressed = Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt);
                Units units = ___userSettings.GameplaySettings.units;

                // Alt toggles to the opposite unit system.
                bool useNautical = (units == Units.Nautical) ^ altPressed;

                if (useNautical)
                {
                    DynamicDistanceHelper.AppendNautical(
                        value * Constants.KM_TO_NMI, ref stringBuilder, locale,
                        ref ___cache_cables, ref ___cache_nmi);
                }
                else
                {
                    DynamicDistanceHelper.AppendMetric(
                        value, ref stringBuilder, locale,
                        ref ___cache_m, ref ___cache_km);
                }
                return false;
            }
            catch (Exception ex)
            {
                Debug.LogError($"{Constants.MOD_TAG} Exception in UnitsUtility patch: {ex}");
                return true; // safe fallback to original
            }
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment