Phase 0 stabilize: lifecycle-aware banner, RoomExplore location line, grounded crate + beacon, tamed beacon HDR

- The center-top phase banner read the CyclePhase label ("AT BASE") inside
  expedition rooms - now shows ON EXPEDITION whenever a run is past Staging.
- The location-line switch had no RoomExplore case, so "choose your boon"
  stuck through the whole loot window - added the portal-steer line.
- Storage crate floated 1u: GridOrigin.y=1 is the CC capsule-CENTER plane;
  CellToWorld returns it, and the crate's mesh pivot is at its base (Turret/
  Wall pivots are centered, which is why structures look grounded). The
  container now sits on the terrain; same grounding for the portal beacon,
  whose pos.y was the capsule plane too.
- Beacon HDR 2.6/3.4 bloomed into a white egg that swallowed the prompt -
  tamed to 1.2/1.55 + slimmed the pillar.

456/456 EditMode before and after; Play smoke: crate at y=0, banner correct
at Staging.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 12:26:19 -07:00
parent 274cf3b791
commit 7c1fee097e
3 changed files with 17 additions and 7 deletions
@@ -152,7 +152,7 @@ namespace ProjectM.Client
_dangerMat.color = new Color(3.2f, 0.28f, 0.18f, 1f); // HDR red (per-zone intensity carried in vertex alpha) _dangerMat.color = new Color(3.2f, 0.28f, 0.18f, 1f); // HDR red (per-zone intensity carried in vertex alpha)
_portalMat = MakeParticleMaterial(); _portalMat = MakeParticleMaterial();
_portalMat.name = "RoomPortal"; _portalMat.name = "RoomPortal";
_portalMat.color = new Color(0.5f, 2.6f, 3.4f, 0.85f); // DR-046: HDR cyan portal glow (pushes past bloom) _portalMat.color = new Color(0.25f, 1.2f, 1.55f, 0.85f); // DR-046: HDR cyan portal glow (Phase 0: tamed — 2.6/3.4 bloomed to a white blob)
// Health-bar materials (UI/Default = always-included URP-compatible UI shader; per-instance Image.color carries alpha). // Health-bar materials (UI/Default = always-included URP-compatible UI shader; per-instance Image.color carries alpha).
Shader uiShader = Shader.Find("UI/Default") ?? Shader.Find("Sprites/Default"); Shader uiShader = Shader.Find("UI/Default") ?? Shader.Find("Sprites/Default");
@@ -1021,10 +1021,11 @@ void TriggerSlash(Vector3 pos, float2 facing, float range, float halfAngle, int
float t = (float)SystemAPI.Time.ElapsedTime; float t = (float)SystemAPI.Time.ElapsedTime;
float breathe = 0.5f + 0.5f * math.sin(t * 3.5f); float breathe = 0.5f + 0.5f * math.sin(t * 3.5f);
var tr = _portalBeacon.transform; var tr = _portalBeacon.transform;
// Cylinder is 2u tall in local space -> scale.y=3 gives a 6u pillar; lift the centre so the base sits on the ground. // Cylinder is 2u tall in local space -> scale.y=2.2 gives a 4.4u pillar; lift the centre so the base sits
tr.position = new Vector3(pos.x, pos.y + 3f, pos.z); // on the TERRAIN (y=0) — pos.y is the CC capsule-center plane (GridOrigin.y=1), 1 u above the ground.
tr.localScale = new Vector3(1.4f + 0.15f * breathe, 3f, 1.4f + 0.15f * breathe); tr.position = new Vector3(pos.x, 2.2f, pos.z);
_portalMat.color = new Color(0.5f, 2.6f, 3.4f, 0.55f + 0.35f * breathe); // glow throb (beacon-only mat, safe to mutate) tr.localScale = new Vector3(0.9f + 0.12f * breathe, 2.2f, 0.9f + 0.12f * breathe);
_portalMat.color = new Color(0.25f, 1.2f, 1.55f, 0.45f + 0.3f * breathe); // glow throb (beacon-only mat; Phase 0: tamed + slimmed — the fat 6u pillar bloomed to a white egg swallowing the prompt)
} }
@@ -180,10 +180,12 @@ namespace ProjectM.Client
int huskCount = _huskQuery.CalculateEntityCount(); int huskCount = _huskQuery.CalculateEntityCount();
// ---- Macro: phase + cycle + countdown (center-top banner) ---- // ---- Macro: phase + cycle + countdown (center-top banner) ----
bool haveRun = SystemAPI.TryGetSingleton<RunInfo>(out var runInfo); // hoisted: the phase banner is lifecycle-aware (Phase 0 fix — it read "AT BASE" inside expedition rooms)
bool haveCycle = SystemAPI.TryGetSingleton<CycleState>(out var cyc); bool haveCycle = SystemAPI.TryGetSingleton<CycleState>(out var cyc);
bool siege = haveCycle && cyc.Phase == CyclePhase.Siege; bool siege = haveCycle && cyc.Phase == CyclePhase.Siege;
bool goalFull = SystemAPI.TryGetSingleton<GoalProgress>(out var goalNow) && goalNow.Target > 0 && goalNow.Charge >= goalNow.Target; bool goalFull = SystemAPI.TryGetSingleton<GoalProgress>(out var goalNow) && goalNow.Target > 0 && goalNow.Charge >= goalNow.Target;
bool finalSiege = siege && goalFull; // END-2: the climactic final siege (goal cap reached) bool finalSiege = siege && goalFull; // END-2: the climactic final siege (goal cap reached)
bool onRun = haveRun && !siege && runInfo.Lifecycle != RunLifecycle.Staging; // mid-run: the base's Calm label is wrong
if (haveCycle) if (haveCycle)
{ {
var endTick = new NetworkTick(cyc.PhaseEndTick); var endTick = new NetworkTick(cyc.PhaseEndTick);
@@ -199,7 +201,7 @@ namespace ProjectM.Client
detail = ""; detail = "";
// END-2: the climax reads distinct (intense red), not a normal incursion/wave. // END-2: the climax reads distinct (intense red), not a normal incursion/wave.
var col = finalSiege || finalArming ? new Color(1f, 0.28f, 0.22f) : PhaseColor(cyc.Phase); var col = finalSiege || finalArming ? new Color(1f, 0.28f, 0.22f) : PhaseColor(cyc.Phase);
_phaseText.text = (finalSiege ? "HOLD THE ENGINE" : PhaseLabel(cyc.Phase)) + (detail.Length > 0 ? " - " + detail : ""); _phaseText.text = (finalSiege ? "HOLD THE ENGINE" : onRun ? "ON EXPEDITION" : PhaseLabel(cyc.Phase)) + (detail.Length > 0 ? " - " + detail : "");
_phaseText.style.color = col; _phaseText.style.color = col;
_cycleText.text = "CYCLE " + cyc.CycleNumber; _cycleText.text = "CYCLE " + cyc.CycleNumber;
_banner.style.borderBottomColor = col; _banner.style.borderBottomColor = col;
@@ -215,7 +217,6 @@ namespace ProjectM.Client
// (the old camera-X + walk-in-gate copy died with the gate; siege/final overrides below still win). // (the old camera-X + walk-in-gate copy died with the gate; siege/final overrides below still win).
var cam = Camera.main; // camera-X region signal still feeds downstream panels (atmosphere/threat) var cam = Camera.main; // camera-X region signal still feeds downstream panels (atmosphere/threat)
bool onExpedition = cam != null && cam.transform.position.x > ExpeditionRegionXMin; bool onExpedition = cam != null && cam.transform.position.x > ExpeditionRegionXMin;
bool haveRun = SystemAPI.TryGetSingleton<RunInfo>(out var runInfo);
SystemAPI.TryGetSingleton<ExpeditionObjective>(out var obj); SystemAPI.TryGetSingleton<ExpeditionObjective>(out var obj);
if (haveRun && !siege && !finalSiege) if (haveRun && !siege && !finalSiege)
{ {
@@ -255,6 +256,10 @@ namespace ProjectM.Client
_locationText.text = "ROOM CLEARED - choose your boon"; _locationText.text = "ROOM CLEARED - choose your boon";
_locationText.style.color = new Color(0.5f, 1f, 0.6f); _locationText.style.color = new Color(0.5f, 1f, 0.6f);
break; break;
case RunLifecycle.RoomExplore: // Phase 0: without this case the RoomReward text stuck through the loot window
_locationText.text = "ROOM CLEAR - grab the loot, take the portal to move on";
_locationText.style.color = new Color(0.5f, 1f, 0.6f);
break;
case RunLifecycle.RouteSelect: case RunLifecycle.RouteSelect:
_locationText.text = "CHOOSE YOUR PATH"; _locationText.text = "CHOOSE YOUR PATH";
_locationText.style.color = new Color(0.55f, 0.85f, 1f); _locationText.style.color = new Color(0.55f, 0.85f, 1f);
@@ -39,6 +39,10 @@ namespace ProjectM.Server
{ {
var container = ecb.Instantiate(spawner.Prefab); var container = ecb.Instantiate(spawner.Prefab);
var position = BaseGridMath.CellToWorld(anchor, spawner.Cell); var position = BaseGridMath.CellToWorld(anchor, spawner.Cell);
// Phase 0: the grid Y (GridOrigin.y=1) is the CC capsule-CENTER plane; center-pivot structure
// meshes look grounded there, but the crate's pivot is at its base -> it floated 1 u. Sit it on
// the terrain surface instead.
position.y = 0f;
// Preserve the prefab's baked scale/rotation (FromPosition would reset Scale to 1). // Preserve the prefab's baked scale/rotation (FromPosition would reset Scale to 1).
var xform = SystemAPI.GetComponent<LocalTransform>(spawner.Prefab); var xform = SystemAPI.GetComponent<LocalTransform>(spawner.Prefab);
xform.Position = position; xform.Position = position;