fix(ui): mouth-ring radius floored at 2x stroke — the radius-smaller-than-stroke draw_arc regime
Two separable findings from the live A/B (real driver): (1) the original zero-ring report was a viewport-framing crop — the ring fired all along at screen (1755,-191), above the frame under the COVER fit; recentering via set_view proved the path live. (2) The real bug once in-frame: at Quarter fit zoom the compensated ring radius (5.0/7.5=0.667 canvas) fell below the floored stroke (1.0 canvas) and draw_arc's stroke filled its own hole — a solid blob, not a ring. Bracket: 1x stroke=blob, 1.5x=hollow recovers, 2x=clean. Fix: zoom_compensated_ring_radius() floors the radius at 2x the paired stroke, wired through _zs_ring_radius() for both ring and halo arcs; verified live producing a clean hollow double-ring. The third member of the Godot sub-canvas-unit rasterizer family (width floor, stroke-vs-width sites, now radius-vs-stroke) — all recorded for the T-1176 render-mechanism discussion. 10 pin tests in the stroke-width suite; revert-verified (floor drop -> named failures); full client suite 3956/3956; gdlint clean. Tickets: T-1170 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -263,6 +263,14 @@ const MOUTH_RING_RADIUS: float = 5.0
|
||||
const MOUTH_HALO_RADIUS: float = 8.0
|
||||
const MOUTH_HALO_ALPHA: float = 0.30
|
||||
|
||||
## T-1170 live round (2026-07-23, coordinator's mouth-ring blob finding):
|
||||
## the minimum radius/stroke RATIO a draw_arc() ring needs to render hollow
|
||||
## rather than degenerate into a solid blob — see
|
||||
## zoom_compensated_ring_radius()'s own doc for the full A/B bracket
|
||||
## evidence (radius=1x stroke -> blob, 1.5x -> hollow, floor set at 2x with
|
||||
## margin over the observed transition).
|
||||
const RING_RADIUS_STROKE_MULTIPLIER: float = 2.0
|
||||
|
||||
## Attractor minimum-strength gate — verbatim from the retired
|
||||
## atlas_marker_overlay.gd GEN_ATTRACTOR_MIN_STRENGTH (:44). Region-only per
|
||||
## the ruling (ATTRACTORS_VISIBLE_BY_RUNG), wave 1 has no attractor rendering
|
||||
@@ -625,3 +633,53 @@ static func zoom_compensated_size(screen_space_size: float, view_zoom: float) ->
|
||||
## zoom_compensated_size()'s own behavior at Region's tiny fit zoom.
|
||||
static func zoom_compensated_stroke_width(screen_space_size: float, view_zoom: float) -> float:
|
||||
return maxf(zoom_compensated_size(screen_space_size, view_zoom), 1.0)
|
||||
|
||||
|
||||
## T-1170 live round (2026-07-23, coordinator's mouth-ring finding): the
|
||||
## RADIUS-SMALLER-THAN-STROKE regime — a THIRD sibling to
|
||||
## zoom_compensated_size()/zoom_compensated_stroke_width(), needed
|
||||
## specifically for draw_arc() RING markers (mouth rings — the only
|
||||
## draw_arc() caller in this file whose radius and stroke width are BOTH
|
||||
## small, zoom-compensated values that can cross each other).
|
||||
##
|
||||
## Root-cause evidence (live A/B bracket, temporary instrumentation, since
|
||||
## reverted — real running client via SR_LIVE=1, real x11/opengl3 driver):
|
||||
## the ORIGINAL "zero ring pixels" report turned out to be a SEPARATE,
|
||||
## already-correct-code issue — the terminus point legitimately sits near
|
||||
## the requesting window's own edge, and the fit-and-center COVER strategy
|
||||
## crops that edge off the visible viewport (screen_p verified computed as
|
||||
## (1755, -191) against a 1920x1080 frame — above the top edge, not a
|
||||
## drawing bug). Panning the view to re-center the SAME point (verified via
|
||||
## AtlasWindowViewer.set_view()) proves the ring genuinely draws — but as a
|
||||
## SOLID BLOB, not a hollow ring, at the production radius/stroke pair
|
||||
## (radius=0.667, stroke=1.0 canvas-local units, Quarter fit zoom 7.5):
|
||||
## draw_arc()'s stroke, centered ON the radius circle, extends inward past
|
||||
## the circle's own center once stroke exceeds ~1x the radius, filling the
|
||||
## hole. Bracket results (stroke fixed at 1.0 canvas-local, radius varied):
|
||||
## radius=0.51 (~stroke/2) -> still a solid blob; radius=1.0 (=stroke) ->
|
||||
## solid blob (the production case); radius=1.5 (1.5x stroke) -> hollow ring
|
||||
## recovers; radius=2.0 (2x stroke) -> hollow ring, cleaner. The blob
|
||||
## persists past the naive geometric threshold (radius > stroke/2, where an
|
||||
## infinitely-thin/perfectly-antialiased ring would already have a hole)
|
||||
## because draw_arc()'s low tessellation (18 points, this file's own call)
|
||||
## plus antialiasing blur eat into the theoretical hole at these tiny
|
||||
## absolute magnitudes — an empirical floor, not a derived one, chosen with
|
||||
## margin over the observed 1.0x-blob/1.5x-hollow transition rather than
|
||||
## shaving the boundary exactly.
|
||||
##
|
||||
## The fix: floor the RADIUS at `stroke * RING_RADIUS_STROKE_MULTIPLIER`
|
||||
## (2.0, the top-of-file const — verified clean in the bracket above)
|
||||
## whenever the naive zoom-compensated radius would fall below it — the
|
||||
## same "floor the OUTPUT, never let a sub-threshold value reach Godot's
|
||||
## renderer" pattern zoom_compensated_stroke_width() already established,
|
||||
## applied to the paired radius/stroke relationship a lone-value floor
|
||||
## can't express (unlike the stroke-width floor, this one's threshold is
|
||||
## RELATIVE to another draw-time value, not an absolute constant). At every
|
||||
## zoom where the naive radius already clears the floor on its own
|
||||
## (Region's dot radii, or any District/Quarter case wide enough), this is
|
||||
## an exact no-op — identical to calling zoom_compensated_size() directly.
|
||||
static func zoom_compensated_ring_radius(
|
||||
screen_space_radius: float, stroke_width_canvas_local: float, view_zoom: float
|
||||
) -> float:
|
||||
var naive_radius: float = zoom_compensated_size(screen_space_radius, view_zoom)
|
||||
return maxf(naive_radius, stroke_width_canvas_local * RING_RADIUS_STROKE_MULTIPLIER)
|
||||
|
||||
@@ -362,6 +362,24 @@ func _zs_stroke(screen_space_size: float, ctx: Dictionary) -> float:
|
||||
return AtlasWindowGeometryNature.zoom_compensated_stroke_width(screen_space_size, ctx["view_zoom"])
|
||||
|
||||
|
||||
## T-1170 live round (2026-07-23, coordinator's mouth-ring blob finding): the
|
||||
## RING-RADIUS-specific sibling of _zs()/_zs_stroke() — every draw_arc() ring
|
||||
## marker whose radius and stroke are BOTH small, zoom-compensated values
|
||||
## (currently: the two _draw_mouth() rings) routes its RADIUS through this
|
||||
## instead of plain _zs(), so the radius never falls at-or-below its own
|
||||
## paired stroke width and degenerates from a hollow ring into a solid blob.
|
||||
## See AtlasWindowGeometryNature.zoom_compensated_ring_radius()'s own doc for
|
||||
## the full A/B bracket evidence (radius=1x stroke -> blob, 1.5x -> hollow,
|
||||
## floor set at 2x with margin). `stroke_canvas_local` is the ALREADY
|
||||
## zoom-compensated stroke value (this function's own caller passes
|
||||
## _zs_stroke()'s result, not a raw screen-space width) — the floor compares
|
||||
## against the SAME canvas-local units the naive radius divide produces.
|
||||
func _zs_ring_radius(screen_space_radius: float, stroke_canvas_local: float, ctx: Dictionary) -> float:
|
||||
return AtlasWindowGeometryNature.zoom_compensated_ring_radius(
|
||||
screen_space_radius, stroke_canvas_local, ctx["view_zoom"]
|
||||
)
|
||||
|
||||
|
||||
## Pixel (row, col) -> fractional district position, wrap-resolved against
|
||||
## the currently-HELD view's own center (ctx["held_center"]) — the
|
||||
## representative wrap-image _pos()'s canvas conversion needs. T-1172: this
|
||||
@@ -616,14 +634,15 @@ func _segment_touches_drawn_water(from_district: Vector2, to_district: Vector2,
|
||||
## also the marker for REAL course termini (Ruling 5b/3e) — one geometry
|
||||
## function, both presentation surfaces.
|
||||
func _draw_mouth(p: Vector2, ctx: Dictionary) -> void:
|
||||
var ring_stroke: float = _zs_stroke(1.5, ctx)
|
||||
draw_arc(
|
||||
p,
|
||||
_zs(AtlasWindowGeometryNature.MOUTH_RING_RADIUS, ctx),
|
||||
_zs_ring_radius(AtlasWindowGeometryNature.MOUTH_RING_RADIUS, ring_stroke, ctx),
|
||||
0.0,
|
||||
TAU,
|
||||
18,
|
||||
COLOR_GEN_MOUTH,
|
||||
_zs_stroke(1.5, ctx)
|
||||
ring_stroke
|
||||
)
|
||||
var halo := Color(
|
||||
COLOR_GEN_MOUTH.r,
|
||||
@@ -631,8 +650,15 @@ func _draw_mouth(p: Vector2, ctx: Dictionary) -> void:
|
||||
COLOR_GEN_MOUTH.b,
|
||||
AtlasWindowGeometryNature.MOUTH_HALO_ALPHA
|
||||
)
|
||||
var halo_stroke: float = _zs_stroke(1.0, ctx)
|
||||
draw_arc(
|
||||
p, _zs(AtlasWindowGeometryNature.MOUTH_HALO_RADIUS, ctx), 0.0, TAU, 22, halo, _zs_stroke(1.0, ctx)
|
||||
p,
|
||||
_zs_ring_radius(AtlasWindowGeometryNature.MOUTH_HALO_RADIUS, halo_stroke, ctx),
|
||||
0.0,
|
||||
TAU,
|
||||
22,
|
||||
halo,
|
||||
halo_stroke
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user