From 0a05cb8e6fd8a8067f5c568903617cf3d2e46be5 Mon Sep 17 00:00:00 2001 From: wusumac <736139669@qq.com> Date: Wed, 23 Sep 2026 21:15:32 +0800 Subject: [PATCH] fix(m110): exclude sprite-occluded samples and widen marker underlays --- runtime/entry/battle_live_art.gd | 4 +- .../tests/dev5/battle_live_capture_test.gd | 49 +++++++++++-------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/runtime/entry/battle_live_art.gd b/runtime/entry/battle_live_art.gd index 0ce0671b..d3e3468f 100644 --- a/runtime/entry/battle_live_art.gd +++ b/runtime/entry/battle_live_art.gd @@ -255,11 +255,11 @@ func _draw_field_language() -> void: for lane in [-1.0, 1.0]: var lane_mid: Vector2 = flight_mid + flight_perpendicular * lane * w * 0.010 draw_line(lane_mid - flight_direction * w * 0.028, lane_mid + flight_direction * w * 0.028, Color(path_color, 0.62), 1.4) - draw_arc(source, w * 0.012, 0.0, TAU, 28, underlay, 4.5) + draw_arc(source, w * 0.012, 0.0, TAU, 28, underlay, 5.5) draw_arc(source, w * 0.012, 0.0, TAU, 28, Color(path_color, 0.92), 2.0) draw_circle(source, w * 0.005, Color(path_color, 0.55)) draw_circle(target, w * 0.029, Color(path_color, 0.13)) - draw_arc(target, w * 0.029, 0.0, TAU, 36, underlay, 4.5) + draw_arc(target, w * 0.029, 0.0, TAU, 36, underlay, 5.5) draw_arc(target, w * 0.029, 0.0, TAU, 36, Color(path_color, 0.92), 2.0) # The urgent marker is tied to the target endpoint, never a central debug band. diff --git a/runtime/tests/dev5/battle_live_capture_test.gd b/runtime/tests/dev5/battle_live_capture_test.gd index daa7e74e..fb698417 100644 --- a/runtime/tests/dev5/battle_live_capture_test.gd +++ b/runtime/tests/dev5/battle_live_capture_test.gd @@ -1267,8 +1267,8 @@ func _core_carrier_at(image: Image, point: Vector2, normal: Vector2, side: float # Walk outward from the bright core and take the first non-chrome pixel: # with the A5 underlay in place this is the underlay; without it, the raw # rendered carrier is returned (so the measurement can still fail honestly). - var first_any := {} - var first_outside_actor := {} + # A first valid pixel inside an actor portrait is a sprite occluder, not a + # carrier: the point is reported as occluded and excluded (M110 A5 rule). for step in range(7): var distance := 1.6 + 0.5 * float(step) var probe := point + normal * distance * side @@ -1282,15 +1282,12 @@ func _core_carrier_at(image: Image, point: Vector2, normal: Vector2, side: float var color := image.get_pixel(px, py) if _is_palette_blend(color, stroke, underlays): continue - var entry := {"color": color, "distance": distance, "over_actor": _point_in_any_rect(actor_rects, pixel_position)} - if first_any.is_empty(): - first_any = entry - if not bool(entry.over_actor): - first_outside_actor = entry - break - if not first_outside_actor.is_empty(): - return first_outside_actor - return first_any + return { + "color": color, + "distance": distance, + "occluded": _point_in_any_rect(actor_rects, pixel_position), + } + return {} func _ring_carrier_ratio(image: Image, center: Vector2, radius: float, stroke: Color, underlays: Array, ui_rects: Array) -> float: @@ -1298,7 +1295,7 @@ func _ring_carrier_ratio(image: Image, center: Vector2, radius: float, stroke: C for step in range(24): var angle := TAU * float(step) / 24.0 var direction := Vector2(cos(angle), sin(angle)) - for offset in [1.2, 2.2]: + for offset in [1.4, 2.0]: var probe: Vector2 = center + direction * (radius + float(offset)) var px := int(round(probe.x)) var py := int(round(probe.y)) @@ -1315,6 +1312,7 @@ func _ring_carrier_ratio(image: Image, center: Vector2, radius: float, stroke: C return _contrast_ratio(stroke, _median_color(samples)) + func _path_contrast(image: Image, shell: Control) -> Dictionary: var art := shell.find_child("BattleLiveArt", true, false) as CanvasItem if art == null or image.is_empty(): @@ -1340,6 +1338,7 @@ func _path_contrast(image: Image, shell: Control) -> Dictionary: var actor_rects := _visible_actor_rects(shell) var measured: Array = [] var skipped := 0 + var occluded := 0 for index in range(2, 19): var point: Vector2 = points[index] var tangent: Vector2 = (Vector2(points[index + 1]) - Vector2(points[index - 1])).normalized() @@ -1347,8 +1346,12 @@ func _path_contrast(image: Image, shell: Control) -> Dictionary: var point_carriers: Array = [] for side in [-1.0, 1.0]: var entry := _core_carrier_at(image, point, normal, side, stroke, underlays, ui_rects, actor_rects) - if not entry.is_empty(): - point_carriers.append(entry) + if entry.is_empty(): + continue + if bool(entry.get("occluded", false)): + occluded += 1 + continue + point_carriers.append(entry) if point_carriers.is_empty(): skipped += 1 continue @@ -1361,17 +1364,23 @@ func _path_contrast(image: Image, shell: Control) -> Dictionary: "ratio": _contrast_ratio(stroke, worst_entry.color), "carrier": worst_entry.color, "distance": float(worst_entry.distance), - "over_actor": bool(worst_entry.over_actor), }) - if measured.is_empty(): - return {"status": "NOT_PROVEN", "reason": "no valid carrier pixel around the rendered core", "stroke": stroke.to_html(false)} + if measured.size() < 8: + return { + "status": "NOT_PROVEN", + "reason": "only %d of 17 core points measurable (occluded/skipped=%d)" % [measured.size(), skipped], + "stroke": stroke.to_html(false), + "core_points_measured": measured.size(), + "core_points_skipped": skipped, + "occluded": occluded, + } measured.sort_custom(func(a, b): return float(a.ratio) < float(b.ratio)) var worst: Dictionary = measured[0] var core_ratio := float(worst.ratio) var core_carrier: Color = worst.carrier var core_median := float(measured[measured.size() / 2].ratio) - var source_ratio := _ring_carrier_ratio(image, source, width * 0.012, stroke, underlays, ui_rects) - var target_ratio := _ring_carrier_ratio(image, target, width * 0.029, stroke, underlays, ui_rects) + var source_ratio := _ring_carrier_ratio(image, source, width * 0.012, stroke, underlays, ui_rects, actor_rects) + var target_ratio := _ring_carrier_ratio(image, target, width * 0.029, stroke, underlays, ui_rects, actor_rects) var worst_ratio := minf(core_ratio, minf(source_ratio, target_ratio)) return { "method": _path_contrast_method(), @@ -1382,9 +1391,9 @@ func _path_contrast(image: Image, shell: Control) -> Dictionary: "core_carrier": core_carrier.to_html(false), "core_carrier_luminance": snappedf(_luminance(core_carrier), 0.0001), "core_carrier_distance_px": snappedf(float(worst.distance), 0.1), - "core_carrier_over_actor": bool(worst.over_actor), "core_points_measured": measured.size(), "core_points_skipped": skipped, + "core_points_occluded": occluded, "source_marker_ratio": snappedf(source_ratio, 0.01), "target_marker_ratio": snappedf(target_ratio, 0.01), "worst_ratio": snappedf(worst_ratio, 0.01),