From 5aaf852b79fb85294daef855453c177050bf707e Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Sun, 6 Sep 2026 20:45:33 +0200 Subject: [PATCH 01/10] Synchronize Farclip runtime value with executable ceiling --- setup_tool_responsive.py | 82 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/setup_tool_responsive.py b/setup_tool_responsive.py index 9741a23..6bff730 100644 --- a/setup_tool_responsive.py +++ b/setup_tool_responsive.py @@ -1,5 +1,7 @@ import json import os +import re +import stat import tkinter as tk from tkinter import messagebox, ttk @@ -7,6 +9,9 @@ import dxvk_fps import setup_tool_dynamic +_FARCLIP_EXE_FLOOR = 3000.0 + + class ResponsiveModernWowSetupTool(setup_tool_dynamic.ModernWowSetupTool): """Production entry point with display-scaling-safe UI and DXVK FPS control.""" @@ -242,6 +247,83 @@ class ResponsiveModernWowSetupTool(setup_tool_dynamic.ModernWowSetupTool): return True + def _desired_normalized_values(self): + desired = super()._desired_normalized_values() + # The EXE field is a maximum allowed Farclip, not the active distance. + # Keep a 3000 baseline ceiling so an existing Config.wtf value cannot + # exceed a newly reduced executable limit. Values explicitly selected + # above 3000 still raise the ceiling so the safety override keeps working. + desired["farclip"] = max(_FARCLIP_EXE_FLOOR, desired["farclip"]) + return desired + + def _configure_farclip_cvar(self, target): + """Apply the selected render distance to WTF/Config.wtf.""" + try: + farclip = int(self.vt_farclip.get()) + except (tk.TclError, TypeError, ValueError) as exc: + raise RuntimeError("Render Distance (Farclip) is not a valid number.") from exc + if farclip <= 0: + raise RuntimeError("Render Distance (Farclip) must be greater than 0.") + + wtf_dir = os.path.join(target, "WTF") + config_path = os.path.join(wtf_dir, "Config.wtf") + staged = config_path + ".modernization-farclip" + original_mode = None + restore_readonly = False + + try: + os.makedirs(wtf_dir, exist_ok=True) + + if os.path.exists(config_path): + original_mode = os.stat(config_path).st_mode + if not (original_mode & stat.S_IWRITE): + os.chmod(config_path, original_mode | stat.S_IWRITE) + restore_readonly = True + + existing = "" + if os.path.exists(config_path): + with open(config_path, "r", encoding="utf-8", errors="ignore") as handle: + existing = handle.read() + + setting = f'SET farclip "{farclip}"' + pattern = re.compile( + r'^\s*SET\s+farclip\s+"[^"]*"\s*$', + re.IGNORECASE | re.MULTILINE, + ) + if pattern.search(existing): + updated = pattern.sub(setting, existing) + else: + if existing and not existing.endswith(("\n", "\r")): + existing += "\n" + updated = existing + setting + "\n" + + with open(staged, "w", encoding="utf-8", newline="") as handle: + handle.write(updated) + os.replace(staged, config_path) + + except OSError as exc: + raise RuntimeError( + "Could not synchronize Render Distance in WTF\\Config.wtf. " + "Close WoW and any program using the file, then try again." + ) from exc + finally: + if os.path.exists(staged): + try: + os.remove(staged) + except OSError: + pass + if restore_readonly and original_mode is not None and os.path.exists(config_path): + try: + os.chmod(config_path, original_mode) + except OSError: + pass + + def configure_script_memory(self, target): + # Keep the existing script-memory and SuperWoW FoV behavior, then make + # the Tool's Render Distance selection authoritative at runtime too. + super().configure_script_memory(target) + self._configure_farclip_cvar(target) + def configure_dxvk(self, target): super().configure_dxvk(target) -- 2.52.0 From d59b922f975ed503d07d0f9f6ac99de67d4106ce Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Sun, 6 Sep 2026 20:45:51 +0200 Subject: [PATCH 02/10] Add Farclip synchronization regression tests --- tests/test_farclip_config_sync.py | 107 ++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 tests/test_farclip_config_sync.py diff --git a/tests/test_farclip_config_sync.py b/tests/test_farclip_config_sync.py new file mode 100644 index 0000000..c46eeea --- /dev/null +++ b/tests/test_farclip_config_sync.py @@ -0,0 +1,107 @@ +import os +import tempfile +import unittest +from unittest import mock + +import setup_tool_dynamic +import setup_tool_responsive + + +class _Var: + def __init__(self, value): + self.value = value + + def get(self): + return self.value + + +class FarclipConfigSyncTests(unittest.TestCase): + def _tool(self, farclip=777): + tool = object.__new__(setup_tool_responsive.ResponsiveModernWowSetupTool) + tool.vt_farclip = _Var(farclip) + return tool + + def test_executable_farclip_ceiling_has_3000_floor(self): + tool = self._tool() + with mock.patch.object( + setup_tool_dynamic.ModernWowSetupTool, + "_desired_normalized_values", + return_value={"farclip": 777.0}, + ): + desired = tool._desired_normalized_values() + self.assertEqual(desired["farclip"], 3000.0) + + def test_executable_farclip_ceiling_keeps_explicit_value_above_3000(self): + tool = self._tool(5000) + with mock.patch.object( + setup_tool_dynamic.ModernWowSetupTool, + "_desired_normalized_values", + return_value={"farclip": 5000.0}, + ): + desired = tool._desired_normalized_values() + self.assertEqual(desired["farclip"], 5000.0) + + def test_existing_farclip_cvar_is_replaced_without_touching_other_settings(self): + tool = self._tool(777) + with tempfile.TemporaryDirectory() as root: + wtf_dir = os.path.join(root, "WTF") + os.makedirs(wtf_dir) + config_path = os.path.join(wtf_dir, "Config.wtf") + with open(config_path, "w", encoding="utf-8") as handle: + handle.write('SET locale "enUS"\nSET farclip "2100"\nSET gxWindow "1"\n') + + tool._configure_farclip_cvar(root) + + with open(config_path, "r", encoding="utf-8") as handle: + result = handle.read() + + self.assertIn('SET locale "enUS"', result) + self.assertIn('SET gxWindow "1"', result) + self.assertIn('SET farclip "777"', result) + self.assertNotIn('SET farclip "2100"', result) + + def test_missing_farclip_cvar_is_appended(self): + tool = self._tool(1500) + with tempfile.TemporaryDirectory() as root: + wtf_dir = os.path.join(root, "WTF") + os.makedirs(wtf_dir) + config_path = os.path.join(wtf_dir, "Config.wtf") + with open(config_path, "w", encoding="utf-8") as handle: + handle.write('SET locale "enUS"') + + tool._configure_farclip_cvar(root) + + with open(config_path, "r", encoding="utf-8") as handle: + result = handle.read() + + self.assertEqual( + result, + 'SET locale "enUS"\nSET farclip "1500"\n', + ) + + def test_missing_config_wtf_is_created(self): + tool = self._tool(1000) + with tempfile.TemporaryDirectory() as root: + tool._configure_farclip_cvar(root) + config_path = os.path.join(root, "WTF", "Config.wtf") + with open(config_path, "r", encoding="utf-8") as handle: + result = handle.read() + self.assertEqual(result, 'SET farclip "1000"\n') + + def test_configure_script_memory_also_synchronizes_farclip(self): + tool = self._tool(777) + with mock.patch.object( + setup_tool_dynamic.ModernWowSetupTool, + "configure_script_memory", + ) as parent_configure, mock.patch.object( + tool, + "_configure_farclip_cvar", + ) as sync_farclip: + tool.configure_script_memory("C:/WoW") + + parent_configure.assert_called_once_with("C:/WoW") + sync_farclip.assert_called_once_with("C:/WoW") + + +if __name__ == "__main__": + unittest.main() -- 2.52.0 From 0eb62fbf046ddf3627d2b7bc00ca74b57d5f6f96 Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Sun, 6 Sep 2026 21:00:31 +0200 Subject: [PATCH 03/10] Move Farclip policy into dynamic installer core --- setup_tool_dynamic.py | 109 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 7 deletions(-) diff --git a/setup_tool_dynamic.py b/setup_tool_dynamic.py index 6a233b8..9ee7483 100644 --- a/setup_tool_dynamic.py +++ b/setup_tool_dynamic.py @@ -80,7 +80,7 @@ _CUSTOM_GLUES_SITES = ( # Numeric fields may already contain legitimate Turtle/Octo/community values. # Preflight only sanity-checks those fixed-offset fields; normalization later -# overwrites them with the exact values selected in the Tool. +# applies the exact Tool policy without fingerprinting community numeric values. _NUMERIC_SITES = ( ("fov", 0x4089B4, "FoV", 0.5, 3.5), ("farclip", 0x40FED8, "Farclip", 777.0, 10000.0), @@ -93,6 +93,7 @@ _CLIENT_BUILD_OFFSET = 0x437BFC _CLIENT_VERSION_OFFSET = 0x437C04 _CLIENT_BUILD = b"5875" _CLIENT_VERSION = b"1.12.1" +_FARCLIP_EXE_CEILING = 3000.0 def _strict_verify_mpq(path): @@ -286,16 +287,17 @@ class ModernWowSetupTool(_ModernWowSetupToolCore): def _vanilla_tweaks_signature(self): signature = super()._vanilla_tweaks_signature() - # Keep the B-total policy generation stable for existing test builds. - # Fixed build/version string anchors are no longer part of preflight. - signature["selected_patch_normalization"] = 3 + # Bump when normalization changes so existing managed installs get one + # clean WoW_Modernized.exe rebuild under the new policy. + signature["selected_patch_normalization"] = 4 signature["source_fingerprint_policy"] = 1 return signature def _desired_normalized_values(self): + selected_farclip = float(self.vt_farclip.get()) desired = { "fov": float(self.vt_fov.get()), - "farclip": float(self.vt_farclip.get()), + "farclip": selected_farclip, "frill": float(self.vt_frill.get()), "nameplate": float(self.vt_nameplate.get()), "maxcam": float(self.vt_maxcam.get()), @@ -304,7 +306,7 @@ class ModernWowSetupTool(_ModernWowSetupToolCore): ranges = ( ("FoV", desired["fov"], 0.5, 3.5), - ("Farclip", desired["farclip"], 100.0, 50000.0), + ("Farclip", selected_farclip, 100.0, _FARCLIP_EXE_CEILING), ("Frill Distance", desired["frill"], 0.0, 10000.0), ("Nameplate Distance", desired["nameplate"], 1.0, 500.0), ("Max Camera Distance", desired["maxcam"], 1.0, 1000.0), @@ -321,8 +323,36 @@ class ModernWowSetupTool(_ModernWowSetupToolCore): "Sound Channels value is outside the supported WoW 1.12.1 range." ) desired["sound_bytes"] = sound_channels.ljust(4, b"\x00") + + # The EXE field is the maximum allowed Farclip, not the active distance. + # Keep it fixed at 3000. The selected runtime value is synchronized to + # Config.wtf separately and may never exceed this ceiling. + desired["farclip"] = _FARCLIP_EXE_CEILING return desired + def validate_limits(self): + if not super().validate_limits(): + return False + + try: + farclip = float(self.vt_farclip.get()) + except (tk.TclError, TypeError, ValueError): + messagebox.showerror( + "Input Error", + "Render Distance (Farclip) must contain a valid number.", + ) + return False + + if not math.isfinite(farclip) or not 100.0 <= farclip <= _FARCLIP_EXE_CEILING: + messagebox.showerror( + "Limit Exceeded", + "Render distance (Farclip) must stay between 100 and 3000. " + "The 3000 hard maximum also applies when Safety Limits are disabled.", + ) + return False + + return True + @staticmethod def _validate_client_identity(data): """Compatibility no-op: Turtle/Octo may move build/version strings.""" @@ -554,10 +584,75 @@ class ModernWowSetupTool(_ModernWowSetupToolCore): except OSError: pass + def _configure_farclip_cvar(self, target): + """Synchronize the Tool-selected runtime Farclip to WTF/Config.wtf.""" + try: + farclip = int(self.vt_farclip.get()) + except (tk.TclError, TypeError, ValueError) as exc: + raise RuntimeError("Render Distance (Farclip) is not a valid number.") from exc + if not 100 <= farclip <= int(_FARCLIP_EXE_CEILING): + raise RuntimeError( + "Render Distance (Farclip) must stay between 100 and 3000." + ) + + wtf_dir = os.path.join(target, "WTF") + config_path = os.path.join(wtf_dir, "Config.wtf") + staged = config_path + ".modernization-farclip" + original_mode = None + restore_readonly = False + + try: + os.makedirs(wtf_dir, exist_ok=True) + + if os.path.exists(config_path): + original_mode = os.stat(config_path).st_mode + if not (original_mode & stat.S_IWRITE): + os.chmod(config_path, original_mode | stat.S_IWRITE) + restore_readonly = True + + existing = "" + if os.path.exists(config_path): + with open(config_path, "r", encoding="utf-8", errors="ignore") as handle: + existing = handle.read() + + setting = f'SET farclip "{farclip}"' + pattern = re.compile( + r'^\s*SET\s+farclip\s+"[^"]*"\s*$', + re.IGNORECASE | re.MULTILINE, + ) + if pattern.search(existing): + updated = pattern.sub(setting, existing) + else: + if existing and not existing.endswith(("\n", "\r")): + existing += "\n" + updated = existing + setting + "\n" + + with open(staged, "w", encoding="utf-8", newline="") as handle: + handle.write(updated) + os.replace(staged, config_path) + + except OSError as exc: + raise RuntimeError( + "Could not synchronize Render Distance in WTF\\Config.wtf. " + "Close WoW and any program using the file, then try again." + ) from exc + finally: + if os.path.exists(staged): + try: + os.remove(staged) + except OSError: + pass + if restore_readonly and original_mode is not None and os.path.exists(config_path): + try: + os.chmod(config_path, original_mode) + except OSError: + pass + def configure_script_memory(self, target): - """Apply base Config.wtf settings, then synchronize SuperWoW FoV.""" + """Apply base Config.wtf settings, then synchronize FoV and Farclip.""" super().configure_script_memory(target) self._configure_superwow_fov_cvar(target) + self._configure_farclip_cvar(target) def run_installation(self): """Run the EXE patch transaction before the installer's first file write. -- 2.52.0 From 0faf9ce25bc495df361ee93da61f9d68ecc2ff51 Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Sun, 6 Sep 2026 21:03:45 +0200 Subject: [PATCH 04/10] Harden fixed Farclip ceiling and migration tests --- setup_tool_responsive.py | 82 ---------------------- tests/test_farclip_config_sync.py | 62 +++++++++++----- tests/test_vanilla_tweaks_normalization.py | 8 +-- 3 files changed, 47 insertions(+), 105 deletions(-) diff --git a/setup_tool_responsive.py b/setup_tool_responsive.py index 6bff730..9741a23 100644 --- a/setup_tool_responsive.py +++ b/setup_tool_responsive.py @@ -1,7 +1,5 @@ import json import os -import re -import stat import tkinter as tk from tkinter import messagebox, ttk @@ -9,9 +7,6 @@ import dxvk_fps import setup_tool_dynamic -_FARCLIP_EXE_FLOOR = 3000.0 - - class ResponsiveModernWowSetupTool(setup_tool_dynamic.ModernWowSetupTool): """Production entry point with display-scaling-safe UI and DXVK FPS control.""" @@ -247,83 +242,6 @@ class ResponsiveModernWowSetupTool(setup_tool_dynamic.ModernWowSetupTool): return True - def _desired_normalized_values(self): - desired = super()._desired_normalized_values() - # The EXE field is a maximum allowed Farclip, not the active distance. - # Keep a 3000 baseline ceiling so an existing Config.wtf value cannot - # exceed a newly reduced executable limit. Values explicitly selected - # above 3000 still raise the ceiling so the safety override keeps working. - desired["farclip"] = max(_FARCLIP_EXE_FLOOR, desired["farclip"]) - return desired - - def _configure_farclip_cvar(self, target): - """Apply the selected render distance to WTF/Config.wtf.""" - try: - farclip = int(self.vt_farclip.get()) - except (tk.TclError, TypeError, ValueError) as exc: - raise RuntimeError("Render Distance (Farclip) is not a valid number.") from exc - if farclip <= 0: - raise RuntimeError("Render Distance (Farclip) must be greater than 0.") - - wtf_dir = os.path.join(target, "WTF") - config_path = os.path.join(wtf_dir, "Config.wtf") - staged = config_path + ".modernization-farclip" - original_mode = None - restore_readonly = False - - try: - os.makedirs(wtf_dir, exist_ok=True) - - if os.path.exists(config_path): - original_mode = os.stat(config_path).st_mode - if not (original_mode & stat.S_IWRITE): - os.chmod(config_path, original_mode | stat.S_IWRITE) - restore_readonly = True - - existing = "" - if os.path.exists(config_path): - with open(config_path, "r", encoding="utf-8", errors="ignore") as handle: - existing = handle.read() - - setting = f'SET farclip "{farclip}"' - pattern = re.compile( - r'^\s*SET\s+farclip\s+"[^"]*"\s*$', - re.IGNORECASE | re.MULTILINE, - ) - if pattern.search(existing): - updated = pattern.sub(setting, existing) - else: - if existing and not existing.endswith(("\n", "\r")): - existing += "\n" - updated = existing + setting + "\n" - - with open(staged, "w", encoding="utf-8", newline="") as handle: - handle.write(updated) - os.replace(staged, config_path) - - except OSError as exc: - raise RuntimeError( - "Could not synchronize Render Distance in WTF\\Config.wtf. " - "Close WoW and any program using the file, then try again." - ) from exc - finally: - if os.path.exists(staged): - try: - os.remove(staged) - except OSError: - pass - if restore_readonly and original_mode is not None and os.path.exists(config_path): - try: - os.chmod(config_path, original_mode) - except OSError: - pass - - def configure_script_memory(self, target): - # Keep the existing script-memory and SuperWoW FoV behavior, then make - # the Tool's Render Distance selection authoritative at runtime too. - super().configure_script_memory(target) - self._configure_farclip_cvar(target) - def configure_dxvk(self, target): super().configure_dxvk(target) diff --git a/tests/test_farclip_config_sync.py b/tests/test_farclip_config_sync.py index c46eeea..6d7a2b2 100644 --- a/tests/test_farclip_config_sync.py +++ b/tests/test_farclip_config_sync.py @@ -4,7 +4,6 @@ import unittest from unittest import mock import setup_tool_dynamic -import setup_tool_responsive class _Var: @@ -17,29 +16,46 @@ class _Var: class FarclipConfigSyncTests(unittest.TestCase): def _tool(self, farclip=777): - tool = object.__new__(setup_tool_responsive.ResponsiveModernWowSetupTool) + tool = object.__new__(setup_tool_dynamic.ModernWowSetupTool) tool.vt_farclip = _Var(farclip) return tool - def test_executable_farclip_ceiling_has_3000_floor(self): - tool = self._tool() - with mock.patch.object( - setup_tool_dynamic.ModernWowSetupTool, - "_desired_normalized_values", - return_value={"farclip": 777.0}, - ): - desired = tool._desired_normalized_values() + @staticmethod + def _add_numeric_vars(tool): + tool.vt_fov = _Var(1.9199) + tool.vt_frill = _Var(300) + tool.vt_nameplate = _Var(41) + tool.vt_maxcam = _Var(100) + tool.vt_soundchan = _Var(64) + + def test_executable_farclip_ceiling_is_always_3000(self): + tool = self._tool(777) + self._add_numeric_vars(tool) + desired = tool._desired_normalized_values() self.assertEqual(desired["farclip"], 3000.0) - def test_executable_farclip_ceiling_keeps_explicit_value_above_3000(self): + tool.vt_farclip = _Var(3000) + desired = tool._desired_normalized_values() + self.assertEqual(desired["farclip"], 3000.0) + + def test_farclip_above_3000_is_rejected_even_with_direct_internal_call(self): tool = self._tool(5000) - with mock.patch.object( - setup_tool_dynamic.ModernWowSetupTool, - "_desired_normalized_values", - return_value={"farclip": 5000.0}, - ): - desired = tool._desired_normalized_values() - self.assertEqual(desired["farclip"], 5000.0) + self._add_numeric_vars(tool) + with self.assertRaisesRegex(RuntimeError, "Farclip"): + tool._desired_normalized_values() + + def test_normalization_signature_bump_forces_existing_install_refresh(self): + tool = self._tool(777) + self._add_numeric_vars(tool) + tool.vt_quickloot = _Var(True) + tool.vt_bg_sound = _Var(True) + tool.vt_laa = _Var(True) + tool.vt_cam_fix = _Var(True) + tool.vt_crossfaction_res = _Var(False) + tool.vt_custom_glues = _Var(True) + tool.vt_bluemoon = _Var(False) + signature = tool._vanilla_tweaks_signature() + self.assertEqual(signature["selected_patch_normalization"], 4) def test_existing_farclip_cvar_is_replaced_without_touching_other_settings(self): tool = self._tool(777) @@ -88,10 +104,18 @@ class FarclipConfigSyncTests(unittest.TestCase): result = handle.read() self.assertEqual(result, 'SET farclip "1000"\n') + def test_farclip_cvar_rejects_value_above_fixed_ceiling(self): + tool = self._tool(3001) + with tempfile.TemporaryDirectory() as root: + with self.assertRaisesRegex(RuntimeError, "3000"): + tool._configure_farclip_cvar(root) + self.assertFalse(os.path.exists(os.path.join(root, "WTF", "Config.wtf"))) + def test_configure_script_memory_also_synchronizes_farclip(self): tool = self._tool(777) + tool.core_plugins = {"SuperWoWhook.dll": _Var(False)} with mock.patch.object( - setup_tool_dynamic.ModernWowSetupTool, + setup_tool_dynamic._ModernWowSetupToolCore, "configure_script_memory", ) as parent_configure, mock.patch.object( tool, diff --git a/tests/test_vanilla_tweaks_normalization.py b/tests/test_vanilla_tweaks_normalization.py index 8990cb1..c5c11cc 100644 --- a/tests/test_vanilla_tweaks_normalization.py +++ b/tests/test_vanilla_tweaks_normalization.py @@ -193,7 +193,7 @@ class VanillaTweaksNormalizationTests(unittest.TestCase): self.assertEqual(result[0x3A4869], 0x14) self.assertEqual(result[0x126:0x128], b"\x0F\x01") self.assertAlmostEqual(self._read_float(result, 0x4089B4), 1.5708, places=4) - self.assertEqual(self._read_float(result, 0x40FED8), 777.0) + self.assertEqual(self._read_float(result, 0x40FED8), 3000.0) self.assertEqual(self._read_float(result, 0x467958), 70.0) self.assertEqual(self._read_float(result, 0x40C448), 20.0) self.assertEqual(self._read_float(result, 0x4089A4), 50.0) @@ -237,7 +237,7 @@ class VanillaTweaksNormalizationTests(unittest.TestCase): self.assertEqual(result[0x3A4869], 0x27) self.assertEqual(result[0x126:0x128], b"\x2F\x01") self.assertAlmostEqual(self._read_float(result, 0x4089B4), 1.9199, places=4) - self.assertEqual(self._read_float(result, 0x40FED8), 1500.0) + self.assertEqual(self._read_float(result, 0x40FED8), 3000.0) self.assertEqual(self._read_float(result, 0x467958), 300.0) self.assertEqual(self._read_float(result, 0x40C448), 41.0) self.assertEqual(self._read_float(result, 0x4089A4), 100.0) @@ -581,7 +581,7 @@ class VanillaTweaksNormalizationTests(unittest.TestCase): self.assertEqual(result[0x3A4869], 0x14) self.assertEqual(result[0x126:0x128], b"\x0F\x01") self.assertAlmostEqual(self._read_float(result, 0x4089B4), 1.5708, places=4) - self.assertEqual(self._read_float(result, 0x40FED8), 777.0) + self.assertEqual(self._read_float(result, 0x40FED8), 3000.0) self.assertEqual(self._read_float(result, 0x467958), 70.0) self.assertEqual(self._read_float(result, 0x40C448), 20.0) self.assertEqual(self._read_float(result, 0x4089A4), 50.0) @@ -627,7 +627,7 @@ class VanillaTweaksNormalizationTests(unittest.TestCase): new_signature = tool._vanilla_tweaks_signature() self.assertNotIn("selected_patch_normalization", old_signature) - self.assertEqual(new_signature["selected_patch_normalization"], 3) + self.assertEqual(new_signature["selected_patch_normalization"], 4) tool.core_plugins["SuperWoWhook.dll"].set(False) self.assertEqual(new_signature, tool._vanilla_tweaks_signature()) -- 2.52.0 From c644cb348142a49378a76ec7c72a817ab355bd5a Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Sun, 6 Sep 2026 21:06:43 +0200 Subject: [PATCH 05/10] Update FoV tests for Farclip synchronization --- tests/test_superwow_fov_sync.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/test_superwow_fov_sync.py b/tests/test_superwow_fov_sync.py index 3c6a6eb..9725b1f 100644 --- a/tests/test_superwow_fov_sync.py +++ b/tests/test_superwow_fov_sync.py @@ -15,12 +15,13 @@ class FakeVar: class SuperWowFovSyncTests(unittest.TestCase): - def _tool(self, *, superwow=True, fov=1.9199, script_memory=False): + def _tool(self, *, superwow=True, fov=1.9199, farclip=777, script_memory=False): tool = object.__new__(dynamic.ModernWowSetupTool) tool.core_plugins = { "SuperWoWhook.dll": FakeVar(superwow), } tool.vt_fov = FakeVar(fov) + tool.vt_farclip = FakeVar(farclip) tool.vt_script_memory = FakeVar(script_memory) return tool @@ -44,6 +45,7 @@ class SuperWowFovSyncTests(unittest.TestCase): with open(path, "r", encoding="utf-8") as handle: result = handle.read() self.assertIn('SET FoV "1.9199"', result) + self.assertIn('SET farclip "777"', result) self.assertIn('SET gxWindow "1"', result) self.assertIn('SET locale "enUS"', result) self.assertNotIn('SET FoV "1.5"', result) @@ -56,7 +58,10 @@ class SuperWowFovSyncTests(unittest.TestCase): with open(path, "r", encoding="utf-8") as handle: result = handle.read() - self.assertEqual(result, 'SET FoV "2.1"\n') + self.assertEqual( + result, + 'SET FoV "2.1"\nSET farclip "777"\n', + ) def test_superwow_disabled_leaves_existing_fov_untouched(self): with tempfile.TemporaryDirectory() as root: @@ -69,7 +74,11 @@ class SuperWowFovSyncTests(unittest.TestCase): self._tool(superwow=False).configure_script_memory(root) with open(path, "r", encoding="utf-8") as handle: - self.assertEqual(handle.read(), original) + result = handle.read() + self.assertIn('SET FoV "1.5"', result) + self.assertIn('SET locale "frFR"', result) + self.assertIn('SET farclip "777"', result) + self.assertNotIn('SET FoV "1.9199"', result) def test_script_memory_and_superwow_fov_are_both_applied(self): with tempfile.TemporaryDirectory() as root: @@ -84,6 +93,7 @@ class SuperWowFovSyncTests(unittest.TestCase): result = handle.read() self.assertIn('SET scriptMemory "0"', result) self.assertIn('SET FoV "1.9199"', result) + self.assertIn('SET farclip "777"', result) def test_readonly_config_mode_is_restored(self): with tempfile.TemporaryDirectory() as root: @@ -98,7 +108,9 @@ class SuperWowFovSyncTests(unittest.TestCase): mode = os.stat(path).st_mode self.assertFalse(mode & stat.S_IWRITE) with open(path, "r", encoding="utf-8") as handle: - self.assertIn('SET FoV "1.9199"', handle.read()) + result = handle.read() + self.assertIn('SET FoV "1.9199"', result) + self.assertIn('SET farclip "777"', result) if __name__ == "__main__": -- 2.52.0 From 85a81d9259051c03a78ef95b67ac2160045221da Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Sun, 6 Sep 2026 21:07:15 +0200 Subject: [PATCH 06/10] Align safety test with fixed Farclip ceiling --- tests/test_final_safety_hardening.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_final_safety_hardening.py b/tests/test_final_safety_hardening.py index ffb2439..e505fc3 100644 --- a/tests/test_final_safety_hardening.py +++ b/tests/test_final_safety_hardening.py @@ -220,7 +220,7 @@ class VanillaTweaksSourceValidationTests(unittest.TestCase): 1.9199, places=4, ) - self.assertEqual(struct.unpack_from(" Date: Sun, 6 Sep 2026 21:19:08 +0200 Subject: [PATCH 07/10] Prepare Farclip final release polish --- .../workflows/finalize-farclip-release.yml | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 .github/workflows/finalize-farclip-release.yml diff --git a/.github/workflows/finalize-farclip-release.yml b/.github/workflows/finalize-farclip-release.yml new file mode 100644 index 0000000..203f5e9 --- /dev/null +++ b/.github/workflows/finalize-farclip-release.yml @@ -0,0 +1,70 @@ +name: Finalize Farclip Release Polish + +on: + push: + branches: + - 'test/farclip-config-sync' + +permissions: + contents: write + +jobs: + finalize: + if: ${{ github.event.head_commit.message == 'Prepare Farclip final release polish' }} + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v4 + with: + ref: test/farclip-config-sync + fetch-depth: 0 + + - name: Apply final Farclip release polish + shell: bash + run: | + python - <<'PY' + from pathlib import Path + + def replace_exact(path, old, new): + p = Path(path) + text = p.read_text(encoding='utf-8') + if old not in text: + raise SystemExit(f'Expected text not found in {path}: {old!r}') + p.write_text(text.replace(old, new, 1), encoding='utf-8') + + replace_exact( + 'setup_tool.py', + ' "farclip": "Increases the maximum terrain render distance. Vanilla default is 777. Tweaks default is 1500.",', + ' "farclip": "Sets the active terrain render distance used by the game. Vanilla default is 777. The Tool keeps the executable Farclip ceiling fixed at 3000.",', + ) + replace_exact( + 'setup_tool.py', + ' self.create_slider_row(frame_nums, 0, "Render distance (Farclip) [Safe Max: 1500]:", self.vt_farclip, 777, 1500, 10000, "farclip")', + ' self.create_slider_row(frame_nums, 0, "Render distance (Farclip) [Safe Max: 1500]:", self.vt_farclip, 777, 1500, 3000, "farclip")', + ) + replace_exact( + 'setup_tool_dynamic.py', + ' signature = super()._vanilla_tweaks_signature()\n # Bump when normalization changes so existing managed installs get one', + ' signature = super()._vanilla_tweaks_signature()\n # Runtime Farclip lives in Config.wtf; the executable ceiling is fixed.\n # Keep the executable signature stable when only Render Distance changes.\n signature["farclip"] = int(_FARCLIP_EXE_CEILING)\n # Bump when normalization changes so existing managed installs get one', + ) + replace_exact( + 'tests/test_farclip_config_sync.py', + ' signature = tool._vanilla_tweaks_signature()\n self.assertEqual(signature["selected_patch_normalization"], 4)', + ' signature = tool._vanilla_tweaks_signature()\n self.assertEqual(signature["selected_patch_normalization"], 4)\n self.assertEqual(signature["farclip"], 3000)\n\n tool.vt_farclip = _Var(1500)\n runtime_changed = tool._vanilla_tweaks_signature()\n self.assertEqual(runtime_changed["farclip"], 3000)\n self.assertEqual(signature, runtime_changed)', + ) + replace_exact( + 'RELEASE_NOTES.md', + '- SuperWoW now uses the same FoV selected by the Modernization Tool by synchronizing the `FoV` CVar in `WTF/Config.wtf`.\n', + '- SuperWoW now uses the same FoV selected by the Modernization Tool by synchronizing the `FoV` CVar in `WTF/Config.wtf`.\n- Render Distance now keeps the executable Farclip ceiling fixed at **3000** while synchronizing the selected runtime value to `WTF/Config.wtf`, preventing stale higher Farclip values from conflicting with `WoW_Modernized.exe`.\n', + ) + PY + + - name: Commit finalized changes + shell: bash + run: | + rm .github/workflows/finalize-farclip-release.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add setup_tool.py setup_tool_dynamic.py tests/test_farclip_config_sync.py RELEASE_NOTES.md .github/workflows/finalize-farclip-release.yml + git commit -m "Finalize Farclip release polish" + git push origin HEAD:test/farclip-config-sync -- 2.52.0 From 26dc31915599ca0c7306cea5064da89b6b17cb5c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:19:16 +0000 Subject: [PATCH 08/10] Finalize Farclip release polish --- .../workflows/finalize-farclip-release.yml | 70 ------------------- RELEASE_NOTES.md | 1 + setup_tool.py | 4 +- setup_tool_dynamic.py | 3 + tests/test_farclip_config_sync.py | 6 ++ 5 files changed, 12 insertions(+), 72 deletions(-) delete mode 100644 .github/workflows/finalize-farclip-release.yml diff --git a/.github/workflows/finalize-farclip-release.yml b/.github/workflows/finalize-farclip-release.yml deleted file mode 100644 index 203f5e9..0000000 --- a/.github/workflows/finalize-farclip-release.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Finalize Farclip Release Polish - -on: - push: - branches: - - 'test/farclip-config-sync' - -permissions: - contents: write - -jobs: - finalize: - if: ${{ github.event.head_commit.message == 'Prepare Farclip final release polish' }} - runs-on: ubuntu-latest - steps: - - name: Checkout branch - uses: actions/checkout@v4 - with: - ref: test/farclip-config-sync - fetch-depth: 0 - - - name: Apply final Farclip release polish - shell: bash - run: | - python - <<'PY' - from pathlib import Path - - def replace_exact(path, old, new): - p = Path(path) - text = p.read_text(encoding='utf-8') - if old not in text: - raise SystemExit(f'Expected text not found in {path}: {old!r}') - p.write_text(text.replace(old, new, 1), encoding='utf-8') - - replace_exact( - 'setup_tool.py', - ' "farclip": "Increases the maximum terrain render distance. Vanilla default is 777. Tweaks default is 1500.",', - ' "farclip": "Sets the active terrain render distance used by the game. Vanilla default is 777. The Tool keeps the executable Farclip ceiling fixed at 3000.",', - ) - replace_exact( - 'setup_tool.py', - ' self.create_slider_row(frame_nums, 0, "Render distance (Farclip) [Safe Max: 1500]:", self.vt_farclip, 777, 1500, 10000, "farclip")', - ' self.create_slider_row(frame_nums, 0, "Render distance (Farclip) [Safe Max: 1500]:", self.vt_farclip, 777, 1500, 3000, "farclip")', - ) - replace_exact( - 'setup_tool_dynamic.py', - ' signature = super()._vanilla_tweaks_signature()\n # Bump when normalization changes so existing managed installs get one', - ' signature = super()._vanilla_tweaks_signature()\n # Runtime Farclip lives in Config.wtf; the executable ceiling is fixed.\n # Keep the executable signature stable when only Render Distance changes.\n signature["farclip"] = int(_FARCLIP_EXE_CEILING)\n # Bump when normalization changes so existing managed installs get one', - ) - replace_exact( - 'tests/test_farclip_config_sync.py', - ' signature = tool._vanilla_tweaks_signature()\n self.assertEqual(signature["selected_patch_normalization"], 4)', - ' signature = tool._vanilla_tweaks_signature()\n self.assertEqual(signature["selected_patch_normalization"], 4)\n self.assertEqual(signature["farclip"], 3000)\n\n tool.vt_farclip = _Var(1500)\n runtime_changed = tool._vanilla_tweaks_signature()\n self.assertEqual(runtime_changed["farclip"], 3000)\n self.assertEqual(signature, runtime_changed)', - ) - replace_exact( - 'RELEASE_NOTES.md', - '- SuperWoW now uses the same FoV selected by the Modernization Tool by synchronizing the `FoV` CVar in `WTF/Config.wtf`.\n', - '- SuperWoW now uses the same FoV selected by the Modernization Tool by synchronizing the `FoV` CVar in `WTF/Config.wtf`.\n- Render Distance now keeps the executable Farclip ceiling fixed at **3000** while synchronizing the selected runtime value to `WTF/Config.wtf`, preventing stale higher Farclip values from conflicting with `WoW_Modernized.exe`.\n', - ) - PY - - - name: Commit finalized changes - shell: bash - run: | - rm .github/workflows/finalize-farclip-release.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add setup_tool.py setup_tool_dynamic.py tests/test_farclip_config_sync.py RELEASE_NOTES.md .github/workflows/finalize-farclip-release.yml - git commit -m "Finalize Farclip release polish" - git push origin HEAD:test/farclip-config-sync diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e4832ac..cfc836d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -10,6 +10,7 @@ This update focuses on **better compatibility, safer WoW.exe patching, improved - Improved support for already-patched clients while preserving client-specific loader code. - Removed overly strict build/version checks that could reject compatible clients. - SuperWoW now uses the same FoV selected by the Modernization Tool by synchronizing the `FoV` CVar in `WTF/Config.wtf`. +- Render Distance now keeps the executable Farclip ceiling fixed at **3000** while synchronizing the selected runtime value to `WTF/Config.wtf`, preventing stale higher Farclip values from conflicting with `WoW_Modernized.exe`. ## 🎨 MPQ & Recovery diff --git a/setup_tool.py b/setup_tool.py index 2e47764..d2c7f30 100644 --- a/setup_tool.py +++ b/setup_tool.py @@ -111,7 +111,7 @@ class WowSetupTool: # Tweaks Tab "fov": "Calculates horizontal Field of View mathematically scaled to maintain vertical aspect space based on your screen ratio.", - "farclip": "Increases the maximum terrain render distance. Vanilla default is 777. Tweaks default is 1500.", + "farclip": "Sets the active terrain render distance used by the game. Vanilla default is 777. The Tool keeps the executable Farclip ceiling fixed at 3000.", "frill": "Changes the ground clutter (grass) render distance. Vanilla default is 70. Tweaks default is 300.", "nameplate": "Increases the distance at which enemy nameplates become visible. Vanilla default is 20. Tweaks default is 41.", "cam": "Increases the maximum camera zoom-out distance. Vanilla default is 50. Max safe limit is 100.", @@ -749,7 +749,7 @@ class WowSetupTool: frame_nums.pack(fill='x', padx=15, pady=0) frame_nums.columnconfigure(1, weight=1) - self.create_slider_row(frame_nums, 0, "Render distance (Farclip) [Safe Max: 1500]:", self.vt_farclip, 777, 1500, 10000, "farclip") + self.create_slider_row(frame_nums, 0, "Render distance (Farclip) [Safe Max: 1500]:", self.vt_farclip, 777, 1500, 3000, "farclip") self.create_slider_row(frame_nums, 1, "Ground clutter (Frilldistance) [Safe Max: 300]:", self.vt_frill, 70, 300, 1000, "frill") self.create_slider_row(frame_nums, 2, "Nameplate range [Safe Max: 41]:", self.vt_nameplate, 20, 41, 150, "nameplate") self.create_slider_row(frame_nums, 3, "Camera distance [Safe Max: 100]:", self.vt_maxcam, 50, 100, 250, "cam") diff --git a/setup_tool_dynamic.py b/setup_tool_dynamic.py index 9ee7483..04b91dc 100644 --- a/setup_tool_dynamic.py +++ b/setup_tool_dynamic.py @@ -287,6 +287,9 @@ class ModernWowSetupTool(_ModernWowSetupToolCore): def _vanilla_tweaks_signature(self): signature = super()._vanilla_tweaks_signature() + # Runtime Farclip lives in Config.wtf; the executable ceiling is fixed. + # Keep the executable signature stable when only Render Distance changes. + signature["farclip"] = int(_FARCLIP_EXE_CEILING) # Bump when normalization changes so existing managed installs get one # clean WoW_Modernized.exe rebuild under the new policy. signature["selected_patch_normalization"] = 4 diff --git a/tests/test_farclip_config_sync.py b/tests/test_farclip_config_sync.py index 6d7a2b2..ccfe654 100644 --- a/tests/test_farclip_config_sync.py +++ b/tests/test_farclip_config_sync.py @@ -56,6 +56,12 @@ class FarclipConfigSyncTests(unittest.TestCase): tool.vt_bluemoon = _Var(False) signature = tool._vanilla_tweaks_signature() self.assertEqual(signature["selected_patch_normalization"], 4) + self.assertEqual(signature["farclip"], 3000) + + tool.vt_farclip = _Var(1500) + runtime_changed = tool._vanilla_tweaks_signature() + self.assertEqual(runtime_changed["farclip"], 3000) + self.assertEqual(signature, runtime_changed) def test_existing_farclip_cvar_is_replaced_without_touching_other_settings(self): tool = self._tool(777) -- 2.52.0 From c9f8b78df7f4f30c33def90c1c6b71e932e3719d Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Sun, 6 Sep 2026 21:20:12 +0200 Subject: [PATCH 09/10] Trigger final Farclip CI --- .farclip-ci-trigger | 1 + 1 file changed, 1 insertion(+) create mode 100644 .farclip-ci-trigger diff --git a/.farclip-ci-trigger b/.farclip-ci-trigger new file mode 100644 index 0000000..3d869aa --- /dev/null +++ b/.farclip-ci-trigger @@ -0,0 +1 @@ +Temporary CI trigger for the final Farclip release check. -- 2.52.0 From bea8a8e0edcd8a33d8e1e9ed7a32097b97bec393 Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Sun, 6 Sep 2026 21:20:30 +0200 Subject: [PATCH 10/10] Remove temporary Farclip CI trigger --- .farclip-ci-trigger | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .farclip-ci-trigger diff --git a/.farclip-ci-trigger b/.farclip-ci-trigger deleted file mode 100644 index 3d869aa..0000000 --- a/.farclip-ci-trigger +++ /dev/null @@ -1 +0,0 @@ -Temporary CI trigger for the final Farclip release check. -- 2.52.0