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]: for lane in [-1.0, 1.0]:
var lane_mid: Vector2 = flight_mid + flight_perpendicular * lane * w * 0.010 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_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_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(source, w * 0.005, Color(path_color, 0.55))
draw_circle(target, w * 0.029, Color(path_color, 0.13)) 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) 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. # 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: # 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 # 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). # rendered carrier is returned (so the measurement can still fail honestly).
var first_any := {} # A first valid pixel inside an actor portrait is a sprite occluder, not a
var first_outside_actor := {} # carrier: the point is reported as occluded and excluded (M110 A5 rule).
for step in range(7): for step in range(7):
var distance := 1.6 + 0.5 * float(step) var distance := 1.6 + 0.5 * float(step)
var probe := point + normal * distance * side 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) var color := image.get_pixel(px, py)
if _is_palette_blend(color, stroke, underlays): if _is_palette_blend(color, stroke, underlays):
continue continue
var entry := {"color": color, "distance": distance, "over_actor": _point_in_any_rect(actor_rects, pixel_position)} return {
if first_any.is_empty(): "color": color,
first_any = entry "distance": distance,
if not bool(entry.over_actor): "occluded": _point_in_any_rect(actor_rects, pixel_position),
first_outside_actor = entry }
break return {}
if not first_outside_actor.is_empty():
return first_outside_actor
return first_any
func _ring_carrier_ratio(image: Image, center: Vector2, radius: float, stroke: Color, underlays: Array, ui_rects: Array) -> float: 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): for step in range(24):
var angle := TAU * float(step) / 24.0 var angle := TAU * float(step) / 24.0
var direction := Vector2(cos(angle), sin(angle)) 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 probe: Vector2 = center + direction * (radius + float(offset))
var px := int(round(probe.x)) var px := int(round(probe.x))
var py := int(round(probe.y)) 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)) return _contrast_ratio(stroke, _median_color(samples))
func _path_contrast(image: Image, shell: Control) -> Dictionary: func _path_contrast(image: Image, shell: Control) -> Dictionary:
var art := shell.find_child("BattleLiveArt", true, false) as CanvasItem var art := shell.find_child("BattleLiveArt", true, false) as CanvasItem
if art == null or image.is_empty(): 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 actor_rects := _visible_actor_rects(shell)
var measured: Array = [] var measured: Array = []
var skipped := 0 var skipped := 0
var occluded := 0
for index in range(2, 19): for index in range(2, 19):
var point: Vector2 = points[index] var point: Vector2 = points[index]
var tangent: Vector2 = (Vector2(points[index + 1]) - Vector2(points[index - 1])).normalized() 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 = [] var point_carriers: Array = []
for side in [-1.0, 1.0]: for side in [-1.0, 1.0]:
var entry := _core_carrier_at(image, point, normal, side, stroke, underlays, ui_rects, actor_rects) 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) point_carriers.append(entry)
if point_carriers.is_empty(): if point_carriers.is_empty():
skipped += 1 skipped += 1
@@ -1361,17 +1364,23 @@ func _path_contrast(image: Image, shell: Control) -> Dictionary:
"ratio": _contrast_ratio(stroke, worst_entry.color), "ratio": _contrast_ratio(stroke, worst_entry.color),
"carrier": worst_entry.color, "carrier": worst_entry.color,
"distance": float(worst_entry.distance), "distance": float(worst_entry.distance),
"over_actor": bool(worst_entry.over_actor),
}) })
if measured.is_empty(): if measured.size() < 8:
return {"status": "NOT_PROVEN", "reason": "no valid carrier pixel around the rendered core", "stroke": stroke.to_html(false)} 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)) measured.sort_custom(func(a, b): return float(a.ratio) < float(b.ratio))
var worst: Dictionary = measured[0] var worst: Dictionary = measured[0]
var core_ratio := float(worst.ratio) var core_ratio := float(worst.ratio)
var core_carrier: Color = worst.carrier var core_carrier: Color = worst.carrier
var core_median := float(measured[measured.size() / 2].ratio) 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 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) 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)) var worst_ratio := minf(core_ratio, minf(source_ratio, target_ratio))
return { return {
"method": _path_contrast_method(), "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": core_carrier.to_html(false),
"core_carrier_luminance": snappedf(_luminance(core_carrier), 0.0001), "core_carrier_luminance": snappedf(_luminance(core_carrier), 0.0001),
"core_carrier_distance_px": snappedf(float(worst.distance), 0.1), "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_measured": measured.size(),
"core_points_skipped": skipped, "core_points_skipped": skipped,
"core_points_occluded": occluded,
"source_marker_ratio": snappedf(source_ratio, 0.01), "source_marker_ratio": snappedf(source_ratio, 0.01),
"target_marker_ratio": snappedf(target_ratio, 0.01), "target_marker_ratio": snappedf(target_ratio, 0.01),
"worst_ratio": snappedf(worst_ratio, 0.01), "worst_ratio": snappedf(worst_ratio, 0.01),