fix(m110): exclude sprite-occluded samples and widen marker underlays

This commit is contained in:
2026-09-23 21:15:32 +08:00
parent 1d72404579
commit 0a05cb8e6f
2 changed files with 31 additions and 22 deletions
+2 -2
View File
@@ -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.
+28 -19
View File
@@ -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,7 +1346,11 @@ 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():
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
@@ -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),