feat(ui): HUD visibility groups — gameplay vs implant layers (D-170)

New HudGroups autoload manages show/hide of related HUD elements as
groups. Exclusive groups (gameplay, implant) are mutually exclusive —
opening the star map hides stance indicator, minimap, interaction
prompts, etc. Closing it restores them.

Gameplay nodes registered in main.gd _ready(). Star map registers as
implant group and uses toggle_group() instead of direct visibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-05 11:01:28 +02:00
co-authored by Claude Opus 4.6
parent 44cc8cda4c
commit f037bcaa81
5 changed files with 120 additions and 2 deletions
+1
View File
@@ -28,6 +28,7 @@ UIStrings="*res://scripts/autoloads/ui_strings.gd"
FogState="*res://scripts/autoloads/fog_state.gd"
AudioManager="*res://scripts/autoloads/audio_manager.gd"
SessionManager="*res://scripts/autoloads/session_manager.gd"
HudGroups="*res://scripts/autoloads/hud_groups.gd"
HardwareDetector="*res://ui/hardware_detector.gd"
[audio]
+88
View File
@@ -0,0 +1,88 @@
extends Node
## HUD visibility group manager (D-170).
##
## Nodes register themselves into groups. When a group's visibility changes,
## all registered nodes show/hide together. This replaces per-panel manual
## hide/show of individual HUD elements.
##
## Groups:
## "gameplay" — stance indicator, minimap, health/time, interaction prompts,
## cursor renderer, inventory, news ticker, examine display
## "implant" — star map, travel planner, GTTR reader, station profile
## "modal" — settings, bug report, loading screen, debug console
## "debug" — debug overlay, gauntlet HUD, checklist
## "always" — nodes that never hide (reserved for future use)
##
## Usage from any node:
## HudGroups.register(self, "gameplay")
## HudGroups.show_group("implant") # shows implant, hides gameplay
## HudGroups.show_group("gameplay") # shows gameplay, hides implant
## HudGroups.toggle_group("implant") # toggle between gameplay and implant
## Which groups are mutually exclusive — showing one hides the others.
## Modal and debug are independent (they overlay on top of anything).
const EXCLUSIVE_GROUPS: Array[String] = ["gameplay", "implant"]
var _groups: Dictionary = {} # group_name -> Array[Control]
var _active_exclusive: String = "gameplay"
func register(node: Control, group: String) -> void:
if not _groups.has(group):
_groups[group] = []
if node not in _groups[group]:
_groups[group].append(node)
# Apply current visibility state
if group in EXCLUSIVE_GROUPS:
node.visible = (group == _active_exclusive)
func unregister(node: Control, group: String) -> void:
if _groups.has(group):
_groups[group].erase(node)
## Show a group. If it's exclusive, hide the other exclusive groups.
func show_group(group: String) -> void:
if group in EXCLUSIVE_GROUPS:
_active_exclusive = group
for g: String in EXCLUSIVE_GROUPS:
_set_group_visible(g, g == group)
else:
_set_group_visible(group, true)
## Hide a group. If exclusive, falls back to "gameplay".
func hide_group(group: String) -> void:
if group in EXCLUSIVE_GROUPS:
show_group("gameplay")
else:
_set_group_visible(group, false)
## Toggle between gameplay and the specified group.
func toggle_group(group: String) -> void:
if _active_exclusive == group:
show_group("gameplay")
else:
show_group(group)
## Check if a group is currently active.
func is_group_active(group: String) -> bool:
if group in EXCLUSIVE_GROUPS:
return _active_exclusive == group
# Non-exclusive: check if any member is visible
if _groups.has(group):
for node: Control in _groups[group]:
if is_instance_valid(node) and node.visible:
return true
return false
func _set_group_visible(group: String, vis: bool) -> void:
if not _groups.has(group):
return
for node: Control in _groups[group]:
if is_instance_valid(node):
node.visible = vis
+8
View File
@@ -62,6 +62,14 @@ func _ready() -> void:
camera.global_position = GameState.player_position * Constants.TILE_SIZE
_camera_anchored = true
# D-170: Register HUD nodes into visibility groups
# Gameplay group — hidden when implant panels are open
for node: Control in [hud, minimap, stance_indicator, cursor_renderer,
interaction_prompt, interaction_list, inventory_grid, news_ticker,
examine_display, world_radial]:
if node:
HudGroups.register(node, "gameplay")
# #775: Initialize extracted components
_consumers = SnapshotConsumers.new().init({
"monologue_display": monologue_display,
+6 -2
View File
@@ -118,6 +118,9 @@ func _ready() -> void:
_info_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_info_panel)
# D-170: Register with HUD visibility groups
HudGroups.register(self, "implant")
_load_data()
if _data_loaded:
_compute_layout()
@@ -140,9 +143,10 @@ func set_insert_active(active: bool) -> void:
visible = false
## Toggle visibility (e.g., from a keybind or button).
## Toggle visibility via HUD group system (D-170).
## Shows implant group (hides gameplay) or vice versa.
func toggle_visible() -> void:
visible = not visible
HudGroups.toggle_group("implant")
if visible:
_dirty = true
+17
View File
@@ -622,4 +622,21 @@ Technical foundation decisions that constrain implementation: engine, client-ser
---
### D-170: HUD visibility groups — gameplay vs implant layers
- **Date:** 2026-04-05
- **Decision:** HUD elements register into named visibility groups via a `HudGroups` autoload. Groups control show/hide of related elements as a unit. Exclusive groups (gameplay, implant) are mutually exclusive — showing one hides the other. Non-exclusive groups (modal, debug) overlay independently.
- **Rationale:** Full-screen implant panels (star map, travel planner, GTTR reader) need to hide gameplay HUD elements (stance, minimap, health, interaction prompts). Without groups, each panel manually hides/shows individual nodes — error-prone and unsustainable as the panel count grows.
- **Groups:**
- `gameplay` — stance indicator, minimap, HUD status, interaction prompts, cursor, inventory, news ticker, examine display. Visible during normal gameplay.
- `implant` — star map, travel planner, GTTR reader, station profile. Fullscreen implant overlays. Showing this hides gameplay.
- `modal` — settings, bug report, loading screen, debug console. Independent — overlays on top of anything.
- `debug` — debug overlay, gauntlet HUD, checklist. Independent.
- `always` — reserved for elements that never hide.
- **API:** `HudGroups.register(node, "gameplay")`, `HudGroups.show_group("implant")`, `HudGroups.toggle_group("implant")`, `HudGroups.is_group_active("implant")`
- **Implementation:** `client/scripts/autoloads/hud_groups.gd` — lightweight autoload, no scene tree manipulation beyond `node.visible`.
- **Raised by:** Jeroen, 2026-04-05 — "Walk" badge visible over fullscreen star map.
- **Dissent:** None.
---
*50 decisions. Last updated: 2026-03-24 (D-101 partial supersession noted; D-166 development cascade added)*