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 <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,30 @@ def _collect_mod_folders(cfg, group_filter: str | None) -> list[tuple[str, Path]
|
|||||||
return result
|
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
|
# Fix: rename folder + update junction
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -174,32 +198,43 @@ def main() -> None:
|
|||||||
print("No @Mod folders found in downloads/.\n")
|
print("No @Mod folders found in downloads/.\n")
|
||||||
sys.exit(0)
|
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
|
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" {'Disk name':<{W_DISK}} {'Group':<{W_GROUP}} {'Server name':<{W_SERVER}} Status")
|
||||||
print(f" {'-'*W_DISK} {'-'*W_GROUP} {'-'*W_SERVER} ------")
|
print(f" {'-'*W_DISK} {'-'*W_GROUP} {'-'*W_SERVER} ------")
|
||||||
|
|
||||||
for group, mod_dir in mods:
|
for group, mod_dir, server_name in raw:
|
||||||
disk_name = mod_dir.name
|
status, server_col = _classify(mod_dir.name, server_name)
|
||||||
server_name = _lookup_server_name(disk_name, mod_dir, index)
|
if status == "OK":
|
||||||
|
|
||||||
if server_name is None:
|
|
||||||
status = "NOT_ON_SERVER"
|
|
||||||
unknown_count += 1
|
|
||||||
server_col = "---"
|
|
||||||
elif server_name == disk_name:
|
|
||||||
status = "OK"
|
|
||||||
ok_count += 1
|
ok_count += 1
|
||||||
server_col = server_name
|
elif status == "MISMATCH":
|
||||||
else:
|
|
||||||
status = "MISMATCH"
|
|
||||||
mismatch_count += 1
|
mismatch_count += 1
|
||||||
server_col = server_name
|
|
||||||
mismatches.append((group, mod_dir, 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")
|
print(f"\n {ok_count} OK, {mismatch_count} mismatch, {unknown_count} not on server\n")
|
||||||
|
|
||||||
|
|||||||
@@ -1064,7 +1064,7 @@ test("all exported symbols are callable or types", _test_exports_are_callable)
|
|||||||
|
|
||||||
group("check_names helpers")
|
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():
|
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: 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)
|
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)
|
# 10. Integration: parse → compare → reporter (offline)
|
||||||
|
|||||||
Reference in New Issue
Block a user