Capture current mission, AI, map and administration development

Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
2026-10-13 22:52:53 +02:00
parent a951a96edc
commit 6a667d8deb
8104 changed files with 2124945 additions and 2061 deletions
@@ -0,0 +1,891 @@
// Explicit local engine fixture, excluded from game/server builds.
#ifdef WORKBENCH
modded class ERR_GameMode
{
protected ref ERR_NativeEnvironmentRestartProbe m_ERRBaseEnvironmentRestartProbe;
protected override string SettingsPath()
{
string enabled, outcome;
if (System.GetCLIParam("errBaseSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "environment-restart")
{
m_ERRBaseEnvironmentRestartProbe = new ERR_NativeEnvironmentRestartProbe(this);
return ERR_NativeEnvironmentRestartProbe.PATH;
}
return super.SettingsPath();
}
protected ref ERR_NativeCasualtyServiceProbe m_ERRCasualtyServiceProbe;
protected ref ERR_NativePrisonerProbe m_ERRPrisonerProbe;
protected ref ERR_NativeGuardCancelProbe m_ERRGuardCancelProbe;
protected ref ERR_NativeLocalAdmissionProbe m_ERRLocalAdmissionProbe;
protected ref ERR_NativePoweredFlightProbe m_ERRPoweredFlightProbe;
protected ref ERR_NativePrisonBreachProbe m_ERRPrisonBreachProbe;
protected ref ERR_NativeInventoryHitZoneProbe m_ERRInventoryHitZoneProbe;
protected ref ERR_NativeItemStationTeardownProbe m_ERRItemStationTeardownProbe;
protected ref ERR_NativeObjectiveLossProbe m_ERRObjectiveLossProbe;
protected ref ERR_NativeMedicalProbe m_ERRBaseMedicalProbe;
protected ref ERR_NativeCasualtyLoadProbe m_ERRCasualtyLoadProbe;
protected ref ERR_NativeEnvironmentProbe m_ERRBaseEnvironmentProbe;
protected bool m_ERRCatalogChecked;
protected bool m_ERRDiscoveryMapChecked;
protected ref ERR_NativePatrolProbe m_ERRPatrolEast;
protected ref ERR_NativePatrolProbe m_ERRPatrolWest;
protected int m_ERRBaseStep;
protected bool m_ERRObjectiveCloseChecked;
protected bool m_ERRStartupFailure;
protected bool m_ERRCacheLoss;
protected string m_ERRExtractionCase;
protected ref ERR_NativeExtractionProbe m_ERRExtractionProbe;
protected ref ERR_NativeTransportProbe m_ERRTransportProbe;
protected ref ERR_NativeLifecycleProbe m_ERRLifecycleProbe;
protected ref ERR_NativeRecoveryProbe m_ERRRecoveryProbe;
protected ref ERR_NativeRadioDefenceProbe m_ERRBaseRadioProbe;
protected string m_ERRActiveCacheId = "everon-supply-1";
protected int m_ERRMedicalDiscoveries;
protected IEntity m_ERRSightBlocker;
protected float m_ERRBaseDeadline;
protected float m_ERRBaseWait;
protected IEntity m_ERRBasePlayer;
protected ERR_LocationComponent m_ERRBaseRadio;
protected ref array<IEntity> m_ERRBaseDocuments = {};
protected override void EOnInit(IEntity owner)
{
super.EOnInit(owner);
string enabled;
if (SCR_Global.IsEditMode() || !Replication.IsServer() || !System.GetCLIParam("errBaseSmoke", enabled) || enabled != "1") return;
string nativeEndCase;
array<string> independentFixtures = {"optional-site-skip", "infantry-combat", "medical-combat", "hunter-faction", "unconscious-camera", "objective-map", "town-garrisons", "site-garrisons", "radio-interaction", "enemy-loadout", "aggression", "native-end-active", "native-end-preparing", "generated-sites", "mortar-station", "opening-tools", "airfield-markers", "air-logistics", "air-assault", "air-recon"};
if (System.GetCLIParam("errSmokeOutcome", nativeEndCase) && independentFixtures.Contains(nativeEndCase)) return;
ERR_NativeObjectiveProbe.BeginRound();
ERR_NativeProgressionIdentityProbe.BeginRound();
string outcome;
m_ERRStartupFailure = System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "startup-failure";
m_ERRExtractionCase = outcome;
if (outcome == "extraction-startup-failure" && m_Factions) m_Factions.ExtractionPrefab = "{259EE7B78C51B624}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469.et";
if (outcome == "extraction-boundary-failure") m_EscapeRadius = 0;
m_ERRCacheLoss = outcome == "cache-loss";
if (m_ERRCacheLoss) m_ERRActiveCacheId = "everon-supply-2";
OnLocationDiscovered().Insert(ERR_BaseDiscovered);
OnStop().Insert(ERR_NativeObjectiveProbe.Stop);
OnStop().Insert(ERR_StopRadioProbe);
OnStop().Insert(ERR_StopHealthProbes);
if (m_ERRStartupFailure && m_Factions) m_Factions.EnemyFactionKey = m_Factions.PlayerFactionKey;
m_ERRBaseDeadline = GetWorld().GetWorldTime() + 210000;
if (outcome == "patrols") m_ERRBaseDeadline = GetWorld().GetWorldTime() + 330000;
OnPhaseChanged().Insert(ERR_BaseResult);
GetGame().GetCallqueue().CallLater(ERR_BaseTick, 500, true);
Print("ERR BASE SMOKE: core-only scripted escape; not manual or multiplayer acceptance");
}
protected bool ERR_BaseCheck(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: base " + detail); return true; }
ERR_NativeObjectiveProbe.Stop();
ERR_StopRadioProbe();
ERR_StopHealthProbes();
Print("ERR SMOKE FAIL: base " + detail, LogLevel.ERROR);
GetGame().GetCallqueue().Remove(ERR_BaseTick);
return false;
}
protected void ERR_StopHealthProbes()
{
ERR_NativeDiscoveryMapProbe.Stop();
ERR_NativeProgressionIdentityProbe.Stop();
if (m_ERRTransportProbe) m_ERRTransportProbe.Stop();
if (m_ERRPatrolEast) m_ERRPatrolEast.Stop();
if (m_ERRPatrolWest) m_ERRPatrolWest.Stop();
if (m_ERRBaseEnvironmentRestartProbe) m_ERRBaseEnvironmentRestartProbe.Stop();
if (m_ERRBaseEnvironmentProbe) m_ERRBaseEnvironmentProbe.Stop();
if (m_ERRPrisonerProbe) m_ERRPrisonerProbe.Stop();
if (m_ERRGuardCancelProbe) m_ERRGuardCancelProbe.Stop();
if (m_ERRLocalAdmissionProbe) m_ERRLocalAdmissionProbe.Stop();
if (m_ERRPoweredFlightProbe) m_ERRPoweredFlightProbe.Stop();
if (m_ERRPrisonBreachProbe) m_ERRPrisonBreachProbe.Stop();
if (m_ERRInventoryHitZoneProbe) m_ERRInventoryHitZoneProbe.Stop();
if (m_ERRItemStationTeardownProbe) m_ERRItemStationTeardownProbe.Stop();
if (m_ERRCasualtyServiceProbe) m_ERRCasualtyServiceProbe.Stop();
if (m_ERRObjectiveLossProbe) m_ERRObjectiveLossProbe.Stop();
if (m_ERRBaseMedicalProbe) m_ERRBaseMedicalProbe.Stop();
if (m_ERRCasualtyLoadProbe) m_ERRCasualtyLoadProbe.Cleanup();
if (m_ERRRecoveryProbe) m_ERRRecoveryProbe.Stop();
}
protected void ERR_StopRadioProbe() { if (m_ERRBaseRadioProbe) m_ERRBaseRadioProbe.Stop(); }
protected void ERR_BaseMove(vector position)
{
vector transform[4];
m_ERRBasePlayer.GetWorldTransform(transform);
transform[3] = ERR_Actors.Ground(position);
ChimeraCharacter.Cast(m_ERRBasePlayer).Teleport(transform);
}
protected ERR_UnlockPrisonAction ERR_BaseLock(IEntity entity)
{
BaseActionsManagerComponent manager = BaseActionsManagerComponent.Cast(entity.FindComponent(BaseActionsManagerComponent));
if (manager)
{
array<BaseUserAction> actions = {};
manager.GetActionsList(actions);
foreach (BaseUserAction action : actions)
{
ERR_UnlockPrisonAction lock = ERR_UnlockPrisonAction.Cast(action);
if (lock) return lock;
}
}
IEntity child = entity.GetChildren();
while (child)
{
ERR_UnlockPrisonAction lock = ERR_BaseLock(child);
if (lock) return lock;
child = child.GetSibling();
}
return null;
}
protected bool ERR_BaseCover()
{
int count;
foreach (IEntity entity : m_OwnedEntities)
{
if (!entity || !entity.GetPrefabData() || entity.GetPrefabData().GetPrefabName() != "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et") continue;
vector transform[4];
entity.GetWorldTransform(transform);
vector up = transform[1];
if (!ERR_BaseCheck(up[1] > 0.99, "outpost barrier is upright")) return false;
vector low, high;
entity.GetWorldBounds(low, high);
vector center = (low + high) * 0.5;
vector across = "0 0 2";
if (high[0] - low[0] < high[2] - low[2]) across = "2 0 0";
TraceParam trace = new TraceParam();
trace.Start = center + across;
trace.End = center - across;
trace.Flags = TraceFlags.ENTS;
trace.LayerMask = TRACE_LAYER_CAMERA;
float fraction = GetWorld().TraceMove(trace, null);
PrintFormat("ERR COVER GEOMETRY: low=%1 high=%2 trace=%3", low, high, fraction);
if (!ERR_BaseCheck(fraction < 1 && trace.TraceEnt == entity, "outpost barrier has physical collision")) return false;
count++;
}
return ERR_BaseCheck(count == 3, "exactly three authored outpost barriers exist");
}
protected void ERR_BaseDiscovered(ERR_LocationComponent location, int previous, int current)
{
if (location.Id() == "everon-medical-1" && previous == 0 && current == 2) m_ERRMedicalDiscoveries++;
}
protected void ERR_BaseResult(ERR_Phase previous, ERR_Phase current)
{
if (!ERR_MissionRules.Terminal(current)) return;
GetGame().GetCallqueue().Remove(ERR_BaseTick);
if (m_ERRExtractionCase == "inventory-hitzone")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 2102, "native inventory callback fixture finished before explicit reset")) return;
ERR_StopHealthProbes();
Print("ERR SMOKE COMPLETE: real one-argument blood event reaches native inventory updater and detached points stay silent; synthetic notification, no injury/manual UI/multiplayer proof");
return;
}
if (m_ERRExtractionCase == "station-teardown")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 2002, "native station teardown fixture finished before explicit reset")) return;
ERR_StopHealthProbes();
Print("ERR SMOKE COMPLETE: isolated off-world station catalog-expiry injection and native callback teardown; not restart reproduction");
return;
}
if (m_ERRExtractionCase == "prison-breach")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1902, "native destroyed-gate crossing finished before explicit reset")) return;
ERR_StopHealthProbes();
Print("ERR SMOKE COMPLETE: actual gate destruction and sampled prison crossing; repositioned fixture, not walking or multiplayer");
return;
}
if (m_ERRExtractionCase == "powered-flight" || m_ERRExtractionCase == "powered-translate")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1802, "native powered-flight fixture finished before explicit test reset")) return;
ERR_StopHealthProbes();
if (m_ERRExtractionCase == "powered-translate")
Print("ERR SMOKE COMPLETE: native powered takeoff, bounded four-metre translation, target hold, contact landing and engine-off; isolated mechanism fixture reset");
else Print("ERR SMOKE COMPLETE: native pilot-input powered takeoff, hover, contact landing and shutdown; isolated mechanism fixture reset");
return;
}
if (m_ERRExtractionCase == "local-admission")
{
// Completion is emitted by the calling tick after synchronous teardown and
// its post-transition invariants; a terminal event alone is insufficient.
ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1700, "local admission fixture received its production fail-closed transition");
return;
}
if (m_ERRExtractionCase == "early-helicopter-loss")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 201 && Players().HasSurvivor() && Extraction().State() == ERR_ExtractionState.LOST, "lost helicopter ends prison phase despite a living prisoner")) return;
ERR_FailureGameOverScreenInfo result = new ERR_FailureGameOverScreenInfo();
if (!ERR_BaseCheck(EndReason().Contains("helicopter was lost") && result.GetDebriefing(0, null) == EndReason(), "early loss has a specific replicated and debriefing reason")) return;
Print("ERR SMOKE COMPLETE: early extraction vehicle loss");
return;
}
if (m_ERRExtractionCase == "casualty-service")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1402, "casualty service fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native casualty service with helper-only identity adapter; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "radio-document-loss" || m_ERRExtractionCase == "radio-document-loss-partial")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1302, "radio document-loss fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native " + m_ERRExtractionCase + "; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "guard-cancel")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1502, "guard cancellation fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native guard-cancel; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "prisoner-kit")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 702, "prisoner-kit fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native prisoner-kit; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "medical")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 602, "medical fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native medical; reset requested by fixture");
return;
}
if ((m_ERRExtractionCase == "casualty-load" || m_ERRExtractionCase == "casualty-direct" || m_ERRExtractionCase == "casualty-bench" || m_ERRExtractionCase == "casualty-remove"))
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 802, "casualty-load fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native " + m_ERRExtractionCase + "; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "environment-restart")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1202, "environment restart round finished before explicit test reset")) return;
PrintFormat("ERR SMOKE COMPLETE: environment restart round %1; reset requested by fixture", m_ERRBaseEnvironmentRestartProbe.Round());
return;
}
if (m_ERRExtractionCase == "environment")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1002, "environment fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native environment; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "patrols")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1102, "both patrol routes finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native finite patrol routes and cleanup; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "recovery")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 502 && EndReason().Contains("All prisoners are unconscious"), "stalled all-unconscious team reaches automatic mission defeat")) return;
if (!ERR_BaseCheck(!RecoveryStatus().IsEmpty() && !AbandonRound(), "terminal event observes active warning and rejects repeated abandonment")) return;
Print("ERR SMOKE COMPLETE: player unconsciousness, native healing and automatic all-unconscious defeat");
return;
}
if (m_ERRExtractionCase == "objective-ui")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 902, "objective UI fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native objective UI close, reopen and deleted-task cleanup; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "lifecycle")
{
if (!ERR_BaseCheck(m_ERRBaseStep == 402, "lifecycle fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: native lifecycle admission guards; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "transport-loss")
{
if (!ERR_BaseCheck(m_ERRBaseStep == 302, "transport fixture finished before explicit test reset")) return;
Print("ERR SMOKE COMPLETE: finite native ground transport; reset requested by fixture");
return;
}
if (m_ERRExtractionCase == "extraction-capacity")
{
if (!ERR_BaseCheck(m_ERRBaseStep == 202, "capacity fixture finished before its explicit test reset")) return;
Print("ERR SMOKE COMPLETE: physical capacity and isolated extraction results; reset requested by fixture");
return;
}
if (!ERR_BaseCheck(current == ERR_Phase.VICTORY && m_ERRBaseStep == 5, "escape and restart reached without companion services")) return;
Print("ERR SMOKE COMPLETE: core-only escape");
}
protected void ERR_BaseTick()
{
float now = GetWorld().GetWorldTime();
if (ERR_NativeProgressionIdentityProbe.Failed()) { ERR_BaseCheck(false, "native progression identity rejection fixture"); return; }
if (now > m_ERRBaseDeadline) { ERR_BaseCheck(false, "timeout at " + m_ERRBaseStep.ToString()); return; }
if (m_ERRExtractionCase == "extraction-startup-failure" || m_ERRExtractionCase == "extraction-boundary-failure")
{
if (PreparationError().IsEmpty()) return;
if (!ERR_BaseCheck(Phase() == ERR_Phase.PREPARING && !Active() && m_Stopped, "invalid extraction setup never starts gameplay")) return;
array<IEntity> players = {};
ERR_Actors.Players(players);
if (!ERR_BaseCheck(players.IsEmpty() && Sites() && Sites().Count() == 0 && m_OwnedEntities.IsEmpty() && !ExtractionVehicle(), "late startup failure rolls back all authored stock, scenery, groups and vehicle")) return;
if (!ERR_BaseCheck(PreparationError().Contains("twelve") && !Transition(ERR_Phase.PRISON), "invalid extraction retains actionable diagnosis and blocks progression")) return;
GetGame().GetCallqueue().Remove(ERR_BaseTick);
Print("ERR SMOKE COMPLETE: expected late extraction startup failure");
return;
}
if (m_ERRStartupFailure)
{
if (PreparationError().IsEmpty()) return;
int presentation = ERR_NativePreparationStatusProbe.Tick(this, now);
if (presentation < 0) { ERR_BaseCheck(false, "actual persistent startup-failure loading presentation within30seconds"); return; }
if (presentation == 0) return;
if (!ERR_BaseCheck(true, "actual failed-start loading widgets persist beyond17seconds with spinner hidden; diagnostic screenshot requested")) return;
if (!ERR_BaseCheck(Phase() == ERR_Phase.PREPARING && !Active() && m_Stopped, "invalid faction setup stops services without starting the mission")) return;
array<IEntity> players = {};
ERR_Actors.Players(players);
if (!ERR_BaseCheck(players.IsEmpty() && !Sites() && m_OwnedEntities.IsEmpty(), "failed setup spawns no players, sites or supplies")) return;
array<string> replies = {};
Admin().Execute(this, true, "status", replies);
if (!ERR_BaseCheck(replies.Count() == 1 && replies[0].Contains("configured player and enemy factions"), "administrator receives retained startup failure cause")) return;
if (!ERR_BaseCheck(!Transition(ERR_Phase.PRISON) && !Transition(ERR_Phase.EXTRACTION), "failed preparation cannot be advanced into gameplay")) return;
GetGame().GetCallqueue().Remove(ERR_BaseTick);
Print("ERR SMOKE COMPLETE: expected startup-failure");
return;
}
if (m_ERRBaseStep == 2100)
{
int result = m_ERRInventoryHitZoneProbe.Tick(now);
if (result < 0) { ERR_BaseCheck(false, "native inventory hitzone callback fixture"); return; }
if (result == 1) { m_ERRBaseStep = 2102; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 2000)
{
int result = m_ERRItemStationTeardownProbe.Tick(now);
if (result < 0) { ERR_BaseCheck(false, "native isolated station teardown fixture"); return; }
if (result == 1) { m_ERRBaseStep = 2002; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 1900)
{
int result = m_ERRPrisonBreachProbe.Tick(now);
if (result < 0) { ERR_BaseCheck(false, "native destroyed-gate progression fixture"); return; }
if (result == 1) { m_ERRBaseStep = 1902; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 1800)
{
int result = m_ERRPoweredFlightProbe.Tick(now);
if (result < 0) { ERR_BaseCheck(false, "native powered-flight feasibility fixture"); return; }
if (result == 1) { m_ERRBaseStep = 1802; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 1700)
{
int result = m_ERRLocalAdmissionProbe.Tick(now);
if (result < 0) { ERR_StopHealthProbes(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1)
{
m_ERRBaseStep = 1702;
GetGame().GetCallqueue().Remove(ERR_BaseTick);
Print("ERR SMOKE COMPLETE: native local admission callback replay and fail-closed method injection; not multiplayer evidence");
}
return;
}
if (m_ERRBaseStep == 1400)
{
int result = m_ERRCasualtyServiceProbe.Tick(now);
if (result < 0) { ERR_BaseCheck(false, "casualty service fixture failed"); return; }
if (result == 1) { m_ERRBaseStep = 1402; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 1300)
{
int result = m_ERRObjectiveLossProbe.Tick(now);
if (result < 0) { ERR_StopHealthProbes(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 1302; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 1500)
{
int result = m_ERRGuardCancelProbe.Tick(now);
if (result < 0) { ERR_StopHealthProbes(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 1502; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 700)
{
int result = m_ERRPrisonerProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 702; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 600)
{
int result = m_ERRBaseMedicalProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 602; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 800)
{
int result = m_ERRCasualtyLoadProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 802; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 1200)
{
int result = m_ERRBaseEnvironmentRestartProbe.Tick(now);
if (result < 0) { ERR_BaseCheck(false, "environment restart fixture failed"); return; }
if (result == 1) { m_ERRBaseStep = 1202; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 1000)
{
int result = m_ERRBaseEnvironmentProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 1002; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 1100)
{
int east = m_ERRPatrolEast.Tick(now);
int west = m_ERRPatrolWest.Tick(now);
if (east < 0 || west < 0) { ERR_StopHealthProbes(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (east == 1 && west == 1)
{
if (!ERR_NativePatrolProbe.StopChecks(this)) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
m_ERRBaseStep = 1102; Transition(ERR_Phase.DEFEAT);
}
return;
}
if (m_ERRBaseStep == 500)
{
int result = m_ERRRecoveryProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1)
{
m_ERRBaseStep = 502;
ERR_BaseCheck(Active() && ERR_Actors.Alive(m_ERRBasePlayer), "fixture leaves a living casualty for the production failure timer");
}
return;
}
if (m_ERRBaseStep == 400)
{
int result = m_ERRLifecycleProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 402; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 300)
{
int result = m_ERRTransportProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 302; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 200)
{
int result = m_ERRExtractionProbe.Tick(now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 202; Transition(ERR_Phase.DEFEAT); }
return;
}
if (m_ERRBaseStep == 901)
{
int result = ERR_NativeObjectiveProbe.TickCloseReopen(this, now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1)
{
if (m_ERRExtractionCase == "objective-ui")
{
if (!ERR_NativeObjectiveProbe.BeginDeletedTaskClose(this, now)) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
m_ERRBaseStep = 903;
}
else { m_ERRObjectiveCloseChecked = true; m_ERRBaseStep = 0; }
}
return;
}
if (m_ERRBaseStep == 903)
{
int result = ERR_NativeObjectiveProbe.TickDeletedTaskClose(this, now);
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 1) { m_ERRBaseStep = 902; Transition(ERR_Phase.DEFEAT); }
return;
}
if (now < m_ERRBaseWait) return;
if (m_ERRBaseStep == 0)
{
array<IEntity> players = {};
ERR_Actors.Players(players, true);
if (players.Count() != 1 || Phase() != ERR_Phase.PRISON) return;
m_ERRBasePlayer = players[0];
if (!m_ERRCatalogChecked)
{
if (!ERR_NativeFactionCatalogProbe.Run(this)) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
m_ERRCatalogChecked = true;
}
if (!m_ERRDiscoveryMapChecked)
{
if (!ERR_NativeDiscoveryMapProbe.Run()) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
m_ERRDiscoveryMapChecked = true;
}
if (m_ERRExtractionCase == "inventory-hitzone")
{
m_ERRInventoryHitZoneProbe = new ERR_NativeInventoryHitZoneProbe(this, m_ERRBasePlayer);
m_ERRBaseStep = 2100;
return;
}
if (m_ERRExtractionCase == "station-teardown")
{
m_ERRItemStationTeardownProbe = new ERR_NativeItemStationTeardownProbe();
m_ERRBaseStep = 2000;
return;
}
if (m_ERRExtractionCase == "prison-breach")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRPrisonBreachProbe = new ERR_NativePrisonBreachProbe(this, m_ERRBasePlayer);
m_ERRBaseStep = 1900;
return;
}
if (m_ERRExtractionCase == "powered-flight" || m_ERRExtractionCase == "powered-translate")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRPoweredFlightProbe = new ERR_NativePoweredFlightProbe(this);
string autohoverMode;
if (System.GetCLIParam("errFlightAutohover", autohoverMode) && autohoverMode == "1")
if (!ERR_BaseCheck(m_ERRPoweredFlightProbe.SetAutohoverMode(true), "owned native autohover experiment selected before allocation")) return;
m_ERRBaseDeadline = now + 250000;
m_ERRBaseStep = 1800;
return;
}
if (m_ERRExtractionCase == "local-admission")
{
m_ERRLocalAdmissionProbe = new ERR_NativeLocalAdmissionProbe(this);
m_ERRBaseStep = 1700;
return;
}
if (m_ERRExtractionCase == "casualty-service")
{
m_ERRCasualtyServiceProbe = new ERR_NativeCasualtyServiceProbe(this);
m_ERRBaseStep = 1400;
return;
}
if (m_ERRExtractionCase == "radio-document-loss" || m_ERRExtractionCase == "radio-document-loss-partial")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRObjectiveLossProbe = new ERR_NativeObjectiveLossProbe(this, m_ERRBasePlayer, m_ERRExtractionCase == "radio-document-loss-partial");
m_ERRBaseStep = 1300;
return;
}
if (m_ERRExtractionCase == "guard-cancel")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRGuardCancelProbe = new ERR_NativeGuardCancelProbe(this);
m_ERRBaseStep = 1500;
return;
}
if (m_ERRExtractionCase == "prisoner-kit")
{
int contentMenu = ERR_ProbeAdminMenu(now);
if (contentMenu < 0) { ERR_BaseCheck(false, "content admin menu"); return; }
if (contentMenu == 0) return;
if (!ERR_NativeAdminSettingsProbe.Run(this)) { ERR_BaseCheck(false, "random settings storage and commands"); return; }
if (!ERR_NativeContentProbe.Run(this)) { ERR_BaseCheck(false, "content registry and backpack"); return; }
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRPrisonerProbe = new ERR_NativePrisonerProbe(this, m_ERRBasePlayer);
m_ERRBaseStep = 700;
return;
}
if (m_ERRExtractionCase == "medical")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRBaseMedicalProbe = new ERR_NativeMedicalProbe(this);
m_ERRBaseStep = 600;
return;
}
if ((m_ERRExtractionCase == "casualty-load" || m_ERRExtractionCase == "casualty-direct" || m_ERRExtractionCase == "casualty-bench" || m_ERRExtractionCase == "casualty-remove"))
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
if (m_ERRExtractionCase == "casualty-remove") m_ERRCasualtyLoadProbe = new ERR_NativeCasualtyRemoveProbe(this);
else m_ERRCasualtyLoadProbe = new ERR_NativeCasualtyLoadProbe(this, m_ERRExtractionCase != "casualty-load", m_ERRExtractionCase == "casualty-bench");
m_ERRBaseStep = 800;
return;
}
if (m_ERRExtractionCase == "environment-restart")
{
if (!ERR_BaseCheck(m_ERRBaseEnvironmentRestartProbe != null, "environment restart selected isolated settings before mode initialization")) return;
m_ERRBaseStep = 1200;
return;
}
if (m_ERRExtractionCase == "environment")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRBaseEnvironmentProbe = new ERR_NativeEnvironmentProbe(this);
m_ERRBaseStep = 1000;
return;
}
if (m_ERRExtractionCase == "patrols")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRPatrolEast = new ERR_NativePatrolProbe(this, "everon-outpost-1");
m_ERRPatrolWest = new ERR_NativePatrolProbe(this, "everon-supply-2");
m_ERRBaseStep = 1100;
return;
}
if (m_ERRExtractionCase == "recovery")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRRecoveryProbe = new ERR_NativeRecoveryProbe(this, m_ERRBasePlayer);
m_ERRBaseStep = 500;
return;
}
if (m_ERRExtractionCase == "lifecycle")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRLifecycleProbe = new ERR_NativeLifecycleProbe(this, m_ERRBasePlayer);
m_ERRBaseStep = 400;
return;
}
if (m_ERRExtractionCase == "transport-survey")
{
ERR_NativeTransportSurvey.Run(this);
GetGame().GetCallqueue().Remove(ERR_BaseTick);
return;
}
if (m_ERRExtractionCase == "transport-loss")
{
m_ERRTransportProbe = new ERR_NativeTransportProbe(this, m_ERRBasePlayer);
m_ERRBaseStep = 300;
return;
}
if (m_ERRExtractionCase == "early-helicopter-loss")
{
if (!ERR_BaseCheck(Extraction().Ready() && Players().HasSurvivor(), "ready helicopter and living prisoner before deliberate loss")) return;
m_ERRBaseStep = 201;
ERR_Actors.DeleteSpawned(ExtractionVehicle());
return;
}
if (m_ERRExtractionCase == "extraction-capacity")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRExtractionProbe = new ERR_NativeExtractionProbe(this);
m_ERRBaseStep = 200;
return;
}
string survey;
if (System.GetCLIParam("errSmokeOutcome", survey) && survey == "site-survey")
{
ERR_NativeSiteSurvey probe = new ERR_NativeSiteSurvey();
probe.Run();
GetGame().GetCallqueue().Remove(ERR_BaseTick);
return;
}
if (!m_ERRObjectiveCloseChecked)
{
if (!ERR_NativeObjectiveProbe.BeginCloseReopen(this, now)) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
m_ERRBaseStep = 901;
return;
}
if (m_ERRExtractionCase == "victory")
{
int adminMenu = ERR_ProbeAdminMenu(now);
if (adminMenu < 0) { ERR_BaseCheck(false, "Core admin menu integration"); return; }
if (adminMenu == 0) return;
}
if (!ERR_NativeAdminSettingsProbe.Run(this) || !ERR_NativeDefinitionProbe.Run() || !ERR_NativeContractsProbe.Run(this, m_ERRBasePlayer) || !ERR_BaseCover() || !ERR_NativeTimeProbe.Run(this)) return;
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
if (!ERR_BaseCheck(Objectives() && Objectives().Current() && Objectives().Current().GetTaskName() == "Escape the prison", "native prison objective exists")) return;
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
ERR_SiteRuntime cache = Sites().Find(m_ERRActiveCacheId);
if (!ERR_BaseCheck(Sites().Count() == 7 && cache && cache.Container && vector.DistanceXZ(cache.Container.GetOrigin(), PrisonPosition()) > 100, "authored supply cache is a separate scavenging location")) return;
array<IEntity> stock = {};
ERR_Inventory.Items(InventoryStorageManagerComponent.Cast(cache.Container.FindComponent(InventoryStorageManagerComponent)), stock);
PrintFormat("ERR BASE STOCK: cache items=%1", stock.Count());
int expectedStock;
foreach (ERR_SiteStock entry : cache.Definition.Stock) expectedStock += entry.Count;
if (!ERR_BaseCheck(stock.Count() >= expectedStock, "cache holds finite physical equipment and objective documents")) return;
if (!ERR_BaseCheck(Command() == null, "no companion HQ or support controller loaded")) return;
if (!ERR_BaseCheck(vector.DistanceXZ(m_ERRBasePlayer.GetOrigin(), PrisonPosition()) < 5, "prisoner spawned in authored prison")) return;
foreach (ERR_LocationComponent location : m_Locations)
if (location.Kind() == ERR_LocationKind.RADIO) m_ERRBaseRadio = location;
ERR_UnlockPrisonAction lock;
foreach (IEntity entity : m_OwnedEntities)
{
if (!entity) continue;
if (!lock) lock = ERR_BaseLock(entity);
}
foreach (IEntity item : stock)
if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_Resources.ObjectiveDocument()) m_ERRBaseDocuments.Insert(item);
if (m_ERRCacheLoss)
{
ERR_Actors.DeleteSpawned(Sites().Find("everon-supply-1").Container);
if (!ERR_BaseCheck(!Sites().Find("everon-supply-1").Container, "primary cache lost before any clues are recovered")) return;
}
if (!ERR_BaseCheck(lock && m_ERRBaseRadio && m_ERRBaseDocuments.Count() == 2 && ERR_Actors.Alive(ExtractionVehicle()), "gate, clues, radio and helicopter created by core")) return;
if (!ERR_NativeProgressionIdentityProbe.Begin(this, m_ERRBasePlayer)) { ERR_BaseCheck(false, "native progression identity gate baseline"); return; }
ERR_BaseMove(lock.GetOwner().GetOrigin() + "1 0 1");
lock.PerformAction(lock.GetOwner(), m_ERRBasePlayer);
if (!ERR_BaseCheck(Phase() == ERR_Phase.RADIO_SEARCH, "gate action begins escape")) return;
if (!ERR_BaseCheck(m_Settings.Set("radioDefenceSeconds", 30), "test defence duration accepted")) return;
SCR_Task prisonTask = SCR_TaskSystem.GetTaskFromTaskID(ERR_Objectives.Id(ERR_Phase.PRISON), false);
if (!ERR_BaseCheck(prisonTask && prisonTask.GetTaskState() == SCR_ETaskState.COMPLETED, "previous native objective completes on phase change")) return;
if (!ERR_BaseCheck(Objectives().Current().GetTaskUIVisibility() == SCR_ETaskUIVisibility.LIST_ONLY, "search objective does not expose a hidden site")) return;
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
if (!ERR_BaseCheck(Sites().Find("everon-supply-1").Location.Knowledge() == 2 && Sites().Find("everon-supply-2").Location.Knowledge() == 2, "both named supply routes revealed after escape")) return;
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && Sites().Find("everon-outpost-1").Location.Knowledge() == 0, "optional sites remain hidden until approached")) return;
ERR_BaseMove(medical.Location.Position() + "0 0 8");
CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent)).SetUnconscious(true);
m_ERRBaseStep = 10;
m_ERRBaseWait = now + 6000;
}
else if (m_ERRBaseStep == 10)
{
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && !Sites().CanObserve(m_ERRBasePlayer, medical), "unconscious player cannot discover optional sites")) return;
CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent)).SetUnconscious(false);
ERR_BaseMove(medical.Location.Position() + "0 0 65");
m_ERRBaseStep = 11;
m_ERRBaseWait = now + 6000;
}
else if (m_ERRBaseStep == 11)
{
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && !Sites().CanObserve(m_ERRBasePlayer, medical), "distant player cannot discover optional sites")) return;
ERR_BaseMove(medical.Location.Position() + "0 0 8");
m_ERRSightBlocker = ERR_Actors.Spawn("{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et", ERR_Actors.Ground(medical.Location.Position() + "-1.5 0 4"));
if (!ERR_BaseCheck(m_ERRSightBlocker != null, "isolated visibility obstacle created")) return;
Own(m_ERRSightBlocker);
vector low, high;
m_ERRSightBlocker.GetWorldBounds(low, high);
PrintFormat("ERR VISIBILITY OBSTACLE: low=%1 high=%2 observer=%3 target=%4", low, high, m_ERRBasePlayer.GetOrigin(), medical.Location.Position());
m_ERRBaseStep = 115;
m_ERRBaseWait = now + 6000;
}
else if (m_ERRBaseStep == 115)
{
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && !Sites().CanObserve(m_ERRBasePlayer, medical), "physical wall prevents nearby site discovery")) return;
ERR_Actors.DeleteSpawned(m_ERRSightBlocker);
m_ERRBaseStep = 12;
m_ERRBaseWait = now + 6000;
}
else if (m_ERRBaseStep == 12)
{
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
if (!ERR_BaseCheck(medical.Location.Knowledge() == 2 && medical.Location.Label() == "Medical stash" && m_ERRMedicalDiscoveries == 1, "conscious nearby player visually discovers named medical stash once")) return;
if (!ERR_BaseCheck(!Sites().CanObserve(m_ERRBasePlayer, Sites().Find("everon-radio-1")), "distant medical-site discovery does not reveal the radio")) return;
m_ERRBaseStep = 1;
}
else if (m_ERRBaseStep == 1 || m_ERRBaseStep == 2)
{
ERR_BaseMove(Sites().Find(m_ERRActiveCacheId).Container.GetOrigin() + "0 0 2");
SCR_InventoryStorageManagerComponent inventory = SCR_InventoryStorageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(SCR_InventoryStorageManagerComponent));
InventoryStorageManagerComponent source = InventoryStorageManagerComponent.Cast(Sites().Find(m_ERRActiveCacheId).Container.FindComponent(InventoryStorageManagerComponent));
BaseInventoryStorageComponent destination = inventory.FindStorageForItem(m_ERRBaseDocuments[m_ERRBaseStep - 1]);
if (!ERR_BaseCheck(destination && source.TryMoveItemToStorage(m_ERRBaseDocuments[m_ERRBaseStep - 1], destination), "physical clue recovered")) return;
m_ERRBaseStep++;
m_ERRBaseWait = now + 2500;
}
else if (m_ERRBaseStep == 3)
{
if (!ERR_BaseCheck(Objectives().Current().GetTaskDescription().Contains("The radio station is identified on your map. Reach"), "objective guidance reacts to discovered radio")) return;
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
ERR_SiteRuntime cache = Sites().Find(m_ERRActiveCacheId);
array<IEntity> stock = {};
ERR_Inventory.Items(InventoryStorageManagerComponent.Cast(cache.Container.FindComponent(InventoryStorageManagerComponent)), stock);
foreach (IEntity item : stock) if (item) SCR_EntityHelper.DeleteEntityAndChildren(item);
if (!ERR_BaseCheck(m_ERRMedicalDiscoveries == 1, "discovery ticks do not repeat the location event")) return;
if (m_ERRCacheLoss && !ERR_BaseCheck(!Sites().Find("everon-supply-1").Container, "lost primary cache stays absent while alternate documents identify the radio")) return;
if (!ERR_BaseCheck(ERR_NativeProgressionIdentityProbe.DocumentPassed(), "actual held-document rejection and restored recovery were exercised this round")) return;
if (!ERR_BaseCheck(m_ERRBaseRadio.Knowledge() == 2, "documents identify radio without Intel addon")) return;
ERR_BaseMove(m_ERRBaseRadio.Position() + "2 0 0");
m_ERRBaseStep = 30;
m_ERRBaseWait = now + 1000;
}
else if (m_ERRBaseStep == 30)
{
m_ERRBaseRadioProbe = new ERR_NativeRadioDefenceProbe(this, m_ERRBasePlayer, m_ERRBaseRadio);
m_ERRBaseStep = 301;
}
else if (m_ERRBaseStep == 301)
{
int result = m_ERRBaseRadioProbe.Tick(now);
if (result < 0) { ERR_StopRadioProbe(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
if (result == 0) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.INACTIVE, "radio feedback remains inactive before tuning")) return;
if (!ERR_BaseCheck(TuneRadio(m_ERRBasePlayer, m_ERRBaseRadio), "identified radio accepts transmission")) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.WAITING && ERR_ObjectiveText.Description(this, false).Contains("Waiting for"), "radio feedback publishes waiting before first occupancy sample")) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.WAITING && Objectives().Current() && Objectives().Current().GetTaskDescription().Contains("Waiting for a conscious defender") && !Objectives().Current().GetTaskDescription().Contains("interrupted"), "native radio task reports waiting before first occupancy sample")) return;
m_ERRBaseStep = 31;
m_ERRBaseWait = now + 5000;
}
else if (m_ERRBaseStep == 31)
{
ERR_SiteRuntime cache = Sites().Find(m_ERRActiveCacheId);
array<IEntity> stock = {};
ERR_Inventory.Items(InventoryStorageManagerComponent.Cast(cache.Container.FindComponent(InventoryStorageManagerComponent)), stock);
if (!ERR_BaseCheck(stock.IsEmpty() && Sites().Prepare() == ERR_PrepareResult.READY, "depleted stock stays empty when preparation is queried again")) return;
ERR_Actors.DeleteSpawned(cache.Container);
if (!ERR_BaseCheck(DefenceOccupied() && DefenceSecondsRemaining() > 0 && DefenceSecondsRemaining() < 30 && Objectives().Current().GetTaskDescription().Contains("seconds remaining"), "native objective reports advancing radio progress")) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.TRANSMITTING && ERR_ObjectiveText.Description(this, false).Contains("Transmission in progress"), "radio guide reports transmitting without querying a countdown")) return;
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent)).SetUnconscious(true);
m_ERRBaseStep = 310;
m_ERRBaseWait = now + 5000;
}
else if (m_ERRBaseStep == 310)
{
CharacterControllerComponent controller = CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent));
if (!ERR_BaseCheck(controller.IsUnconscious() && ERR_Actors.Alive(m_ERRBasePlayer) && Players().HasSurvivor() && Phase() == ERR_Phase.RADIO_DEFENCE, "unconscious survivor remains revivable during the failure grace period")) return;
if (!ERR_BaseCheck(!DefenceOccupied() && DefenceSecondsRemaining() == 30, "unconscious radio defender cannot advance transmission")) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.INTERRUPTED && ERR_ObjectiveText.Description(this, false).Contains("Progress has reset"), "radio guide reports interruption after unconsciousness")) return;
SCR_PlayerController playerController = SCR_PlayerController.Cast(GetGame().GetPlayerController());
if (!ERR_BaseCheck(playerController.GetControlledEntity() == m_ERRBasePlayer && playerController.ERR_ObserverState() == ERR_ObserverReason.NONE && !playerController.ERR_HasObserverCamera(), "unconscious player retains their body and camera")) return;
controller.SetUnconscious(false);
m_ERRBaseStep = 311;
m_ERRBaseWait = now + 6000;
}
else if (m_ERRBaseStep == 311)
{
if (!ERR_BaseCheck(ERR_Actors.CanFight(m_ERRBasePlayer) && DefenceOccupied() && DefenceSecondsRemaining() < 30, "regaining consciousness resumes transmission from zero")) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.TRANSMITTING && ERR_ObjectiveText.Description(this, false).Contains("Transmission in progress"), "radio guide returns to transmitting after recovery")) return;
int playerId = GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(m_ERRBasePlayer);
if (!ERR_BaseCheck(!Players().CanEnter(playerId, Phase()), "regaining consciousness does not grant another life")) return;
ERR_BaseMove(m_ERRBaseRadio.Position() + "90 0 0");
m_ERRBaseStep = 32;
m_ERRBaseWait = now + 2500;
}
else if (m_ERRBaseStep == 32)
{
if (!ERR_BaseCheck(!Sites().Find(m_ERRActiveCacheId).Container, "lost supply cache is not replaced")) return;
if (!ERR_BaseCheck(!DefenceOccupied() && DefenceSecondsRemaining() == 30 && Objectives().Current().GetTaskDescription().Contains("interrupted"), "objective reports interruption and reset")) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.INTERRUPTED && ERR_ObjectiveText.Description(this, false).Contains("Return a conscious survivor"), "radio guide reports interruption after leaving the area")) return;
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
ERR_BaseMove(m_ERRBaseRadio.Position() + "2 0 0");
m_ERRBaseStep = 4;
m_ERRBaseWait = now + 33000;
}
else if (m_ERRBaseStep == 4)
{
if (!ERR_BaseCheck(Phase() == ERR_Phase.EXTRACTION, "radio defence reveals extraction") || !ERR_NativeTimeProbe.VerifyProgression(this)) return;
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.INACTIVE, "radio feedback clears after completion")) return;
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
CompartmentAccessComponent access = CompartmentAccessComponent.Cast(m_ERRBasePlayer.FindComponent(CompartmentAccessComponent));
BaseCompartmentSlot slot = access.FindFreeCompartment(ExtractionVehicle(), ECompartmentType.PILOT);
if (!ERR_BaseCheck(slot && access.GetInVehicle(ExtractionVehicle(), slot, true, -1, ECloseDoorAfterActions.INVALID, false), "player physically boards extraction helicopter")) return;
m_ERRBaseStep = 5;
m_ERRBaseWait = now + 3000;
}
else if (m_ERRBaseStep == 5)
{
if (!ERR_BaseCheck(CompartmentAccessComponent.GetVehicleIn(m_ERRBasePlayer) == ExtractionVehicle(), "extraction requires real occupancy")) return;
vector transform[4];
ExtractionVehicle().GetWorldTransform(transform);
transform[3] = m_EscapeCenter + Vector(m_EscapeRadius + 100, 150, 0);
ExtractionVehicle().SetWorldTransform(transform);
m_ERRBaseWait = now + 4000;
}
}
}
#endif
@@ -0,0 +1,166 @@
#ifdef WORKBENCH
// Read-only fixture access to native UI identities. No replacement widgets.
modded class SCR_InventoryMenuUI
{
bool ERR_HitZoneProbeReady(IEntity actor)
{
return ERR_NativeInventoryHitZoneProbe.Enabled() && m_Player == actor && m_pStorageListUI
&& m_pStorageListUI.GetStorage() && m_PlayerRenderAttributes && m_wAttachmentPointsContainer;
}
bool ERR_HitZoneProbePoints(out array<ref SCR_InventoryHitZonePointContainerUI> points)
{
points.Clear();
if (!ERR_NativeInventoryHitZoneProbe.Enabled() || m_aHitZonePoints.Count() != 7) return false;
foreach (SCR_InventoryHitZonePointContainerUI point : m_aHitZonePoints) points.Insert(point);
return true;
}
}
modded class SCR_InventoryHitZonePointContainerUI
{
protected SCR_HitZone m_ERRProbeBlood;
protected int m_ERRProbeUpdates;
protected bool m_ERRProbeFalseRefresh;
protected override void UpdateHitZoneState(SCR_HitZone hz, bool updateAttachmentStorage = true)
{
super.UpdateHitZoneState(hz, updateAttachmentStorage);
if (m_ERRProbeBlood && hz == m_ERRProbeBlood)
{
m_ERRProbeUpdates++;
if (!updateAttachmentStorage) m_ERRProbeFalseRefresh = true;
}
}
bool ERR_HitZoneProbeWatch(ERR_GameMode mode, SCR_HitZone blood)
{
if (!ERR_NativeInventoryHitZoneProbe.Enabled() || !mode || mode != m_ERRHitZoneMode
|| !blood || !m_ERRHitZoneSources.Contains(blood)) return false;
m_ERRProbeBlood = blood; m_ERRProbeUpdates = 0; m_ERRProbeFalseRefresh = false;
return true;
}
bool ERR_HitZoneProbeOnce() { return m_ERRProbeUpdates == 1 && !m_ERRProbeFalseRefresh; }
bool ERR_HitZoneProbeDetached() { return !m_ERRHitZoneMode && m_ERRHitZoneSources.IsEmpty(); }
void ERR_HitZoneProbeStop() { m_ERRProbeBlood = null; }
}
// Emits one real one-argument damage-state notification without changing health.
// It proves callback/native UI integration, not a natural injury or manual UI use.
class ERR_NativeInventoryHitZoneProbe
{
protected ERR_GameMode m_Mode;
protected SCR_PlayerController m_Controller;
protected IEntity m_Actor;
protected SCR_InventoryStorageManagerComponent m_Inventory;
protected SCR_CharacterBloodHitZone m_Blood;
protected SCR_InventoryMenuUI m_Menu;
protected ref array<ref SCR_InventoryHitZonePointContainerUI> m_Points = {};
protected int m_Deadline;
protected int m_Stage;
protected int m_Result;
protected bool m_Stopped;
protected bool m_CloseRequested;
protected float m_Health;
static bool Enabled()
{
string enabled, outcome;
return Replication.IsServer() && System.GetCLIParam("errBaseSmoke", enabled) && enabled == "1"
&& System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "inventory-hitzone";
}
void ERR_NativeInventoryHitZoneProbe(ERR_GameMode mode, IEntity actor)
{
m_Mode = mode; m_Actor = actor; m_Deadline = System.GetTickCount() + 30000;
m_Controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
}
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: inventory hitzone " + detail); return true; }
Print("ERR SMOKE FAIL: inventory hitzone " + detail, LogLevel.ERROR);
m_Result = -1; Stop(); return false;
}
protected bool Context()
{
return Enabled() && m_Mode && m_Mode == ERR_GameMode.Get() && m_Mode.Active()
&& m_Controller && m_Controller == GetGame().GetPlayerController() && m_Controller.GetControlledEntity() == m_Actor
&& m_Mode.Players() && m_Mode.Players().CurrentActor(m_Actor) && ERR_Actors.CanFight(m_Actor);
}
protected bool OwnsMenu()
{
return m_Menu && GetGame().GetMenuManager().FindMenuByPreset(ChimeraMenuPreset.Inventory20Menu) == m_Menu;
}
protected bool UnchangedHealth() { return m_Blood && m_Blood.GetHealth() == m_Health; }
int Tick(float now)
{
if (m_Result != 0) return m_Result;
if (m_Stopped) return -1;
if (!Context()) { Check(false, "original conscious local round body changed"); return -1; }
if (System.GetTickCount() >= m_Deadline) { Check(false, "fixed 30-second UI/callback deadline"); return -1; }
if (m_Stage == 0)
{
if (!Check(!GetGame().GetMenuManager().GetTopMenu(), "no existing menu adopted")) return -1;
m_Inventory = SCR_InventoryStorageManagerComponent.Cast(m_Actor.FindComponent(SCR_InventoryStorageManagerComponent));
SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(m_Actor.FindComponent(SCR_CharacterDamageManagerComponent));
if (damage) m_Blood = damage.GetBloodHitZone();
if (!Check(m_Inventory && m_Blood, "original native inventory and blood event source present")) return -1;
m_Health = m_Blood.GetHealth();
m_Inventory.OpenInventory();
m_Menu = SCR_InventoryMenuUI.Cast(GetGame().GetMenuManager().FindMenuByPreset(ChimeraMenuPreset.Inventory20Menu));
if (!Check(OwnsMenu(), "ordinary native inventory opens without input automation")) return -1;
m_Stage = 1; return 0;
}
if (m_Stage == 1)
{
if (!Check(OwnsMenu() && UnchangedHealth(), "owned inventory and original blood health retained")) return -1;
if (!m_Menu.ERR_HitZoneProbeReady(m_Actor)) return 0;
m_Menu.InspectItem(null); // Native character inspection creates its normal seven containers over several frames.
m_Stage = 2; return 0;
}
if (m_Stage == 2)
{
if (!Check(OwnsMenu() && UnchangedHealth(), "native inspection retains original menu and health")) return -1;
if (!m_Menu.ERR_HitZoneProbePoints(m_Points)) return 0;
array<SCR_InventoryHitZonePointContainerUI> seen = {};
foreach (SCR_InventoryHitZonePointContainerUI point : m_Points)
{
if (!Check(point && !seen.Contains(point) && point.ERR_HitZoneProbeWatch(m_Mode, m_Blood), "unique native point retains original blood subscription")) return -1;
seen.Insert(point);
}
if (!Check(UnchangedHealth() && m_Blood.GetOnDamageStateChanged(false), "original blood event and health retained before notification")) return -1;
m_Blood.GetOnDamageStateChanged(false).Invoke(m_Blood);
foreach (SCR_InventoryHitZonePointContainerUI updated : m_Points)
if (!Check(updated.ERR_HitZoneProbeOnce(), "one-argument event reached native updater once with explicit true")) return -1;
if (!Check(UnchangedHealth() && OwnsMenu(), "notification changes no blood health or menu ownership")) return -1;
bool closeAccepted = GetGame().GetMenuManager().CloseMenu(m_Menu);
m_CloseRequested = closeAccepted;
if (!Check(closeAccepted, "exact owned inventory close request accepted")) return -1;
m_Stage = 3; return 0;
}
if (m_Stage == 3)
{
MenuBase current = GetGame().GetMenuManager().FindMenuByPreset(ChimeraMenuPreset.Inventory20Menu);
if (current == m_Menu && m_Menu) return 0;
if (!Check(!current, "native inventory closes without adopting a replacement")) return -1;
foreach (SCR_InventoryHitZonePointContainerUI detached : m_Points)
if (!Check(detached && detached.ERR_HitZoneProbeDetached(), "native detach cleared original point subscriptions")) return -1;
if (!Check(UnchangedHealth() && m_Blood.GetOnDamageStateChanged(false), "original blood event and health retained before notification")) return -1;
m_Blood.GetOnDamageStateChanged(false).Invoke(m_Blood);
foreach (SCR_InventoryHitZonePointContainerUI oldPoint : m_Points)
if (!Check(oldPoint.ERR_HitZoneProbeOnce(), "closed original point receives no late native update")) return -1;
if (!Check(UnchangedHealth(), "blood health unchanged through complete callback proof")) return -1;
m_Menu = null; m_Result = 1; Stop(); return 1;
}
Check(false, "unexpected fixture stage"); return -1;
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
foreach (SCR_InventoryHitZonePointContainerUI point : m_Points) if (point) point.ERR_HitZoneProbeStop();
m_Points.Clear();
if (GetGame() && GetGame().GetMenuManager() && !m_CloseRequested && OwnsMenu())
{
m_CloseRequested = true;
GetGame().GetMenuManager().CloseMenu(m_Menu);
}
m_Menu = null; m_Blood = null; m_Inventory = null;
}
}
#endif
@@ -0,0 +1,369 @@
// Detached mounted counts only. Carried and cargo ammunition never enter these totals.
class ERR_HQMountedAmmoSnapshot
{
bool Complete;
int Weapons;
int Muzzles;
int Magazines;
int Rounds;
int Capacity;
int Chambers;
int LowMagazines;
ERR_HQMountedAmmoSnapshot Snapshot()
{
ERR_HQMountedAmmoSnapshot copy = new ERR_HQMountedAmmoSnapshot();
copy.Complete = Complete; copy.Weapons = Weapons; copy.Muzzles = Muzzles;
copy.Magazines = Magazines; copy.Rounds = Rounds; copy.Capacity = Capacity;
copy.Chambers = Chambers; copy.LowMagazines = LowMagazines;
return copy;
}
bool Valid()
{
if (Weapons < 0 || Weapons > 32 || Muzzles < 0 || Muzzles > 128 || Muzzles > Weapons * 8) return false;
if (Magazines < 0 || Magazines > Muzzles || Magazines > 128) return false;
if (Capacity < Magazines || Capacity > Magazines * 100000 || Rounds < 0 || Rounds > Capacity) return false;
if (Chambers < 0 || Chambers > Muzzles * 32 || LowMagazines < 0 || LowMagazines > Magazines) return false;
return !Complete || (Weapons == 0 || (Muzzles > 0 && Magazines > 0));
}
}
#ifdef WORKBENCH
// Ephemeral opt-in fixture diagnostics; never part of radio reports or policy.
class ERR_HQMountedAmmoDiagnostic
{
int Reasons;
int Nodes;
int Structural;
int Cargo;
int Allowed;
int Managers;
string Detail;
string FirstPrefab;
int FirstPurpose = -1;
// Called before Reject, once per reason in this explicit diagnostic sample.
// These labels describe sampled relationships; they never authorize a weapon.
void SlotEvidence(int reason, IEntity entity, WeaponSlotComponent mount, InventoryStorageSlot binding, InventoryItemComponent item, array<IEntity> allowed)
{
if ((Reasons & reason) != 0) return;
if (reason == 64) Detail += "mount-slot-evidence:";
else Detail += "zero-muzzle-slot-evidence:";
if (item) Detail += "item=present;";
else Detail += "item=absent;";
InventoryStorageSlot parent = null;
if (item) parent = item.GetParentSlot();
if (!parent) Detail += "parent=null;";
else if (parent == binding) Detail += "parent=same-binding;";
else Detail += "parent=different-binding;";
if (!binding) Detail += "binding=null;";
else
{
if (binding.GetAttachedEntity() == entity) Detail += "binding-attached=exact;";
else Detail += "binding-attached=other-or-null;";
if (binding.GetParentContainer() == mount) Detail += "binding-container=exact-mount;";
else Detail += "binding-container=other-or-null;";
}
IEntity owner = null;
if (mount) owner = mount.GetOwner();
if (owner && allowed.Contains(owner)) Detail += "mount-owner=allowed;";
else Detail += "mount-owner=absent-or-foreign;";
if (owner && owner.GetPrefabData()) Detail += "mount-prefab=" + owner.GetPrefabData().GetPrefabName() + ";";
if (!parent) return;
if (parent.GetAttachedEntity() == entity) Detail += "parent-attached=exact;";
else Detail += "parent-attached=other-or-null;";
WeaponSlotComponent parentMount = WeaponSlotComponent.Cast(parent.GetParentContainer());
if (parent.GetParentContainer() == mount && mount) Detail += "parent-container=exact-mount;";
else if (parentMount) Detail += "parent-container=other-mount;";
else if (!parent.GetParentContainer()) Detail += "parent-container=null;";
else Detail += "parent-container=non-mount;";
if (!parentMount) return;
IEntity parentOwner = parentMount.GetOwner();
if (parentOwner && allowed.Contains(parentOwner)) Detail += "parent-mount-owner=allowed;";
else Detail += "parent-mount-owner=absent-or-foreign;";
if (parentOwner && parentOwner.GetPrefabData()) Detail += "parent-mount-prefab=" + parentOwner.GetPrefabData().GetPrefabName() + ";";
if (parentMount.GetSlotInfo() == parent) Detail += "parent-mount-slot=exact;";
else Detail += "parent-mount-slot=other-or-null;";
if (parentMount.GetWeaponEntity() == entity) Detail += "parent-mount-weapon=exact;";
else Detail += "parent-mount-weapon=other-or-null;";
}
void Reject(int reason, string detail, IEntity entity, int purpose = -1)
{
if ((Reasons & reason) == 0) Detail += detail + ";";
if (Reasons == 0)
{
FirstPurpose = purpose;
if (entity && entity.GetPrefabData()) FirstPrefab = entity.GetPrefabData().GetPrefabName();
}
Reasons = Reasons | reason;
}
}
#endif
class ERR_HQMountedAmmoSampler
{
#ifdef WORKBENCH
protected static ref ERR_HQMountedAmmoDiagnostic s_Diagnostic;
static void BeginDiagnostic() { s_Diagnostic = new ERR_HQMountedAmmoDiagnostic(); }
static ERR_HQMountedAmmoDiagnostic EndDiagnostic()
{
ref ERR_HQMountedAmmoDiagnostic result = s_Diagnostic; s_Diagnostic = null; return result;
}
#endif
// Native mounted weapons and loaded magazines use InventoryStorageSlot too,
// but their container is a weapon slot/muzzle rather than deposit storage.
static bool Installed(IEntity entity, InventoryStorageSlot slot, array<IEntity> allowed)
{
if (!entity || !slot) return false;
WeaponSlotComponent mount = WeaponSlotComponent.Cast(slot.GetParentContainer());
if (mount && allowed.Contains(mount.GetOwner()) && mount.GetSlotInfo() == slot && mount.GetWeaponEntity() == entity) return true;
BaseMuzzleComponent muzzle = BaseMuzzleComponent.Cast(slot.GetParentContainer());
if (!muzzle || !allowed.Contains(muzzle.GetOwner())) return false;
BaseMagazineComponent magazine = muzzle.GetMagazine();
return magazine && magazine.GetOwner() == entity;
}
protected static void AddWeapon(BaseWeaponComponent weapon, array<IEntity> allowed, array<BaseWeaponComponent> weapons, ERR_HQMountedAmmoSnapshot result)
{
if (!weapon || !weapon.GetOwner() || !allowed.Contains(weapon.GetOwner()))
{
#ifdef WORKBENCH
IEntity owner = null;
if (weapon) owner = weapon.GetOwner();
if (s_Diagnostic) s_Diagnostic.Reject(16, "weapon-owner-not-allowed", owner);
#endif
result.Complete = false; return;
}
// Native managers can expose both a slot and its physical weapon. Normalize
// one exact current installation before deduplication, never another slot.
WeaponSlotComponent mount = WeaponSlotComponent.Cast(weapon);
if (mount)
{
IEntity mountedEntity = mount.GetWeaponEntity();
InventoryStorageSlot binding = mount.GetSlotInfo();
if (!mountedEntity || !allowed.Contains(mountedEntity) || !binding || binding.GetParentContainer() != mount)
{
#ifdef WORKBENCH
if (s_Diagnostic) s_Diagnostic.Reject(32, "mount-binding-invalid", mountedEntity);
#endif
result.Complete = false; return;
}
InventoryItemComponent mountedItem = InventoryItemComponent.Cast(mountedEntity.FindComponent(InventoryItemComponent));
if (!mountedItem || mountedItem.GetParentSlot() != binding)
{
#ifdef WORKBENCH
if (s_Diagnostic)
{
s_Diagnostic.SlotEvidence(64, mountedEntity, mount, binding, mountedItem, allowed);
s_Diagnostic.Reject(64, "mount-item-slot-mismatch", mountedEntity);
}
#endif
result.Complete = false; return;
}
weapon = BaseWeaponComponent.Cast(mountedEntity.FindComponent(BaseWeaponComponent));
if (!weapon || WeaponSlotComponent.Cast(weapon) || weapon.GetOwner() != mountedEntity)
{
#ifdef WORKBENCH
if (s_Diagnostic) s_Diagnostic.Reject(128, "mount-weapon-invalid", mountedEntity);
#endif
result.Complete = false; return;
}
}
if (weapons.Contains(weapon)) return;
if (weapons.Count() >= 32)
{
#ifdef WORKBENCH
if (s_Diagnostic) s_Diagnostic.Reject(256, "weapon-limit32", weapon.GetOwner());
#endif
result.Complete = false; return;
}
weapons.Insert(weapon);
}
protected static void Collect(IEntity root, ERR_HQMountedAmmoSnapshot result, array<IEntity> allowed, array<BaseWeaponComponent> weapons)
{
if (!root || ChimeraCharacter.Cast(root))
{
#ifdef WORKBENCH
if (s_Diagnostic) s_Diagnostic.Reject(1, "invalid-root", root);
#endif
result.Complete = false; return;
}
result.Complete = true;
array<IEntity> nodes = {root};
// Known deposit cargo must not consume the mounted hierarchy budget. Both
// categories retain independent caps; the unique queue is capped at their sum.
// Skipped crew/unknown nodes remain structural and cannot borrow cargo space.
int structuralNodes = 0;
int cargoNodes = 0;
for (int cursor = 0; cursor < nodes.Count(); cursor++)
{
IEntity entity = nodes[cursor];
bool character = ChimeraCharacter.Cast(entity) != null;
bool excludedCargo;
bool unknownStorage;
// Exact installed slots are checked before deposit exclusion. Never walk
// the children of known cargo or seated character roots.
if (!character)
{
InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
if (item && item.GetParentSlot() && !Installed(entity, item.GetParentSlot(), allowed))
{
BaseInventoryStorageComponent storage = item.GetParentSlot().GetStorage();
if (!storage || storage.GetPurpose() == EStoragePurpose.PURPOSE_UNSET || storage.GetPurpose() == EStoragePurpose.PURPOSE_ANY)
{
#ifdef WORKBENCH
int purpose = -1;
if (storage) purpose = storage.GetPurpose();
if (s_Diagnostic) s_Diagnostic.Reject(2, "ambiguous-storage", entity, purpose);
#endif
result.Complete = false; unknownStorage = true;
}
else excludedCargo = (storage.GetPurpose() & EStoragePurpose.PURPOSE_DEPOSIT) != 0;
}
}
if (excludedCargo)
{
cargoNodes++;
if (cargoNodes > 128)
{
#ifdef WORKBENCH
if (s_Diagnostic) s_Diagnostic.Reject(2048, "cargo-limit128", entity);
#endif
result.Complete = false; break;
}
continue;
}
structuralNodes++;
if (structuralNodes > 128)
{
#ifdef WORKBENCH
if (s_Diagnostic) s_Diagnostic.Reject(1024, "structural-limit128", entity);
#endif
result.Complete = false; break;
}
if (character || unknownStorage) continue;
allowed.Insert(entity);
IEntity child = entity.GetChildren();
while (child)
{
bool repeated = nodes.Contains(child);
if (repeated || nodes.Count() >= 256)
{
#ifdef WORKBENCH
if (s_Diagnostic)
{
if (repeated) s_Diagnostic.Reject(4, "repeated-node", child);
else s_Diagnostic.Reject(8, "queue-limit256", child);
}
#endif
result.Complete = false; break;
}
nodes.Insert(child);
child = child.GetSibling();
}
}
#ifdef WORKBENCH
if (s_Diagnostic)
{
s_Diagnostic.Nodes = nodes.Count(); s_Diagnostic.Allowed = allowed.Count();
s_Diagnostic.Structural = structuralNodes; s_Diagnostic.Cargo = cargoNodes;
}
#endif
foreach (IEntity part : allowed)
{
BaseWeaponManagerComponent manager = BaseWeaponManagerComponent.Cast(part.FindComponent(BaseWeaponManagerComponent));
if (manager)
{
#ifdef WORKBENCH
if (s_Diagnostic) s_Diagnostic.Managers++;
#endif
array<BaseWeaponComponent> listed = {}; manager.GetWeapons(listed);
if (listed.Count() > 8)
{
#ifdef WORKBENCH
if (s_Diagnostic) s_Diagnostic.Reject(512, "manager-limit8", part);
#endif
result.Complete = false;
}
else foreach (BaseWeaponComponent entry : listed) AddWeapon(entry, allowed, weapons, result);
}
BaseWeaponComponent mounted = BaseWeaponComponent.Cast(part.FindComponent(BaseWeaponComponent));
if (mounted) AddWeapon(mounted, allowed, weapons, result);
}
}
// These installed resources are inert selection/periscope controls, not guns.
// Only omit them after the caller observed an empty native muzzle list and
// reciprocal current weapon-slot ownership in this same sampled hierarchy.
protected static bool NonFiringPlaceholder(BaseWeaponComponent weapon, array<IEntity> allowed)
{
if (!weapon || !weapon.GetOwner()) return false;
IEntity owner = weapon.GetOwner();
if (!allowed.Contains(owner) || !owner.GetPrefabData()) return false;
ResourceName resource = owner.GetPrefabData().GetPrefabName();
if (resource != "{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et"
&& resource != "{7E1DD1CE4D6A23F4}Prefabs/Weapons/Core/VehiclePeriscope_DummyWeapon_Base.et") return false;
InventoryItemComponent item = InventoryItemComponent.Cast(owner.FindComponent(InventoryItemComponent));
if (!item || !item.GetParentSlot()) return false;
InventoryStorageSlot slot = item.GetParentSlot();
WeaponSlotComponent mount = WeaponSlotComponent.Cast(slot.GetParentContainer());
return mount && allowed.Contains(mount.GetOwner()) && mount.GetSlotInfo() == slot && mount.GetWeaponEntity() == owner;
}
static ERR_HQMountedAmmoSnapshot Inspect(IEntity root)
{
ERR_HQMountedAmmoSnapshot result = new ERR_HQMountedAmmoSnapshot();
array<IEntity> allowed = {};
array<BaseWeaponComponent> weapons = {};
Collect(root, result, allowed, weapons);
result.Weapons = weapons.Count();
array<BaseMuzzleComponent> seenMuzzles = {};
array<BaseMagazineComponent> seenMagazines = {};
array<IEntity> magazineOwners = {};
foreach (BaseWeaponComponent weapon : weapons)
{
array<BaseMuzzleComponent> muzzles = {}; weapon.GetMuzzlesList(muzzles);
if (muzzles.IsEmpty())
{
if (NonFiringPlaceholder(weapon, allowed)) result.Weapons--;
else
{
#ifdef WORKBENCH
if (s_Diagnostic)
{
IEntity owner = weapon.GetOwner();
InventoryItemComponent item = InventoryItemComponent.Cast(owner.FindComponent(InventoryItemComponent));
WeaponSlotComponent mount = null;
InventoryStorageSlot binding = null;
if (item && item.GetParentSlot()) mount = WeaponSlotComponent.Cast(item.GetParentSlot().GetParentContainer());
if (mount) binding = mount.GetSlotInfo();
s_Diagnostic.SlotEvidence(4096, owner, mount, binding, item, allowed);
s_Diagnostic.Reject(4096, "zero-muzzle-not-proven-placeholder", owner);
}
#endif
result.Complete = false;
}
continue;
}
if (muzzles.Count() > 8) { result.Complete = false; continue; }
foreach (BaseMuzzleComponent muzzle : muzzles)
{
if (!muzzle || !muzzle.GetOwner() || !allowed.Contains(muzzle.GetOwner())) { result.Complete = false; continue; }
if (seenMuzzles.Contains(muzzle)) continue;
if (seenMuzzles.Count() >= 128) { result.Complete = false; continue; }
seenMuzzles.Insert(muzzle); result.Muzzles++;
int barrels = muzzle.GetBarrelsCount();
if (barrels < 1 || barrels > 32) result.Complete = false;
else for (int barrel = 0; barrel < barrels; barrel++)
if (muzzle.IsBarrelChambered(barrel)) result.Chambers++;
BaseMagazineComponent magazine = muzzle.GetMagazine();
// Projectile-only weapons and absent magazines are unknown, never empty.
if (!magazine || !magazine.GetOwner() || !allowed.Contains(magazine.GetOwner())) { result.Complete = false; continue; }
if (seenMagazines.Contains(magazine)) continue;
if (magazineOwners.Contains(magazine.GetOwner())) { result.Complete = false; continue; }
seenMagazines.Insert(magazine); magazineOwners.Insert(magazine.GetOwner());
int rounds = magazine.GetAmmoCount();
int capacity = magazine.GetMaxAmmoCount();
if (rounds < 0 || capacity < 1 || capacity > 100000 || rounds > capacity) { result.Complete = false; continue; }
result.Magazines++; result.Rounds += rounds; result.Capacity += capacity;
if (rounds <= capacity * 0.25) result.LowMagazines++;
}
}
return result;
}
}
@@ -0,0 +1,301 @@
"""Execute actual mounted sampler/snapshot methods against bounded native doubles."""
import re,os
from pathlib import Path
from policy_harness import ROOT, run
p=Path(os.environ.get('ERR_MOUNTED_AMMO_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedAmmoSampler.c'))
s=p.read_text()
for forbidden in ('SetAmmoCount','GetOrigin','GetWorld','Spawn','TryMove','DeleteEntity','GetAgents'):
assert forbidden not in s, forbidden
code=re.sub(r'//[^\n]*','',s).replace('protected ','').replace('class ','struct ')
code=code.replace('BaseWeaponManagerComponent.Cast(part.FindComponent(BaseWeaponManagerComponent))','part->manager')
code=code.replace('BaseWeaponComponent.Cast(part.FindComponent(BaseWeaponComponent))','part->weapon')
code=code.replace('BaseWeaponComponent.Cast(mountedEntity.FindComponent(BaseWeaponComponent))','mountedEntity->weapon')
code=code.replace('InventoryItemComponent.Cast(mountedEntity.FindComponent(InventoryItemComponent))','mountedEntity->item')
code=code.replace('WeaponSlotComponent.Cast(weapon)','dynamic_cast<Mount*>(weapon)').replace('binding.GetParentContainer()','binding->mount')
code=code.replace('InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent))','entity->item')
code=code.replace('InventoryItemComponent.Cast(owner.FindComponent(InventoryItemComponent))','owner->item').replace('ResourceName resource', 'string resource')
code=code.replace('WeaponSlotComponent.Cast(slot.GetParentContainer())','slot->mount').replace('BaseMuzzleComponent.Cast(slot.GetParentContainer())','slot->muzzle')
code=code.replace('WeaponSlotComponent.Cast(parent.GetParentContainer())','parent->mount').replace('WeaponSlotComponent.Cast(item.GetParentSlot().GetParentContainer())','item->GetParentSlot()->mount')
code=code.replace('parent.GetParentContainer()', 'parent->Container()')
code=code.replace('EStoragePurpose.','EStoragePurpose::').replace('GetParentSlot().','GetParentSlot()->')
code=code.replace('ChimeraCharacter.Cast(entity) != nullptr','entity->character').replace('ChimeraCharacter.Cast(entity) != null','entity->character')
code=code.replace('ChimeraCharacter.Cast(root)','root->character').replace('ChimeraCharacter.Cast(entity)','entity->character')
code=code.replace('protected static ref ', 'inline static ').replace('static ref ', 'inline static ').replace('ref ', '')
code=re.sub(r'\bERR_HQMountedAmmoDiagnostic (?=\w)', 'ERR_HQMountedAmmoDiagnostic* ',code)
code=code.replace('s_Diagnostic.', 's_Diagnostic->').replace('GetPrefabData().', 'GetPrefabData()->')
code=re.sub(r'\bERR_HQMountedAmmoSnapshot (?=\w)','ERR_HQMountedAmmoSnapshot* ',code)
code=code.replace('array<','Array<').replace('foreach (','for (')
code=re.sub(r'"[^"]*"|\bnull\b',lambda m:m.group() if m.group().startswith(chr(34)) else 'nullptr',code)
code=code.replace('Array<IEntity> allowed, Array<BaseWeaponComponent> weapons,','Array<IEntity>& allowed, Array<BaseWeaponComponent>& weapons,')
code=code.replace('Array<IEntity> allowed, Array<BaseWeaponComponent> weapons)', 'Array<IEntity>& allowed, Array<BaseWeaponComponent>& weapons)')
for name in ('copy','result','weapon','root','entity','child','part','manager','mounted','muzzle','magazine','item','storage','mount','mountedEntity','mountedItem','owner','parent','binding','parentMount','parentOwner'):
code=re.sub(r'\b'+name+r'\.',name+'->',code)
code=re.sub(r'\b(bool|int) (\w+);',r'\1 \2=0;',code).replace('\n}\n','\n};\n')
program=(r'''
#include <cassert>
#include <algorithm>
#include <vector>
#include <string>
using string=std::string;
struct Prefab{string name="fixture";string GetPrefabName(){return name;}};
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;
int Count(){return this->size();}bool IsEmpty(){return this->empty();}
void Insert(T n){this->push_back(n);}bool Contains(T n){return std::find(this->begin(),this->end(),n)!=this->end();}};
struct Entity;struct Weapon;struct Muzzle;struct Magazine;struct Manager;struct Mount;
struct EStoragePurpose{static const int PURPOSE_UNSET=0,PURPOSE_DEPOSIT=1,PURPOSE_WEAPON_PROXY=2,PURPOSE_ATTACHMENT_PROXY=4,PURPOSE_ANY=127;};
struct Storage{int purpose=EStoragePurpose::PURPOSE_DEPOSIT;int GetPurpose(){return purpose;}};
struct Slot{Mount* mount=nullptr;Muzzle* muzzle=nullptr;Storage* storage=nullptr;Entity* attached=nullptr;Entity* GetAttachedEntity(){return attached;}void* Container(){return mount?static_cast<void*>(mount):muzzle?static_cast<void*>(muzzle):static_cast<void*>(storage);}Storage* GetStorage(){return storage;}};
struct Item{Slot* slot=nullptr;Slot* GetParentSlot(){return slot;}};
using InventoryStorageSlot=Slot*;using WeaponSlotComponent=Mount*;
using InventoryItemComponent=Item*;using BaseInventoryStorageComponent=Storage*;
using IEntity=Entity*;using BaseWeaponComponent=Weapon*;using BaseMuzzleComponent=Muzzle*;
using BaseMagazineComponent=Magazine*;using BaseWeaponManagerComponent=Manager*;
struct Entity{Prefab prefab;Prefab*GetPrefabData(){return &prefab;}Item* item=nullptr;bool character=false;Entity* child=nullptr;Entity* sibling=nullptr;Manager* manager=nullptr;Weapon* weapon=nullptr;
int reads=0;Entity* GetChildren(){reads++;assert(reads<=1);return child;}Entity* GetSibling(){return sibling;}};
struct Magazine{IEntity owner=nullptr;int rounds=25,capacity=100,reads=0;
IEntity GetOwner(){return owner;}int GetAmmoCount(){reads++;return rounds;}int GetMaxAmmoCount(){return capacity;}};
struct Muzzle{IEntity owner=nullptr;Magazine* mag=nullptr;int barrels=1,chambered=1,reads=0;
IEntity GetOwner(){return owner;}int GetBarrelsCount(){return barrels;}
bool IsBarrelChambered(int b){reads++;return b<chambered;}Magazine* GetMagazine(){return mag;}};
struct Weapon{virtual ~Weapon(){}IEntity owner=nullptr;Array<Muzzle*> muzzles;int reads=0;
IEntity GetOwner(){return owner;}void GetMuzzlesList(Array<Muzzle*>& out){reads++;out=muzzles;}};
struct Mount:Weapon{IEntity weapon=nullptr;Slot* slot=nullptr;
Slot* GetSlotInfo(){return slot;}IEntity GetWeaponEntity(){return weapon;}};
struct Manager{Array<Weapon*> weapons;int reads=0;void GetWeapons(Array<Weapon*>& out){reads++;out=weapons;}};
'''+code+r'''
struct Rig{Entity root,gun,magazine,crew,rifle;Manager manager,crewManager;Weapon weapon,carried;
Muzzle muzzle,carriedMuzzle;Magazine mag,carriedMag;
Rig(){root.child=&gun;gun.sibling=&magazine;magazine.sibling=&crew;crew.character=true;crew.child=&rifle;
root.manager=&manager;manager.weapons={&weapon,&weapon};gun.weapon=&weapon;weapon.owner=&gun;weapon.muzzles={&muzzle,&muzzle};
muzzle.owner=&gun;muzzle.mag=&mag;mag.owner=&magazine;
crew.manager=&crewManager;crewManager.weapons={&carried};rifle.weapon=&carried;carried.owner=&rifle;
carried.muzzles={&carriedMuzzle};carriedMuzzle.owner=&rifle;carriedMuzzle.mag=&carriedMag;carriedMag.owner=&rifle;}
ERR_HQMountedAmmoSnapshot* Inspect(){return ERR_HQMountedAmmoSampler::Inspect(&root);}
};
int main(){
// Installed BMP has three live gun components plus two inert native controls.
// Exercise actual Inspect using exact documented resources and reciprocal slots.
for(string name:{string("{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et"),string("{7E1DD1CE4D6A23F4}Prefabs/Weapons/Core/VehiclePeriscope_DummyWeapon_Base.et")}){
Rig r;Entity dummy;Weapon inert;Mount mount;Item item;Slot slot;dummy.prefab.name=name;
r.crew.sibling=&dummy;dummy.weapon=&inert;inert.owner=&dummy;dummy.item=&item;item.slot=&slot;slot.mount=&mount;mount.owner=&r.root;mount.slot=&slot;mount.weapon=&dummy;
r.manager.weapons.Insert(&mount);r.manager.weapons.Insert(&inert);
auto s=r.Inspect();assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Muzzles==1&&s->Magazines==1&&s->Rounds==25&&s->Capacity==100&&s->Chambers==1&&inert.reads==1);
}
// A root containing only a known installed inert control is completely unarmed.
{Entity root,dummy;Weapon inert;Mount mount;Item item;Slot slot;root.child=&dummy;dummy.prefab.name="{7E1DD1CE4D6A23F4}Prefabs/Weapons/Core/VehiclePeriscope_DummyWeapon_Base.et";dummy.weapon=&inert;inert.owner=&dummy;dummy.item=&item;item.slot=&slot;slot.mount=&mount;mount.owner=&root;mount.slot=&slot;mount.weapon=&dummy;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(s->Complete&&s->Valid()&&s->Weapons==0&&s->Muzzles==0&&s->Magazines==0&&s->Rounds==0&&s->Capacity==0&&s->Chambers==0&&inert.reads==1);}
// Both BMP controls coexist: decrement each exactly once while retaining the gun.
{Rig r;Entity dummy[2];Weapon inert[2];Mount mount[2];Item item[2];Slot slot[2];r.crew.sibling=&dummy[0];dummy[0].sibling=&dummy[1];
dummy[0].prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";dummy[1].prefab.name="{7E1DD1CE4D6A23F4}Prefabs/Weapons/Core/VehiclePeriscope_DummyWeapon_Base.et";
for(int i=0;i<2;i++){dummy[i].weapon=&inert[i];inert[i].owner=&dummy[i];dummy[i].item=&item[i];item[i].slot=&slot[i];slot[i].mount=&mount[i];mount[i].owner=&r.root;mount[i].slot=&slot[i];mount[i].weapon=&dummy[i];r.manager.weapons.Insert(&mount[i]);r.manager.weapons.Insert(&inert[i]);}
auto s=r.Inspect();assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Muzzles==1&&s->Magazines==1&&s->Rounds==25&&s->Capacity==100&&s->Chambers==1&&inert[0].reads==1&&inert[1].reads==1);}
// Unknown no-muzzle weapons and broken/foreign installation remain incomplete.
for(int bad=0;bad<6;bad++){
Rig r;Entity dummy,foreign;Weapon inert;Mount mount;Item item;Slot slot,other;dummy.prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";
r.crew.sibling=&dummy;dummy.weapon=&inert;inert.owner=&dummy;dummy.item=&item;item.slot=&slot;slot.mount=&mount;mount.owner=&r.root;mount.slot=&slot;mount.weapon=&dummy;
if(bad==0)dummy.prefab.name="{0000000000000000}Prefabs/Weapons/Core/Armament_Empty.et";
if(bad==1)dummy.item=nullptr;
if(bad==2)item.slot=nullptr;
if(bad==3)mount.owner=&foreign;
if(bad==4)mount.slot=&other;
if(bad==5)mount.weapon=&foreign;
auto s=r.Inspect();assert(!s->Complete&&s->Valid());
}
// A known resource carrying an actual muzzle is never silently omitted.
{Rig r;r.gun.prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";auto s=r.Inspect();assert(s->Complete&&s->Weapons==1&&s->Rounds==25&&r.muzzle.reads==1);}
// An excluded cargo subtree cannot turn a stored placeholder into mounted armament.
{Rig r;Entity cargo;Weapon inert;Item item;Slot slot;Storage storage;r.crew.sibling=&cargo;cargo.prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";cargo.item=&item;item.slot=&slot;slot.storage=&storage;cargo.weapon=&inert;inert.owner=&cargo;auto s=r.Inspect();assert(s->Complete&&s->Weapons==1&&inert.reads==0);}
{auto s=ERR_HQMountedAmmoSampler::Inspect(nullptr);assert(!s->Complete&&s->Valid());}
{Entity root;root.character=true;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&root.reads==0);}
{Entity root;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(s->Complete&&s->Valid()&&s->Weapons==0);}
{Rig r;auto s=r.Inspect();assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Muzzles==1&&s->Magazines==1);
assert(s->Rounds==25&&s->Capacity==100&&s->Chambers==1&&s->LowMagazines==1);
assert(r.weapon.reads==1&&r.muzzle.reads==1&&r.mag.reads==1&&r.crew.reads==0&&r.crewManager.reads==0&&r.carried.reads==0);
auto copy=s->Snapshot();s->Complete=false;s->Rounds=0;s->Capacity=0;s->LowMagazines=0;
assert(copy->Complete&&copy->Rounds==25&&copy->Capacity==100&&copy->LowMagazines==1&&copy->Weapons==1&&copy->Muzzles==1&&copy->Magazines==1&&copy->Chambers==1);}
for(int rounds:{0,24,25,26,100}){Rig r;r.mag.rounds=rounds;auto s=r.Inspect();assert(s->Complete&&s->Valid());assert(s->LowMagazines==(rounds<=25));}
{Rig r;Muzzle second;second.owner=&r.gun;second.mag=&r.mag;second.chambered=0;r.weapon.muzzles.Insert(&second);
auto s=r.Inspect();assert(s->Complete&&s->Muzzles==2&&s->Magazines==1&&s->Chambers==1&&s->Rounds==25&&r.mag.reads==1);}
// Manager references into skipped characters, foreign roots, or nulls never count.
for(int kind=0;kind<3;kind++){Rig r;Entity foreign;Weapon w;w.owner=&foreign;
r.manager.weapons.Insert(kind==0?&r.carried:kind==1?&w:nullptr);auto s=r.Inspect();assert(!s->Complete&&s->Valid()&&s->Weapons==1);}
for(int kind=0;kind<3;kind++){Rig r;Entity foreign;
if(kind==0)r.muzzle.owner=&foreign;
if(kind==1)r.mag.owner=&foreign;
if(kind==2)r.mag.owner=nullptr;
auto s=r.Inspect();assert(!s->Complete&&s->Valid()&&s->Rounds==0&&s->Magazines==0);}
// Deposited rifles and nested magazines are excluded; installed magazine slots remain admitted.
{Rig r;Entity cargo,rifle,magEntity;Item item;Slot slot;Storage storage;Weapon rifleWeapon;
r.crew.sibling=&cargo;cargo.child=&rifle;rifle.child=&magEntity;rifle.item=&item;item.slot=&slot;slot.storage=&storage;
rifle.weapon=&rifleWeapon;rifleWeapon.owner=&rifle;auto s=r.Inspect();assert(s->Complete&&s->Weapons==1&&rifle.reads==0&&magEntity.reads==0);}
{Rig r;Item item;Slot slot;Storage storage;r.magazine.item=&item;item.slot=&slot;slot.storage=&storage;
storage.purpose=EStoragePurpose::PURPOSE_ATTACHMENT_PROXY;auto s=r.Inspect();assert(s->Complete&&s->Rounds==25&&s->Magazines==1);}
for(int purpose:{0,127}){Rig r;Item item;Slot slot;Storage storage;r.gun.item=&item;item.slot=&slot;slot.storage=&storage;
storage.purpose=purpose;auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Valid());}
// Native purposes support combined flags; any deposit bit excludes the subtree.
{Rig r;Item item;Slot slot;Storage storage;r.gun.item=&item;item.slot=&slot;slot.storage=&storage;
storage.purpose=EStoragePurpose::PURPOSE_DEPOSIT|EStoragePurpose::PURPOSE_ATTACHMENT_PROXY;
auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Valid());}
// Manager references cannot rescue a cargo weapon.
{Rig r;Item item;Slot slot;Storage storage;r.gun.item=&item;item.slot=&slot;slot.storage=&storage;
auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Valid());}
// Exact native weapon-slot and muzzle containers are not deposit storage.
for(int purpose:{-1,0,127}){Rig r;Item gunItem,magItem;Slot gunSlot,magSlot;Storage storage;Mount mount;
mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&gunSlot;gunSlot.mount=&mount;r.gun.item=&gunItem;gunItem.slot=&gunSlot;
magSlot.muzzle=&r.muzzle;r.magazine.item=&magItem;magItem.slot=&magSlot;
if(purpose>=0){storage.purpose=purpose;gunSlot.storage=&storage;magSlot.storage=&storage;}
auto s=r.Inspect();assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Magazines==1&&s->Rounds==25&&s->Chambers==1);}
// No generic missing-storage bypass: foreign container owners, stale slots and
// substituted attached items fail closed, including foreign loaded magazines.
for(int fault=0;fault<6;fault++){Rig r;Entity foreign;Item item;Slot slot,other;Mount mount;
r.gun.item=&item;item.slot=&slot;slot.mount=&mount;mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&slot;
if(fault==0)mount.owner=&foreign;
if(fault==1)mount.owner=&r.crew;
if(fault==2)mount.weapon=&foreign;
if(fault==3)mount.slot=&other;
if(fault==4)slot.mount=nullptr;
if(fault==5)mount.owner=nullptr;
auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Rounds==0&&s->Valid());}
for(int fault=0;fault<3;fault++){Rig r;Entity foreign;Item item;Slot slot;
r.magazine.item=&item;item.slot=&slot;slot.muzzle=&r.muzzle;
if(fault==0)r.muzzle.owner=&foreign;
if(fault==1)r.muzzle.mag=nullptr;
if(fault==2)r.mag.owner=&foreign;
auto s=r.Inspect();assert(!s->Complete&&s->Magazines==0&&s->Rounds==0&&s->Valid());}
// An exact loaded magazine inside an excluded cargo rifle does not re-admit it.
{Rig r;Item item,magItem;Slot slot,magSlot;Storage deposit;r.gun.item=&item;item.slot=&slot;slot.storage=&deposit;
r.magazine.item=&magItem;magItem.slot=&magSlot;magSlot.muzzle=&r.muzzle;
auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Magazines==0&&s->Valid());}
// Native manager/FindComponent expose the wrapper and the physical weapon.
// A UAZ's single PKM must be one weapon even when all those paths overlap.
{Rig r;Mount mount;Item item;Slot binding;mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&binding;
binding.mount=&mount;item.slot=&binding;r.gun.item=&item;r.root.weapon=&mount;mount.muzzles=r.weapon.muzzles;
r.manager.weapons={&mount,&r.weapon,&mount};auto s=r.Inspect();
assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Muzzles==1&&s->Magazines==1&&s->Rounds==25&&s->Chambers==1);
assert(mount.reads==0&&r.weapon.reads==1&&r.mag.reads==1);}
// Unknown/foreign/empty/nested/stale slot endpoints never become real guns.
for(int fault=0;fault<11;fault++){Rig r;Entity foreign;Mount mount,other;Item item;Slot binding,stale;
mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&binding;binding.mount=&mount;item.slot=&binding;r.gun.item=&item;
r.root.weapon=&mount;r.manager.weapons={&mount};
if(fault==0)mount.weapon=nullptr;
if(fault==1)mount.weapon=&foreign;
if(fault==2)mount.weapon=&r.root; // nested/self wrapper
if(fault==3)binding.mount=&other;
if(fault==4)item.slot=&stale;
if(fault==5)mount.slot=nullptr;
if(fault==6)r.gun.item=nullptr;
if(fault==7)mount.owner=&foreign;
if(fault==8)r.gun.weapon=nullptr;
if(fault==9){other.owner=&r.gun;r.gun.weapon=&other;} // valid first binding, nested wrapper endpoint
if(fault==10)r.weapon.owner=&foreign; // component found on target belongs elsewhere
auto s=r.Inspect();assert(!s->Complete&&s->Valid()&&s->Weapons<=1&&mount.reads==0&&other.reads==0);}
// Thirty-two genuine installed guns still fit the weapon cap despite wrappers.
{Entity root;Array<Entity> hosts(32),guns(32),mags(32);Array<Weapon> weapons(32);Array<Mount> mounts(32);
Array<Muzzle> muzzles(32);Array<Magazine> magazines(32);Array<Item> items(32);Array<Slot> bindings(32);
root.child=&hosts[0];for(int i=0;i<32;i++){
if(i+1<32)hosts[i].sibling=&hosts[i+1];
hosts[i].child=&guns[i];guns[i].child=&mags[i];hosts[i].weapon=&mounts[i];guns[i].weapon=&weapons[i];
mounts[i].owner=&hosts[i];mounts[i].weapon=&guns[i];mounts[i].slot=&bindings[i];bindings[i].mount=&mounts[i];
items[i].slot=&bindings[i];guns[i].item=&items[i];weapons[i].owner=&guns[i];weapons[i].muzzles={&muzzles[i]};
mounts[i].muzzles=weapons[i].muzzles;muzzles[i].owner=&guns[i];muzzles[i].mag=&magazines[i];magazines[i].owner=&mags[i];}
auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(s->Complete&&s->Valid()&&s->Weapons==32&&s->Muzzles==32&&s->Magazines==32&&s->Rounds==800);}
// Missing/projectile-only magazines and unsupported native values stay incomplete.
for(int kind=0;kind<9;kind++){Rig r;
if(kind==0)r.muzzle.mag=nullptr;
if(kind==1)r.weapon.muzzles={nullptr};
if(kind==2)r.weapon.muzzles={};
if(kind==3)r.muzzle.barrels=0;
if(kind==4)r.muzzle.barrels=33;
if(kind==5)r.mag.rounds=-1;
if(kind==6)r.mag.capacity=0;
if(kind==7)r.mag.capacity=100001;
if(kind==8)r.mag.rounds=101;
auto s=r.Inspect();assert(!s->Complete&&s->Valid());}
{Rig r;r.weapon.muzzles=Array<Muzzle*>(9,&r.muzzle);auto s=r.Inspect();assert(!s->Complete&&s->Muzzles==0&&r.muzzle.reads==0);}
{Rig r;r.manager.weapons=Array<Weapon*>(9,&r.weapon);auto s=r.Inspect();assert(!s->Complete&&s->Weapons==1&&s->Valid());}
// Entity budget also bounds enqueue and cycles, rather than only processed work.
for(int count:{128,129,300}){std::vector<Entity> nodes(count);for(int i=0;i<count-1;i++)nodes[i].child=&nodes[i+1];
auto s=ERR_HQMountedAmmoSampler::Inspect(&nodes[0]);assert(s->Complete==(count==128));assert(s->Valid());
int reads=0;for(auto& e:nodes)reads+=e.reads;assert(reads==128);}
{Entity root,child;root.child=&child;child.sibling=&child;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&s->Valid());}
{Entity root,child;root.child=&child;child.child=&root;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&s->Valid());}
// Maximum weapon and muzzle bounds remain valid detached counts on overflow.
for(int count:{32,33}){Entity root;std::vector<Entity> nodes(count);std::vector<Weapon> weapons(count);
root.child=&nodes[0];for(int i=0;i<count;i++){nodes[i].weapon=&weapons[i];weapons[i].owner=&nodes[i];if(i+1<count)nodes[i].sibling=&nodes[i+1];}
auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&s->Weapons==32&&s->Valid());}
{Entity root;std::vector<Entity> nodes(18);std::vector<Weapon> weapons(17);std::vector<Muzzle> muzzles(136);Magazine mag;mag.owner=&nodes[17];
root.child=&nodes[0];for(int i=0;i<18;i++){if(i+1<18)nodes[i].sibling=&nodes[i+1];if(i==17)continue;
nodes[i].weapon=&weapons[i];weapons[i].owner=&nodes[i];for(int j=0;j<8;j++){auto& m=muzzles[i*8+j];m.owner=&nodes[i];m.mag=&mag;weapons[i].muzzles.Insert(&m);}}
auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&s->Muzzles==128&&s->Magazines==1&&s->Chambers==128&&s->Valid());}
{Rig r;Magazine alias;alias.owner=&r.magazine;Muzzle second;second.owner=&r.gun;second.mag=&alias;r.weapon.muzzles.Insert(&second);
auto s=r.Inspect();assert(!s->Complete&&s->Magazines==1&&s->Rounds==25&&alias.reads==0&&s->Valid());}
// Realistic production load: 128 known deposited items plus mounted root,
// gun, loaded magazine and a pruned original crew root exceed128 in total.
// Cargo-first and structure-first traversal both retain the same mounted counts.
for(bool cargoFirst:{false,true}){Rig r;std::vector<Weapon> cargoWeapons(128);std::vector<Entity> cargo(128);std::vector<Item> items(128);std::vector<Slot> slots(128);Storage deposit;
for(int i=0;i<128;i++){cargo[i].weapon=&cargoWeapons[i];cargoWeapons[i].owner=&cargo[i];cargo[i].item=&items[i];items[i].slot=&slots[i];slots[i].storage=&deposit;if(i<127)cargo[i].sibling=&cargo[i+1];}
if(cargoFirst){r.root.child=&cargo[0];cargo.back().sibling=&r.gun;}else{r.crew.sibling=&cargo[0];}
auto snapshot=r.Inspect();assert(snapshot->Complete&&snapshot->Weapons==1&&snapshot->Magazines==1&&snapshot->Rounds==25&&snapshot->Valid());
for(auto& item:cargo){assert(item.reads==0);}for(auto& gun:cargoWeapons){assert(gun.reads==0);}assert(r.weapon.reads==1&&r.carried.reads==0);}
// Independent128 structural/128 cargo limits, in either order and interleaved.
for(int order:{0,1,2})for(int scenario:{0,1,2,3}){
int structures=scenario==1?129:128;int cargoCount=scenario==2||scenario==3?129:(scenario==1?100:128);
if(scenario==2)structures=4;
std::vector<Entity> nodes(structures),cargo(cargoCount);std::vector<Item> items(cargoCount);std::vector<Slot> slots(cargoCount);Storage deposit;std::vector<Entity*> sequence;
for(int i=0;i<cargoCount;i++){cargo[i].item=&items[i];items[i].slot=&slots[i];slots[i].storage=&deposit;}
if(order==0){for(auto& e:cargo)sequence.push_back(&e);for(int i=1;i<structures;i++)sequence.push_back(&nodes[i]);}
if(order==1){for(int i=1;i<structures;i++)sequence.push_back(&nodes[i]);for(auto& e:cargo)sequence.push_back(&e);}
if(order==2){for(int i=0;i<std::max(structures-1,cargoCount);i++){if(i<cargoCount)sequence.push_back(&cargo[i]);if(i+1<structures)sequence.push_back(&nodes[i+1]);}}
nodes[0].child=sequence[0];for(int i=0;i<int(sequence.size())-1;i++)sequence[i]->sibling=sequence[i+1];
auto snapshot=ERR_HQMountedAmmoSampler::Inspect(&nodes[0]);assert(snapshot->Complete==(scenario==0)&&snapshot->Weapons==0&&snapshot->Valid());
int reads=0;for(auto& e:nodes)reads+=e.reads;assert(reads<=128);for(auto& e:cargo)assert(e.reads==0);
}
// Wire validation rejects impossible or malicious counts.
{ERR_HQMountedAmmoSnapshot s;assert(s.Valid());s.Weapons=33;assert(!s.Valid());s.Weapons=1;s.Muzzles=9;assert(!s.Valid());
s.Muzzles=1;s.Magazines=2;assert(!s.Valid());s.Magazines=1;s.Capacity=100001;assert(!s.Valid());s.Capacity=100;s.Rounds=101;assert(!s.Valid());
s.Rounds=25;s.Chambers=33;assert(!s.Valid());s.Chambers=1;s.LowMagazines=2;assert(!s.Valid());s.LowMagazines=1;s.Complete=true;assert(s.Valid());}
}
''')
run(program)
print('PASS: actual mounted hierarchy sampling, ownership exclusion, deduplication, completeness/caps, quarter threshold and detached validation; no runtime policy or ammunition mutation')
# WORKBENCH records are opt-in. Re-run every original geometry/count assertion
# in that build too, plus exact failure witnesses from the same traversal.
cases=r'''
// Exact reason64 distinguishes absent item, null slot and differing slot,
// while actual Inspect remains incomplete and retains the same ammunition.
for(int fault=0;fault<3;fault++){
Rig r;Mount mount;Item item;Slot binding,other;Storage storage;storage.purpose=EStoragePurpose::PURPOSE_ATTACHMENT_PROXY;other.storage=&storage;
mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&binding;binding.mount=&mount;binding.attached=&r.gun;
r.manager.weapons={&mount};r.gun.item=fault==0?nullptr:&item;
if(fault==2)item.slot=&other;
other.mount=&mount;other.attached=&r.gun;
ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();
assert(!snap->Complete&&snap->Weapons==1&&snap->Rounds==25&&(d->Reasons&64));
assert(d->Detail.find(fault==0?"item=absent;":"item=present;")!=string::npos);
assert(d->Detail.find(fault==2?"parent=different-binding;":"parent=null;")!=string::npos);
assert(d->Detail.find("binding-attached=exact;")!=string::npos&&d->Detail.find("binding-container=exact-mount;")!=string::npos);
assert(d->Detail.find("mount-owner=allowed;")!=string::npos&&d->Detail.find("mount-prefab=fixture;")!=string::npos);
if(fault==2)assert(d->Detail.find("parent-mount-slot=other-or-null;")!=string::npos);
}
{Rig r;r.weapon.muzzles={};ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();
assert(!snap->Complete&&snap->Weapons==1&&snap->Rounds==0&&(d->Reasons&4096));
assert(d->Detail.find("zero-muzzle-not-proven-placeholder;")!=string::npos&&d->Detail.find("zero-muzzle-slot-evidence:item=absent;parent=null;")!=string::npos);}
''' + r'''
{Rig r;ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(snap->Complete&&d->Reasons==0&&d->Nodes==4&&d->Allowed==3&&d->Managers==1);assert(!ERR_HQMountedAmmoSampler::EndDiagnostic());}
{Entity root;root.child=&root;ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=ERR_HQMountedAmmoSampler::Inspect(&root);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&d->Reasons==4&&d->Nodes==1&&d->Allowed==1&&root.reads==1);}
{Entity root;std::vector<Entity> children(128);root.child=&children[0];for(int i=0;i<127;i++)children[i].sibling=&children[i+1];ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=ERR_HQMountedAmmoSampler::Inspect(&root);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&d->Reasons==1024&&d->Nodes==129&&d->Allowed==128&&d->Structural==129&&d->Cargo==0&&children[127].reads==0);}
for(int purpose:{-1,0,127}){Rig r;Item item;Slot slot;Storage storage;r.gun.item=&item;item.slot=&slot;r.gun.prefab.name="first-gun";if(purpose>=0){slot.storage=&storage;storage.purpose=purpose;}ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&(d->Reasons&2)&&(d->Reasons&16)&&d->FirstPrefab=="first-gun"&&d->FirstPurpose==purpose&&d->Detail.find("ambiguous-storage;")!=string::npos);}
{Entity root;Manager manager;root.manager=&manager;manager.weapons.resize(9,nullptr);ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=ERR_HQMountedAmmoSampler::Inspect(&root);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&d->Reasons==512&&d->Managers==1&&manager.reads==1);}
{Rig r;Mount mount;mount.owner=&r.root;r.manager.weapons={&mount};ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&(d->Reasons&32));}
{ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=ERR_HQMountedAmmoSampler::Inspect(nullptr);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&d->Reasons==1&&d->Nodes==0&&d->Allowed==0);}
'''
cases+=r'''
for(int which:{0,1}){Entity root;int cargoCount=which==0?129:256;std::vector<Entity> cargo(cargoCount);std::vector<Item> items(cargoCount);std::vector<Slot> slots(cargoCount);Storage storage;root.child=&cargo[0];
for(int i=0;i<cargoCount;i++){cargo[i].item=&items[i];items[i].slot=&slots[i];slots[i].storage=&storage;if(i+1<cargoCount)cargo[i].sibling=&cargo[i+1];}
ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snapshot=ERR_HQMountedAmmoSampler::Inspect(&root);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snapshot->Complete&&d->Cargo==129&&d->Structural==1&&(d->Reasons&2048));
if(which==0){assert(d->Nodes==130&&!(d->Reasons&8));}else{assert(d->Nodes==256&&(d->Reasons&8));}}
'''
run('#define WORKBENCH\n'+program.replace('int main(){','int main(){'+cases))
print('PASS: WORKBENCH opt-in diagnostics distinguish cycles/cap/storage/manager/mount failures without repeated native traversal')
@@ -0,0 +1,87 @@
"""Actual probe state machine/counters; menu, actor and invoker boundaries doubled."""
import ast,os,re
from pathlib import Path
from types import SimpleNamespace
from policy_harness import ROOT,body,run
BASE=Path(os.environ.get('ERR_INVENTORY_PROBE_ROOT',ROOT))
p=Path(os.environ.get('ERR_NATIVE_INVENTORY_SOURCE',BASE/'addons/core/Scripts/Game/ERR/00_Core/ERR_NativeInventoryHitZoneProbe.c'))
s=p.read_text()
assert s.startswith('#ifdef WORKBENCH') and s.rstrip().endswith('#endif')
for forbidden in ['SetHealth(', 'FullHeal(', 'ForceUnconsciousness(', 'SetOrigin(', 'CreateWidgets(', 'InputManager', 'ERR_ClearHitZoneCallbacks(']: assert forbidden not in s
assert 'super.UpdateHitZoneState(hz, updateAttachmentStorage);' in s
assert 'm_Menu.InspectItem(null);' in s and 'm_Deadline = System.GetTickCount() + 30000' in s
assert 'out array<ref SCR_InventoryHitZonePointContainerUI> points' in s
def cpp(s):
for a,b in [('protected override ',''),('protected ',''),('override ',''),('super.UpdateHitZoneState','Native::UpdateHitZoneState'),('ERR_NativeInventoryHitZoneProbe','Probe'),('SCR_InventoryHitZonePointContainerUI','Point'),('SCR_CharacterDamageManagerComponent','Damage'),('SCR_InventoryStorageManagerComponent','Inventory'),('SCR_CharacterBloodHitZone','Zone'),('SCR_HitZone','Zone'),('ERR_GameMode','Mode'),('SCR_PlayerController','Controller'),('SCR_InventoryMenuUI','Menu'),('MenuBase','Menu'),('IEntity','Actor*'),('string ','string '),('null','nullptr'),('foreach (','for ('),('ref ',''),('array<Point> seen = {};','Array<Point*> seen;')]:s=s.replace(a,b)
for name in ['mode','damage','point','updated','detached','oldPoint']:
s=re.sub(r'\b'+name+r'\.',name+'->',s)
for name in ['m_Mode','m_Controller','m_Actor','m_Inventory','m_Blood','m_Menu']:s=s.replace(name+'.',name+'->')
for cls in ['Probe','Mode','Controller','Damage','Inventory','Menu','System','Replication','ERR_Actors','ChimeraMenuPreset']:s=s.replace(cls+'.',cls+'::')
for cls,var in [('Mode','mode'),('Zone','hz'),('Zone','blood'),('Point','point'),('Point','updated'),('Point','detached'),('Point','oldPoint'),('Menu','current'),('Damage','damage')]:s=s.replace(cls+' '+var,cls+'* '+var)
s=s.replace('GetGame().','GetGame()->').replace('GetMenuManager().','GetMenuManager()->').replace('Players().','Players()->').replace('GetOnDamageStateChanged(false).','GetOnDamageStateChanged(false)->')
s=s.replace('FindComponent(Inventory)','FindComponent(1)').replace('FindComponent(Damage)','FindComponent(2)')
s=s.replace('Print("ERR SMOKE FAIL: inventory hitzone " + detail, LogLevel.ERROR);','Print("ERR SMOKE FAIL: inventory hitzone " + detail);')
return s
point='\n'.join(cpp(body(p,sig)) for sig in ['protected override void UpdateHitZoneState(','bool ERR_HitZoneProbeWatch(','bool ERR_HitZoneProbeOnce(','bool ERR_HitZoneProbeDetached(','void ERR_HitZoneProbeStop('])
probe='\n'.join(cpp(body(p,sig)) for sig in ['void ERR_NativeInventoryHitZoneProbe(','protected bool Check(','protected bool Context(','protected bool OwnsMenu(','protected bool UnchangedHealth(','int Tick(','void Stop('])
probe=probe.replace('void Probe(', 'Probe(')
run(r'''
#include <cassert>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
using string=std::string;template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;void Clear(){this->clear();}void Insert(T x){this->push_back(x);}bool Contains(T x){return std::find(this->begin(),this->end(),x)!=this->end();}bool IsEmpty(){return this->empty();}};
struct Actor;struct Mode;struct Point;struct Menu;struct Probe{static bool Enabled(){return enabled;}static inline bool enabled=true;};
namespace System{int now=0;int GetTickCount(){return now;}}
namespace ChimeraMenuPreset{int Inventory20Menu=1;}
void Print(string){}
struct Event{std::function<void()>fire;int calls=0;void Invoke(void*){calls++;if(fire)fire();}};
struct Zone{float health=100;Event event;float GetHealth(){return health;}Event*GetOnDamageStateChanged(bool create){assert(!create);return &event;}};
struct Damage{Zone*blood;Zone*GetBloodHitZone(){return blood;}static Damage*Cast(void*p){return static_cast<Damage*>(p);}};
struct Inventory{int opens=0;std::function<void()>opening;void OpenInventory(){opens++;if(opening)opening();}static Inventory*Cast(void*p){return static_cast<Inventory*>(p);}};
struct Actor{Inventory*inventory;Damage*damage;bool fit=true;void*FindComponent(int n){return n==1?static_cast<void*>(inventory):static_cast<void*>(damage);}};
namespace ERR_Actors{bool CanFight(Actor*a){return a&&a->fit;}}
struct Controller{Actor*actor;Actor*GetControlledEntity(){return actor;}static Controller*Cast(Controller*p){return p;}};
struct Roster{Actor*actor;bool CurrentActor(Actor*a){return a==actor;}};
struct Mode{static inline Mode*current=nullptr;Roster roster;bool active=true;static Mode*Get(){return current;}bool Active(){return active;}Roster*Players(){return &roster;}};
struct Native{int updates=0;void UpdateHitZoneState(Zone*,bool){updates++;}};
struct Point:Native{Zone*m_ERRProbeBlood=nullptr;int m_ERRProbeUpdates=0;bool m_ERRProbeFalseRefresh=false;Mode*m_ERRHitZoneMode=nullptr;Array<Zone*>m_ERRHitZoneSources;
POINT
};
struct Menu{Array<Point*>points;Actor*actor=nullptr;bool ready=true;int inspect=0;bool ERR_HitZoneProbeReady(Actor*a){return ready&&actor==a;}void InspectItem(void*){inspect++;}bool ERR_HitZoneProbePoints(Array<Point*>&out){out=points;return points.size()==7;}static Menu*Cast(Menu*m){return m;}};
struct Manager{Menu*current=nullptr;bool foreignTop=false,accept=true;int closes=0;void*GetTopMenu(){return foreignTop||current?this:nullptr;}Menu*FindMenuByPreset(int){return current;}bool CloseMenu(Menu*m){assert(m==current);closes++;return accept;}};
struct Game{Controller*controller;Manager manager;Controller*GetPlayerController(){return controller;}Manager*GetMenuManager(){return &manager;}};Game*game=nullptr;Game*GetGame(){return game;}
struct Actual:Probe{Mode*m_Mode;Controller*m_Controller;Actor*m_Actor;Inventory*m_Inventory=nullptr;Zone*m_Blood=nullptr;Menu*m_Menu=nullptr;Array<Point*>m_Points;int m_Deadline=0,m_Stage=0,m_Result=0;bool m_Stopped=false,m_CloseRequested=false;float m_Health=0;
PROBE
};
struct Fixture{Zone blood;Damage damage{&blood};Inventory inventory;Actor actor{&inventory,&damage};Controller controller{&actor};Mode mode;Game world{&controller,{}};Menu menu;Point points[7];Fixture(){System::now=0;Probe::enabled=true;game=&world;Mode::current=&mode;mode.roster.actor=&actor;menu.actor=&actor;inventory.opening=[&](){world.manager.current=&menu;};for(auto&p:points){p.m_ERRHitZoneMode=&mode;p.m_ERRHitZoneSources.Insert(&blood);menu.points.Insert(&p);}blood.event.fire=[&](){for(auto&p:points)if(!p.m_ERRHitZoneSources.IsEmpty())p.UpdateHitZoneState(&blood,true);};}
void Close(){world.manager.current=nullptr;for(auto&p:points){p.m_ERRHitZoneMode=nullptr;p.m_ERRHitZoneSources.Clear();}}
};
int main(){
{Fixture f;Actual p(&f.mode,&f.actor);assert(p.Tick(0)==0&&f.inventory.opens==1);assert(p.Tick(0)==0&&f.menu.inspect==1);assert(p.Tick(0)==0&&f.blood.event.calls==1&&f.world.manager.closes==1);assert(p.Tick(0)==0&&f.world.manager.closes==1);f.Close();assert(p.Tick(0)==1&&f.blood.event.calls==2&&f.blood.health==100);assert(p.Tick(0)==1);for(auto&x:f.points)assert(x.updates==1);}
{Fixture f;Actual p(&f.mode,&f.actor);f.world.manager.foreignTop=true;assert(p.Tick(0)==-1&&f.inventory.opens==0&&f.world.manager.closes==0);}
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);f.menu.ready=false;assert(p.Tick(0)==0&&f.menu.inspect==0);System::now=30000;assert(p.Tick(0)==-1&&f.world.manager.closes==1);}
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);f.menu.points.pop_back();assert(p.Tick(0)==0&&f.blood.event.calls==0);p.Stop();assert(f.world.manager.closes==1);}
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);f.menu.points[6]=f.menu.points[0];assert(p.Tick(0)==-1&&f.blood.event.calls==0);}
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);f.blood.event.fire=[&](){for(auto&x:f.points)x.UpdateHitZoneState(&f.blood,false);};assert(p.Tick(0)==-1);}
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);p.Tick(0);f.world.manager.current=nullptr;assert(p.Tick(0)==-1);}// missing real detach
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);p.Tick(0);f.Close();f.blood.event.fire=[&](){f.points[0].UpdateHitZoneState(&f.blood,true);};assert(p.Tick(0)==-1);}// late retained callback
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);f.world.manager.accept=false;assert(p.Tick(0)==-1&&f.world.manager.closes==2);}
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);f.blood.health=99;assert(p.Tick(0)==-1);}
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);Menu foreign;f.world.manager.current=&foreign;assert(p.Tick(0)==-1&&f.world.manager.closes==0);}
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);Controller replacement{&f.actor};f.world.controller=&replacement;assert(p.Tick(0)==-1);}
{Fixture f;Actual p(&f.mode,&f.actor);Probe::enabled=false;assert(p.Tick(0)==-1&&f.inventory.opens==0);}
}
'''.replace('POINT',point).replace('PROBE',probe.replace('Probe(', 'Actual(')))
base=(BASE/'addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c').read_text()
assert 'm_ERRInventoryHitZoneProbe.Tick(now)' in base and 'm_ERRInventoryHitZoneProbe.Stop()' in base
assert 'current == ERR_Phase.DEFEAT && m_ERRBaseStep == 2102' in base
assert 'if (result == 1) { m_ERRBaseStep = 2102; Transition(ERR_Phase.DEFEAT); }' in base
runner=(BASE/'tools/headless_workbench.py').read_text();tree=ast.parse(runner)
node=next(n for n in tree.body if isinstance(n,ast.If) and "a.smoke == 'inventory-hitzone'" in ast.unparse(n.test))
for project,rounds,timeout,bad in [('core',1,240,False),('chat',1,240,True),('core',2,240,True),('core',1,239,True)]:
assert eval(compile(ast.Expression(node.test),'<selector>','eval'),{'a':SimpleNamespace(smoke='inventory-hitzone',project=project,rounds=rounds,timeout=timeout)})==bad
assert runner.count("'inventory-hitzone'")>=3
print('PASS: actual native-probe lifecycle/counters with doubled boundaries; fixed deadline, exact seven sources, true forwarding, async close, no late updates and selector wiring')
@@ -0,0 +1,257 @@
#!/usr/bin/env python3
"""Run bounded native Workbench smoke tests inside a windowless Gamescope display.
Requires Linux, gamescope, Proton, and a disposable COPY of a working Tools compat prefix.
No desktop rules or existing Wine processes are changed. This is not a dedicated server.
"""
import argparse, fcntl, json, os, shutil, signal, subprocess, time
from pathlib import Path
from native_provenance import snapshot, persist, bindings, seal_run
from headless_game import assert_prefix_idle, cleanup_owned_run
from external_addons import policy as external_policy, prepare_profile, verify_profile
ROOT = Path(__file__).resolve().parents[1]
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--steam', type=Path, default=Path.home()/'.local/share/Steam')
p.add_argument('--external-addons', type=Path, help='Installed RHS/WCS/BMP source root; expose only audited closure in a separate Chat profile')
p.add_argument('--compat', type=Path, required=True, help='Disposable cloned Proton compatdata directory outside Steam')
p.add_argument('--profile', type=Path, required=True, help='Separate disposable successor Workbench profile')
p.add_argument('--smoke', choices=['crash-start', 'crash-geometry', 'normal-escape-route', 'escape-route', 'optional-site-skip', 'infantry-combat', 'medical-combat', 'hunter-faction', 'unconscious-camera', 'objective-map', 'town-garrisons', 'site-garrisons', 'radio-interaction', 'enemy-loadout', 'aggression', 'air-logistics', 'air-assault', 'air-recon', 'airfield-markers', 'opening-tools', 'mortar-station', 'generated-sites', 'hq-local-handoff', 'infantry-transfer', 'hq-body', 'convoy', 'supply-convoys', 'supply-travel', 'mounted-ammo', 'alarm-hearing', 'hq-memory', 'hq-awareness', 'aircraft-field', 'aircraft-route', 'native-end-active', 'native-end-preparing', 'aircraft-control', 'aircraft-cancel', 'aircraft-translate', 'map-ownership', 'mortar-refresh', 'mortar-salvo', 'mortar-courier', 'station-teardown', 'inventory-hitzone', 'prison-breach', 'powered-flight', 'powered-translate', 'local-admission', 'mortar-expiry', 'mortar-transfer', 'mortar-fire', 'patrol-hq', 'victory', 'lost-helicopter', 'player-death', 'radio-network', 'hq-response', 'mortar-inventory', 'startup-failure', 'site-survey', 'cache-loss', 'extraction-capacity', 'early-helicopter-loss', 'extraction-startup-failure', 'extraction-boundary-failure', 'transport-loss', 'transport-survey', 'lifecycle', 'objective-ui', 'recovery', 'prisoner-kit', 'medical', 'casualty-load', 'casualty-direct', 'casualty-bench', 'casualty-remove', 'environment', 'environment-restart', 'patrols', 'radio-document-loss', 'radio-document-loss-partial', 'guard-cancel', 'casualty-service'], default='victory')
p.add_argument('--trace-random-radius', action='store_true', help='Workbench-only read-only zero-radius caller traces; one normal Chat round, capped at sixteen records')
p.add_argument('--maintenance-refuel', action='store_true', help='Opt-in normal-route check: original held Southern fuel can transfers finite fuel through its native UAZ action; one arranged tank depletion, no stock creation')
p.add_argument('--mounted-swap-eligibility', action='store_true', help='Read-only mounted-ammo variant: one exact finite cargo/mounted magazine CanSwap call; no transfer or radio proof')
p.add_argument('--mounted-bmp', action='store_true', help='Read-only original BMP slot4 complete mounted counts and its natural HQ radio delivery; isolates unrelated opening hunters before unlock')
p.add_argument('--mine-stock', action='store_true', help='Read-only mounted-ammo check of exact callback-proved finite PMN4 cargo before and after natural radio delivery')
p.add_argument('--mounted-courier', action='store_true', help='Mounted-ammo variant: intentionally deplete one original installed magazine, then require natural HQ radio, finite pickup, native exchange and original-seat return')
p.add_argument('--mounted-intercept', action='store_true', help='After convoy driving, two synthetic witness samples exercise real HQ radio/planning and original escort detour/hold/expiry/resume')
p.add_argument('--convoy-cover', action='store_true', help='After convoy driving, arrange the existing prisoner to verify native sighting/radio HOLD and RESUME')
p.add_argument('--field-terrain-audit', action='store_true', help='After a fixed aircraft-field survey refusal, log one read-only 1681-node coarse terrain grid; never selects or qualifies a corridor')
p.add_argument('--field-service-cancel', action='store_true', help='Explicit aircraft-field variant: Stop the original retained service during measured cruise; requires physical cancellation landing')
p.add_argument('--route-service-cancel', action='store_true', help='Explicit aircraft-route variant: stop the retained service after actual airborne translation and require controlled cancellation landing')
p.add_argument('--station-teardown-diagnostics', action='store_true', help='Opt-in bounded native deletion diagnostics; preserves all validation errors')
p.add_argument('--flight-autohover', action='store_true', help='Opt-in owned native helicopter autohover experiment; requires Core powered-flight and actor-local action inputs')
p.add_argument('--flight-action-inputs', action='store_true', help='Experimental exact-AI ActionManager inputs; Core powered-flight fixture only')
p.add_argument('--flight-input-diagnostics', action='store_true', help='Opt-in read-only powered-flight input/application and native rotor signal observations')
p.add_argument('--recon-takeover-only', action='store_true', help='Air-recon fixture: only native faction handover/release and explicit aircraft deletion')
p.add_argument('--body-recovery-only', action='store_true', help='Air-logistics fixture: only physical corpse inspection and returned document intelligence')
p.add_argument('--rounds', type=int, default=2)
p.add_argument('--timeout', type=int, default=360)
p.add_argument('--project', choices=['core', 'chat'], default='chat', help='Diagnostic fixture family (both load the combined mod)')
a = p.parse_args()
if a.smoke == 'inventory-hitzone' and (a.project != 'core' or a.rounds != 1 or a.timeout < 240): p.error('inventory-hitzone requires --project core --rounds 1 and timeout at least 240 seconds')
if a.smoke == 'crash-start' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 240): p.error('crash-start requires Chat, one round and timeout at least 240 seconds')
if a.smoke == 'crash-geometry' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 180): p.error('crash-geometry requires Chat, one round and timeout at least180 seconds')
if a.trace_random_radius and (a.smoke != 'normal-escape-route' or a.project != 'chat' or a.rounds != 1 or a.timeout < 420): p.error('--trace-random-radius requires Chat normal-escape-route, one round and timeout at least 420 seconds')
if a.smoke == 'objective-map' and (a.project != 'core' or a.rounds != 1): p.error('objective-map requires --project core --rounds 1')
if a.smoke == 'unconscious-camera' and a.project != 'core': p.error('unconscious-camera requires --project core to keep the general smoke fixture inactive')
if a.mine_stock:
if a.smoke != 'mounted-ammo' or a.project != 'chat' or a.rounds != 1 or a.timeout < 360:
p.error('--mine-stock requires --smoke mounted-ammo --project chat --rounds 1 and timeout at least 360 seconds')
if a.mounted_courier or a.mounted_swap_eligibility:
p.error('--mine-stock cannot combine with --mounted-courier or --mounted-swap-eligibility')
if a.maintenance_refuel and (a.smoke != 'normal-escape-route' or a.project != 'chat' or a.rounds != 1 or a.timeout < 420): p.error('--maintenance-refuel requires Chat normal-escape-route, one round and timeout at least 420 seconds')
if a.body_recovery_only and (a.smoke != 'air-logistics' or a.project != 'core' or a.rounds != 1): p.error('--body-recovery-only requires Core air-logistics and one round')
if a.recon_takeover_only and (a.smoke != 'air-recon' or a.project != 'core' or a.rounds != 1): p.error('--recon-takeover-only requires Core air-recon and one round')
steam, compat, profile = a.steam.resolve(), a.compat.resolve(), a.profile.resolve()
if not 1 <= a.rounds <= 10 or not 60 <= a.timeout <= 1800:
p.error('Use 1..10 rounds and a 60..1800 second timeout')
if a.field_terrain_audit and a.smoke != 'aircraft-field': p.error('--field-terrain-audit requires --smoke aircraft-field')
if a.field_terrain_audit and a.field_service_cancel: p.error('--field-terrain-audit and --field-service-cancel are mutually exclusive')
if a.field_service_cancel and a.smoke != 'aircraft-field': p.error('--field-service-cancel requires --smoke aircraft-field')
if a.route_service_cancel and a.smoke != 'aircraft-route': p.error('--route-service-cancel requires --smoke aircraft-route')
if a.smoke == 'mortar-courier' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 500):
p.error('mortar-courier requires --project chat --rounds 1 --timeout at least 500')
if a.smoke in {'mortar-refresh', 'mortar-salvo', 'mortar-expiry', 'mortar-transfer', 'mortar-fire'} and (a.project != 'chat' or a.rounds != 1 or a.timeout < 400):
p.error('mortar-refresh/mortar-salvo/mortar-expiry/mortar-transfer/mortar-fire requires --project chat --rounds 1 --timeout at least 400')
if a.smoke in {'aircraft-field', 'aircraft-route', 'aircraft-control', 'aircraft-cancel', 'aircraft-translate'} and (a.project != 'chat' or a.rounds != 1 or a.timeout < 420): p.error('aircraft-field/aircraft-route/aircraft-control/aircraft-cancel/aircraft-translate requires Chat, one round, and at least 420 seconds')
if a.mounted_swap_eligibility and a.smoke != 'mounted-ammo': p.error('--mounted-swap-eligibility requires --smoke mounted-ammo')
if a.mounted_courier and (a.smoke != 'mounted-ammo' or a.project != 'chat' or a.rounds != 1 or a.timeout < 780): p.error('--mounted-courier requires --smoke mounted-ammo --project chat --rounds 1 and timeout at least780 seconds')
if a.mounted_courier and a.mounted_swap_eligibility: p.error('--mounted-courier and --mounted-swap-eligibility are mutually exclusive')
if a.mounted_intercept and (a.smoke != 'convoy' or a.project != 'chat' or a.rounds != 1 or a.timeout < 900): p.error('--mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds')
if a.mounted_intercept and a.convoy_cover: p.error('--mounted-intercept and --convoy-cover are mutually exclusive')
if a.convoy_cover and a.smoke != 'convoy': p.error('--convoy-cover requires --smoke convoy')
if a.convoy_cover and a.timeout < 900: p.error('--convoy-cover requires timeout at least900 seconds')
if a.smoke == 'supply-travel' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 480): p.error('supply-travel requires Chat, one round, and at least480 seconds')
if a.smoke == 'supply-convoys' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 900): p.error('supply-convoys requires Chat, one round, and at least900 seconds')
if a.smoke == 'alarm-hearing' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 360): p.error('alarm-hearing requires Chat, exactly one round, and timeout at least360 seconds')
if a.smoke == 'mounted-ammo' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 360): p.error('mounted-ammo requires Chat, exactly one round, and timeout at least360 seconds')
if a.mounted_bmp and (a.smoke != 'mounted-ammo' or a.project != 'chat' or a.rounds != 1 or a.timeout < 360 or a.mine_stock or a.mounted_courier or a.mounted_swap_eligibility): p.error('--mounted-bmp requires Chat mounted-ammo, one round, timeout at least360 seconds, without mine/courier/swap flags')
if a.smoke == 'convoy' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 600): p.error('convoy requires Chat, exactly one round, and timeout at least600 seconds')
if a.smoke == 'infantry-transfer' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 360): p.error('infantry-transfer requires Chat, one round and timeout at least360 seconds')
if a.smoke == 'hq-local-handoff' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 360): p.error('hq-local-handoff requires Chat, one round and timeout at least360 seconds')
if a.smoke in {'hq-memory', 'hq-awareness', 'hq-body'} and (a.project != 'chat' or a.rounds != 1): p.error('hq-memory/hq-awareness/hq-body requires Chat and exactly one round')
if a.smoke == 'map-ownership' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 180): p.error('map-ownership requires Chat, one round, and at least 180 seconds')
if a.smoke == 'powered-translate' and (a.project != 'core' or not a.flight_action_inputs or not a.flight_autohover): p.error('powered-translate requires Core with flight-action-inputs and flight-autohover')
if a.flight_autohover and (a.project != 'core' or a.smoke not in {'powered-flight', 'powered-translate'} or not a.flight_action_inputs): p.error('flight-autohover requires Core powered-flight with flight-action-inputs')
if a.flight_action_inputs and (a.project != 'core' or a.smoke not in {'powered-flight', 'powered-translate'}): p.error('flight-action-inputs requires the Core powered-flight fixture')
if a.smoke in {'powered-flight', 'powered-translate'} and (a.project != 'core' or a.rounds != 1 or a.timeout < 420): p.error('powered-flight requires Core, one round, and at least 420 seconds')
if a.smoke == 'station-teardown' and (a.project != 'core' or a.rounds != 1): p.error('station-teardown requires --project core --rounds 1')
if a.smoke in {'native-end-active', 'native-end-preparing'}:
if a.project != 'core' or a.rounds != 2 or a.timeout < 120: p.error('native-end fixtures require Core, exactly two rounds and at least 120 seconds')
fixture_names = ['erri-native-end-round.json', 'erri-native-end-round.json.tmp', 'erri-native-end-round.json.bak']
if any((profile/name).exists() or (profile/name).is_symlink() for name in fixture_names): p.error('native-end fixture paths must not preexist; preserve prior evidence before retrying')
if a.smoke == 'environment-restart':
if a.project != 'core' or a.rounds != 2: p.error('environment-restart requires --project core --rounds 2')
fixture_names = ['erri-environment-restart.json', 'erri-environment-restart.json.bak', 'erri-environment-restart.json.tmp', 'erri-environment-restart-round.json']
if any((profile/name).exists() or (profile/name).is_symlink() for name in fixture_names): p.error('environment-restart fixture paths must not preexist; preserve or move prior evidence before retrying')
if compat.is_relative_to(steam) or not (compat/'pfx/system.reg').is_file():
p.error('--compat must be a disposable cloned prefix outside the Steam installation')
if a.external_addons and profile == Path('/tmp/erri-workbench-profile'): p.error('Use a separate external-dependency profile; preserve the vanilla test profile')
if not a.external_addons: p.error('The combined mod requires --external-addons pointing to installed authorized RHS/WCS/BMP dependencies')
prefix_lock = (compat/'.erri-workbench.lock').open('a')
try: fcntl.flock(prefix_lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError: p.error('Another test runner owns this disposable Wine prefix')
# The lock must be held before the preflight; never repair a busy prefix here.
try: assert_prefix_idle(compat)
except (OSError,ValueError) as error: p.error(str(error))
if profile.is_relative_to(ROOT) or profile.is_relative_to(steam):
p.error('--profile must be outside source and Steam directories')
if not shutil.which('gamescope') or not shutil.which('pactl'): p.error('gamescope and pactl are required')
wb = steam/'steamapps/common/Arma Reforger Tools/Workbench'
proton = steam/'steamapps/common/Proton - Experimental/proton'
vanilla = steam/'steamapps/common/Arma Reforger/addons/data'
if not proton.is_file() or not (wb/'ArmaReforgerWorkbenchSteamDiag.exe').is_file():
p.error('Stable Reforger Tools and Proton Experimental must already be installed')
profile.mkdir(parents=True, exist_ok=True)
addons = profile/'addons'
addons.mkdir(exist_ok=True)
link = addons/'ArmaReforger'
if not link.exists(): link.symlink_to(vanilla, target_is_directory=True)
external_inputs = None
external_cache = profile/'external-source-cache.json'
allowed_profile_addons = {'ArmaReforger', 'saves'}
if a.external_addons:
try:
external_rules = external_policy(ROOT)
external_inputs = prepare_profile(addons, a.external_addons, external_rules, external_cache)
allowed_profile_addons.update('ERRI_'+guid for guid in external_inputs)
except (OSError, ValueError) as error: p.error(str(error))
if link.resolve() != vanilla.resolve() or any(x.name not in allowed_profile_addons for x in addons.iterdir()) or any((addons/'saves').rglob('*.gproj')):
p.error('Test profile addons must contain only vanilla and the explicit audited external closure')
profile_arg = 'Z:'+str(profile)
# Refuse concurrency instead of terminating anybody else's editor.
for proc in Path('/proc').iterdir():
if not proc.name.isdigit(): continue
try:
args = (proc/'cmdline').read_bytes().split(b'\0')
if profile_arg.encode() in args and any(x.endswith(b'ArmaReforgerWorkbenchSteamDiag.exe') for x in args):
p.error('Another Workbench already uses this test profile')
except (FileNotFoundError, PermissionError, ProcessLookupError): pass
command = ['gamescope', '--backend', 'headless', '-W', '1280', '-H', '720', '-w', '1280', '-h', '720', '-r', '30', '--', str(proton), 'run', str(wb/'ArmaReforgerWorkbenchSteamDiag.exe'), '-gproj', 'Z:'+str(ROOT/'addons/core/addon.gproj'), '-addonsDir', 'Z:'+str(ROOT/'addons'), '-profile', profile_arg, '-logconsole', '-wbModule=WorldEditor', '-load', 'Worlds/ERR_Everon.ent', '-plugin=ERR_ValidateWorldPlugin', '-errPlay', '1', '-errSmoke', '1', '-errSmokeOutcome', a.smoke, '-noThrow', '-disableCrashReporter']
if a.convoy_cover: command += ['-errConvoyCover', '1']
if a.mounted_intercept: command += ['-errMountedIntercept', '1']
if a.mounted_swap_eligibility: command += ['-errMountedSwapEligibility', '1']
if a.mounted_courier: command += ['-errMountedCourier', '1']
if a.mounted_bmp: command += ['-errMountedBMP', '1']
if a.mine_stock: command += ['-errMineStock', '1']
if a.maintenance_refuel: command += ['-errMaintenanceRefuel', '1']
if a.trace_random_radius: command += ['-errTraceRandomRadius', '1']
if a.field_service_cancel: command += ['-errSmokeFieldServiceCancel', '1']
if a.field_terrain_audit: command += ['-errSmokeFieldTerrainAudit', '1']
if a.route_service_cancel: command += ['-errSmokeRouteServiceCancel', '1']
if a.station_teardown_diagnostics: command += ['-errStationTeardownDiag', '1']
if a.flight_input_diagnostics: command += ['-errFlightInputDiag', '1']
if a.flight_action_inputs: command += ['-errFlightActionInputs', '1']
if a.flight_autohover: command += ['-errFlightAutohover', '1']
if a.recon_takeover_only: command += ['-errReconTakeoverOnly', '1']
if a.body_recovery_only: command += ['-errBodyRecoveryOnly', '1']
if a.project == 'core':
if a.smoke not in {'optional-site-skip', 'infantry-combat', 'medical-combat', 'hunter-faction', 'unconscious-camera', 'objective-map', 'town-garrisons', 'site-garrisons', 'radio-interaction', 'enemy-loadout', 'aggression', 'air-logistics', 'air-assault', 'air-recon', 'airfield-markers', 'opening-tools', 'mortar-station', 'generated-sites', 'native-end-active', 'native-end-preparing', 'station-teardown', 'inventory-hitzone', 'prison-breach', 'powered-flight', 'powered-translate', 'local-admission', 'victory', 'startup-failure', 'site-survey', 'cache-loss', 'extraction-capacity', 'early-helicopter-loss', 'extraction-startup-failure', 'extraction-boundary-failure', 'transport-loss', 'transport-survey', 'lifecycle', 'objective-ui', 'recovery', 'prisoner-kit', 'medical', 'casualty-load', 'casualty-direct', 'casualty-bench', 'casualty-remove', 'environment', 'environment-restart', 'patrols', 'radio-document-loss', 'radio-document-loss-partial', 'guard-cancel', 'casualty-service'}: p.error('Unsupported Core-only fixture')
command[command.index('-plugin=ERR_ValidateWorldPlugin')] = '-plugin=ERR_ValidateBaseWorldPlugin'
command[command.index('-errSmoke')] = '-errBaseSmoke'
if a.smoke in {'station-teardown', 'inventory-hitzone', 'prison-breach', 'powered-flight', 'powered-translate', 'local-admission', 'cache-loss', 'extraction-capacity', 'early-helicopter-loss', 'extraction-startup-failure', 'extraction-boundary-failure', 'transport-loss', 'transport-survey', 'lifecycle', 'objective-ui', 'recovery', 'prisoner-kit', 'medical', 'casualty-load', 'casualty-direct', 'casualty-bench', 'casualty-remove', 'environment', 'environment-restart', 'patrols', 'radio-document-loss', 'radio-document-loss-partial', 'guard-cancel', 'casualty-service'} and a.project != 'core': p.error('This fixture requires --project core')
if a.smoke in {'startup-failure', 'site-survey', 'transport-survey', 'extraction-startup-failure', 'extraction-boundary-failure'} and (a.project != 'core' or a.rounds != 1): p.error('This diagnostic requires --project core --rounds 1')
env = os.environ.copy()
env.update(STEAM_COMPAT_DATA_PATH=str(compat), STEAM_COMPAT_CLIENT_INSTALL_PATH=str(steam), WINEPREFIX=str(compat/'pfx'))
# A test-only sink prevents startup sounds; a stream check also catches a saved
# explicit Wine device selection. Never change the user's default sink.
silent_sink = 'erri_tests_silent'
sinks = json.loads(subprocess.check_output(['pactl', '-f', 'json', 'list', 'short', 'sinks'], text=True))
if not any(sink['name'] == silent_sink for sink in sinks):
subprocess.run(['pactl', 'load-module', 'module-null-sink', 'sink_name='+silent_sink, 'sink_properties=device.description=ERRI_Silent_Tests'], check=True, stdout=subprocess.DEVNULL)
subprocess.run(['pactl', 'set-sink-mute', silent_sink, '1'], check=True)
subprocess.run(['pactl', 'set-sink-volume', silent_sink, '0%'], check=True)
sinks = json.loads(subprocess.check_output(['pactl', '-f', 'json', 'list', 'short', 'sinks'], text=True))
silent_index = next(sink['index'] for sink in sinks if sink['name'] == silent_sink)
env.update(PULSE_SINK=silent_sink, PULSE_PROP='application.id=org.escapistsrereintroduced.tests')
muted_streams = set()
def silence_test_streams():
streams = json.loads(subprocess.check_output(['pactl', '-f', 'json', 'list', 'sink-inputs'], text=True))
for stream in streams:
props = stream.get('properties', {})
owned = props.get('application.id') == 'org.escapistsrereintroduced.tests'
pid = props.get('application.process.id', '')
if not owned and pid.isdigit():
try:
target = str(compat/'pfx').encode()
values = (Path('/proc')/pid/'environ').read_bytes().split(b'\0')
owned = any(value.startswith(b'WINEPREFIX=') and value[len(b'WINEPREFIX='):].rstrip(b'/') == target for value in values)
except (FileNotFoundError, PermissionError, ProcessLookupError): pass
if not owned: continue
index = str(stream['index'])
if stream.get('sink') != silent_index:
subprocess.run(['pactl', 'move-sink-input', index, silent_sink], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if not stream.get('mute'):
subprocess.run(['pactl', 'set-sink-input-mute', index, '1'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if index not in muted_streams:
muted_streams.add(index)
print('Test audio routed to silent output and muted (stream '+index+')', flush=True)
# Snapshot addon bytes before launching any engine process.
captured_sources = snapshot(ROOT, command)
recorded_sessions = set()
started = time.time()
log_root = profile/'logs'
previous = set(log_root.glob('logs_*')) if log_root.exists() else set()
result, session, complete_at = 'timeout', None, None
runner_error = None
cleanup_ok = True
cleanup_report = None
child = None
with (profile/'headless-launch.log').open('w') as output:
try:
child = subprocess.Popen(command, cwd=wb, env=env, stdout=output, stderr=subprocess.STDOUT, start_new_session=True)
while time.time()-started < a.timeout:
silence_test_streams()
candidates = [x for x in log_root.glob('logs_*') if x not in previous]
if candidates:
session = max(candidates, key=lambda x:x.stat().st_mtime)
if session not in recorded_sessions:
persist(session, captured_sources)
if external_inputs:
(session/'external-addons-inputs.json').write_text(json.dumps(external_inputs, indent=2)+'\n')
recorded_sessions.add(session)
console = session/'console.log'
content = console.read_text(errors='replace') if console.exists() else ''
if ('Reintroduced: scenario preparation failed' in content and a.smoke not in {'startup-failure', 'extraction-startup-failure', 'extraction-boundary-failure'}) or 'ERR SMOKE FAIL:' in content or 'Virtual Machine Exception' in content or 'Can\'t compile "Game"' in content:
result = 'test-failed'; break
if content.count('ERR SMOKE COMPLETE:') >= a.rounds:
if complete_at is None: complete_at = time.time()
if time.time()-complete_at >= 6: result = 'smoke-completed'; break
if child.poll() is not None: result = 'process-exited'; break
time.sleep(1)
except KeyboardInterrupt:
result = 'interrupted'
except (OSError,ValueError,subprocess.SubprocessError) as error:
result = 'runner-failed'; runner_error = str(error)
finally:
result_before_cleanup = result
if child is not None:
cleanup_report = cleanup_owned_run(child,proton,env,compat)
cleanup_ok = cleanup_report['cleanup_confirmed']
if not cleanup_ok:
result = 'cleanup-failed'
cleanup_error = '; '.join(cleanup_report['errors'])
runner_error = (runner_error + '; ' if runner_error else '') + cleanup_error
external_unchanged = True
if external_inputs:
try: external_unchanged = verify_profile(external_inputs, a.external_addons, external_rules, external_cache, addons)
except (OSError, ValueError): external_unchanged = False
if not external_unchanged: result = 'external-inputs-changed'
stopped_evidence = seal_run(ROOT, session, captured_sources) if cleanup_ok and session else {}
if stopped_evidence and not stopped_evidence['source_unchanged_after_run']: result = 'source-changed'
summary = {**stopped_evidence, **bindings(captured_sources), 'external_addons_unchanged_after_run':external_unchanged, 'external_addon_count':len(external_inputs or {}), 'external_addon_inputs':'external-addons-inputs.json' if external_inputs else None, 'started_unix':started, 'result':result, 'result_before_cleanup':result_before_cleanup, 'cleanup_confirmed':cleanup_ok, 'cleanup_report':cleanup_report, 'runner_error':runner_error, 'session':str(session) if session else None, 'rounds_required':a.rounds, 'project':a.project, 'display':'headless Gamescope 1280x720/30Hz', 'dedicated_server':False, 'audio':'silent sink and per-test-stream mute'}
if session:
(session/'runner.json').write_text(json.dumps(summary, indent=2)+'\n')
shutil.copyfile(profile/'headless-launch.log', session/'headless-launch.log')
print(json.dumps(summary, indent=2), flush=True)
if result != 'smoke-completed': raise SystemExit(1)
# All native errors still fail the report even when the scripted sequence completed.
raise SystemExit(subprocess.call(['python3', str(ROOT/'tools/native_report.py'), str(session/'console.log'), '--require-completions', str(a.rounds)]))