Hazard: Blight geyser — permanent periodic both-sides AoE (Phase 1.5b bundle 3)
Review-first netcode slice (design review wf_900e9965-8f0 -> 11 folded; post-impl wf_5c8299f8-299 clean). A PERMANENT periodic telegraphed AoE in Blight-biome rooms only; one new [GhostField] uint NextEruptTick. - Geyser component + GeyserEruptSystem (server): inverted invalid-tick guard (a baked-0 tick must NEVER erupt -> lazy-stamps born-correct instead of the party-wiping per-tick barrage), inline both-sides gather (SourceNetworkId=-1), reschedule = now + period (never +=), never destroyed. - GeyserTelegraphSystem (client): absolute-tick growing warning disc + erupt burst on the >0->=<0 crossing, latched per NextEruptTick + arm-guard (never edge-detects the replicated field -> no phantom on 0->stamp / relevancy re-entry). Reuses ScorchDecalSystem + DynamicLightSystem. - Seeded in RoomFieldSystem (Blight-gated on plan.Biome, born-correct staggered stamp from live ServerTick), GeyserFieldSpawner + authoring wired into the Gameplay subscene. Geyser.prefab duplicated from ResourceNode (no collider). - BuildDisc promoted to FeedbackFx (shared with the barrel fuse ring). - 474/474 EditMode incl. the unstamped-storm regression; live no-storm end-to-end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.NetCode;
|
||||
using Unity.Transforms;
|
||||
|
||||
namespace ProjectM.Server
|
||||
@@ -155,6 +156,45 @@ namespace ProjectM.Server
|
||||
}
|
||||
}
|
||||
|
||||
// BLIGHT GEYSER (OPTIONAL singleton; Geyser_Build_Spec / review wf_900e9965-8f0): a PERMANENT
|
||||
// periodic BOTH-SIDES AoE hazard, ONLY in Blight-biome rooms (gate on the LOCAL plan, like the
|
||||
// cover block). Never Boss rooms — a DESIGN choice (the geyser has no collider, so unlike cover it
|
||||
// is NOT the depenetration concern). Distinct hash sub-stream 0x6E7; keep-out ring around origin.
|
||||
// BORN-CORRECT: stamp NextEruptTick from the LIVE ServerTick (staggered per instance so eruptions
|
||||
// desync) so the first snapshot never carries the 0 sentinel; if NetworkTime is invalid this tick
|
||||
// the geyser ships 0 and GeyserEruptSystem lazy-stamps it born-correct instead (never a storm).
|
||||
if (plan.Biome == RoomBiomeId.Blight
|
||||
&& plan.RoomType != RoomTypeId.Boss
|
||||
&& SystemAPI.TryGetSingleton<GeyserFieldSpawner>(out var geyser)
|
||||
&& geyser.Prefab != Entity.Null)
|
||||
{
|
||||
uint eruptStamp = 0u;
|
||||
if (SystemAPI.TryGetSingleton<NetworkTime>(out var gnt) && gnt.ServerTick.IsValid)
|
||||
eruptStamp = gnt.ServerTick.TickIndexForValidTick;
|
||||
var gBaked = SystemAPI.GetComponent<LocalTransform>(geyser.Prefab);
|
||||
var grng = new Random(RunMapMath.Hash(run.RunSeed, (uint)run.CurrentNodeId, 0x6E7u) | 1u);
|
||||
int gCount = math.clamp(geyser.Count, 0, 4);
|
||||
const float GeyserKeepOut = 7f;
|
||||
for (int i = 0; i < gCount; i++)
|
||||
{
|
||||
float3 pos = origin;
|
||||
for (int attempt = 0; attempt < 8; attempt++)
|
||||
{
|
||||
pos = RoomLayoutMath.ScatterInShape(plan.ShapeId, origin, i, gCount, ref grng);
|
||||
if (math.distance(pos.xz, origin.xz) >= GeyserKeepOut) break;
|
||||
}
|
||||
if (math.distance(pos.xz, origin.xz) < GeyserKeepOut) continue; // unlucky draws: drop the piece
|
||||
var e = ecb.Instantiate(geyser.Prefab);
|
||||
ecb.SetComponent(e, gBaked.WithPosition(pos));
|
||||
// born-correct + per-instance stagger so geysers desync; 0 only if NetworkTime was invalid.
|
||||
uint next = eruptStamp != 0u
|
||||
? TickUtil.NonZero(eruptStamp + Tuning.GeyserPeriodTicks + (uint)i * 60u)
|
||||
: 0u;
|
||||
ecb.SetComponent(e, new Geyser { NextEruptTick = next });
|
||||
ecb.AddComponent(e, new RoomTag { Room = room });
|
||||
}
|
||||
}
|
||||
|
||||
rf.LastSpawnedRoomEpoch = run.RoomEpoch;
|
||||
SystemAPI.SetComponent(spawnerEntity, rf);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user