Slice 1: combat readability + HUD declutter (DR-038)

Four playtest do-now wins:
- Enemy health bars: pooled world-space Canvas, on-damage-sticky + fade,
  always-on <25% HP (CombatFeedbackSystem; no new replication).
- Telegraph fix: new baked client-safe EnemyTelegraph sizes the danger-cone ramp
  per enemy (0->1 ending at impact, fixes the Charger plateau); windup 18->22;
  a windup scale-pulse.
- Build-mode toggle: BuildPaletteState.PaletteOpen hides the palette by default,
  Tab / gamepad-Y toggles, with a discovery chip (HudSystem/BuildSendSystem).
- Charger committed-lunge tell: [GhostEnabledBit] IsLunging derived once/tick from
  LungeState (the Dead idiom); the danger cone persists through the lunge.

345/345 EditMode (+3 IsLunging derive tests); Play-validated: ghost-hash change
did not break the handshake, bake correct (telegraph on all enemies, IsLunging
baked-disabled on the Charger, replicated to client), no runtime errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 12:48:08 -07:00
parent 5292940f9d
commit f3eccec524
12 changed files with 360 additions and 24 deletions
@@ -75,7 +75,7 @@ namespace ProjectM.Client
Label _aetherNum, _oreNum, _bioNum, _chargeNum;
// build palette + hints
VisualElement _paletteRow, _hintBar, _facingArrow;
VisualElement _paletteRow, _hintBar, _facingArrow, _buildDiscoveryChip;
bool _paletteBuilt, _hintBuilt, _hintConveyor;
byte _hintScheme = 255;
readonly Dictionary<byte, PaletteItem> _palette = new();
@@ -295,7 +295,8 @@ namespace ProjectM.Client
// ---- Build palette + control hints (bottom-center) ----
UpdatePalette(aether, ore, bio, onExpedition);
bool buildActive = BuildPaletteState.Active && !onExpedition && _paletteBuilt;
bool paletteOpen = BuildPaletteState.PaletteOpen && !onExpedition && _paletteBuilt;
bool buildActive = paletteOpen && BuildPaletteState.Active;
if (buildActive)
{
byte scheme = AimPresentation.Scheme;
@@ -309,6 +310,9 @@ namespace ProjectM.Client
{
_hintBar.style.display = DisplayStyle.None;
}
// Build-mode discovery chip: a subtle "Tab/Y — BUILD" hint when the palette is hidden at base (Slice 1).
_buildDiscoveryChip.style.display = (!onExpedition && !BuildPaletteState.PaletteOpen)
? DisplayStyle.Flex : DisplayStyle.None;
// ---- Per-player vitals ----
bool found = false;
@@ -453,7 +457,8 @@ namespace ProjectM.Client
}
if (!_paletteBuilt) { _paletteRow.style.display = DisplayStyle.None; return; }
_paletteRow.style.display = onExpedition ? DisplayStyle.None : DisplayStyle.Flex;
bool showPalette = !onExpedition && BuildPaletteState.PaletteOpen;
_paletteRow.style.display = showPalette ? DisplayStyle.Flex : DisplayStyle.None;
foreach (var kv in _palette)
{
var item = kv.Value;
@@ -591,6 +596,7 @@ namespace ProjectM.Client
BuildResources(root);
BuildPaletteRow(root);
BuildHintBar(root);
BuildDiscoveryChip(root);
BuildDowned(root);
BuildInventory(root);
BuildRunBanner(root);
@@ -844,6 +850,27 @@ namespace ProjectM.Client
_hintBar.style.display = DisplayStyle.None;
root.Add(_hintBar);
}
void BuildDiscoveryChip(VisualElement root)
{
// Slice 1 HUD declutter: a subtle bottom-center chip teaching the build-mode toggle, shown only while
// the palette is CLOSED at base. The glyph uses the text fallback ("Tab"/"Y") — no HudTheme sprite needed.
bool pad = AimPresentation.Scheme == InputSchemeId.Gamepad;
_buildDiscoveryChip = new VisualElement();
_buildDiscoveryChip.style.position = Position.Absolute;
_buildDiscoveryChip.style.bottom = 28; _buildDiscoveryChip.style.left = 0; _buildDiscoveryChip.style.right = 0;
_buildDiscoveryChip.style.flexDirection = FlexDirection.Row;
_buildDiscoveryChip.style.justifyContent = Justify.Center;
_buildDiscoveryChip.style.alignItems = Align.Center;
_buildDiscoveryChip.pickingMode = PickingMode.Ignore;
_buildDiscoveryChip.style.opacity = 0.6f;
_buildDiscoveryChip.Add(HudUi.Glyph(null, pad ? "Y" : "Tab", 26));
var lbl = HudUi.Text("BUILD", 12, MenuUi.SubCol, TextAnchor.MiddleLeft);
lbl.style.marginLeft = 5;
_buildDiscoveryChip.Add(lbl);
_buildDiscoveryChip.style.display = DisplayStyle.None;
root.Add(_buildDiscoveryChip);
}
void BuildDowned(VisualElement root)
{