Portal made visible: cyan beacon pillar + always-on RoomExplore steer prompt (DR-046 follow-up)

The room-exit portal was invisible in the first playtest - players loitered
out the 30s ExploreGrace timeout. Adds RegionMath.ExpeditionPortalPos as the
single client-derivable portal-position authority (HUD prompt + beacon can't
drift), a pooled breathing HDR pillar in CombatFeedbackSystem shown only
during RoomExplore, and an in-range/out-of-range two-stage prompt.

Also repairs the comment-swallowed _portalMat initialization (NRE in
OnStartRunning killed ALL combat feedback each session and auto-paused the
editor via Error Pause). 456/456 EditMode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 12:20:04 -07:00
parent e6cbba5e25
commit 274cf3b791
5 changed files with 92 additions and 13 deletions
@@ -1949,23 +1949,25 @@ namespace ProjectM.Client
{
var root = _doc != null ? _doc.rootVisualElement : null; if (root == null) return;
if (!_portalBuilt) { BuildPortalPrompt(root); _portalBuilt = true; }
bool show = false;
bool show = false, inRange = false;
if (haveRun && runInfo.Lifecycle == RunLifecycle.RoomExplore
&& SystemAPI.TryGetSingleton<BaseAnchor>(out var anchor))
{
float3 center = BaseGridMath.PlotCenter(anchor);
float3 portalPos = RegionMath.ExpeditionRoomOrigin(center, (byte)(runInfo.CurrentRoom & 1));
portalPos.z += Tuning.PortalOffsetZ;
show = true; // room cleared -> ALWAYS steer the player to the (now visible) portal, not only when in range
float3 portalPos = RegionMath.ExpeditionPortalPos(BaseGridMath.PlotCenter(anchor), (byte)(runInfo.CurrentRoom & 1));
foreach (var lt in SystemAPI.Query<RefRO<LocalTransform>>().WithAll<PlayerTag, GhostOwnerIsLocal>())
{
if (math.distance(lt.ValueRO.Position.xz, portalPos.xz) <= Tuning.PortalInteractRange)
inRange = math.distance(lt.ValueRO.Position.xz, portalPos.xz) <= Tuning.PortalInteractRange;
if (inRange)
{
show = true;
var kb = UnityEngine.InputSystem.Keyboard.current;
if (kb != null && kb.eKey.wasPressedThisFrame) PortalInteractSendSystem.Interact();
}
break;
}
_portalPrompt.text = inRange
? "PRESS E TO LEAVE — the haul comes home"
: "ROOM CLEAR — reach the glowing portal to move on";
}
_portalPrompt.style.display = show ? DisplayStyle.Flex : DisplayStyle.None;
}