From 2ab6d87532458514369cf781a8579835b5f367f2 Mon Sep 17 00:00:00 2001 From: revernomad17 Date: Tue, 7 Apr 2026 17:15:30 +0700 Subject: [PATCH] fix check_names false positives from server steam_id collisions When a server folder's meta.cpp publishedid appears in a local folder that has a completely different name, the steam_id lookup was returning a wrong MISMATCH. Added a two-pass classification: any proposed rename target that is already an exact OK match for another folder is reclassified as NOT_ON_SERVER (steam_id collision) instead of MISMATCH. _resolve_status moved to module level and takes ok_disk_names as a parameter so it can be unit-tested independently. Co-Authored-By: Claude Sonnet 4.6 --- check_names.py | 69 +++++++++++++++++++++++++++++++++++++------------- test_suite.py | 32 ++++++++++++++++++++++- 2 files changed, 83 insertions(+), 18 deletions(-) diff --git a/check_names.py b/check_names.py index b780640..b53f7ff 100644 --- a/check_names.py +++ b/check_names.py @@ -97,6 +97,30 @@ def _collect_mod_folders(cfg, group_filter: str | None) -> list[tuple[str, Path] return result +# --------------------------------------------------------------------------- +# Classification helpers +# --------------------------------------------------------------------------- + +def _resolve_status( + disk_name: str, + server_name: str | None, + ok_disk_names: set[str], +) -> tuple[str, str]: + """ + Return (status, display_col) for one mod, after false-positive filtering. + + A MISMATCH is downgraded to NOT_ON_SERVER when the proposed server name is + already the exact disk name of another folder (steam_id collision on server). + """ + if server_name is None: + return "NOT_ON_SERVER", "---" + if server_name == disk_name: + return "OK", server_name + if server_name in ok_disk_names: + return "NOT_ON_SERVER", "--- (steam_id collision)" + return "MISMATCH", server_name + + # --------------------------------------------------------------------------- # Fix: rename folder + update junction # --------------------------------------------------------------------------- @@ -174,32 +198,43 @@ def main() -> None: print("No @Mod folders found in downloads/.\n") sys.exit(0) - # ---- Classify each mod ---- + # ---- Pass 1: raw lookup for every mod ---- + # result row: (group, mod_dir, server_name | None) + raw: list[tuple[str, Path, str | None]] = [] + for group, mod_dir in mods: + raw.append((group, mod_dir, _lookup_server_name(mod_dir.name, mod_dir, index))) + + # ---- Pass 2: discard false positives caused by steam_id collisions ---- + # If a server name is already an exact disk name (i.e. an OK match exists), + # any other folder whose steam_id lookup points to that same server name is a + # false positive — the server has a bad/shared publishedid in meta.cpp. + # Reclassify those entries as NOT_ON_SERVER. + ok_disk_names: set[str] = { + mod_dir.name + for _, mod_dir, server_name in raw + if server_name == mod_dir.name # exact match = genuinely OK + } + def _classify(disk_name: str, server_name: str | None) -> tuple[str, str]: + return _resolve_status(disk_name, server_name, ok_disk_names) + + # ---- Print table ---- ok_count = mismatch_count = unknown_count = 0 - mismatches: list[tuple[str, Path, str]] = [] # (group, mod_dir, server_name) + mismatches: list[tuple[str, Path, str]] = [] print(f" {'Disk name':<{W_DISK}} {'Group':<{W_GROUP}} {'Server name':<{W_SERVER}} Status") print(f" {'-'*W_DISK} {'-'*W_GROUP} {'-'*W_SERVER} ------") - for group, mod_dir in mods: - disk_name = mod_dir.name - server_name = _lookup_server_name(disk_name, mod_dir, index) - - if server_name is None: - status = "NOT_ON_SERVER" - unknown_count += 1 - server_col = "---" - elif server_name == disk_name: - status = "OK" + for group, mod_dir, server_name in raw: + status, server_col = _classify(mod_dir.name, server_name) + if status == "OK": ok_count += 1 - server_col = server_name - else: - status = "MISMATCH" + elif status == "MISMATCH": mismatch_count += 1 - server_col = server_name mismatches.append((group, mod_dir, server_name)) + else: + unknown_count += 1 - print(f" {disk_name:<{W_DISK}} {group:<{W_GROUP}} {server_col:<{W_SERVER}} {status}") + print(f" {mod_dir.name:<{W_DISK}} {group:<{W_GROUP}} {server_col:<{W_SERVER}} {status}") print(f"\n {ok_count} OK, {mismatch_count} mismatch, {unknown_count} not on server\n") diff --git a/test_suite.py b/test_suite.py index 889ff2a..f601d9d 100644 --- a/test_suite.py +++ b/test_suite.py @@ -1064,7 +1064,7 @@ test("all exported symbols are callable or types", _test_exports_are_callable) group("check_names helpers") -from check_names import _server_name_from_url, _read_local_steam_id, _lookup_server_name +from check_names import _server_name_from_url, _read_local_steam_id, _lookup_server_name, _resolve_status def _test_server_name_from_url(): @@ -1143,6 +1143,36 @@ test("_lookup_server_name: falls back to name", _test_lookup_server_name_by_name test("_lookup_server_name: steam_id beats name fallback", _test_lookup_server_name_steam_id_beats_name) test("_lookup_server_name: not found -> None", _test_lookup_server_name_not_found) +# _resolve_status tests (the false-positive filter) + +def _test_resolve_status_ok(): + status, col = _resolve_status("@ace", "@ace", ok_disk_names={"@ace"}) + assert_eq(status, "OK") + assert_eq(col, "@ace") + +def _test_resolve_status_mismatch(): + status, col = _resolve_status("@ACE3", "@ace3", ok_disk_names={"@cba_a3"}) + assert_eq(status, "MISMATCH") + assert_eq(col, "@ace3") + +def _test_resolve_status_not_found(): + status, col = _resolve_status("@Unknown", None, ok_disk_names=set()) + assert_eq(status, "NOT_ON_SERVER") + +def _test_resolve_status_steam_id_collision(): + # server_name is already correctly held by another folder → false positive + ok = {"@Realistic Ragdoll Physics"} + status, col = _resolve_status("@NIArms All in One- ACE Compatibility", + "@Realistic Ragdoll Physics", + ok_disk_names=ok) + assert_eq(status, "NOT_ON_SERVER", "Should be reclassified due to collision") + assert "collision" in col + +test("_resolve_status: OK match", _test_resolve_status_ok) +test("_resolve_status: genuine mismatch", _test_resolve_status_mismatch) +test("_resolve_status: not found", _test_resolve_status_not_found) +test("_resolve_status: steam_id collision reclassified as NOT_ON_SERVER", _test_resolve_status_steam_id_collision) + # --------------------------------------------------------------------------- # 10. Integration: parse → compare → reporter (offline)