From a18194519b199d7d7a4a12cebadc2430971b1b52 Mon Sep 17 00:00:00 2001 From: michael Date: Sun, 4 Oct 2026 11:18:23 -0400 Subject: [PATCH] review: prod-readiness fix pass (3-agent code review + 2 self-found) HIGH: - rpc shadow dropped tagger_game_object_id and target_location (wrong vararg capture, args shifted); now forwards all varargs verbatim via unpack - rpc shadow could NEVER install: ensure() read context.smart_tag_system (client-side instance) instead of the updating self; now armed per-instance, client shadow still pinned to the context instance only - on_setting_changed debug_mode branch returned before the snapshot write: Debug Mode toggles were dead mid-session and the ON-notify read the stale value (AGENTS.md trap again, in my own fix); debug_chat/debug_hud now update the snapshot live too - capacitance retention unit bug: widget relabel to charges (-10..10) left the legacy /100 in load + change handler + breed menu round-trip (*100) + breed setter (/100), making every retention threshold 100x too small; removed all four conversions (health-threshold percent semantics untouched) MEDIUM: - TARGETS capture was observer-non-neutral: MARKED candidate fell through into full re-evaluation (burned shared visibility budget); now skips like vanilla - omni_diag_counter was an implicit global incremented in the prod hot path; now a file-local, only touched under debug_mode - hack skull/check/blacklist state survived mission end (stale unit handles + stamped _automark_hack_order flags); mod:reset_servo_skull_local_state() runs on state exit, which also clears omni_last/omni_candidates/last_skull_confirm/banners LOW: dead-tag release now honors per-class reset_cooldown; SERVO_SKULL gate checks class before doing GameSession reads; mod:diag hard-gated on debug_mode internally (diag_log.txt is never created for normal users anymore; un-gates OMNI-BOOT/banner/load probes); hits[] pooled per call; early no-candidates return no longer dangles omni_rejects/omni_hud_units; omni_last allocation gated on hud_capture; omni_hud_cur reset at entry and per-iteration before actor resolution (no cross-candidate leakage); builder runs only while a level world exists (no menu-side pcall spam); debug_hud_y fallback 24->0 matches default; px unit_text localization added; six new widgets had orphan *_tooltip loc keys - DMF only shows tooltips via an explicit tooltip field, added --- AutoMark/scripts/mods/AutoMark/AutoMark.lua | 39 +++++++++----- .../scripts/mods/AutoMark/AutoMark_data.lua | 6 +++ .../mods/AutoMark/AutoMark_localization.lua | 5 ++ .../mods/AutoMark/mark/servo_skull_mark.lua | 54 +++++++++++++++---- .../AutoMark/setting/servo_skull_setting.lua | 4 +- .../AutoMark/targeting/custom_targeting.lua | 39 +++++++++++--- .../scripts/mods/AutoMark/ui/debug_hud.lua | 20 +++---- .../scripts/mods/AutoMark/utils/utils.lua | 3 ++ 8 files changed, 129 insertions(+), 41 deletions(-) diff --git a/AutoMark/scripts/mods/AutoMark/AutoMark.lua b/AutoMark/scripts/mods/AutoMark/AutoMark.lua index 5d2fe3f..77ed08f 100644 --- a/AutoMark/scripts/mods/AutoMark/AutoMark.lua +++ b/AutoMark/scripts/mods/AutoMark/AutoMark.lua @@ -59,9 +59,9 @@ local mod_settings = { noospheric_command_boost_special = mod:get("noospheric_command_boost_special") or false, noospheric_command_boost_boss = mod:get("noospheric_command_boost_boss") or false, capacitance_retention = mod:get("capacitance_retention") or false, - capacitance_retention_elite_threshold = (mod:get("capacitance_retention_elite_threshold") or 0) / 100, - capacitance_retention_special_threshold = (mod:get("capacitance_retention_special_threshold") or 0) / 100, - capacitance_retention_boss_threshold = (mod:get("capacitance_retention_boss_threshold") or 0) / 100, + capacitance_retention_elite_threshold = mod:get("capacitance_retention_elite_threshold") or 0, + capacitance_retention_special_threshold = mod:get("capacitance_retention_special_threshold") or 0, + capacitance_retention_boss_threshold = mod:get("capacitance_retention_boss_threshold") or 0, servo_skull_mark_sticky_targeting = mod:get("servo_skull_mark_sticky_targeting") or false, servo_skull_mark_sticky_targeting_elite = mod:get("servo_skull_mark_sticky_targeting_elite") or false, servo_skull_mark_sticky_targeting_special = mod:get("servo_skull_mark_sticky_targeting_special") or false, @@ -372,21 +372,31 @@ mod.on_game_state_changed = function(status, state_name) context.game_mode_valid = false -- menu mark info rest reset_context() + if mod.reset_servo_skull_local_state then + mod:reset_servo_skull_local_state() + end + mod.omni_candidates = nil + mod.omni_candidates_pass = nil + mod.omni_last = nil + mod.last_skull_confirm = nil + mod.omni_gates_banner_done = nil end end end -- Mod Setting Change mod.on_setting_changed = function(setting_id) - if setting_id == "debug_mode" then + if setting_id == "debug_mode" or setting_id == "debug_chat" or setting_id == "debug_hud" then + local value = mod:get(setting_id) + mod_settings[setting_id] = value if mod_settings.debug_mode then + mod:diag("OMNI-HUD setting changed:", setting_id, "=", tostring(value)) + end + if setting_id == "debug_mode" and value then mod:notify("AutoMark DEBUG ON: silent file diagnostics now write to mods/AutoMark/diag_log.txt and keep growing - remember to turn Debug Mode off when you are done!") end return end - if setting_id == "debug_hud" or setting_id == "debug_chat" then - mod:diag("OMNI-HUD setting changed:", setting_id, "=", tostring(mod_settings[setting_id])) - end local result = mod:get(setting_id) local class_name = mod:get("class_selection") -- Normal Mod Settings @@ -394,7 +404,6 @@ mod.on_setting_changed = function(setting_id) if setting_id == "companion_health_threshold" or setting_id == "servo_skull_cancel_mark_health_threshold" then result = result / 100 elseif setting_id == "capacitance_retention_elite_threshold" or setting_id == "capacitance_retention_special_threshold" or setting_id == "capacitance_retention_boss_threshold" then - result = result / 100 if result == -0 and 1 / result < 0 then mod:set(setting_id .. "_negative_zero", true, false) else @@ -492,16 +501,18 @@ local function is_tag_valid(tag_name) elseif tag_name == TAG_NAMES.ENEMY_TAG then return context.class_name ~= "veteran" or not context.has_focus_target elseif tag_name == TAG_NAMES.SERVO_SKULL_TAG then - local tg = is_training_grounds() + if context.class_name ~= "cryptic" or not context.has_servo_skull then + return false + end local state_ok = mod:is_servo_skull_ready_to_shoot() - local charge_ok = tg or can_noospheric_command_boost() + local charge_ok = is_training_grounds() or can_noospheric_command_boost() if not (state_ok and charge_ok) and mod_settings.debug_mode then mod.omni_gate_diag_counter = (mod.omni_gate_diag_counter or 0) + 1 if mod.omni_gate_diag_counter % 15 == 1 then mod:diag(string.format("OMNI-GATE closed counter=%d state=%s state_ok=%s charge_ok=%s", mod.omni_gate_diag_counter, mod:debug_servo_skull_state(), tostring(state_ok), tostring(charge_ok))) end end - return context.class_name == "cryptic" and context.has_servo_skull and state_ok and charge_ok + return state_ok and charge_ok end return false end @@ -533,7 +544,9 @@ local function auto_mark_by_tag(tag_name, t, fixed_frame) mod:print_debug("Auto Mark target dead, releasing tag") smart_tag_system:cancel_tag(marked_tag._id, player_unit, true) marked_tag = nil - tag_context.cooldown = 0 + if mod:get_class_settings(tag_name).reset_cooldown then + tag_context.cooldown = 0 + end end end end @@ -719,7 +732,7 @@ mod:hook_safe(CLASS.PlayerUnitSmartTargetingExtension, "fixed_update", return end - if not mod.omni_gates_banner_done then + if not mod.omni_gates_banner_done and mod_settings.debug_mode then mod.omni_gates_banner_done = true local game_mode_manager = Managers.state.game_mode mod:diag(string.format("OMNI-GATES class=%s companion=%s skull=%s mode=%s valid=%s toggle=%s debug=%s", tostring(context.class_name), tostring(context.has_companion), tostring(context.has_servo_skull), game_mode_manager and tostring(game_mode_manager:game_mode_name()) or "nil", tostring(context.game_mode_valid), tostring(mod_settings.toggle_mod), tostring(mod_settings.debug_mode))) diff --git a/AutoMark/scripts/mods/AutoMark/AutoMark_data.lua b/AutoMark/scripts/mods/AutoMark/AutoMark_data.lua index 705c2ab..542617f 100644 --- a/AutoMark/scripts/mods/AutoMark/AutoMark_data.lua +++ b/AutoMark/scripts/mods/AutoMark/AutoMark_data.lua @@ -173,14 +173,17 @@ local widgets = { setting_id = "debug_chat", type = "checkbox", default_value = false, + tooltip = "debug_chat_tooltip", }, { setting_id = "debug_hud", type = "checkbox", default_value = false, + tooltip = "debug_hud_tooltip", sub_widgets = { { setting_id = "debug_hud_x", + tooltip = "debug_hud_x_tooltip", type = "numeric", default_value = 25, range = { 0, 3440 }, @@ -189,6 +192,7 @@ local widgets = { }, { setting_id = "debug_hud_y", + tooltip = "debug_hud_y_tooltip", type = "numeric", default_value = 0, range = { -1440, 1440 }, @@ -203,11 +207,13 @@ local widgets = { setting_id = "omni_mark", type = "checkbox", default_value = false, + tooltip = "omni_mark_tooltip", sub_widgets = { { setting_id = "omni_mark_ignore_los", type = "checkbox", default_value = false, + tooltip = "omni_mark_ignore_los_tooltip", }, }, }, diff --git a/AutoMark/scripts/mods/AutoMark/AutoMark_localization.lua b/AutoMark/scripts/mods/AutoMark/AutoMark_localization.lua index 290e399..b290cbd 100644 --- a/AutoMark/scripts/mods/AutoMark/AutoMark_localization.lua +++ b/AutoMark/scripts/mods/AutoMark/AutoMark_localization.lua @@ -900,6 +900,11 @@ local localization = { percent = { en = "%%", }, + px = { + en = "px", + ["zh-cn"] = "像素", + ["zh-tw"] = "像素", + }, charges = { en = "charge(s)", ["zh-cn"] = "层充能", diff --git a/AutoMark/scripts/mods/AutoMark/mark/servo_skull_mark.lua b/AutoMark/scripts/mods/AutoMark/mark/servo_skull_mark.lua index 0871649..5c0031f 100644 --- a/AutoMark/scripts/mods/AutoMark/mark/servo_skull_mark.lua +++ b/AutoMark/scripts/mods/AutoMark/mark/servo_skull_mark.lua @@ -34,8 +34,13 @@ local CANCEL_INTERVAL = 0.5 -- metatable.__index) never land on this instance's call path — so we rawset an instance-level -- shadow that beats every lookup chain. It replicates set_tag's host accept-branch -- (_generate_tag_id -> _create_tag_locally -> group limit -> rpc to clients) minus the gate. -local function ensure_automark_tag_hook() +local function ensure_automark_tag_hook(instance) local smart_tag_system = context.smart_tag_system + if instance and instance._is_server then + -- the embedded-server-side system (host players only): context holds the CLIENT-side + -- instance, so the incoming RPC shadow must be armed on the updating instance itself + smart_tag_system = instance + end if not smart_tag_system then return end @@ -60,16 +65,17 @@ local function ensure_automark_tag_hook() mod.omni_rpc_shadow_fired = true mod:diag("OMNI-RPC-SHADOW FIRE (server-side testify window)") end - local target_game_object_id, target_level_index, target_location = ... + -- forward EVERYTHING verbatim: real signature is + -- (self, channel_id, template_name_id, tagger_game_object_id, target_game_object_id, target_level_index, target_location) + local args = { ... } local saved_testify = GameParameters.testify GameParameters.testify = true local ok = pcall(function() - orig_rpc(self, channel_id, template_name_id, target_game_object_id, target_level_index, target_location) + orig_rpc(self, channel_id, template_name_id, unpack(args)) end) GameParameters.testify = saved_testify - if not ok and mod_settings.debug_mode and not mod.omni_rpc_shadow_err_logged then - mod.omni_rpc_shadow_err_logged = true - mod:diag("OMNI-RPC-SHADOW orig handler threw") + if not ok then + mod:diag("OMNI-RPC-SHADOW orig handler threw, template=", tostring(template_name_id)) end end rawset(smart_tag_system, "rpc_request_set_smart_tag", patched_rpc) @@ -77,6 +83,9 @@ local function ensure_automark_tag_hook() mod:diag("OMNI-RPC-SHADOW INSTALLED on server-state system") return end + if smart_tag_system ~= context.smart_tag_system then + return + end if mod.omni_hack_patched_sys == smart_tag_system then return end @@ -146,7 +155,7 @@ mod:hook_safe(CLASS.SmartTagSystem, "update", mod.omni_update_hook_seen = true mod:diag("OMNI-BOOT smart_tag_system:update seen, is_server=", tostring(self._is_server), " ctx_same=", tostring(self == context.smart_tag_system)) end - ensure_automark_tag_hook() + ensure_automark_tag_hook(self) end) local function servo_skull_mark_callback() @@ -256,7 +265,11 @@ function mod:is_servo_skull_hacking() return false end - local game_session = Managers.state.game_session:game_session() + local game_session_manager = Managers.state.game_session + local game_session = game_session_manager and game_session_manager:game_session() + if not game_session then + return false + end local game_object_id = Managers.state.unit_spawner:game_object_id(companion_unit) local state = get_game_object_field(game_session, game_object_id, "state") return state == servo_skull_states.hacking @@ -269,7 +282,11 @@ function mod:is_servo_skull_ready_to_shoot() return false end - local game_session = Managers.state.game_session:game_session() + local game_session_manager = Managers.state.game_session + local game_session = game_session_manager and game_session_manager:game_session() + if not game_session then + return false + end local game_object_id = Managers.state.unit_spawner:game_object_id(companion_unit) local state = get_game_object_field(game_session, game_object_id, "state") return state == servo_skull_states.following or state == servo_skull_states.following_shooting or state == servo_skull_states.following_shooting_ability @@ -282,7 +299,11 @@ function mod:debug_servo_skull_state() return "no_unit" end - local game_session = Managers.state.game_session:game_session() + local game_session_manager = Managers.state.game_session + local game_session = game_session_manager and game_session_manager:game_session() + if not game_session then + return "no_unit" + end local game_object_id = Managers.state.unit_spawner:game_object_id(companion_unit) return tostring(get_game_object_field(game_session, game_object_id, "state")) end @@ -587,3 +608,16 @@ function mod:auto_cancel_servo_skull_mark(dt, t, fixed_frame) end end end + +function mod:reset_servo_skull_local_state() + hack_check = nil + skull_mark_check = nil + hack_interval = 0 + cancel_interval = 0 + for unit, entry in pairs(mod._hack_blacklist) do + if entry.ext then + entry.ext._automark_hack_order = nil + end + mod._hack_blacklist[unit] = nil + end +end diff --git a/AutoMark/scripts/mods/AutoMark/setting/servo_skull_setting.lua b/AutoMark/scripts/mods/AutoMark/setting/servo_skull_setting.lua index 7f49749..a433a75 100644 --- a/AutoMark/scripts/mods/AutoMark/setting/servo_skull_setting.lua +++ b/AutoMark/scripts/mods/AutoMark/setting/servo_skull_setting.lua @@ -21,7 +21,7 @@ function mod:set_menu_servo_skull_breed_settings(breed_name) mod:set("noospheric_command_boost_breed_override", breed_settings and breed_settings.override or false, false) mod:set("servo_skull_range_limitation_breed", breed_settings and breed_settings.range_limitation or 0, false) mod:set("noospheric_command_boost_breed_toggle", breed_settings and breed_settings.toggle or false, false) - mod:set("capacitance_retention_breed_threshold", (breed_settings and breed_settings.threshold or 0) * 100, false) + mod:set("capacitance_retention_breed_threshold", breed_settings and breed_settings.threshold or 0, false) mod:set("servo_skull_mark_sticky_targeting_breed", breed_settings and breed_settings.sticky_targeting or false, false) mod:set("servo_skull_cancel_mark_breed_time_threshold", breed_settings and breed_settings.time_threshold or 0, false) mod:set("servo_skull_cancel_mark_breed_health_threshold", (breed_settings and breed_settings.health_threshold or 0) * 100, false) @@ -38,7 +38,7 @@ function mod:set_servo_skull_breed_setting(breed_name, setting_id, value) elseif setting_id == "noospheric_command_boost_breed_toggle" then noospheric_command_breed_settings[breed_name].toggle = value elseif setting_id == "capacitance_retention_breed_threshold" then - noospheric_command_breed_settings[breed_name].threshold = value / 100 + noospheric_command_breed_settings[breed_name].threshold = value if value == -0 and 1 / value < 0 then noospheric_command_breed_settings[breed_name].threshold_negative_zero = true else diff --git a/AutoMark/scripts/mods/AutoMark/targeting/custom_targeting.lua b/AutoMark/scripts/mods/AutoMark/targeting/custom_targeting.lua index 36caceb..346f4e3 100644 --- a/AutoMark/scripts/mods/AutoMark/targeting/custom_targeting.lua +++ b/AutoMark/scripts/mods/AutoMark/targeting/custom_targeting.lua @@ -61,6 +61,8 @@ local AIM_CHECK_MAX_HITS = 64 local AIM_CHECK_MIN_RADIUS = 1 local AIM_CHECK_RADIUS_PER_METER = 0.052 local omni_rejects = nil +local omni_diag_counter = 0 +local omni_hits_pool = {} local omni_hud_units = nil local omni_hud_cur = nil local function omni_reject(reason) @@ -599,13 +601,17 @@ function mod:find_auto_mark_target_unit(min_range, max_range, tag_name, tag_cont local hits, num_hits, hits_are_units local hud_capture = mod_settings.debug_hud or mod_settings.debug_mode omni_hud_units = nil + omni_hud_cur = nil if hud_capture then omni_hud_units = {} mod.omni_candidates = nil + else + mod.omni_candidates = nil + mod.omni_candidates_pass = nil end if omni_mark then -- enumerate smart-taggable units directly; physics sphere sweeps get swamped by world geometry actors - hits = {} + hits = omni_hits_pool num_hits = 0 local unit_extension_data = smart_tag_system._unit_extension_data local max_range_squared = max_range * max_range @@ -616,7 +622,13 @@ function mod:find_auto_mark_target_unit(min_range, max_range, tag_name, tag_cont local dist_squared = Vector3_distance_squared(position, ray_origin) if dist_squared <= max_range_squared then num_hits = num_hits + 1 - hits[num_hits] = { unit = unit, position = position } + local hit_entry = hits[num_hits] + if not hit_entry then + hit_entry = {} + hits[num_hits] = hit_entry + end + hit_entry.unit = unit + hit_entry.position = position if omni_hud_units then local unit_data_extension = ScriptUnit_extension(unit, "unit_data_system") local breed = unit_data_extension and unit_data_extension._breed @@ -629,9 +641,18 @@ function mod:find_auto_mark_target_unit(min_range, max_range, tag_name, tag_cont end end end + for i = num_hits + 1, #hits do + local stale = hits[i] + stale.unit = nil + stale.position = nil + end hits_are_units = true - omni_diag_counter = (omni_diag_counter or 0) + 1 - omni_rejects = mod_settings.debug_mode and omni_diag_counter % 10 == 1 and {} or nil + if mod_settings.debug_mode then + omni_diag_counter = omni_diag_counter + 1 + omni_rejects = omni_diag_counter % 10 == 1 and {} or nil + else + omni_rejects = nil + end if omni_rejects then local counts, ordered_names = {}, {} for i = 1, num_hits do @@ -655,6 +676,8 @@ function mod:find_auto_mark_target_unit(min_range, max_range, tag_name, tag_cont end if not hits or num_hits <= 0 then + omni_rejects = nil + omni_hud_units = nil if hud_capture then mod.omni_candidates = mod.omni_candidates or {} mod.omni_candidates_pass = tag_name @@ -719,6 +742,7 @@ function mod:find_auto_mark_target_unit(min_range, max_range, tag_name, tag_cont for i = 1, num_hits do local hit = hits[i] local hit_unit, hit_unit_center_pos + omni_hud_cur = nil if hits_are_units then hit_unit = hit.unit hit_unit_center_pos = hit.position @@ -733,6 +757,7 @@ function mod:find_auto_mark_target_unit(min_range, max_range, tag_name, tag_cont omni_hud_cur = omni_hud_units and omni_hud_units[hit_unit] if omni_hud_cur and hit_unit == marked_unit then omni_hud_cur.res = "MARKED" + goto continue elseif hit_unit == player_unit or hit_unit == marked_unit or canceled_units[hit_unit] or not HEALTH_ALIVE[hit_unit] then omni_reject("skip_player_dead") goto continue @@ -903,8 +928,10 @@ elseif tag_name == TAG_NAMES.SERVO_SKULL_TAG then omni_rejects = nil end - mod.omni_last = { tag_name = tag_name, best = best_unit_breed_name, cands = num_hits } - mod.omni_candidates_pass = tag_name + if hud_capture then + mod.omni_last = { tag_name = tag_name, best = best_unit_breed_name, cands = num_hits } + mod.omni_candidates_pass = tag_name + end if best_unit ~= marked_unit then return best_unit, best_unit_tag, best_unit_is_dormant_daemonhost diff --git a/AutoMark/scripts/mods/AutoMark/ui/debug_hud.lua b/AutoMark/scripts/mods/AutoMark/ui/debug_hud.lua index bb50bb1..645cd0f 100644 --- a/AutoMark/scripts/mods/AutoMark/ui/debug_hud.lua +++ b/AutoMark/scripts/mods/AutoMark/ui/debug_hud.lua @@ -170,6 +170,15 @@ local function draw_inner(dt) return end + rebuild_acc = rebuild_acc + dt + if rebuild_acc >= REBUILD_INTERVAL then + rebuild_acc = 0 + local ok, err = pcall(mod.build_debug_hud_lines, mod) + if not ok then + mod:diag("OMNI-HUD builder error:", tostring(err)) + end + end + local buf = mod.debug_hud_last local n = #buf if n == 0 then @@ -185,7 +194,7 @@ local function draw_inner(dt) local pad = math.floor(6 * scale + 0.5) local margin_x = math.floor((mod_settings.debug_hud_x or 25) * scale + 0.5) - local margin_y = math.floor((mod_settings.debug_hud_y or 24) * scale + 0.5) + local margin_y = math.floor((mod_settings.debug_hud_y or 0) * scale + 0.5) local maxw = 0 for i = 1, n do @@ -247,14 +256,5 @@ mod.update = function (dt) return end - rebuild_acc = rebuild_acc + dt - if rebuild_acc >= REBUILD_INTERVAL then - rebuild_acc = 0 - local ok, err = pcall(mod.build_debug_hud_lines, mod) - if not ok then - mod:diag("OMNI-HUD builder error:", tostring(err)) - end - end - draw(dt) end diff --git a/AutoMark/scripts/mods/AutoMark/utils/utils.lua b/AutoMark/scripts/mods/AutoMark/utils/utils.lua index 183d9c1..3795b18 100644 --- a/AutoMark/scripts/mods/AutoMark/utils/utils.lua +++ b/AutoMark/scripts/mods/AutoMark/utils/utils.lua @@ -96,6 +96,9 @@ end -- sandbox has no printf, so append to a plain text file instead (Mods.lua.io again). -- never call unguarded: mod:diag swallows its own failures. function mod:diag(...) + if not mod_settings.debug_mode then + return + end local args = { ... } local ok_diag, err_diag = pcall(function() local file = Mods.lua.io.open("./../mods/AutoMark/diag_log.txt", "a")