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.
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.
Dynamic Distance Labels
https://steamcommunity.com/sharedfiles/filedetails/?id=3405289645
Opus 4.7 recap
| # | 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 |
| # | 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 |
| # | 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 |
Source/
βββ Constants.cs (new)
βββ ModLoader.cs (cleaned)
βββ UnitsUtilityPatch.cs (renamed + rewritten)
βββ DynamicDistanceHelper.cs (deduplicated)
βββ MapLineUIPatches.cs (leak fixed)
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.ProcessMetricUnits β AppendMetric to reflect what they actually do (append to a StringBuilder, not "process").Constants if you want them user-configurable later.LeftAlt/RightAlt.Camera.main lookup in MapLineUIPatches with a cached reference (Camera.main is a FindObjectWithTag call internally).[HarmonyPatch] guard via Prepare() that logs and skips if UnitsUtility.locale field is missing (future-proofing against game updates)./Bundles - no changes
Constants.cs
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
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
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
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
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
}
}
}
}
Brighter Nights
https://steamcommunity.com/sharedfiles/filedetails/?id=3699304305
Opus 4.7 review
Details
Recap: Total Improvements Over Original
π§Ή Code structure
[HarmonyPatch]on wrapper classforcedLightFadeDay/Nightboth = 0)NightDetectorclassConfig(single source of truth)π‘οΈ Robustness
Harmony.PatchAllexception kills mod loadMapCameraController.Updatepatch can spam exceptions every frame_failedkillβswitchDebug.LogException[BrighterNights] ...prefix β easier to grep logsπ§ Maintainability
constinConfigBrighterNightsMod.HarmonyIdconstantβοΈ 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
BrighterNights.cs) β avoids UBOAT's flaky multiβfile Roslyn compile.mod.jsonpermissions stay minimal:["Reflection"]β no IO needed.usinglist (UBOAT's namespace layout is unintuitive; trimming usings broke compilation).π§ Lessons learned (for future UBOAT mods)
usingdirectives in UBOAT mods even if they look unused β type locations are not where you'd guess (NonSerializedInGameStatelives inUBOAT.Game.Core.Serialization, not the framework namespace;BrighterNights/CameraModelive inUBOAT.Game.Scene, not.Camera).BadImageFormatExceptionon mod load = compile errors above it. Always scroll up in the log; the exception itself is just Mono refusing the broken bytes Roslyn produced.if (_failed) return;at the top, set incatch) are the right pattern for anything that runs inUpdate/ postfix hot paths β one stale reflection cache shouldn't tank your frame rate with exception spam.BrighterNights.csDetails