Merge branch 'sprint-32/client'
This commit is contained in:
+2
-1
@@ -31,9 +31,10 @@ Format based on [Keep a Changelog](https://keepachangelog.com/).
|
||||
### Changed
|
||||
- Pre-push hook now validates JSON syntax on changed files (python3 -m json.tool)
|
||||
- HUD status panel: merged TimeDisplay into ImplantPanel — time, health, perception in one themed panel (#786)
|
||||
- HUD moved from UILayer (20) to InsertOverlay (10) for bloom treatment per D-051
|
||||
- HUD moved from UILayer (20) to InsertOverlay (10) for bloom treatment per D-049
|
||||
- Interaction prompt: converted from PanelContainer+StyleBoxFlat to ImplantPanel+ImplantDataRow (#788)
|
||||
- Minimap: added ImplantTheme-styled container frame behind circular display (#787)
|
||||
- Stance indicator: SVG icons with alpha-mask tint shader replace text-only labels (#787, D-086)
|
||||
- Amended 7 stale D-records with supersession notes (D-012, D-035, D-044, D-061, D-086, D-090, D-119)
|
||||
- Closed 4 resolved scope questions (Q-002, Q-004, Q-005, Q-007)
|
||||
- Fixed questions.md index (was 61 questions, actually 96 — 33 architecture Qs missing from index)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
shader_type canvas_item;
|
||||
// D-086: Runtime icon tint — replaces color channel with tint_color, preserves alpha mask.
|
||||
// Applied to SVG-sourced TextureRect nodes so stance/status icons can be color-coded at runtime.
|
||||
// Default: #c8d0e0 (insert chrome) — identical to the SVG's authored stroke color.
|
||||
uniform vec4 tint_color : source_color = vec4(0.784, 0.816, 0.878, 1.0);
|
||||
|
||||
void fragment() {
|
||||
float a = texture(TEXTURE, UV).a;
|
||||
COLOR = vec4(tint_color.rgb, a * tint_color.a);
|
||||
}
|
||||
@@ -122,14 +122,14 @@ func test_minimap_placeholder_exists_in_ui_layer() -> void:
|
||||
assert_that(_instance.get_node_or_null("InsertOverlay/Minimap")).is_not_null()
|
||||
|
||||
|
||||
func test_hud_exists_in_ui_layer() -> void:
|
||||
# D-049: HUD must be in UILayer.
|
||||
func test_hud_exists_in_insert_overlay() -> void:
|
||||
# D-051: HUD moved to InsertOverlay (layer 10) for bloom treatment (#786, PR #115).
|
||||
var scene := MAIN_SCENE
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
assert_that(_instance.get_node_or_null("UILayer/HUD")).is_not_null()
|
||||
assert_that(_instance.get_node_or_null("InsertOverlay/HUD")).is_not_null()
|
||||
|
||||
|
||||
func test_interaction_list_exists_in_insert_overlay() -> void:
|
||||
@@ -313,3 +313,33 @@ func test_tile_renderer_skips_nonzero_z() -> void:
|
||||
# z=1 and z=2 tiles should NOT be present (-1 = no cell)
|
||||
assert_that(tile_renderer.get_cell_source_id(Vector2i(1, 0))).is_equal(-1)
|
||||
assert_that(tile_renderer.get_cell_source_id(Vector2i(3, 0))).is_equal(-1)
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Stance indicator icon tests (#787)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_stance_indicator_applies_known_stance() -> void:
|
||||
# #787: _apply_stance updates icon + label for each known stance.
|
||||
var scene := MAIN_SCENE
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
var si = _instance.get_node_or_null("UILayer/StanceIndicator")
|
||||
assert_that(si).is_not_null()
|
||||
si._apply_stance("Careful")
|
||||
assert_that(si.get_current_stance()).is_equal("Careful")
|
||||
|
||||
|
||||
func test_stance_indicator_unknown_stance_no_crash() -> void:
|
||||
# #787: Unknown stance falls back gracefully — no crash, no texture change.
|
||||
var scene := MAIN_SCENE
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
var si = _instance.get_node_or_null("UILayer/StanceIndicator")
|
||||
assert_that(si).is_not_null()
|
||||
si._apply_stance("InvalidStance")
|
||||
assert_that(si.get_current_stance()).is_equal("InvalidStance")
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
[ext_resource type="Script" path="res://ui/gauntlet_hud.gd" id="1_gauntlet"]
|
||||
|
||||
; #496: Gauntlet HUD — room timer + personal bests, top-right below StanceIndicator (bottom at y:244)
|
||||
; #496: Gauntlet HUD — room timer + personal bests, top-right below StanceIndicator (bottom at y:246), 8px gap
|
||||
[node name="GauntletHUD" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 1
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
offset_left = -160.0
|
||||
offset_top = 252.0
|
||||
offset_top = 254.0
|
||||
offset_right = -16.0
|
||||
offset_bottom = 278.0
|
||||
offset_bottom = 280.0
|
||||
grow_horizontal = 0
|
||||
mouse_filter = 2
|
||||
script = ExtResource("1_gauntlet")
|
||||
|
||||
@@ -1,23 +1,48 @@
|
||||
extends Control
|
||||
|
||||
## D-053: Stance indicator — implant-styled badge (D-169).
|
||||
## Sprint/Walk/Careful/Crouch. Color-coded for quick read.
|
||||
## Sprint/Walk/Careful/Crouch. Icon (D-086) + color-coded label.
|
||||
|
||||
const STANCE_COLORS := {
|
||||
"Sprint": Color("#d45d5d"), # Red — fast, loud, dangerous
|
||||
"Walk": Color("#c8d0e0"), # Default — neutral white-blue
|
||||
"Careful": Color("#6bc9a6"), # Green — quiet, observant
|
||||
"Walk": Color("#c8d0e0"), # Default — neutral white-blue
|
||||
"Careful": Color("#6bc9a6"), # Green — quiet, observant
|
||||
"Crouch": Color("#e8c547"), # Amber — very quiet, slow
|
||||
}
|
||||
|
||||
const STANCE_ICONS := {
|
||||
"Sprint": "res://assets/icons/icon_stance_sprint.svg",
|
||||
"Walk": "res://assets/icons/icon_stance_walk.svg",
|
||||
"Careful": "res://assets/icons/icon_stance_careful.svg",
|
||||
"Crouch": "res://assets/icons/icon_stance_crouch.svg",
|
||||
}
|
||||
|
||||
var _current_stance: String = "Walk"
|
||||
var _panel: ImplantPanel
|
||||
var _row: ImplantDataRow
|
||||
var _icon: TextureRect
|
||||
var _label: Label
|
||||
var _icon_mat: ShaderMaterial
|
||||
var _icon_cache: Dictionary = {} # stance name → Texture2D
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
var theme_res := load("res://ui/implant/default_implant.tres") as ImplantTheme
|
||||
var icon_shader := load("res://shaders/icon_tint.gdshader") as Shader
|
||||
if not theme_res:
|
||||
push_error("StanceIndicator: failed to load implant theme")
|
||||
return
|
||||
if not icon_shader:
|
||||
push_error("StanceIndicator: failed to load icon_tint shader")
|
||||
return
|
||||
|
||||
# Preload all stance icons at startup
|
||||
for stance_name in STANCE_ICONS:
|
||||
var tex := load(STANCE_ICONS[stance_name]) as Texture2D
|
||||
if tex:
|
||||
_icon_cache[stance_name] = tex
|
||||
else:
|
||||
push_error("StanceIndicator: failed to load icon for %s" % stance_name)
|
||||
|
||||
_panel = ImplantPanel.new()
|
||||
_panel.name = "StancePanel"
|
||||
@@ -25,17 +50,52 @@ func _ready() -> void:
|
||||
_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_panel)
|
||||
|
||||
_row = ImplantDataRow.new("Walk", STANCE_COLORS["Walk"])
|
||||
_panel.add_component(_row)
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_theme_constant_override("separation", 4)
|
||||
hbox.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
|
||||
_icon = TextureRect.new()
|
||||
_icon.expand_mode = TextureRect.EXPAND_KEEP_SIZE
|
||||
_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
_icon.custom_minimum_size = Vector2(20.0, 20.0) # D-086: 20×20 viewBox
|
||||
_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_icon_mat = ShaderMaterial.new()
|
||||
_icon_mat.shader = icon_shader
|
||||
_icon_mat.set_shader_parameter("tint_color", STANCE_COLORS["Walk"])
|
||||
_icon.material = _icon_mat
|
||||
hbox.add_child(_icon)
|
||||
|
||||
_label = Label.new()
|
||||
_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_label.add_theme_font_size_override("font_size", theme_res.font_body)
|
||||
hbox.add_child(_label)
|
||||
|
||||
_panel.add_component(hbox)
|
||||
_apply_stance("Walk")
|
||||
|
||||
|
||||
func _apply_stance(stance: String) -> void:
|
||||
_current_stance = stance
|
||||
var color: Color = STANCE_COLORS.get(stance, Color("#c8d0e0"))
|
||||
|
||||
if _icon:
|
||||
var tex: Texture2D = _icon_cache.get(stance)
|
||||
if tex:
|
||||
_icon.texture = tex
|
||||
if _icon_mat:
|
||||
_icon_mat.set_shader_parameter("tint_color", color)
|
||||
|
||||
if _label:
|
||||
_label.text = stance
|
||||
_label.add_theme_color_override("font_color", color)
|
||||
|
||||
|
||||
func update_from_state() -> void:
|
||||
var stance: String = GameState.player_stance
|
||||
if stance == _current_stance:
|
||||
return
|
||||
_current_stance = stance
|
||||
var color: Color = STANCE_COLORS.get(stance, Color("#c8d0e0"))
|
||||
_row.set_content(stance, color)
|
||||
_apply_stance(stance)
|
||||
|
||||
|
||||
func get_current_stance() -> String:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
[ext_resource type="Script" path="res://ui/stance_indicator.gd" id="1_stance"]
|
||||
|
||||
; D-053: Stance indicator — below minimap (bottom at y:176), 24px gap.
|
||||
; Control height 44px to contain ImplantPanel (padding 12+12 + row 18 = 42px minimum).
|
||||
; Control height 46px: border(1) + padding(12) + icon(20) + padding(12) + border(1).
|
||||
[node name="StanceIndicator" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 1
|
||||
@@ -12,7 +12,7 @@ anchor_right = 1.0
|
||||
offset_left = -100.0
|
||||
offset_top = 200.0
|
||||
offset_right = -16.0
|
||||
offset_bottom = 244.0
|
||||
offset_bottom = 246.0
|
||||
grow_horizontal = 0
|
||||
mouse_filter = 2
|
||||
script = ExtResource("1_stance")
|
||||
|
||||
Reference in New Issue
Block a user