From 3b920e0715a9483a97496eb0949cb407e41fb097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1=C4=8Dek?= Date: Thu, 15 Oct 2026 12:39:04 +0200 Subject: [PATCH] Build realistic roadblocks with staggered barriers and guarded positions --- .../ERR/00_Core/ERR_NativeRoadblocksProbe.c | 74 ++++++++++++- .../Scripts/Game/ERR/10_AI/ERR_Roadblocks.c | 101 ++++++++++++++---- docs/vanilla-resources.json | 4 + tests/test_roadblock_background.py | 28 ++--- tests/test_roadblock_layout.py | 98 +++++++++++++++++ tools/check.py | 2 +- 6 files changed, 267 insertions(+), 40 deletions(-) create mode 100644 tests/test_roadblock_layout.py diff --git a/addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRoadblocksProbe.c b/addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRoadblocksProbe.c index 1ab8c3b..0fc2eee 100644 --- a/addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRoadblocksProbe.c +++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRoadblocksProbe.c @@ -17,11 +17,79 @@ modded class ERR_Roadblocks m_Mode.Settings().Set("roadblockCount", 30); return super.Prepare(definition); } + protected int m_ERRLayoutPosts; + protected bool m_ERRLayoutFailed, m_ERRLayoutCaptured; + protected SCR_ManualCamera m_ERRLayoutCamera; + override protected void BuildPost() + { + string outcome; + if (!System.GetCLIParam("errSmokeOutcome", outcome) || outcome != "roadblocks") { super.BuildPost(); return; } + // Inspect the actual spawned models before the transaction clears its refs. + if (m_BuildHold) + { + bool valid = m_BuildCovers.Count() == 10 && m_BuildSpawned.Count() == 15; + for (int i = 0; i < m_BuildCovers.Count() && valid; i++) + { + ERR_RoadblockProp prop = m_BuildCovers[i]; + IEntity entity = m_BuildSpawned[i + 4]; + vector low, high; entity.GetBounds(low, high); + if (m_ERRLayoutPosts == 0) PrintFormat("ERR ROADBLOCK PROP NATIVE: prefab=%1 low=%2 high=%3", prop.Prefab, low, high); + if (Math.Max(Math.AbsFloat(low[0]), Math.AbsFloat(high[0])) > prop.HalfWidth + || Math.Max(Math.AbsFloat(low[2]), Math.AbsFloat(high[2])) > prop.HalfDepth || high[1] > prop.Height) valid = false; + // Every actual prop corner must stay outside the four-metre centre lane. + for (int x = 0; x < 2; x++) + for (int z = 0; z < 2; z++) + { + vector corner = low; + if (x) corner[0] = high[0]; + if (z) corner[2] = high[2]; + vector delta = entity.CoordToParent(corner) - m_Centre; + if (Math.AbsFloat(vector.Dot(delta, m_Right)) < 2) valid = false; + } + } + if (!valid) { m_ERRLayoutFailed = true; Print("ERR SMOKE FAIL: roadblock prop geometry or centre lane", LogLevel.ERROR); } + if (!m_ERRLayoutCaptured && m_Mode.Active()) + { + m_ERRLayoutCaptured = true; + EntitySpawnParams params = new EntitySpawnParams(); + m_ERRLayoutCamera = SCR_ManualCamera.Cast(GetGame().SpawnEntity(SCR_ManualCamera, GetGame().GetWorld(), params)); + if (m_ERRLayoutCamera) + { + vector camera[4]; + vector eye = m_Centre - m_Forward * 18 + m_Right * 12 + "0 13 0"; + Math3D.DirectionAndUpMatrix(vector.Direction(eye, m_Centre), "0 1 0", camera); + camera[3] = eye; m_ERRLayoutCamera.SetWorldTransform(camera); m_ERRLayoutCamera.SetVerticalFOV(65); + GetGame().GetCameraManager().SetCamera(m_ERRLayoutCamera); + GetGame().GetCallqueue().CallLater(ERR_ProbeCaptureLayout, 3000, false); + } + } + m_ERRLayoutPosts++; + } + super.BuildPost(); + } + protected void ERR_ProbeCaptureLayout() + { + System.MakeScreenshot("$logs:err-roadblock-layout"); + GetGame().GetCallqueue().CallLater(ERR_ProbeCloseLayoutCamera, 1000, false); + } + protected void ERR_ProbeCloseLayoutCamera() + { + if (!m_ERRLayoutCamera) return; + GetGame().GetCameraManager().SetCamera(GetGame().GetPlayerController().GetPlayerCamera()); + m_ERRLayoutCamera.Terminate(); delete m_ERRLayoutCamera; m_ERRLayoutCamera = null; + } + override void Stop() + { + GetGame().GetCallqueue().Remove(ERR_ProbeCaptureLayout); + GetGame().GetCallqueue().Remove(ERR_ProbeCloseLayoutCamera); + ERR_ProbeCloseLayoutCamera(); + super.Stop(); + } int ERR_ProbePostCount() { return m_Positions.Count(); } bool ERR_ProbeBackgroundPending() { return m_Background; } bool ERR_ProbeCompleteRoadblocks() { - if (m_Target != 30 || m_Positions.Count() != 30 || m_Background || !m_Ready) return false; + if (m_ERRLayoutFailed || m_ERRLayoutPosts < 30 || m_Target != 30 || m_Positions.Count() != 30 || m_Background || !m_Ready) return false; foreach (SCR_AIGroup group : m_Groups) { ERR_ForceRecord record = m_Mode.Forces().FindGroup(group); @@ -58,7 +126,7 @@ modded class ERR_GameMode super.EOnInit(owner); string outcome; if (!Replication.IsServer() || SCR_Global.IsEditMode() || !System.GetCLIParam("errSmokeOutcome", outcome) || outcome != "roadblocks") return; - m_ERRRoadblockDeadline = System.GetTickCount() + 600000; + m_ERRRoadblockDeadline = System.GetTickCount() + 900000; GetGame().GetCallqueue().CallLater(ERR_RoadblockProbeTick, 500, true); OnStop().Insert(ERR_StopRoadblockProbe); } @@ -85,7 +153,7 @@ modded class ERR_GameMode } if (posts.ERR_ProbeBackgroundPending()) return; if (!posts.ERR_ProbeCompleteRoadblocks() || posts.ERR_ProbePostCount() <= m_ERRRoadblockInitial) { ERR_RoadblockProbeFail("30 physical four-guard posts, ledger and HQ admission"); return; } - Print("ERR SMOKE PASS: roadblocks 30 physical posts / 120 guards; background completed after prison unlock, no extra posts on repeat tick"); + Print("ERR SMOKE PASS: roadblocks 30 physical posts / 120 guards / 300 checkpoint props; verified model footprints and centre lane; background completed after prison unlock, no extra posts on repeat tick"); ERR_StopRoadblockProbe(); Transition(ERR_Phase.DEFEAT); Print("ERR SMOKE COMPLETE: roadblocks background completion on Everon; scripted local fixture"); diff --git a/addons/core/Scripts/Game/ERR/10_AI/ERR_Roadblocks.c b/addons/core/Scripts/Game/ERR/10_AI/ERR_Roadblocks.c index 33a5e21..5f2ff9a 100644 --- a/addons/core/Scripts/Game/ERR/10_AI/ERR_Roadblocks.c +++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_Roadblocks.c @@ -22,6 +22,20 @@ class ERR_RoadblockRules } } +// A small, staged composition: each prop has its own heading and clearance box. +class ERR_RoadblockProp +{ + ResourceName Prefab; + vector Position, Angles, Right, Forward; + float HalfWidth, HalfDepth, Height; + void ERR_RoadblockProp(ResourceName prefab, vector position, vector forward, float halfWidth, float halfDepth, float height) + { + Prefab = prefab; Position = position; Forward = forward; + Right = Vector(forward[2], 0, -forward[0]); Angles = forward.VectorToAngles(); + HalfWidth = halfWidth; HalfDepth = halfDepth; Height = height; + } +} + class ERR_Roadblocks { protected ERR_GameMode m_Mode; @@ -39,11 +53,15 @@ class ERR_Roadblocks protected SCR_AIGroup m_BuildGroup; protected AIWaypoint m_BuildHold; protected ref array m_BuildSpawned = {}; - protected ref array m_BuildPositions = {}, m_BuildCovers = {}; + protected ref array m_BuildPositions = {}; + protected ref array m_BuildCovers = {}; protected int m_BuildActors, m_BuildCoverCount; protected ref array m_Roster = {}; protected ref array m_Loaded = {}; - static const ResourceName COVER = "{CFFEB23FBEA00322}Prefabs/Props/Military/Sandbags/Sandbag_01_short_burlap.et"; + static const ResourceName COVER = "{21D83950DF36F250}Prefabs/Props/Military/Sandbags/Sandbag_01_long_burlap.et"; + static const ResourceName BARRIER = "{64C9C1D1C2C1B38D}Prefabs/Props/Infrastructure/Roadblocks/RoadBlock_01.et"; + static const ResourceName OBSTACLE = "{A46D45922F8CAD6C}Prefabs/Props/Military/Fortification/CzechHedgehog_01_painted.et"; + static const ResourceName SIGN = "{C1977A326F2AA011}Prefabs/Structures/Signs/Military/SignCheckpoint_01_stop.et"; void ERR_Roadblocks(ERR_GameMode mode) { m_Mode = mode; m_Mining = new ERR_RoadblockMining(mode); } void MineTick(array units, array attempted, float now) { if (!m_Stopped) m_Mining.Tick(units, attempted, now); } bool Contains(SCR_AIGroup group) { return group && m_Groups.Contains(group); } @@ -72,7 +90,7 @@ class ERR_Roadblocks if (profile && profile.IsValid()) source = profile.GetResource().ToEntitySource(); if (!source || !source.Get("m_aUnitPrefabSlots", m_Roster) || m_Roster.IsEmpty()) return false; // Preload before physical creation. A failed prefab never leaves half a post. - array resources = {COVER, "{EC9F1AA42D0B7863}Prefabs/ERR/PrisonPatrol.et", ERR_Resources.DefendWaypoint()}; + array resources = {COVER, BARRIER, OBSTACLE, SIGN, "{EC9F1AA42D0B7863}Prefabs/ERR/PrisonPatrol.et", ERR_Resources.DefendWaypoint()}; foreach (ResourceName actor : m_Roster) resources.Insert(actor); foreach (ResourceName prefab : resources) { @@ -119,8 +137,8 @@ class ERR_Roadblocks if (m_Forward.Length() < 0.1) return false; m_Forward.Normalize(); m_Right = Vector(m_Forward[2], 0, -m_Forward[0]); - // Keep all physical cover outside the travelled road. Even a narrow road has - // a >=6m centre gap between these compact sandbag positions. + // Protected guard positions sit on the shoulders; approach barriers narrow + // wider roads while preserving a straight vehicle passage on every width. m_Shoulder = Math.Max(road.GetWidth() * 0.5 + 1.5, 4); m_Candidates++; m_CandidateStarted = System.GetTickCount(); return true; @@ -135,32 +153,68 @@ class ERR_Roadblocks } return true; } - protected bool CoverClear(vector point) + protected bool CoverClear(ERR_RoadblockProp prop) { + vector point = prop.Position; if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), point)) return false; for (int x = -1; x <= 1; x++) for (int z = -1; z <= 1; z++) { - vector sample = ERR_Actors.Ground(point + Vector(x * 1.5, 0, z * 1.5)); + vector sample = ERR_Actors.Ground(point + prop.Right * (x * prop.HalfWidth) + prop.Forward * (z * prop.HalfDepth)); if (Math.AbsFloat(sample[1] - point[1]) > 0.3) return false; } + // Planned props have separate local footprints. Exclude our own transaction + // from the enclosing world-axis trace, which otherwise falsely overlaps + // adjacent pieces at diagonal road headings. Still reject a moving guard. + foreach (IEntity builtEntity : m_BuildSpawned) + { + if (!ChimeraCharacter.Cast(builtEntity)) continue; + vector delta = builtEntity.GetOrigin() - point; + if (Math.AbsFloat(vector.Dot(delta, prop.Right)) < prop.HalfWidth + 0.6 + && Math.AbsFloat(vector.Dot(delta, prop.Forward)) < prop.HalfDepth + 0.6 && Math.AbsFloat(delta[1]) < 2) return false; + } + // Conservative world-axis enclosure of the rotated prop, including its feet. + float extentX = Math.AbsFloat(prop.Right[0]) * prop.HalfWidth + Math.AbsFloat(prop.Forward[0]) * prop.HalfDepth; + float extentZ = Math.AbsFloat(prop.Right[2]) * prop.HalfWidth + Math.AbsFloat(prop.Forward[2]) * prop.HalfDepth; TraceBox trace = new TraceBox(); trace.Start = point + "0 0.15 0"; trace.End = trace.Start + "0 0.01 0"; - trace.Mins = "-1.5 0 -1.5"; trace.Maxs = "1.5 2 1.5"; trace.Flags = TraceFlags.WORLD | TraceFlags.ENTS; + trace.Mins = Vector(-extentX, 0, -extentZ); trace.Maxs = Vector(extentX, prop.Height, extentZ); trace.Flags = TraceFlags.WORLD | TraceFlags.ENTS; + trace.ExcludeArray = m_BuildSpawned; return GetGame().GetWorld().TraceMove(trace, null) == 1; } + protected void Layout(out array covers) + { + covers.Clear(); + // Footprints enclose the native model bounds, including small placement margins. + for (int side = -1; side <= 1; side += 2) + { + // Two long sandbag walls shelter each pair from the road and its approach. + AddProp(covers, COVER, side * m_Shoulder, 0, m_Right, 1.9, 0.7, 1); + AddProp(covers, COVER, side * (m_Shoulder + 2.8), side * 2.8, m_Forward, 1.9, 0.7, 1); + // Opposing, staggered stop lines: the inner ends leave a >=4m centre lane. + AddProp(covers, BARRIER, side * 3.3, side * 8, m_Forward, 1.2, 0.4, 1.3); + AddProp(covers, OBSTACLE, side * (m_Shoulder + 4), side * 8, m_Forward, 1, 1, 1.6); + AddProp(covers, SIGN, side * 3, side * 12, m_Forward * (side * 1.0), 0.45, 0.4, 0.8); + } + } + protected void AddProp(array covers, ResourceName prefab, float across, float along, vector forward, float halfWidth, float halfDepth, float height) + { + vector point = ERR_Actors.Ground(m_Centre + m_Right * across + m_Forward * along); + covers.Insert(new ERR_RoadblockProp(prefab, point, forward, halfWidth, halfDepth, height)); + } protected void Rollback(array spawned, SCR_AIGroup group) { foreach (IEntity entity : spawned) ERR_Actors.DeleteSpawned(entity); ERR_Actors.DeleteSpawned(group); } // Start a transaction; physical pieces are created separately in BuildPost. - protected bool SpawnPost(array positions, array covers) + protected bool SpawnPost(array positions, array covers) { - if (!PlayersClear() || m_Mode.Forces().Count() >= 250 || m_BuildGroup || positions.Count() != 4 || covers.Count() != 2) return false; + if (!PlayersClear() || m_Mode.Forces().Count() >= 250 || m_BuildGroup || positions.Count() != 4 || covers.Count() != 10) return false; m_BuildGroup = SCR_AIGroup.Cast(ERR_Actors.Spawn("{EC9F1AA42D0B7863}Prefabs/ERR/PrisonPatrol.et", m_Centre)); if (!m_BuildGroup) return false; m_BuildGroup.SetFaction(m_Mode.EnemyFaction()); m_BuildGroup.SetSpawnImmediately(false); m_BuildGroup.SetDeleteWhenEmpty(false); m_BuildGroup.SetPermanentLOD(0); - m_BuildPositions.Copy(positions); m_BuildCovers.Copy(covers); + m_BuildPositions.Copy(positions); m_BuildCovers.Clear(); + foreach (ERR_RoadblockProp plannedProp : covers) m_BuildCovers.Insert(plannedProp); m_BuildActors = 0; m_BuildCoverCount = 0; return true; } @@ -188,9 +242,9 @@ class ERR_Roadblocks } if (m_BuildCoverCount < m_BuildCovers.Count()) { - vector coverPosition = m_BuildCovers[m_BuildCoverCount]; - if (!CoverClear(coverPosition)) { CancelPost(); return; } - IEntity cover = ERR_Actors.Spawn(COVER, coverPosition, m_Forward.VectorToAngles()); + ERR_RoadblockProp prop = m_BuildCovers[m_BuildCoverCount]; + if (!CoverClear(prop)) { CancelPost(); return; } + IEntity cover = ERR_Actors.Spawn(prop.Prefab, prop.Position, prop.Angles); if (!cover) { CancelPost(); return; } m_BuildSpawned.Insert(cover); m_BuildCoverCount++; return; @@ -210,26 +264,26 @@ class ERR_Roadblocks m_BuildGroup.AddWaypoint(m_BuildHold); m_Mode.RegisterGroup(m_BuildGroup); m_BuildGroup.ActivateAI(); m_Mining.Register(m_BuildGroup, m_BuildHold, m_Right); - PrintFormat("Reintroduced [roadblocks]: %1 at %2 guards=4 roadPassage=open HQ=registered", id, m_Centre); + PrintFormat("Reintroduced [roadblocks]: %1 at %2 guards=4 props=10 roadPassage=open HQ=registered", id, m_Centre); m_BuildSpawned.Clear(); m_BuildGroup = null; m_BuildHold = null; m_BuildPositions.Clear(); m_BuildCovers.Clear(); m_NextSearch = System.GetTickCount() + 2000; } // 0: reject, 1: awaiting native tile, 2: ready. - protected int Plan(out array positions, out array covers) + protected int Plan(out array positions, out array covers) { positions.Clear(); covers.Clear(); NavmeshWorldComponent mesh = GetGame().GetAIWorld().GetNavmeshWorldComponent("Soldiers"); if (!mesh || !PlayersClear()) return 0; + Layout(covers); + foreach (ERR_RoadblockProp prop : covers) + if (!CoverClear(prop)) return 0; map reserved = new map(); for (int side = -1; side <= 1; side += 2) { - vector cover = ERR_Actors.Ground(m_Centre + m_Right * (side * m_Shoulder)); - if (!CoverClear(cover)) return 0; - covers.Insert(cover); for (int row = -1; row <= 1; row += 2) { - vector point = ERR_Actors.Ground(m_Centre + m_Right * (side * (m_Shoulder + 3)) + m_Forward * (row * 3)); + vector point = ERR_Actors.Ground(m_Centre + m_Right * (side * (m_Shoulder + 2.5)) + m_Forward * (side * (row - 1.5))); if (!mesh.IsTileLoaded(point)) { mesh.LoadTileIn(point); return 1; } vector reachable; if (!mesh.GetReachablePoint(point, 1, reachable) || vector.DistanceXZ(point, reachable) > 1 || !m_Placement.Clear(point, reserved)) return 0; @@ -260,7 +314,7 @@ class ERR_Roadblocks { m_Ready = true; m_Background = true; m_Definition = definition; m_NextReport = System.GetTickCount() + 60000; - GetGame().GetCallqueue().CallLater(BackgroundStep, 500, true); + GetGame().GetCallqueue().CallLater(BackgroundStep, 200, true); ERR_HQ.Event("ROADBLOCKS", Status() + " Initial search yielded; remaining posts will continue in background."); return ERR_PrepareResult.READY; } @@ -285,7 +339,8 @@ class ERR_Roadblocks } return; } - array positions = {}, covers = {}; + array positions = {}; + array covers = {}; int plan = Plan(positions, covers); if (plan == 1 && System.GetTickCount() - m_CandidateStarted < 2000) return; if (plan == 2) @@ -318,7 +373,7 @@ class ERR_Roadblocks if (now >= m_NextReport) { m_NextReport = now + 60000; - PrintFormat("Reintroduced [roadblocks background]: placed=%1/%2 attempts=%3 steps=%4 workMs=%5 maxStepMs=%6; still searching safe locations", m_Positions.Count(), m_Target, m_Attempts, m_BackgroundSteps, m_BackgroundWork, m_MaxBackgroundStep); + PrintFormat("Reintroduced [roadblocks background]: placed=%1/%2 attempts=%3 steps=%4 workMs=%5 maxStepMs=%6 candidates=%7 plans=%8; still searching safe locations", m_Positions.Count(), m_Target, m_Attempts, m_BackgroundSteps, m_BackgroundWork, m_MaxBackgroundStep, m_Candidates, m_Planned); } } void Stop() diff --git a/docs/vanilla-resources.json b/docs/vanilla-resources.json index acf5746..23e06ca 100644 --- a/docs/vanilla-resources.json +++ b/docs/vanilla-resources.json @@ -110,7 +110,10 @@ "Prefabs/MP/ScriptedChatEntity.et": "F69BC912AC8236F9", "Prefabs/Props/EmptyStorages/USSR_AmmoBox_empty.et": "EBBC859CCD94D96D", "Prefabs/Props/Furniture/Table_01.et": "53506DCB398C7EBE", + "Prefabs/Props/Infrastructure/Roadblocks/RoadBlock_01.et": "64C9C1D1C2C1B38D", "Prefabs/Props/Military/Antennas/Antenna_FIA_02/Antenna_FIA_02_base.et": "B3949F6D215A9F78", + "Prefabs/Props/Military/Fortification/CzechHedgehog_01_painted.et": "A46D45922F8CAD6C", + "Prefabs/Props/Military/Sandbags/Sandbag_01_long_burlap.et": "21D83950DF36F250", "Prefabs/Props/Military/Sandbags/Sandbag_01_short_burlap.et": "CFFEB23FBEA00322", "Prefabs/Props/Wrecks/UH1H_wreck/UH1H_wreck_static.et": "74DCE1AEC6832E18", "Prefabs/Structures/BuildingParts/Doors/DoorSet_Base.et": "87CADB5ADC989993", @@ -119,6 +122,7 @@ "Prefabs/Structures/Military/Camps/TentSmallUS_01/TentSmallUS_01.et": "01AE5FD77A9A4C21", "Prefabs/Structures/Military/Camps/TentUSSR_01/TentUSSR_01_base.et": "BE5D5CFA90224646", "Prefabs/Structures/Military/Camps/TentUS_01/TentUSMedical_01.et": "F8427C3EFC52644E", + "Prefabs/Structures/Signs/Military/SignCheckpoint_01_stop.et": "C1977A326F2AA011", "Prefabs/Structures/Walls/BarbedWire/BarbedTape_01/BarbedTape_01_Long_noStake.et": "46904AF5D473FB75", "Prefabs/Structures/Walls/BarbedWire/BarbedTape_01/BarbedTape_01_Short_noStake.et": "CAE1588B6265A2AB", "Prefabs/Structures/Walls/Concrete/ConcreteWall_01/ConcreteWall_01_3m.et": "60E5729BEA18F875", diff --git a/tests/test_roadblock_background.py b/tests/test_roadblock_background.py index 9816e3b..9c2cab7 100644 --- a/tests/test_roadblock_background.py +++ b/tests/test_roadblock_background.py @@ -5,8 +5,8 @@ p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_Roadblocks.c' s='\n'.join(body(p,signature) for signature in ('protected ERR_PrepareResult Finish(', 'ERR_PrepareResult Prepare(', 'protected void SearchStep(', 'protected void BackgroundStep()', 'void Stop()')) s=re.sub(r'#ifdef WORKBENCH.*?#endif','',s,flags=re.S) s=s.replace('protected ', '').replace('ERR_ScenarioDefinition definition', 'Definition* definition') -s=s.replace('array positions = {}, covers = {};', 'Array positions, covers;') -s=s.replace('GetGame().GetCallqueue().CallLater(BackgroundStep, 500, true)', 'queue.Schedule(500)').replace('GetGame().GetCallqueue().Remove(BackgroundStep)', 'queue.Remove()') +s=s.replace('array positions = {};', 'Array positions;').replace('array covers = {};', 'Array covers;') +s=s.replace('GetGame().GetCallqueue().CallLater(BackgroundStep, 200, true)', 'queue.Schedule(200)').replace('GetGame().GetCallqueue().Remove(BackgroundStep)', 'queue.Remove()') s=s.replace('GetGame().GetWorldEntity().GetWorldBounds(m_Low, m_High)', 'Bounds(m_Low, m_High)').replace('GetGame().GetPlayerManager().GetPlayerCount()', 'players') s=s.replace('m_Mode.Settings().Get("roadblockCount")', 'm_Mode->target').replace('m_Mode.Forces().Count()', 'm_Mode->ledger') s=s.replace('m_Mode.', 'm_Mode->').replace('System.GetTickCount()', 'Now()').replace('Replication.IsServer()', 'server').replace('ERR_GameMode.Get()', 'current') @@ -27,7 +27,7 @@ int clockMs=0,players=1;bool server=true; int Now(){return clockMs;}bool GetGame(){return true;}void Bounds(int&,int&){}bool ValidBounds(int,int){return true;} templatevoid PrintFormat(T...){}void Event(string,string){} templatestruct Array:std::vector{int Count(){return this->size();}void Clear(){this->clear();}}; -struct Queue{int scheduled=0,removed=0;bool active=false;void Schedule(int ms){assert(ms==500);scheduled++;active=true;}void Remove(){removed++;active=false;}}; +struct Queue{int scheduled=0,removed=0;bool active=false;void Schedule(int ms){assert(ms==200);scheduled++;active=true;}void Remove(){removed++;active=false;}}; struct Mode{int target=30,ledger=0,phase=Phase::PRISON;bool active=true;int Phase(){return phase;}bool Active(){return active;}};Mode* current=nullptr; struct Definition{}; struct Mining{bool stopped=false;void Stop(){stopped=true;}}; @@ -99,9 +99,10 @@ print('PASS: actual roadblock scheduler yields initial deadline/attempt cap to b # Run actual staged physical creation and rollback, with native entities doubled. s='\n'.join(body(p,sig) for sig in ('protected bool SpawnPost(', 'protected void CancelPost()', 'protected void BuildPost()')) -s=s.replace('protected ', '').replace('array positions','Array& positions').replace('array covers','Array& covers') +s=s.replace('protected ', '').replace('array positions','Array& positions').replace('array covers','Array& covers').replace('ERR_RoadblockProp prop', 'Prop prop') s=s.replace('SCR_AIGroup.Cast','Node::Cast').replace('AIWaypoint.Cast','Node::Cast').replace('IEntity actor','Node* actor').replace('IEntity cover','Node* cover') s=s.replace('foreach (IEntity ownedEntity : m_BuildSpawned)','for (Node* ownedEntity : m_BuildSpawned)').replace('vector point','int point').replace('vector coverPosition','int coverPosition') +s=s.replace('foreach (ERR_RoadblockProp plannedProp : covers)', 'for (Prop plannedProp : covers)') s=s.replace('m_Forward.VectorToAngles()', '0').replace('m_Positions.Count().ToString()', 'std::to_string(m_Positions.Count())') s=s.replace('GetGame().GetWorld().GetWorldTime()', 'Now()').replace('System.GetTickCount()', 'Now()').replace('null','nullptr') s=s.replace('m_BuildGroup.', 'm_BuildGroup->').replace('m_BuildHold.', 'm_BuildHold->') @@ -122,31 +123,32 @@ struct Ledger{int count=0,registered=0;bool accept=true;int Count(){return count struct Mode{Ledger ledger;int owned=0,groups=0;Ledger& Forces(){return ledger;}int EnemyFaction(){return 1;}void Own(Node*n){assert(n);owned++;}void RegisterGroup(Node*){groups++;}}; struct Placement{bool clear=true;bool Clear(int,void*){return clear;}}; struct Mining{int registered=0;void Register(Node*,Node*,int){registered++;}}; +struct Prop{string Prefab="cover";int Position=5,Angles=0;}; struct Build{ Mode m_Mode;Placement m_Placement;Mining m_Mining;Node* m_BuildGroup=nullptr;Node* m_BuildHold=nullptr; - Array m_BuildPositions,m_BuildCovers,m_Positions;Arraym_BuildSpawned,m_Groups;Arraym_Roster={"one","two"}; + Array m_BuildPositions,m_Positions;Arraym_BuildCovers;Arraym_BuildSpawned,m_Groups;Arraym_Roster={"one","two"}; int m_BuildActors=0,m_BuildCoverCount=0,m_Centre=10,m_Right=0,m_NextSearch=0,rolledBack=0;string COVER="cover";bool playersClear=true,coversClear=true; - bool PlayersClear(){return playersClear;}bool CoverClear(int){return coversClear;} + bool PlayersClear(){return playersClear;}bool CoverClear(Prop){return coversClear;} void Rollback(Array&entities,Node*group){rolledBack+=entities.Count()+(group?1:0);} '''+s+r''' }; -int main(){Array positions={1,2,3,4},covers={5,6}; +int main(){Array positions={1,2,3,4};Arraycovers(10); {Build b;int start=ERR_Actors::calls;assert(b.SpawnPost(positions,covers));assert(ERR_Actors::calls==start+1); - for(int piece=0;piece<7;piece++){int calls=ERR_Actors::calls;b.BuildPost();assert(ERR_Actors::calls==calls+1&&b.m_Positions.Count()==0&&b.m_Mode.owned==0);} - b.BuildPost();assert(!b.m_BuildGroup&&b.m_Positions.Count()==1&&b.m_Groups.Count()==1&&b.m_Mode.owned==8&&b.m_Mode.groups==1&&b.m_Mining.registered==1&&b.m_NextSearch==2100); + for(int piece=0;piece<15;piece++){int calls=ERR_Actors::calls;b.BuildPost();assert(ERR_Actors::calls==calls+1&&b.m_Positions.Count()==0&&b.m_Mode.owned==0);} + b.BuildPost();assert(!b.m_BuildGroup&&b.m_Positions.Count()==1&&b.m_Groups.Count()==1&&b.m_Mode.owned==16&&b.m_Mode.groups==1&&b.m_Mining.registered==1&&b.m_NextSearch==2100); } // Player approaches at any stage: no registered post and every owned piece removed. - for(int stage=0;stage<=7;stage++){Build b;assert(b.SpawnPost(positions,covers));for(int i=0;i covers', 'Array& covers') +s=s.replace('ResourceName ', 'string ').replace('vector ', 'Vec ').replace('ERR_Actors.Ground','Ground').replace('new ERR_RoadblockProp','new Prop') +run(r''' +#include +#include +#include +#include +#include +#include +using string=std::string; +struct Vec{double x=0,y=0,z=0;Vec operator+(Vec b)const{return{x+b.x,y+b.y,z+b.z};}Vec operator-(Vec b)const{return{x-b.x,y-b.y,z-b.z};}Vec operator*(double k)const{return{x*k,y*k,z*k};}}; +double dot(Vec a,Vec b){return a.x*b.x+a.y*b.y+a.z*b.z;} +Vec Ground(Vec p){p.y=50;return p;} +templatestruct Array:std::vector{void Clear(){for(auto*p:*this)delete p;this->clear();}void Insert(T p){this->push_back(p);}}; +struct Prop{string Prefab;Vec Position,Forward,Right;double HalfWidth,HalfDepth,Height; + Prop(string p,Vec v,Vec f,double w,double d,double h):Prefab(p),Position(v),Forward(f),Right({f.z,0,-f.x}),HalfWidth(w),HalfDepth(d),Height(h){} +}; +struct Checkpoint{Vec m_Centre={1000,50,2000},m_Forward,m_Right;double m_Shoulder; + string COVER="cover",BARRIER="barrier",OBSTACLE="obstacle",SIGN="sign"; +'''+s+r''' +}; +int main(){ + for(double width:{4.,6.,10.,16.})for(int degrees=0;degrees<360;degrees+=15){ + Checkpoint l;double angle=degrees*3.141592653589793/180; + l.m_Forward={std::sin(angle),0,std::cos(angle)};l.m_Right={std::cos(angle),0,-std::sin(angle)}; + l.m_Shoulder=std::max(width*.5+1.5,4.);Arrayprops;l.Layout(props);assert(props.size()==10); + std::mapcounts;std::vectorbarriers; + for(auto*p:props){counts[p->Prefab]++; + Vec delta=p->Position-l.m_Centre;double across=dot(delta,l.m_Right),along=dot(delta,l.m_Forward); + // Whole footprints (not just origins) preserve a four-metre straight passage. + double reach=std::abs(dot(p->Right,l.m_Right))*p->HalfWidth+std::abs(dot(p->Forward,l.m_Right))*p->HalfDepth; + assert(std::abs(across)-reach>=2.-1e-8); + if(p->Prefab=="barrier")barriers.push_back(along); + if(p->Prefab=="sign")assert(dot(p->Forward,l.m_Forward)*along>0); // both approaches face outward + // Guard starting volumes cannot intersect any prop in the planned post. + for(int side:{-1,1})for(int row:{-1,1}){ + Vec guard=l.m_Centre+l.m_Right*(side*(l.m_Shoulder+2.5))+l.m_Forward*(side*(row-1.5)); + Vec rel=guard-p->Position; + assert(std::abs(dot(rel,p->Right))>p->HalfWidth+.45 || std::abs(dot(rel,p->Forward))>p->HalfDepth+.45); + } + } + assert(counts["cover"]==4&&counts["barrier"]==2&&counts["obstacle"]==2&&counts["sign"]==2); + assert(barriers.size()==2&&std::abs(barriers[0]-barriers[1])>15.9); + // No physical prop footprint intersects another (separating axes). + for(size_t i=0;iRight,a->Forward,b->Right,b->Forward}){ + double ar=std::abs(dot(a->Right,axis))*a->HalfWidth+std::abs(dot(a->Forward,axis))*a->HalfDepth; + double br=std::abs(dot(b->Right,axis))*b->HalfWidth+std::abs(dot(b->Forward,axis))*b->HalfDepth; + if(std::abs(dot(a->Position-b->Position,axis))>=ar+br)separate=true; + } + assert(separate); + } + props.Clear(); + } +} +''') +print('PASS: shipped checkpoint layout preserves vehicle lane, opposing signs, staggered barriers, clear guard slots and separate props across 4–16m roads and all tested headings') + +# Execute the actual clearance path: owned pieces are excluded only after +# checking guards; terrain and foreign obstructions remain authoritative. +s=body(p,'protected bool CoverClear(').replace('protected ', '').replace('ERR_RoadblockProp prop','Prop prop') +s=s.replace('vector ', 'Vec ').replace('foreach (IEntity builtEntity : m_BuildSpawned)', 'for (Node* builtEntity : m_BuildSpawned)') +s=s.replace('ChimeraCharacter.Cast(builtEntity)', 'builtEntity->character').replace('builtEntity.GetOrigin()', 'builtEntity->point') +s=s.replace('ERR_Actors.Ground','Ground').replace('Vector(', 'Vec(').replace('Math.AbsFloat', 'std::abs').replace('vector.Dot','dot') +s=s.replace('ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), point)', 'water').replace('GetGame().GetWorld().TraceMove(trace, null)', 'TraceMove(trace)') +s=s.replace('TraceBox trace = new TraceBox();','TraceBox trace;').replace('"0 0.15 0"','Vec(0,.15,0)').replace('"0 0.01 0"','Vec(0,.01,0)').replace('TraceFlags.', 'TraceFlags::') +run(r''' +#include +#include +#include +struct Vec {double v[3];Vec(double x=0,double y=0,double z=0):v{x,y,z}{}double& operator[](int i){return v[i];}Vec operator+(Vec b){return {v[0]+b[0],v[1]+b[1],v[2]+b[2]};}Vec operator-(Vec b){return {v[0]-b[0],v[1]-b[1],v[2]-b[2]};}Vec operator*(double n){return {v[0]*n,v[1]*n,v[2]*n};}}; +double dot(Vec a,Vec b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} +struct Node{bool character;Vec point;}; +struct Prop{Vec Position,Right={1,0,0},Forward={0,0,1};double HalfWidth=1.9,HalfDepth=.7,Height=1;}; +namespace TraceFlags{int WORLD=1,ENTS=2;} +struct TraceBox{Vec Start,End,Mins,Maxs;int Flags=0;std::vectorExcludeArray;}; +struct Clearance{ + bool water=false,blocked=false;double slope=0;int traces=0;std::vectorm_BuildSpawned; + Vec Ground(Vec p){p[1]=slope*p[0];return p;} + double TraceMove(TraceBox trace){traces++;assert(trace.Flags==3&&trace.ExcludeArray==m_BuildSpawned);assert(std::abs(trace.Mins[0]+1.9)<1e-5&&std::abs(trace.Maxs[2]-.7)<1e-5);return blocked?0:1;} +'''+s+r''' +}; +int main(){ + Prop prop;Clearance c;assert(c.CoverClear(prop)); + c.water=true;assert(!c.CoverClear(prop));c.water=false; + c.slope=.2;assert(!c.CoverClear(prop));c.slope=0; + c.blocked=true;assert(!c.CoverClear(prop));c.blocked=false; + Node ownProp{false,{0,0,0}},guard{true,{3,0,0}};c.m_BuildSpawned={&ownProp,&guard};assert(c.CoverClear(prop)); + int traces=c.traces;guard.point={2,0,0};assert(!c.CoverClear(prop)&&c.traces==traces); + guard.point={0,0,1.4};assert(c.CoverClear(prop));guard.point={0,0,1};assert(!c.CoverClear(prop)); +} +''') +print('PASS: actual prop clearance excludes owned geometry while retaining moved-guard, foreign-obstruction, water and terrain rejection') diff --git a/tools/check.py b/tools/check.py index 136c832..e3995f2 100644 --- a/tools/check.py +++ b/tools/check.py @@ -121,7 +121,7 @@ def check(): assert set(mapped)==set(inventory['script_paths']), 'Original inventory has untracked or unknown entries' print(f'PASS: {len(mapped)} original script entries tracked across {len(feature_ids)} unfinished responsibilities') print(f'PASS: {len(ids)} isolated addons, acyclic dependencies, {len(resources)} unique resources, exact vanilla/authorized external references') - for name in ['test_optional_motorbikes.py', 'test_server_memory_sampler.py', 'test_roadblocks.py', 'test_roadblock_background.py', 'test_roadblock_mining.py', 'test_crash_start.py', 'test_start_settings.py', 'test_intercept_vehicle_footprint.py', 'test_bmp_intercept_geometry.py', 'test_native_random_radius_trace.py', 'test_native_random_radius_runner.py', 'test_native_supply_travel.py', 'test_native_supply_travel_hook.py', 'test_native_supply_travel_runner.py', 'test_native_mounted_intercept.py', 'test_hq_mounted_dispatch.py', 'test_hq_mounted_intercept.py', 'test_hq_mounted_intercept_pair.py', 'test_convoy_intercept_pair.py', 'test_native_maintenance_refuel.py', 'test_mine_intel.py', 'test_mine_intel_discovery_integration.py', 'test_infantry_combat.py', 'test_enemy_medicine.py', 'test_hunter_combat.py', 'test_hunter_stuck.py', 'test_hq_local_awareness.py', 'test_unconscious_controls.py', 'test_town_garrisons.py', 'test_field_sites.py', 'test_gameplay_session_regressions.py', 'test_gameplay_review_fixes.py', 'test_server_log_regressions.py', 'test_mortar_load_ack.py', 'test_mortar_spotting.py', 'test_hunter_profiles.py', 'test_hunter_lifecycle.py', 'test_hunter_capture.py', 'test_hunter_opening.py', 'test_hunter_search_memory.py', 'test_hunter_reserve.py', 'test_hunter_sector_search.py', 'test_enemy_awareness.py', 'test_intel_body_inspection.py', 'test_body_recovery_lifecycle.py', 'test_radio_document_defence.py', 'test_air_support_policy.py', 'test_air_logistics_orders.py', 'test_air_recon.py', 'test_scouts.py', 'test_mortar_station.py', 'test_random_settings.py', 'test_debug_ai.py', 'test_generated_sites.py', 'test_terrain_sites.py', 'test_terrain_adapter.py', 'test_terrain_airfields.py','test_terrain_takeover.py', 'test_terrain_presentation.py', 'test_terrain_selection.py', 'test_faction_selection.py', 'test_faction_menu.py', 'test_content_policy.py', 'test_hq_road_intercept.py', 'test_hq_delivered_task.py', 'test_hq_local_handoff.py', 'test_hq_local_handoff_intake.py', 'test_native_local_handoff_probe.py', 'test_infantry_resupply_waypoints.py', 'test_infantry_placement_settle.py', 'test_native_infantry_fixture_lifecycle.py', 'test_native_infantry_cargo.py', 'test_native_infantry_setup.py', 'test_infantry_ammo_access.py', 'test_infantry_resupply_planning.py', 'test_infantry_supply_pool.py', 'test_hq_multiconvoy_supply.py', 'test_enemy_gunner_skill.py', 'test_native_memory_deadlines.py', 'test_convoy_auto_settings.py', 'test_convoy_startup.py', 'test_failure_result_refresh.py', 'test_infantry_resupply_coordinator.py', 'test_infantry_mine_coordinator.py', 'test_infantry_mine_transfer.py', 'test_infantry_ammo_transfer.py', 'test_finite_magazine_reservation.py', 'test_mounted_ammo_transfer.py', 'test_mounted_ammo_pickup.py', 'test_mounted_resupply.py', 'test_mounted_supply_integration.py', 'test_hq_ammo_reporting.py', 'test_enemy_radio.py', 'test_native_maintenance_stock.py', 'test_packed_external_game.py', 'test_hq_radio_node.py', 'test_vehicle_maintenance_stock.py', 'test_optional_transport.py', 'test_vehicle_entry_diagnostics.py', 'test_vehicle_security_native_probe.py', 'test_vehicle_security.py', 'test_vehicle_alarm.py', 'test_vehicle_alarm_perception.py', 'test_native_convoy_mine_stock.py', 'test_native_mine_stock_runner.py', 'test_native_mounted_ammo_probe.py', 'test_native_mounted_swap_probe.py', 'test_native_mounted_courier_probe.py', 'test_native_alarm_hearing.py', 'test_native_convoy_cover_probe.py', 'test_enemy_optics.py', 'test_convoy_combat.py', 'test_ai_stinger_admin.py', 'test_hq_ammo_dispatch.py', 'test_hq_convoy_combat.py', 'test_hq_ammo_sampler.py', 'test_hq_mine_sampler.py', 'test_hq_mounted_ammo_sampler.py', 'test_hq_mounted_binding.py', 'test_hq_vehicle_seats.py', 'test_native_hq_body_probe.py', 'test_hq_delivered_casualties.py', 'test_external_profile_review.py', 'test_hq_uplink_fairness.py', 'test_actor_group.py', 'test_native_package_external.py', 'test_native_convoy_probe.py', 'test_convoy_placement.py', 'test_convoy_road_plan.py', 'test_hq_convoy_dispatch.py', 'test_convoy_admission.py', 'test_convoy_dispatch.py', 'test_convoy_lifecycle.py', 'test_convoy_survivors.py', 'test_convoy_budget.py','test_supply_convoys.py', 'test_supply_budget_admission.py', 'test_mounted_initial_stock.py', 'test_convoy_mine_stock.py', 'test_convoy_column.py', 'test_hq_body_observer.py', 'test_hq_status_sampler.py', 'test_hq_watch.py', 'test_hq_assignment_lifecycle.py', 'test_force_ledger.py', 'test_enemy_equipment.py', 'test_enemy_loadout.py', 'test_automatic_equipment.py', 'test_engineer_mine.py', 'test_engineer_mine_site.py', 'test_engineer_mine_hq.py', 'test_engineer_mine_posts.py', 'test_hq_doctrine.py', 'test_hq_coverage.py', 'test_hq_hypotheses.py', 'test_hq_local_coverage.py', 'test_hq_operations.py', 'test_hq_squad_judgment.py', 'test_hq_tactical_ground.py', 'test_hq_operation_speech.py', 'test_hq_hunter_operations.py', 'test_hq_coordinated.py', 'test_hq_execution.py', 'test_ai_pursuit_replay.py', 'test_native_coordinated_runner.py', 'test_hq_task_reservations.py', 'test_hq_pursuit.py', 'test_hq_memory.py', 'test_external_addons.py', 'test_admin_tick_delivery.py', 'test_project_schema.py', 'test_admin_session.py', 'test_admin_providers.py', 'test_admin_menu.py', 'test_server_item_catalog.py', 'test_item_preferences.py', 'test_journal_readability.py', 'test_admin_player_tools.py', 'test_intel_admin.py', 'test_intel_reroll.py', 'test_intel_discovery_feedback.py', 'test_hq_report_selection.py', 'test_casualty_feedback.py', 'test_initial_admission_exhaustion.py', 'test_prison_held_action_proof.py', 'test_prison_held_action_evidence.py', 'test_prison_held_input_adapter.py', 'test_prison_held_runner.py', 'test_prison_coarse_facing.py', 'test_aircraft_field_probe.py', 'test_aircraft_field_runner.py', 'test_aircraft_field_rules.py', 'test_aircraft_field_envelope.py', 'test_aircraft_field_flight.py', 'test_command_maintenance.py', 'test_aircraft_service.py', 'test_aircraft_service_cancel_runner.py', 'test_prison_action_selection_proof.py', 'test_prison_action_selection_evidence.py', 'test_ordinary_gate_choices.py', 'test_aircraft_control_bridge.py', 'test_prison_alignment_proof.py', 'test_prison_alignment_evidence.py', 'test_aircraft_route_demand.py', 'test_prison_approach_proof.py', 'test_prison_approach_evidence.py', 'test_ordinary_gate_evidence.py', 'test_ordinary_gate_observer.py', 'test_ordinary_look_observer.py', 'test_ordinary_look_evidence.py', 'test_relative_look_runner.py', 'test_radio_waiting_text.py', 'test_inventory_control_runner.py', 'test_inventory_layout.py', 'test_headless_input_proof.py', 'test_headless_input_exercise_runner.py', 'test_private_prefix_cleanup.py', 'test_runner_private_cleanup.py', 'test_ordinary_input_runner.py', 'test_ordinary_input_observer.py', 'test_aircraft_route_envelope.py', 'test_aircraft_route_policy.py', 'test_aircraft_route_probe.py', 'test_rough_intel_feedback.py', 'test_headless_display_probe.py', 'test_headless_display_runner.py', 'test_preparation_status.py', 'test_hint_delivery.py', 'test_native_end_lifecycle.py', 'test_native_end_runner.py', 'test_escape_result_wording.py', 'test_native_stats.py', 'test_aircraft_translation.py', 'test_spawn_retry_budget.py', 'test_spawn_preload_timeout.py', 'test_action_feedback.py', 'test_native_aircraft_control.py', 'test_aircraft_flight_policy.py', 'test_aircraft_flight_envelope.py', 'test_ordinary_screenshot.py', 'test_participant_selection.py', 'test_native_shared_map_ownership.py', 'test_shared_map_ownership.py', 'test_intel_registration.py', 'test_mortar_activity_completion.py', 'test_packed_chat_game.py', 'test_native_mortar_refresh.py', 'test_archive_packed.py', 'test_mortar_refresh.py', 'test_packed_core_game.py', 'test_pak_inventory.py', 'test_native_mortar_salvo.py', 'test_spectator_guidance.py', 'test_spectator_recovery.py', 'test_native_package.py', 'test_item_station_teardown.py', 'test_source_bundle.py', 'test_native_flight_footprint.py', 'test_native_flight_input_diagnostics.py', 'test_mortar_logistics.py', 'test_mortar_courier_storage.py','test_powered_flight_pad.py','test_prison_escape_policy.py','test_powered_flight_policy.py','test_flight_action_runner.py','test_native_local_admission_probe.py','test_mortar_report_ledger.py','test_dedicated_native.py','test_native_mortar_transfer.py','test_mortar_aim_renewal.py','test_dedicated_preflight.py','test_mortar_transactions.py','test_native_mortar_probe.py','test_diagnostic_no_splash.py','test_casualty_incapacitation.py','test_casualty_remove_probe.py','test_extraction_placement_policy.py','test_guard_cancellation_policy.py','test_casualty_ground_policy.py','test_casualty_reservation_death.py','test_casualty_loading_policy.py','test_initial_guard_policy.py','test_headless_game_runner.py','test_vanilla_game_control.py','test_archive_game_control.py','test_site_discovery_policy.py','test_faction_catalog_scope.py','test_mission_rules.py','test_admin_setting_policy.py', 'test_player_admission_policy.py','test_progression_identity.py','test_discovery_map_position.py','test_local_admission.py','test_prison_placement_policy.py', 'test_recovery_policy.py', 'test_native_inventory_hitzone.py','test_inventory_hitzone_compat.py','test_player_medical.py','test_time_policy.py','test_environment_policy.py','test_view_distance.py','test_environment_restart_runner.py', 'test_inventory_enumeration_policy.py','test_radio_defence_policy.py','test_radio_feedback_policy.py','test_performance_comparison.py','test_release_check.py','test_settings_migration.py','hq_network_regression.py','helicopter_availability_regression.py','mortar_salvo_regression.py']: + for name in ['test_optional_motorbikes.py', 'test_server_memory_sampler.py', 'test_roadblocks.py', 'test_roadblock_background.py', 'test_roadblock_layout.py', 'test_roadblock_mining.py', 'test_crash_start.py', 'test_start_settings.py', 'test_intercept_vehicle_footprint.py', 'test_bmp_intercept_geometry.py', 'test_native_random_radius_trace.py', 'test_native_random_radius_runner.py', 'test_native_supply_travel.py', 'test_native_supply_travel_hook.py', 'test_native_supply_travel_runner.py', 'test_native_mounted_intercept.py', 'test_hq_mounted_dispatch.py', 'test_hq_mounted_intercept.py', 'test_hq_mounted_intercept_pair.py', 'test_convoy_intercept_pair.py', 'test_native_maintenance_refuel.py', 'test_mine_intel.py', 'test_mine_intel_discovery_integration.py', 'test_infantry_combat.py', 'test_enemy_medicine.py', 'test_hunter_combat.py', 'test_hunter_stuck.py', 'test_hq_local_awareness.py', 'test_unconscious_controls.py', 'test_town_garrisons.py', 'test_field_sites.py', 'test_gameplay_session_regressions.py', 'test_gameplay_review_fixes.py', 'test_server_log_regressions.py', 'test_mortar_load_ack.py', 'test_mortar_spotting.py', 'test_hunter_profiles.py', 'test_hunter_lifecycle.py', 'test_hunter_capture.py', 'test_hunter_opening.py', 'test_hunter_search_memory.py', 'test_hunter_reserve.py', 'test_hunter_sector_search.py', 'test_enemy_awareness.py', 'test_intel_body_inspection.py', 'test_body_recovery_lifecycle.py', 'test_radio_document_defence.py', 'test_air_support_policy.py', 'test_air_logistics_orders.py', 'test_air_recon.py', 'test_scouts.py', 'test_mortar_station.py', 'test_random_settings.py', 'test_debug_ai.py', 'test_generated_sites.py', 'test_terrain_sites.py', 'test_terrain_adapter.py', 'test_terrain_airfields.py','test_terrain_takeover.py', 'test_terrain_presentation.py', 'test_terrain_selection.py', 'test_faction_selection.py', 'test_faction_menu.py', 'test_content_policy.py', 'test_hq_road_intercept.py', 'test_hq_delivered_task.py', 'test_hq_local_handoff.py', 'test_hq_local_handoff_intake.py', 'test_native_local_handoff_probe.py', 'test_infantry_resupply_waypoints.py', 'test_infantry_placement_settle.py', 'test_native_infantry_fixture_lifecycle.py', 'test_native_infantry_cargo.py', 'test_native_infantry_setup.py', 'test_infantry_ammo_access.py', 'test_infantry_resupply_planning.py', 'test_infantry_supply_pool.py', 'test_hq_multiconvoy_supply.py', 'test_enemy_gunner_skill.py', 'test_native_memory_deadlines.py', 'test_convoy_auto_settings.py', 'test_convoy_startup.py', 'test_failure_result_refresh.py', 'test_infantry_resupply_coordinator.py', 'test_infantry_mine_coordinator.py', 'test_infantry_mine_transfer.py', 'test_infantry_ammo_transfer.py', 'test_finite_magazine_reservation.py', 'test_mounted_ammo_transfer.py', 'test_mounted_ammo_pickup.py', 'test_mounted_resupply.py', 'test_mounted_supply_integration.py', 'test_hq_ammo_reporting.py', 'test_enemy_radio.py', 'test_native_maintenance_stock.py', 'test_packed_external_game.py', 'test_hq_radio_node.py', 'test_vehicle_maintenance_stock.py', 'test_optional_transport.py', 'test_vehicle_entry_diagnostics.py', 'test_vehicle_security_native_probe.py', 'test_vehicle_security.py', 'test_vehicle_alarm.py', 'test_vehicle_alarm_perception.py', 'test_native_convoy_mine_stock.py', 'test_native_mine_stock_runner.py', 'test_native_mounted_ammo_probe.py', 'test_native_mounted_swap_probe.py', 'test_native_mounted_courier_probe.py', 'test_native_alarm_hearing.py', 'test_native_convoy_cover_probe.py', 'test_enemy_optics.py', 'test_convoy_combat.py', 'test_ai_stinger_admin.py', 'test_hq_ammo_dispatch.py', 'test_hq_convoy_combat.py', 'test_hq_ammo_sampler.py', 'test_hq_mine_sampler.py', 'test_hq_mounted_ammo_sampler.py', 'test_hq_mounted_binding.py', 'test_hq_vehicle_seats.py', 'test_native_hq_body_probe.py', 'test_hq_delivered_casualties.py', 'test_external_profile_review.py', 'test_hq_uplink_fairness.py', 'test_actor_group.py', 'test_native_package_external.py', 'test_native_convoy_probe.py', 'test_convoy_placement.py', 'test_convoy_road_plan.py', 'test_hq_convoy_dispatch.py', 'test_convoy_admission.py', 'test_convoy_dispatch.py', 'test_convoy_lifecycle.py', 'test_convoy_survivors.py', 'test_convoy_budget.py','test_supply_convoys.py', 'test_supply_budget_admission.py', 'test_mounted_initial_stock.py', 'test_convoy_mine_stock.py', 'test_convoy_column.py', 'test_hq_body_observer.py', 'test_hq_status_sampler.py', 'test_hq_watch.py', 'test_hq_assignment_lifecycle.py', 'test_force_ledger.py', 'test_enemy_equipment.py', 'test_enemy_loadout.py', 'test_automatic_equipment.py', 'test_engineer_mine.py', 'test_engineer_mine_site.py', 'test_engineer_mine_hq.py', 'test_engineer_mine_posts.py', 'test_hq_doctrine.py', 'test_hq_coverage.py', 'test_hq_hypotheses.py', 'test_hq_local_coverage.py', 'test_hq_operations.py', 'test_hq_squad_judgment.py', 'test_hq_tactical_ground.py', 'test_hq_operation_speech.py', 'test_hq_hunter_operations.py', 'test_hq_coordinated.py', 'test_hq_execution.py', 'test_ai_pursuit_replay.py', 'test_native_coordinated_runner.py', 'test_hq_task_reservations.py', 'test_hq_pursuit.py', 'test_hq_memory.py', 'test_external_addons.py', 'test_admin_tick_delivery.py', 'test_project_schema.py', 'test_admin_session.py', 'test_admin_providers.py', 'test_admin_menu.py', 'test_server_item_catalog.py', 'test_item_preferences.py', 'test_journal_readability.py', 'test_admin_player_tools.py', 'test_intel_admin.py', 'test_intel_reroll.py', 'test_intel_discovery_feedback.py', 'test_hq_report_selection.py', 'test_casualty_feedback.py', 'test_initial_admission_exhaustion.py', 'test_prison_held_action_proof.py', 'test_prison_held_action_evidence.py', 'test_prison_held_input_adapter.py', 'test_prison_held_runner.py', 'test_prison_coarse_facing.py', 'test_aircraft_field_probe.py', 'test_aircraft_field_runner.py', 'test_aircraft_field_rules.py', 'test_aircraft_field_envelope.py', 'test_aircraft_field_flight.py', 'test_command_maintenance.py', 'test_aircraft_service.py', 'test_aircraft_service_cancel_runner.py', 'test_prison_action_selection_proof.py', 'test_prison_action_selection_evidence.py', 'test_ordinary_gate_choices.py', 'test_aircraft_control_bridge.py', 'test_prison_alignment_proof.py', 'test_prison_alignment_evidence.py', 'test_aircraft_route_demand.py', 'test_prison_approach_proof.py', 'test_prison_approach_evidence.py', 'test_ordinary_gate_evidence.py', 'test_ordinary_gate_observer.py', 'test_ordinary_look_observer.py', 'test_ordinary_look_evidence.py', 'test_relative_look_runner.py', 'test_radio_waiting_text.py', 'test_inventory_control_runner.py', 'test_inventory_layout.py', 'test_headless_input_proof.py', 'test_headless_input_exercise_runner.py', 'test_private_prefix_cleanup.py', 'test_runner_private_cleanup.py', 'test_ordinary_input_runner.py', 'test_ordinary_input_observer.py', 'test_aircraft_route_envelope.py', 'test_aircraft_route_policy.py', 'test_aircraft_route_probe.py', 'test_rough_intel_feedback.py', 'test_headless_display_probe.py', 'test_headless_display_runner.py', 'test_preparation_status.py', 'test_hint_delivery.py', 'test_native_end_lifecycle.py', 'test_native_end_runner.py', 'test_escape_result_wording.py', 'test_native_stats.py', 'test_aircraft_translation.py', 'test_spawn_retry_budget.py', 'test_spawn_preload_timeout.py', 'test_action_feedback.py', 'test_native_aircraft_control.py', 'test_aircraft_flight_policy.py', 'test_aircraft_flight_envelope.py', 'test_ordinary_screenshot.py', 'test_participant_selection.py', 'test_native_shared_map_ownership.py', 'test_shared_map_ownership.py', 'test_intel_registration.py', 'test_mortar_activity_completion.py', 'test_packed_chat_game.py', 'test_native_mortar_refresh.py', 'test_archive_packed.py', 'test_mortar_refresh.py', 'test_packed_core_game.py', 'test_pak_inventory.py', 'test_native_mortar_salvo.py', 'test_spectator_guidance.py', 'test_spectator_recovery.py', 'test_native_package.py', 'test_item_station_teardown.py', 'test_source_bundle.py', 'test_native_flight_footprint.py', 'test_native_flight_input_diagnostics.py', 'test_mortar_logistics.py', 'test_mortar_courier_storage.py','test_powered_flight_pad.py','test_prison_escape_policy.py','test_powered_flight_policy.py','test_flight_action_runner.py','test_native_local_admission_probe.py','test_mortar_report_ledger.py','test_dedicated_native.py','test_native_mortar_transfer.py','test_mortar_aim_renewal.py','test_dedicated_preflight.py','test_mortar_transactions.py','test_native_mortar_probe.py','test_diagnostic_no_splash.py','test_casualty_incapacitation.py','test_casualty_remove_probe.py','test_extraction_placement_policy.py','test_guard_cancellation_policy.py','test_casualty_ground_policy.py','test_casualty_reservation_death.py','test_casualty_loading_policy.py','test_initial_guard_policy.py','test_headless_game_runner.py','test_vanilla_game_control.py','test_archive_game_control.py','test_site_discovery_policy.py','test_faction_catalog_scope.py','test_mission_rules.py','test_admin_setting_policy.py', 'test_player_admission_policy.py','test_progression_identity.py','test_discovery_map_position.py','test_local_admission.py','test_prison_placement_policy.py', 'test_recovery_policy.py', 'test_native_inventory_hitzone.py','test_inventory_hitzone_compat.py','test_player_medical.py','test_time_policy.py','test_environment_policy.py','test_view_distance.py','test_environment_restart_runner.py', 'test_inventory_enumeration_policy.py','test_radio_defence_policy.py','test_radio_feedback_policy.py','test_performance_comparison.py','test_release_check.py','test_settings_migration.py','hq_network_regression.py','helicopter_availability_regression.py','mortar_salvo_regression.py']: subprocess.run([sys.executable,str(ROOT/'tests'/name)],check=True) print('All repository checks passed. Native compilation and multiplayer tests are separate requirements.') if __name__=='__main__':check()