using ProjectM.Simulation;
using Unity.Burst;
using Unity.Entities;
using Unity.NetCode;
namespace ProjectM.Server
{
///
/// Server-only, deterministic harvester production — the FRONT of the M7 auto-gather chain
/// (Harvester → Conveyor → Fabricator). Each machine is a fixed-yield generator:
/// every server ticks it deposits of its
/// configured (byte) resource into its OWN server-only buffer (NOT the global
/// ledger — a conveyor pulls it onward, or it sits buffered). Mirrors TurretFireSystem's exact
/// now-extraction (NetworkTime.ServerTick.TickIndexForValidTick) +
/// cooldown idiom, and runs in the plain server SimulationSystemGroup
/// [UpdateAfter(PredictedSimulationSystemGroup)] (the predicted group is OrderFirst → UpdateBefore is
/// ignored). Production mutates a DynamicBuffer in place (not a structural change) → no ECB needed.
///
/// SINGLE GATED CATCH-UP PATH (offline-quit safe, NO wall-clock minting): a never-processed machine
/// (LastProcessedTick==0) is initialised this tick and produces nothing; otherwise
/// awards floor((now-LastProcessedTick)/period) cycles, clamped
/// to , and the tick fields are re-stamped. All catch-up is
/// WITHIN-SESSION tick math; the stockpile is preserved across quit by the persistence layer, never re-minted
/// from a saved wall-clock.
///
///
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(PredictedSimulationSystemGroup))]
public partial struct HarvesterProductionSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate();
state.RequireForUpdate(state.GetEntityQuery(ComponentType.ReadOnly()));
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var serverTick = SystemAPI.GetSingleton().ServerTick;
if (!serverTick.IsValid)
return;
uint now = serverTick.TickIndexForValidTick;
foreach (var (ps, harvester, output) in
SystemAPI.Query, RefRO, DynamicBuffer>())
{
int period = harvester.ValueRO.PeriodTicks; // CyclesDue clamps to max(1, period)
// Never-processed (baked/just-placed) machine: initialise the catch-up baseline, produce nothing.
if (ProductionMath.NeedsInit(ps.ValueRO.LastProcessedTick))
{
ps.ValueRW.LastProcessedTick = TickUtil.NonZero(now);
ps.ValueRW.NextTick = TickUtil.NonZero(now + (uint)System.Math.Max(1, period));
continue;
}
int cycles = ProductionMath.CyclesDue(
serverTick, ps.ValueRO.NextTick, ps.ValueRO.LastProcessedTick, period, Tuning.MaxProductionCatchup);
if (cycles <= 0)
continue; // still cooling down / nothing due
// Fixed-yield generation into the machine's own output slot (byte id; ledger conversion happens
// only at the global-ledger boundary, which this machine never crosses directly).
MachineSlotMath.Deposit(output, harvester.ValueRO.ResourceId, harvester.ValueRO.Yield * cycles);
uint p = (uint)System.Math.Max(1, period);
ps.ValueRW.LastProcessedTick = TickUtil.NonZero(now);
ps.ValueRW.NextTick = TickUtil.NonZero(now + p);
}
}
}
}