using Unity.Mathematics; namespace ProjectM.Simulation { /// /// Pure, deterministic composition math for the expedition zone-enemy wave — no RNG state, no wall-clock — so the /// per-epoch wave is reproducible across restarts/saves and EditMode-unit-testable without an ECS world (mirrors /// / ProductionMath). The highest-leverage Slice-3 variety lever: the encounter /// COMPOSITION shifts grunt-heavy -> charger-heavy as the expedition epoch climbs (grunt count stays /// fixed; the per-epoch growth is all chargers). /// public static class ZoneEnemyMath { /// /// Total enemies in this epoch's wave: the baked + /// baseline plus one extra per epoch beyond the first (a gentle ramp). Lower-bounded at 1 so an occupied /// expedition always has a fight. is the monotonic sortie counter (>=1 in practice). /// public static int WaveSize(int epoch, int gruntsPerWave, int chargersPerWave) { int e = math.max(1, epoch); int baseCount = math.max(0, gruntsPerWave) + math.max(0, chargersPerWave); return math.max(1, baseCount + (e - 1)); } /// /// Deterministic grunt/charger pick for spawn of this epoch's wave. The charger /// count is + (epoch - 1), clamped to the wave size, assigned to the LAST /// slots; everything earlier is a Grunt. So the grunt count stays fixed at /// and the wave skews charger-heavy as the epoch climbs. Returns true for a Charger slot. Stable per /// (epoch, slot) — a replayed wave is identical. Pure integer math (Burst-safe; no enum, no RNG). /// public static bool IsChargerSlot(int epoch, int slot, int gruntsPerWave, int chargersPerWave) { int e = math.max(1, epoch); int size = WaveSize(epoch, gruntsPerWave, chargersPerWave); int chargers = math.clamp(math.max(0, chargersPerWave) + (e - 1), 0, size); int s = ((slot % size) + size) % size; return s >= size - chargers; } } }