fix(ui): Atlas header gets its implant panel scrim — legible on any terrain (T-1197)

Root cause was not the suspected fade race — no animation exists
anywhere under client/ui/implant (grep-proven, and settle+2 vs
settle+90 captures were byte-identical). _build_screen_header() was
the single ImplantHeader call site in the codebase that added the
header bare instead of through ImplantPanel.add_component(), so its
fixed light-gray text washed out against pale terrain (Quarter upland
scatter, District olive) while reading fine on dark ocean. Wrapped in
an ImplantPanel like every sibling — the theme's panel_bg scrim makes
it legible everywhere. One committed golden (atlas_GJ820Bc_District)
already carried the ghosting baked in, confirming the bug was static;
all 13 goldens regenerated per the header change, two independent
live-server runs byte-identical (T-1157 stability discipline).
Verified live at the original evidence center: header crisp at
settle+2/+30/+90.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 14:46:55 +02:00
co-authored by Claude Fable 5
parent 47279184d2
commit 41144f317f
14 changed files with 33 additions and 4 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 487 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 658 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 56 KiB

@@ -145,6 +145,7 @@ var _canvas: Node2D = null
var _terrain_layer: StepCanvasTerrainLayer = null
var _annotation_layer: StepCanvasAnnotationLayer = null
var _screen_header: ImplantHeader = null
var _screen_header_panel = null # ImplantPanel — T-1197 backing scrim (untyped: no class_name cycle needed)
var _overlay_bar = null
var _legend_panel = null
var _request = null # StepCanvasRequest
@@ -691,14 +692,42 @@ func _apply_transform() -> void:
# =============================================================================
## T-1197: the header used to be a bare ImplantHeader added straight to this
## Control, drawn directly over the terrain canvas with no backing — every
## OTHER implant chrome element (the legend below it) gets its scrim from
## ImplantPanel's `panel_bg` StyleBoxFlat, but a lone ImplantHeader has no
## panel of its own (see implant_header.gd — just two Labels). Its fixed
## opaque text color reads fine against the near-black viewer background or
## dark ocean terrain (the common case at rung 0's letterboxed corner and most
## fixed-rung captures), but against pale/light terrain — Quarter's white
## upland scatter, District's olive/tan dry terrain — the contrast collapses
## to the point of unreadability (Araminta's T-1196 observation,
## scratchpad t1196/gj1002b_quarter.png; reproduced baked into the committed
## golden atlas_GJ820Bc_District.png). This is a static contrast defect, not a
## timing race — there is no fade/tween anywhere in this cluster (confirmed by
## reading every script under client/ui/implant), so no harness settle change
## could fix it. Wrapping in an ImplantPanel (same component every other
## header-bearing screen in the implant UI already uses — index_screen.gd,
## planet_screen.gd, system_screen.gd, reach_screen.gd, kind_menu_screen.gd,
## detail_screen.gd, overview_screen.gd, character_creation.gd, and the
## legend right below THIS header — step_canvas_viewer.gd's bare add_child()
## was the only outlier) gives the header the same opaque scrim as every
## sibling chrome element, guaranteeing contrast regardless of what terrain
## renders underneath.
func _build_screen_header() -> void:
var PanelScript := load("res://ui/implant/implant_panel.gd")
_screen_header_panel = PanelScript.new()
_screen_header_panel.name = "ScreenHeaderPanel"
_screen_header_panel.position = Vector2(PANEL_MARGIN, 16.0)
_screen_header_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
_screen_header_panel.custom_minimum_size.x = 320.0
_screen_header_panel.theme_resource = _implant_theme # set BEFORE add_child(), matching _build_legend_panel()'s own ordering
add_child(_screen_header_panel)
_screen_header = ImplantHeader.new()
_screen_header.position = Vector2(PANEL_MARGIN, 16.0)
_screen_header.custom_minimum_size.x = 320.0
_screen_header.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_screen_header)
if _implant_theme:
_screen_header.apply_implant_theme(_implant_theme)
_screen_header_panel.add_component(_screen_header)
func _refresh_screen_header() -> void: