fix: 360 omni marking now enumerates smart-tag registry instead of physics sweep

linear_sphere_sweep returns only a capped hit table (no count) and its
slots flood with world-geometry actors, starving real enemies behind the
player (crash: nil num_hits compare; then silent no-marks). Candidates
now come from smart_tag_system._unit_extension_data (alive, breed-typed,
in range) — geometry can't interfere, LOS gates still apply unless
omni_mark_ignore_los. OMNI-DIAG candidate/reject logging kept, gated on
debug_mode.
This commit is contained in:
2026-10-03 07:15:32 -04:00
parent 791c68b64e
commit bd8b3f2f72
@@ -60,7 +60,12 @@ local AIM_CHECK_FILTER = "filter_player_ping_target_selectio
local AIM_CHECK_MAX_HITS = 64 local AIM_CHECK_MAX_HITS = 64
local AIM_CHECK_MIN_RADIUS = 1 local AIM_CHECK_MIN_RADIUS = 1
local AIM_CHECK_RADIUS_PER_METER = 0.052 local AIM_CHECK_RADIUS_PER_METER = 0.052
local OMNI_MAX_HITS = 128 local omni_rejects = nil
local function omni_reject(reason)
if omni_rejects then
omni_rejects[reason] = (omni_rejects[reason] or 0) + 1
end
end
-- Params -- Params
local visibility_raycast_object = nil local visibility_raycast_object = nil
@@ -581,11 +586,50 @@ function mod:find_auto_mark_target_unit(min_range, max_range, tag_name, tag_cont
-- hit unit list: forward raycast, or full-sphere sweep when omni mark is enabled -- hit unit list: forward raycast, or full-sphere sweep when omni mark is enabled
local omni_mark = mod_settings.omni_mark local omni_mark = mod_settings.omni_mark
if mod_settings.debug_mode and not mod.omni_diag_settings_logged then
mod.omni_diag_settings_logged = true
mod:info(string.format("OMNI-DIAG settings: omni_mark=%s ignore_los=%s debug=%s", tostring(omni_mark), tostring(mod_settings.omni_mark_ignore_los), tostring(mod_settings.debug_mode)))
end
local ray_origin, forward, right, up = smart_targeting_extension:_targeting_parameters() local ray_origin, forward, right, up = smart_targeting_extension:_targeting_parameters()
local hits, num_hits, hits_use_actor local hits, num_hits, hits_are_units
if omni_mark then if omni_mark then
hits, num_hits = PhysicsWorld_linear_sphere_sweep(smart_targeting_extension._physics_world, ray_origin, ray_origin + forward * max_range, max_range, OMNI_MAX_HITS, "types", "both", "collision_filter", COLLISION_FILTER, "report_initial_overlap", true) -- enumerate smart-taggable units directly; physics sphere sweeps get swamped by world geometry actors
hits_use_actor = true hits = {}
num_hits = 0
local unit_extension_data = smart_tag_system._unit_extension_data
local max_range_squared = max_range * max_range
if unit_extension_data then
for unit, smart_tag_extension in pairs(unit_extension_data) do
if HEALTH_ALIVE[unit] and smart_tag_extension._target_type == "breed" then
local position = Unit_world_position(unit, 1)
if Vector3_distance_squared(position, ray_origin) <= max_range_squared then
num_hits = num_hits + 1
hits[num_hits] = { unit = unit, position = position }
end
end
end
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 omni_rejects then
local counts, ordered_names = {}, {}
for i = 1, num_hits do
local unit_data_extension = ScriptUnit_extension(hits[i].unit, "unit_data_system")
local breed = unit_data_extension and unit_data_extension._breed
local key = breed and breed.name or "?"
if counts[key] == nil then
counts[key] = 0
ordered_names[#ordered_names + 1] = key
end
counts[key] = counts[key] + 1
end
local name_parts = {}
for _, n in ipairs(ordered_names) do
name_parts[#name_parts + 1] = n .. "x" .. counts[n]
end
mod:info(string.format("OMNI-DIAG candidates=%d breeds=[%s]", num_hits, table.concat(name_parts, ",")))
end
else else
hits, num_hits = PhysicsWorld_raycast(smart_targeting_extension._physics_world, ray_origin, forward, max_range, "all", "collision_filter", COLLISION_FILTER) hits, num_hits = PhysicsWorld_raycast(smart_targeting_extension._physics_world, ray_origin, forward, max_range, "all", "collision_filter", COLLISION_FILTER)
end end
@@ -650,14 +694,21 @@ function mod:find_auto_mark_target_unit(min_range, max_range, tag_name, tag_cont
for i = 1, num_hits do for i = 1, num_hits do
local hit = hits[i] local hit = hits[i]
local hit_actor = hits_use_actor and hit.actor or hit[INDEX_ACTOR] local hit_unit, hit_unit_center_pos
if not hit_actor then if hits_are_units then
goto continue hit_unit = hit.unit
hit_unit_center_pos = hit.position
else
local hit_actor = hit[INDEX_ACTOR]
if not hit_actor then
omni_reject("no_actor")
goto continue
end
hit_unit = Actor_unit(hit_actor)
end end
local hit_unit = Actor_unit(hit_actor)
-- ignore player unit, already marked unit and dead unit -- ignore player unit, already marked unit and dead unit
if hit_unit == player_unit or hit_unit == marked_unit or canceled_units[hit_unit] or not HEALTH_ALIVE[hit_unit] then if 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 goto continue
end end
@@ -665,11 +716,13 @@ function mod:find_auto_mark_target_unit(min_range, max_range, tag_name, tag_cont
local hit_unit_breed_data = hit_unit_data_extension and hit_unit_data_extension._breed local hit_unit_breed_data = hit_unit_data_extension and hit_unit_data_extension._breed
-- ignore untaggable unit -- ignore untaggable unit
if not hit_unit_breed_data or hit_unit_breed_data.smart_tag_target_type ~= "breed" or hit_unit_breed_data.faction_name == "imperium" or hit_unit_breed_data.unit_template_name ~= "minion" then if not hit_unit_breed_data or hit_unit_breed_data.smart_tag_target_type ~= "breed" or hit_unit_breed_data.faction_name == "imperium" or hit_unit_breed_data.unit_template_name ~= "minion" then
omni_reject("untaggable")
goto continue goto continue
end end
local hit_unit_breed_name = hit_unit_breed_data.name local hit_unit_breed_name = hit_unit_breed_data.name
if best_unit_is_sticky_targeting and hit_unit_breed_name ~= "renegade_netgunner" then if best_unit_is_sticky_targeting and hit_unit_breed_name ~= "renegade_netgunner" then
omni_reject("sticky1")
goto continue goto continue
end end
@@ -684,12 +737,14 @@ function mod:find_auto_mark_target_unit(min_range, max_range, tag_name, tag_cont
or IGNORE_EXECUTION_ORDER_FORCE_MARK_BREED_NAMES[hit_unit_breed_name] or IGNORE_EXECUTION_ORDER_FORCE_MARK_BREED_NAMES[hit_unit_breed_name]
or hit_unit_is_dormant_daemonhost or hit_unit_is_dormant_daemonhost
then then
omni_reject("priority_or_group")
goto continue goto continue
end end
end end
local hit_unit_is_attacking_player = hit_unit_attacking_unit_id == player_unit_id local hit_unit_is_attacking_player = hit_unit_attacking_unit_id == player_unit_id
if best_unit_is_sticky_targeting and not hit_unit_is_attacking_player then if best_unit_is_sticky_targeting and not hit_unit_is_attacking_player then
omni_reject("sticky2")
goto continue goto continue
end end
@@ -706,43 +761,53 @@ function mod:find_auto_mark_target_unit(min_range, max_range, tag_name, tag_cont
end end
if hit_unit_score < best_unit_score then if hit_unit_score < best_unit_score then
omni_reject("lower_score")
goto continue goto continue
end end
local hit_unit_center_pos = Actor_world_bounds(hit_actor) if not hit_unit_center_pos then
hit_unit_center_pos = Actor_world_bounds(hit[INDEX_ACTOR])
end
local to_hit_unit_center = hit_unit_center_pos - ray_origin local to_hit_unit_center = hit_unit_center_pos - ray_origin
local distance = Vector3_length(to_hit_unit_center) local distance = Vector3_length(to_hit_unit_center)
-- filter unit by range -- filter unit by range
if distance < min_range or distance > max_range then if distance < min_range or distance > max_range then
omni_reject("out_of_range")
goto continue goto continue
end end
-- prefer the closer unit on score ties (hit order is arbitrary for the omni sweep) -- prefer the closer unit on score ties (hit order is arbitrary for the omni sweep)
if hit_unit_score == best_unit_score and distance >= best_unit_distance then if hit_unit_score == best_unit_score and distance >= best_unit_distance then
omni_reject("tie_distance")
goto continue goto continue
end end
local hit_unit_tag = smart_tag_system:unit_tag(hit_unit) local hit_unit_tag = smart_tag_system:unit_tag(hit_unit)
if tag_name == TAG_NAMES.ENEMY_TAG then if tag_name == TAG_NAMES.ENEMY_TAG then
if hit_unit_tag then if hit_unit_tag then
omni_reject("already_tagged")
goto continue goto continue
end end
elseif tag_name == TAG_NAMES.VETERAN_TAG then elseif tag_name == TAG_NAMES.VETERAN_TAG then
if hit_unit_is_dormant_daemonhost then if hit_unit_is_dormant_daemonhost then
if hit_unit_tag then if hit_unit_tag then
omni_reject("vet_tagged")
goto continue goto continue
end end
else else
if not is_focus_target_tag_target_valid(hit_unit, hit_unit_tag, hit_unit_breed_name, hit_unit_attacking_unit_id, removed_units) then if not is_focus_target_tag_target_valid(hit_unit, hit_unit_tag, hit_unit_breed_name, hit_unit_attacking_unit_id, removed_units) then
omni_reject("vet_invalid")
goto continue goto continue
end end
end end
elseif tag_name == TAG_NAMES.COMPANION_TAG then elseif tag_name == TAG_NAMES.COMPANION_TAG then
if not is_cyber_mastiff_tag_target_valid(hit_unit, hit_unit_tag, hit_unit_breed_name, hit_unit_breed_data, hit_unit_attacking_unit_id, game_session, removed_units, distance, companion_position, hit_unit_center_pos) then if not is_cyber_mastiff_tag_target_valid(hit_unit, hit_unit_tag, hit_unit_breed_name, hit_unit_breed_data, hit_unit_attacking_unit_id, game_session, removed_units, distance, companion_position, hit_unit_center_pos) then
omni_reject("companion_invalid")
goto continue goto continue
end end
elseif tag_name == TAG_NAMES.SERVO_SKULL_TAG then elseif tag_name == TAG_NAMES.SERVO_SKULL_TAG then
if not is_servo_skull_tag_target_valid(hit_unit, hit_unit_tag, hit_unit_breed_name, hit_unit_breed_data, hit_unit_attacking_unit_id, game_session, removed_units, companion_position, hit_unit_center_pos, max_ability_charges, remaining_capacitance) then if not is_servo_skull_tag_target_valid(hit_unit, hit_unit_tag, hit_unit_breed_name, hit_unit_breed_data, hit_unit_attacking_unit_id, game_session, removed_units, companion_position, hit_unit_center_pos, max_ability_charges, remaining_capacitance) then
omni_reject("skull_invalid")
goto continue goto continue
end end
end end
@@ -761,6 +826,7 @@ function mod:find_auto_mark_target_unit(min_range, max_range, tag_name, tag_cont
end end
if not visible then if not visible then
omni_reject("not_visible")
goto continue goto continue
end end
@@ -777,6 +843,16 @@ function mod:find_auto_mark_target_unit(min_range, max_range, tag_name, tag_cont
::continue:: ::continue::
end end
if omni_rejects then
local parts = {}
for k, v in pairs(omni_rejects) do
parts[#parts + 1] = k .. "=" .. v
end
table.sort(parts)
mod:info(string.format("OMNI-DIAG pass[%s] best=%s rejects=[%s]", tostring(tag_name), tostring(best_unit_breed_name), table.concat(parts, ",")))
omni_rejects = nil
end
if best_unit ~= marked_unit then if best_unit ~= marked_unit then
return best_unit, best_unit_tag, best_unit_is_dormant_daemonhost return best_unit, best_unit_tag, best_unit_is_dormant_daemonhost
end end