using Unity.Entities;
namespace ProjectM.Simulation
{
///
/// Baked, server-only tuning for the composite ThreatDirector (the data-driven base-attack scheduler). Lives
/// on the global CycleDirector entity but is NOT a [GhostField] — the server alone decides when a siege
/// fires; clients learn about it only through the replicated flip to Siege.
/// Flat scalar fields (never an enum/array — dodges the MCP authoring-drop gotcha + stays Burst-trivial). This
/// slice wires only the POST-EXPEDITION source; the Heat/Schedule fields are baked-but-inert so those sources
/// drop in later additively with NO re-bake (server-only layout, not a ghost serializer change).
///
public struct ThreatConfig : IComponentData
{
// ---- Post-expedition retaliation (the only source wired this slice) ----
/// 1 = a completed expedition (a player returning to base) can draw a retaliation siege.
public byte PostExpeditionEnabled;
/// Telegraph/arming delay (server ticks) between the trigger and the siege actually spawning.
public uint PostExpeditionDelayTicks;
/// Siege size floor (Husk count) for a post-expedition retaliation.
public int SizeBase;
/// Extra Husks per unit of resources hauled back this run (0 = a flat siege).
public int SizePerExpeditionResource;
/// How a pending siege starts (see ).
public byte StartCondition;
/// Max server ticks a Siege may run before it auto-collapses (remaining Husks culled) so an unattended/empty-base siege can never soft-lock. 0 = no cap.
public uint SiegeTimeoutTicks;
// ---- Reserved, present-but-inert this slice (additive later, no re-bake) ----
public byte HeatEnabled;
public float HeatPerTickAtBase;
public float HeatPerHarvest;
public float HeatThreshold;
public byte ScheduleEnabled;
public uint ScheduleIntervalTicks;
/// Extra Husks per surviving wave for a SCHEDULED base siege (size = SizeBase + this*WaveNumber). 0 = flat SizeBase.
public int ScheduleSizePerWave;
}
/// Start-condition constants for (bytes — never an enum, never in an RPC).
public static class ThreatStartCondition
{
/// DEFAULT: arm via the telegraph countdown () then fire — even at an empty base.
public const byte Immediate = 0;
/// Hold the pending siege until ≥1 player is in the base region OR the arm tick + a grace window elapses (bounded — never a soft-lock).
public const byte RequirePlayerAtBase = 1;
}
///
/// Server-only runtime state of the ThreatDirector, on the global CycleDirector entity beside
/// . NOT replicated. is the single documented entry
/// point: any source (post-expedition, dev tools, later Heat/Schedule) sets it; CyclePhaseSystem
/// consumes it on the Calm→Siege edge and zeroes it. All stored ticks are wrap-safe (TickUtil.NonZero +
/// NetworkTick compares), never raw uint.
///
public struct ThreatState : IComponentData
{
/// Husk count of the armed siege; 0 = none pending. Consumed (zeroed) by CyclePhaseSystem at Siege entry.
public int PendingSiegeSize;
/// Server tick the pending siege fires (telegraph). 0 = fire as soon as seen. Routed through TickUtil.NonZero.
public uint ArmTick;
/// Server tick the current Siege began (0 = not under siege). The bounded-resolution timeout measures from here (TickUtil.NonZero) so an unattended/empty-base siege can never soft-lock.
public uint SiegeStartTick;
/// Count of expeditions completed (a player returned to base). Drives the post-expedition source + stats.
public int ExpeditionsCompleted;
/// Return events the gate has signalled but the director has not yet consumed (the gate teleports the player out of its radius, so one increment per return — natural de-dup).
public int PendingReturns;
/// Accumulated heat (inert this slice; the Heat source reads/writes it later).
public float Heat;
/// Next scheduled-siege tick (inert this slice; the Schedule source uses it later). TickUtil.NonZero when used.
public uint NextScheduledTick;
}
}