#ifdef WORKBENCH // Explicit synthetic observation only. Delivery, HQ scheduling and native fire // are unchanged; this helper never writes directly into the HQ contact list. modded class ERR_HQ { int ERR_QueueMortarFixtureReport(SCR_AIGroup source, IEntity target, vector position, float now) { if (!ERR_NativeMortarFiringProbe.Enabled() || !target || !m_Contacts.IsEmpty()) return 0; foreach (ERR_HQUnit unit : m_Units) { if (unit.Group != source || !unit.ReservedForSupport || !unit.Radio || unit.Uplink || unit.PendingReport) continue; ERR_HQRoute route = m_Network.Route(unit.Radio); if (!route || route.Nodes.IsEmpty()) return 0; ERR_HQReport report = new ERR_HQReport(); report.MessageId = m_Journal.Message(); report.Target = target; report.Source = source; report.Position = position; report.ObservedAt = now; report.DetailsObservedAt = now; report.HasVelocity = true; report.Velocity = vector.Zero; report.TravelMode = "on foot"; report.SeenEscapists = 1; report.SeenWeapons = "fixture observation"; route.MessageId = report.MessageId; route.Payload = "EXPLICIT SYNTHETIC OBSERVATION: " + report.Description(now); unit.PendingReport = report; unit.Uplink = route; m_Journal.Write("FIXTURE REPORT", route.Payload, report.MessageId); return report.MessageId; } return 0; } void ERR_RemoveMortarFixtureReport(IEntity target) { if (!target) return; for (int i = m_Contacts.Count() - 1; i >= 0; i--) if (m_Contacts[i].Target == target) m_Contacts.Remove(i); foreach (ERR_HQUnit unit : m_Units) { if (unit.PendingReport && unit.PendingReport.Target == target) { unit.PendingReport = null; unit.Uplink = null; } if ((unit.PendingOrder && unit.PendingOrder.Target == target) || (unit.Assignment && unit.Assignment.Target == target)) unit.Release(); } } } // Production sets Released immediately before reserving shell airspace, then // invokes native inventory transfer. Observe that existing commit boundary so // even a synchronous native projectile has a prior recorded authorization. modded class ERR_MortarBattery { override void ReserveShellAirspace(float now) { super.ReserveShellAirspace(now); ERR_NativeMortarFiringProbe probe = ERR_NativeMortarFiringProbe.Active(); if (probe) probe.NativeCommit(this, now); } override void Cancel(string reason = "cancel requested") { ERR_NativeMortarFiringProbe probe = ERR_NativeMortarFiringProbe.Active(); if (probe) probe.NativeClosing(this, reason, LastFailure); super.Cancel(reason); } } class ERR_NativeMortarEvidenceRules { static bool CommitFresh(float observed, float expectedObserved, float committed) { return observed == expectedObserved && observed >= 0 && committed >= observed && committed - observed <= ERR_MortarPrediction.MAX_REPORT_AGE_MS; } static bool EventBound(bool pending, int order, int currentOrder, int shot, int currentShot, float committed, float emitted) { // The fixture has its own overall deadline. A committed shell does not gain // a second authorization expiry while native physical transfer completes. return pending && order == currentOrder && shot == currentShot && emitted >= committed; } static bool ExpiredClosure(float observed, float closed, string failure) { return observed >= 0 && closed > observed + ERR_MortarPrediction.MAX_REPORT_AGE_MS && (failure.Contains("delivered target report expired") || failure.Contains("observation older than 60s")); } } // Observe only the actual native event. Node SUCCESS, accepted inventory moves // and committed shell loading are never counted as physical projectiles. modded class SCR_MortarMuzzleComponent { override void OnWeaponFired(IEntity effectEntity, BaseMuzzleComponent muzzle, IEntity projectileEntity) { ERR_NativeMortarFiringProbe probe = ERR_NativeMortarFiringProbe.Active(); if (probe) probe.NativeProjectile(GetOwner(), projectileEntity); super.OnWeaponFired(effectEntity, muzzle, projectileEntity); } } class ERR_NativeMortarFiringProbe { protected static ERR_NativeMortarFiringProbe s_Active; protected ERR_GameMode m_Mode; protected ERR_MortarBattery m_Battery; protected IEntity m_Gun; protected SCR_AIGroup m_Group; protected IEntity m_Target; protected vector m_Impact; protected ref array m_Crew = {}; protected ref array m_InitialShells = {}; protected ref array m_Projectiles = {}; protected int m_Source; protected int m_Order; protected int m_Step; protected int m_Events; protected int m_StoppedShells; protected float m_Deadline; protected float m_NextDiagnostic; protected float m_StopUntil; protected float m_ObservedAt; protected bool m_WaitForExpiry; protected int m_FinalEvents; protected bool m_CommitPending; protected float m_CommittedAt; protected int m_CommitOrder; protected int m_CommitShot; protected int m_Commits; protected bool m_ClosureSeen; protected bool m_ClosureExpired; protected bool m_ClosureComplete; protected bool m_Unsafe; protected bool m_BadProjectile; protected bool m_SawUplink; protected bool m_SawDownlink; protected bool m_SawActivity; protected bool m_Stopped; protected string m_BadReason; static bool Enabled() { string enabled, outcome; return Replication.IsServer() && System.GetCLIParam("errSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && (outcome == "mortar-fire" || outcome == "mortar-expiry"); } static ERR_NativeMortarFiringProbe Active() { return s_Active; } void ERR_NativeMortarFiringProbe(ERR_GameMode mode) { m_Mode = mode; string outcome; System.GetCLIParam("errSmokeOutcome", outcome); m_WaitForExpiry = outcome == "mortar-expiry"; m_Deadline = GetGame().GetWorld().GetWorldTime() + 180000; if (Enabled() && !s_Active) s_Active = this; } protected bool Check(bool condition, string detail) { if (condition) { Print("ERR SMOKE PASS: mortar firing " + detail); return true; } Print("ERR SMOKE FAIL: mortar firing " + detail, LogLevel.ERROR); Stop(); return false; } protected void Shells(out array shells) { shells.Clear(); foreach (IEntity actor : m_Crew) { if (!actor) continue; InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent)); array items = {}; ERR_Inventory.Items(inventory, items); foreach (IEntity item : items) if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_MortarBattery.SHELL && !shells.Contains(item)) shells.Insert(item); } } protected bool CheckNoOccupants(IEntity entity) { if (ChimeraCharacter.Cast(entity) || Vehicle.Cast(entity)) { m_Unsafe = true; return false; } return true; } protected bool EmptyImpact(vector position) { m_Unsafe = false; GetGame().GetWorld().QueryEntitiesByAABB(position - "200 5000 200", position + "200 5000 200", CheckNoOccupants, null, EQueryEntitiesFlags.ALL); return !m_Unsafe; } protected bool SelectImpact(ERR_MortarSystem system) { // Bounded candidate survey: no clearance assumption from coordinates alone. array offsets = {"-800 0 -600", "-600 0 -800", "0 0 -1000", "600 0 -800", "-1000 0 0", "800 0 -600", "-800 0 600", "0 0 1000"}; foreach (vector offset : offsets) { vector candidate = ERR_Actors.Ground(m_Battery.Position + offset); if (candidate[1] < 1 || !EmptyImpact(candidate) || !system.Safe(m_Battery, candidate, 75)) continue; bool clear = true; for (int shot = 0; shot < ERR_MortarSalvo.ROUNDS; shot++) { vector spread = ERR_Actors.Ground(candidate + ERR_MortarSalvo.Offset(m_Battery.SiteId, m_Battery.OrderId + 1, shot)); if (!system.Safe(m_Battery, spread, 75)) { clear = false; break; } } if (!clear) continue; m_Impact = candidate; PrintFormat("ERR MORTAR FIXTURE IMPACT: position=%1 batteryDistance=%2; no characters/vehicles in 400m square; production settlement/friendly exclusions passed", candidate, vector.DistanceXZ(candidate, m_Battery.Position)); return true; } return false; } protected void Diagnose(float now) { if (now < m_NextDiagnostic) return; m_NextDiagnostic = now + 5000; if (!m_Battery) { Print("ERR MORTAR FIXTURE: waiting for one authored battery"); return; } PrintFormat("ERR MORTAR FIXTURE: step=%1 startup=%2 source=M%3 observedEvents=%4 ready=%5 failure=%6", m_Step, m_Battery.StartupActivated, m_Source, m_Events, m_Battery.Operational(), m_Battery.SpawnFailure); m_Battery.SiteStatus(now); } void NativeCommit(ERR_MortarBattery battery, float now) { if (m_Stopped || battery != m_Battery) return; if ((m_Step != 1 && m_Step != 3) || m_CommitPending || !battery.CommandOpen || !battery.Released || battery.SourceId != m_Source || battery.OrderId != m_Order || !battery.Report || battery.Report.MessageId != m_Source || !ERR_NativeMortarEvidenceRules.CommitFresh(battery.Report.ObservedAt, m_ObservedAt, now) || battery.AimedAt < 0 || now < battery.AimedAt || now - battery.AimedAt > 10000 || battery.FlightTime <= 0) { m_BadProjectile = true; m_BadReason = "physical commit lacks immutable fresh report/current order/native aim, or duplicates pending commit"; return; } m_CommitPending = true; m_CommittedAt = now; m_CommitOrder = battery.OrderId; m_CommitShot = battery.OrderShots; m_Commits++; PrintFormat("ERR MORTAR FIXTURE COMMIT: count=%1 order=%2 shot=%3 source=M%4 observedAge=%5; recorded before native inventory transfer", m_Commits, m_CommitOrder, m_CommitShot, m_Source, now - m_ObservedAt); } void NativeClosing(ERR_MortarBattery battery, string reason, string failure) { if (m_Stopped || !m_WaitForExpiry || battery != m_Battery || !battery.CommandOpen || battery.SourceId != m_Source || m_Source <= 0) return; float now = GetGame().GetWorld().GetWorldTime(); m_ClosureSeen = true; m_ClosureComplete = battery.OrderShots == ERR_MortarSalvo.ROUNDS && m_Events == ERR_MortarSalvo.ROUNDS; m_ClosureExpired = ERR_NativeMortarEvidenceRules.ExpiredClosure(m_ObservedAt, now, failure); PrintFormat("ERR MORTAR FIXTURE CLOSURE: order=%1 observedAge=%2 reason=%3 lastFailure=%4 complete=%5 expiredWithheld=%6", battery.OrderId, now - m_ObservedAt, reason, failure, m_ClosureComplete, m_ClosureExpired); if (battery.Stopped() || battery.OrderId != m_Order || (!m_ClosureExpired && !m_ClosureComplete)) { m_BadProjectile = true; m_BadReason = "expiry evidence interrupted by Stop or unrelated/incomplete order closure"; } } void NativeProjectile(IEntity gun, IEntity projectile) { if (m_Stopped || !m_Battery || gun != m_Gun) return; if (!projectile) { m_BadProjectile = true; m_BadReason = "native fired event has no physical projectile"; return; } if (m_Projectiles.Contains(projectile)) { m_BadProjectile = true; m_BadReason = "same physical projectile observed twice"; return; } m_Projectiles.Insert(projectile); m_Events++; float now = GetGame().GetWorld().GetWorldTime(); if ((m_Step != 1 && m_Step != 3) || !m_Battery.Released || m_Battery.SourceId != m_Source || !ERR_NativeMortarEvidenceRules.EventBound(m_CommitPending, m_CommitOrder, m_Battery.OrderId, m_CommitShot, m_Battery.OrderShots, m_CommittedAt, now)) { m_BadProjectile = true; m_BadReason = "projectile lacks the recorded fresh physical commit for its current order/shot"; } m_CommitPending = false; PrintFormat("ERR MORTAR FIXTURE PROJECTILE: event=%1 entity=%2 origin=%3 order=%4 source=M%5 aimAge=%6 flight=%7; impact is not measured", m_Events, projectile, projectile.GetOrigin(), m_Battery.OrderId, m_Battery.SourceId, now - m_Battery.AimedAt, m_Battery.FlightTime); } int Tick(float now) { if (m_Stopped) return -1; if (!Enabled() || s_Active != this || !m_Mode || !m_Mode.Active()) { Check(false, "explicit active fixture context retained"); return -1; } ERR_HQ hq = ERR_HQ.Get(); ERR_MortarSystem system; if (hq) system = hq.Mortars(); if (!system) { Check(false, "real HQ mortar service available"); return -1; } if (!m_Battery) { array guns = {}; system.AppendGuns(guns); if (guns.Count() == 1) { m_Gun = guns[0]; m_Battery = system.FindGun(m_Gun); } } Diagnose(now); if (now >= m_Deadline) { Check(false, "bounded deadline; inspect readiness/route/activity/aim diagnostics"); return -1; } if (m_BadProjectile) { Check(false, m_BadReason); return -1; } if (!m_Battery) return 0; if (!m_Battery.SpawnFailure.IsEmpty()) { Check(false, "battery startup failed: " + m_Battery.SpawnFailure); return -1; } if (m_Step == 0) { if (!m_Battery.StartupActivated || !m_Battery.Operational() || !hq.MortarConnected(m_Battery.Group)) return 0; m_Group = m_Battery.Group; if (!Check(m_Group && m_Group.IsExpandComplete() && m_Group.GetAgentsCount() == 4 && m_Battery.TotalShots == 0 && m_Battery.OrderId == 0 && !m_Battery.CommandOpen && !m_Battery.Activity && !m_Battery.RadioOrder, "untasked original gun and complete four-person crew start without shots")) return -1; array agents = {}; m_Group.GetAgents(agents); foreach (AIAgent agent : agents) { IEntity actor; if (agent) actor = agent.GetControlledEntity(); if (!Check(ERR_Actors.CanFight(actor) && !m_Crew.Contains(actor), "initial physical crew member is conscious and unique")) return -1; m_Crew.Insert(actor); } if (!Check(m_Battery.RadioOperator && ERR_HQRadio.Capability(m_Battery.RadioOperator) == 2, "dedicated actual manpack supplies the radio route")) return -1; Shells(m_InitialShells); if (!Check(m_InitialShells.Count() == 27 && m_Battery.CountShells() == 27, "exactly 27 distinct physical initial HE shells")) return -1; if (!Check(SelectImpact(system), "bounded candidate survey finds an unoccupied production-safe impact area")) return -1; m_Target = ERR_Actors.Spawn(ERR_Resources.IntelFolder(), m_Impact); if (!Check(m_Target != null, "fixture-owned inert report identity exists; no enemy perception is claimed")) return -1; m_Source = hq.ERR_QueueMortarFixtureReport(m_Group, m_Target, m_Impact, now); if (!Check(m_Source > 0, "synthetic observation enters actual existing radio uplink once")) return -1; m_ObservedAt = now; m_SawUplink = true; m_Deadline = now + 90000; m_Step = 1; return 0; } if (m_WaitForExpiry && m_Battery.Stopped()) { Check(false, "battery must remain running throughout natural expiry observation"); return -1; } if (m_Battery.Report && (m_Battery.Report.MessageId != m_Source || m_Battery.Report.ObservedAt != m_ObservedAt)) { Check(false, "delivered report identity and original observation timestamp remain immutable"); return -1; } if (m_Battery.Gun != m_Gun || m_Battery.Group != m_Group) { Check(false, "original gun and crew identity retained"); return -1; } if (!EmptyImpact(m_Impact)) { Check(false, "impact exclusion stays unoccupied before native firing/cancellation"); return -1; } array remaining = {}; Shells(remaining); foreach (IEntity item : remaining) if (!m_InitialShells.Contains(item)) { Check(false, "no replacement HE shell appears after initialization"); return -1; } if (m_Step == 1) { if (m_Battery.RadioOrder && m_Battery.SourceId == m_Source) { m_SawDownlink = true; m_Order = m_Battery.OrderId; } if (m_Battery.Activity && m_Battery.Report && m_Battery.Report.MessageId == m_Source) m_SawActivity = true; if (m_Battery.OrderId > 1 || (m_Battery.SourceId != 0 && m_Battery.SourceId != m_Source)) { Check(false, "one unchanged fixture source creates only one HQ order"); return -1; } if (m_Events == 0) return 0; if (!Check(m_Events == 1 && m_Battery.TotalShots == 1 && m_Battery.OrderShots == 1 && !m_Battery.Released && m_SawUplink && m_SawDownlink && m_SawActivity && m_Order == 1, "one physical projectile follows real uplink/downlink, native activity and fresh aim")) return -1; if (!Check(remaining.Count() == 26 && m_Battery.CountShells() == 26, "one fired projectile consumes one of 27 original inventory shells")) return -1; if (m_WaitForExpiry) { m_Step = 3; m_Deadline = m_ObservedAt + ERR_MortarPrediction.MAX_REPORT_AGE_MS + 50000; return 0; } m_FinalEvents = m_Events; // Cancel on this later fixture tick, never reentrantly inside the firing node. hq.ERR_RemoveMortarFixtureReport(m_Target); system.Stop(); system.Stop(); if (!Check(!m_Battery.CommandOpen && !m_Battery.Activity && !m_Battery.RadioOrder, "explicit support Stop closes native order and route idempotently")) return -1; m_StoppedShells = remaining.Count(); m_StopUntil = now + 12000; m_Deadline = m_StopUntil + 2000; m_Step = 2; return 0; } if (m_Step == 3) { if (m_Battery.OrderId != m_Order || m_Battery.SourceId != m_Source) { Check(false, "unchanged delivered observation received a second order"); return -1; } if (m_Events > ERR_MortarSalvo.ROUNDS || m_Battery.TotalShots > ERR_MortarSalvo.ROUNDS) { Check(false, "single observation cannot exceed finite salvo"); return -1; } if (now < m_ObservedAt + ERR_MortarPrediction.MAX_REPORT_AGE_MS + 25000) return 0; if (!Check(m_ClosureSeen && (m_ClosureExpired || m_ClosureComplete) && !m_CommitPending && m_Commits == m_Events && m_Events >= 1 && m_Battery.TotalShots == m_Events && m_Battery.OrderShots == m_Events && remaining.Count() == 27 - m_Events && !m_Battery.CommandOpen && !m_Battery.Activity && !m_Battery.RadioOrder && !m_Battery.Report, "explicit expired-withheld closure or complete finite salvo, with only fresh commits and observed physical shells consumed")) return -1; m_FinalEvents = m_Events; // Keep HQ running and the expired contact present throughout this next wait. m_StoppedShells = remaining.Count(); m_StopUntil = now + 12000; m_Deadline = m_StopUntil + 2000; m_Step = 4; return 0; } if (now < m_StopUntil) return 0; if (!Check(m_Battery.TotalShots == m_FinalEvents && m_Events == m_FinalEvents && m_Battery.OrderId == m_Order && remaining.Count() == m_StoppedShells && !m_Battery.CommandOpen && !m_Battery.Activity && !m_Battery.RadioOrder, "closed order issues no duplicate order, projectile or replenishment over 12 seconds")) return -1; array finalCrew = {}; m_Group.GetAgents(finalCrew); if (!Check(finalCrew.Count() == 4 && m_Battery.Gun == m_Gun, "original group and gun retain four crew")) return -1; foreach (AIAgent finalAgent : finalCrew) if (!Check(finalAgent && m_Crew.Contains(finalAgent.GetControlledEntity()) && ERR_Actors.CanFight(finalAgent.GetControlledEntity()), "final conscious crew entity belongs to original physical roster")) return -1; if (m_WaitForExpiry) { hq.ERR_RemoveMortarFixtureReport(m_Target); system.Stop(); system.Stop(); } Stop(); return 1; } void Stop() { if (m_Stopped) return; m_Stopped = true; ERR_HQ hq = ERR_HQ.Get(); if (hq) { hq.ERR_RemoveMortarFixtureReport(m_Target); if (m_Battery && m_Battery.SourceId == m_Source && m_Source > 0) m_Battery.Cancel("mortar firing fixture cleanup"); } ERR_Actors.DeleteSpawned(m_Target); m_Target = null; if (s_Active == this) s_Active = null; } void ~ERR_NativeMortarFiringProbe() { Stop(); } } #endif