using Unity.Mathematics; using Unity.NetCode; namespace ProjectM.Simulation { /// /// Pure, deterministic catch-up + cooldown math shared by the M7 production systems (Harvester/Conveyor/ /// Fabricator). No RNG/wall-clock -> server-authoritative. The single GATED catch-up path: a never-processed /// machine () initializes first; a cooling machine yields 0; a due machine yields /// floor(elapsed/period) clamped to [0, maxCatchup]; period is guarded by max(1,...). Cooldown is persisted as /// REMAINING ticks (epoch-independent) so a save survives the server-tick origin reset on a fresh session. /// public static class ProductionMath { /// True for a never-processed machine (baked/just-placed) — initialize the baseline before producing. public static bool NeedsInit(uint lastProcessedTick) => lastProcessedTick == 0u; /// /// Cycles to award THIS process. 0 if cooling ( newer than ) /// or nothing elapsed; otherwise floor(elapsed/period) clamped to [0, ]. /// ==0 is the inactive sentinel (never read as a future cooling tick). The lower /// bound is 0 (not 1): when genuinely due the NextTick gate guarantees elapsed>=period, so a sub-period /// edge (e.g. a freshly restored remaining==0 machine) floors to 0 rather than minting prematurely. /// is guarded by max(1,...) so a 0 never divides. /// public static int CyclesDue(NetworkTick now, uint nextTick, uint lastProcessedTick, int period, int maxCatchup) { int p = math.max(1, period); if (nextTick != 0u) { var next = new NetworkTick(nextTick); if (next.IsValid && next.IsNewerThan(now)) return 0; // still cooling down } int since = now.TicksSince(new NetworkTick(TickUtil.NonZero(lastProcessedTick))); if (since <= 0) return 0; return math.clamp(since / p, 0, maxCatchup); } /// Remaining cooldown ticks to PERSIST (epoch-independent): 0 if inactive or already due, else nextTick-now. public static uint RemainingTicks(uint nextTick, uint nowTick) => nextTick == 0u ? 0u : (nextTick > nowTick ? nextTick - nowTick : 0u); /// Re-anchor a persisted remaining cooldown to the current tick origin on restore (NonZero-guarded). public static uint RestoreNextTick(uint nowTick, uint remaining) => TickUtil.NonZero(nowTick + remaining); } }