Files
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

585 lines
42 KiB
Diff

--- a/addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c
@@ -54,7 +54,7 @@
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", "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"};
+ 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();
--- /dev/null
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_NativeObjectiveMapProbe.c
@@ -0,0 +1,361 @@
+#ifdef WORKBENCH
+// Explicit UI mechanism fixture. It creates no intel, tasks, actors or inventory
+// and never changes annotation restrictions. Shared-root evidence is NOT a
+// physical held-texture, manual-input, restricted-platform or multiplayer test.
+modded class ERR_ObjectiveMapPresentation
+{
+ Widget ERR_ProbeRoot() { return m_Root; }
+ ERR_ObjectiveMapElement ERR_ProbeElement() { return m_Element; }
+ Widget ERR_ProbeCanvas() { return m_Canvas; }
+ Widget ERR_ProbeContainer() { return m_Container; }
+ bool ERR_ProbeStopped() { return !m_Started && !m_Root && !m_Element; }
+}
+modded class ERR_DiscoveryMarker
+{
+ ERR_ObjectiveMapPresentation ERR_ProbePresentation() { return m_ERRPresentation; }
+}
+modded class SCR_MapMarkerManagerComponent
+{
+ int ERR_ProbeAnnotationCount() { return m_aStaticMarkers.Count() + m_aDisabledMarkers.Count(); }
+}
+class ERR_NativeObjectiveMapProbe
+{
+ protected ERR_GameMode m_Mode;
+ protected SCR_MapEntity m_Map;
+ protected SCR_MapMarkerManagerComponent m_Annotations;
+ protected ref ERR_DiscoveryMarker m_Marker;
+ protected ref ERR_ObjectiveMapPresentation m_Presentation;
+ protected SCR_MapMenuUI m_Menu;
+ protected Widget m_SharedRoot, m_ReusedRoot, m_ExpectedRoot, m_PreviousWidget;
+ protected ref MapConfiguration m_Config;
+ protected MapConfiguration m_Ready;
+ protected vector m_Point;
+ protected int m_Stage, m_AnnotationCount;
+ protected int m_Deadline;
+ protected float m_Wait;
+ protected bool m_Stopped, m_Done, m_CloseRequested, m_CloseObserved;
+ static bool Enabled()
+ {
+ string enabled, outcome;
+ return System.GetCLIParam("errBaseSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "objective-map";
+ }
+ void ERR_NativeObjectiveMapProbe(ERR_GameMode mode)
+ {
+ m_Mode = mode;
+ m_Deadline = System.GetTickCount() + 90000;
+ SCR_MapEntity.GetOnMapOpenComplete().Insert(Ready);
+ SCR_MapEntity.GetOnMapClose().Insert(Closed);
+ }
+ protected void Ready(MapConfiguration config) { m_Ready = config; }
+ protected void Closed(MapConfiguration config)
+ {
+ if (m_CloseRequested && config == m_Config) m_CloseObserved = true;
+ }
+ protected bool Check(bool condition, string detail)
+ {
+ if (condition) { Print("ERR SMOKE PASS: objective overlay " + detail); return true; }
+ Print("ERR SMOKE FAIL: objective overlay " + detail, LogLevel.ERROR);
+ Stop(); return false;
+ }
+ protected bool OwnsMap()
+ {
+ if (!m_Map || SCR_MapEntity.GetMapInstance() != m_Map || !m_Map.IsOpen()) return false;
+ return m_Config && m_Map.GetMapConfig() == m_Config && m_Config.RootWidgetRef == m_ExpectedRoot && m_Map.GetMapMenuRoot() == m_ExpectedRoot;
+ }
+ protected bool OpenStandard()
+ {
+ if (!Check(m_Map && !m_Map.IsOpen() && !m_Menu, "standard map starts from closed fixture-owned state")) return false;
+ m_Ready = null;
+ m_Menu = SCR_MapMenuUI.Cast(GetGame().GetMenuManager().OpenMenu(ChimeraMenuPreset.MapMenu));
+ if (!Check(m_Menu && m_Map.IsOpen(), "ordinary native MapMenu opens without simulated input")) return false;
+ m_ExpectedRoot = m_Menu.GetRootWidget();
+ m_Config = m_Map.GetMapConfig();
+ return Check(OwnsMap() && m_Config.MapEntityMode == EMapEntityMode.FULLSCREEN, "ordinary full-screen configuration and exact menu root pinned");
+ }
+ protected bool CloseStandard()
+ {
+ if (!Check(m_Menu && OwnsMap() && !m_CloseRequested, "only exact owned standard menu may request close once")) return false;
+ m_CloseRequested = true;
+ m_CloseObserved = false;
+ return Check(GetGame().GetMenuManager().CloseMenu(m_Menu), "native standard menu close request accepted");
+ }
+ protected int StandardClosed()
+ {
+ if (!Check(m_CloseRequested && m_Map && SCR_MapEntity.GetMapInstance() == m_Map, "pending close retains original native map identity")) return -1;
+ if (m_Map.IsOpen())
+ {
+ if (!Check(OwnsMap(), "pending close cannot adopt a foreign open map")) return -1;
+ return 0;
+ }
+ if (!m_CloseObserved) return 0;
+ if (!Check(!m_Presentation.ERR_ProbeRoot(), "native map close callback removes objective widget before reopening")) return -1;
+ m_Menu = null;
+ m_CloseRequested = false;
+ m_CloseObserved = false;
+ return 1;
+ }
+ protected Widget CreateSharedRoot()
+ {
+ Widget root = GetGame().GetWorkspace().CreateWidgets("{255E1265F702B4AE}UI/layouts/ERRMap/SharedMapRenderTarget.layout");
+ if (!root) return null;
+ RTTextureWidget target = RTTextureWidget.Cast(root.FindAnyWidget("RTTexture0"));
+ if (!target) { root.RemoveFromHierarchy(); return null; }
+ // Expose the real root for geometry/screenshot checks. No model render target
+ // is bound, so these observations cannot qualify the physical held texture.
+ target.SetEnabled(false);
+ root.SetVisible(true);
+ return root;
+ }
+ protected bool OpenShared(Widget root)
+ {
+ if (!Check(root && !m_Map.IsOpen(), "separate real shared-map render layout exists before opening")) return false;
+ m_Ready = null; m_ExpectedRoot = root;
+ m_Config = m_Map.SetupMapConfig(EMapEntityMode.PLAIN, "{A786DD4868598F15}Configs/Map/MapPlain.conf", root);
+ if (!Check(m_Config != null, "native shared plain-map configuration prepared")) return false;
+ m_Map.OpenMap(m_Config);
+ return Check(OwnsMap(), "shared map uses exact authored root and configuration");
+ }
+ protected bool ReadyToInspect()
+ {
+ return OwnsMap() && m_Ready == m_Config && !m_Map.IsZooming();
+ }
+ protected void Center(float zoom)
+ {
+ int position[2]; m_Marker.GetWorldPos(position);
+ m_Map.ZoomPanSmooth(zoom, position[0] + 100.0 / zoom, position[1], 0.1);
+ }
+ protected bool Inspect(string label, bool rough)
+ {
+ if (!Check(ReadyToInspect(), "native configuration finished initialization and camera remains fixture-owned")) return false;
+ Widget root = m_Presentation.ERR_ProbeRoot();
+ ERR_ObjectiveMapElement element = m_Presentation.ERR_ProbeElement();
+ Widget container = m_ExpectedRoot.FindAnyWidget("ERR_ObjectiveOverlay");
+ Widget nativeIcons = m_ExpectedRoot.FindAnyWidget("UIIconsContainer");
+ if (!container && nativeIcons) container = nativeIcons.GetParent();
+ if (!Check(root && element && container && container != m_Map.GetMapWidget() && root.GetParent() == container && m_Presentation.ERR_ProbeContainer() == container && m_Presentation.ERR_ProbeCanvas() == m_Map.GetMapWidget(), "actual authored layout handler belongs to the exact supported objective container and native canvas")) return false;
+ TextWidget text = TextWidget.Cast(root.FindAnyWidget("ObjectiveText"));
+ ImageWidget image = ImageWidget.Cast(root.FindAnyWidget("Image"));
+ if (!Check(text && image && text.GetText() == label && root.IsVisible(), "actual visible widget contains the current objective label and icon widget")) return false;
+ int width, height; image.GetImageSize(0, width, height);
+ if (!Check(width > 0 && height > 0, "actual icon image resource has nonzero native dimensions")) return false;
+ int point[2]; m_Marker.GetWorldPos(point);
+ int knowledge = 2; if (rough) knowledge = 1;
+ if (!Check(point[0] == ERR_DiscoveryMapRules.Coordinate(m_Point[0], knowledge) && point[1] == ERR_DiscoveryMapRules.Coordinate(m_Point[2], knowledge), "presentation retains only requested rough or identified map coordinates")) return false;
+ vector model = element.GetPos();
+ if (!Check(model[0] == point[0] && model[2] == point[1], "native handler receives only frozen disclosed coordinates")) return false;
+ int x, y; m_Map.WorldToScreen(point[0], point[1], x, y, true);
+ float cx, cy, ox, oy;
+ m_Map.GetMapWidget().GetScreenPos(cx, cy);
+ container.GetScreenPos(ox, oy);
+ vector placed = FrameSlot.GetPos(root);
+ if (!Check(Math.AbsFloat(placed[0] - GetGame().GetWorkspace().DPIUnscale(cx + x - ox)) < 1 && Math.AbsFloat(placed[1] - GetGame().GetWorkspace().DPIUnscale(cy + y - oy)) < 1, "native frame position matches current pan zoom DPI and canvas-to-container offset")) return false;
+ float ix, iy, iw, ih;
+ image.GetScreenPos(ix, iy); image.GetScreenSize(iw, ih);
+ PrintFormat("ERR OBJECTIVE PIXELS: expected=%1,%2 actual=%3,%4 stage=%5", cx + x, cy + y, ix + iw * 0.5, iy + ih * 0.5, m_Stage);
+ return Check(Math.AbsFloat(ix + iw * 0.5 - cx - x) <= 3 && Math.AbsFloat(iy + ih * 0.5 - cy - y) <= 3, "actual rendered icon box is centered on disclosed native pixel position");
+ }
+ int Tick(float now)
+ {
+ if (m_Done) return 1;
+ if (m_Stopped) return -1;
+ if (!Enabled() || !m_Mode || ERR_GameMode.Get() != m_Mode || !m_Mode.Active() || System.GetTickCount() >= m_Deadline) { Check(false, "fixed ninety-second deadline and original active mission"); return -1; }
+ if (now < m_Wait) return 0;
+ if (m_Stage >= 16 && m_Stage <= 23 && !Check(ReadyToInspect(), "capture hold retains exact active map before any following mutation")) return -1;
+ if (m_Stage == 0)
+ {
+ m_Map = SCR_MapEntity.GetMapInstance();
+ m_Annotations = SCR_MapMarkerManagerComponent.GetInstance();
+ PlayerController controller = GetGame().GetPlayerController();
+ if (!Check(m_Map && !m_Map.IsOpen() && m_Annotations && controller && controller.GetControlledEntity(), "closed native map and actual local participant available")) return -1;
+ m_Point = controller.GetControlledEntity().GetOrigin() + "1037.25 0 1063.75";
+ m_AnnotationCount = m_Annotations.ERR_ProbeAnnotationCount();
+ m_Marker = new ERR_DiscoveryMarker();
+ m_Marker.SetDiscoveryPosition(m_Point, 1); m_Marker.SetLocationIcon(ERR_LocationKind.RADIO, "", 1);
+ m_Marker.SetCustomText("Unknown location (500 m area)");
+ m_Annotations.ERR_InsertDiscoveryMarker(m_Marker);
+ m_Presentation = m_Marker.ERR_ProbePresentation();
+ if (!Check(m_Presentation != null, "real discovery insertion uses separate objective presentation")) return -1;
+ if (!OpenStandard()) return -1;
+ m_Stage = 1; m_Wait = now + 1000; return 0;
+ }
+ if (m_Stage == 1 || m_Stage == 7 || m_Stage == 9 || m_Stage == 11)
+ {
+ if (!ReadyToInspect()) return 0;
+ Center(0.3); m_Stage++; m_Wait = now + 1000; return 0;
+ }
+ if (m_Stage == 2)
+ {
+ if (!Inspect("Unknown location (500 m area)", true)) return -1;
+ System.MakeScreenshot("$logs:err-objective-map-standard-rough");
+ Print("ERR OBJECTIVE CAPTURE READY: standard-rough");
+ m_Stage = 16; m_Wait = now + 1500; return 0;
+ }
+ if (m_Stage == 16)
+ {
+ m_PreviousWidget = m_Presentation.ERR_ProbeRoot();
+ m_Marker.SetDiscoveryPosition(m_Point, 2); m_Marker.SetLocationIcon(ERR_LocationKind.RADIO, "", 2); m_Marker.SetCustomText("Radio station");
+ Center(0.6); m_Stage = 3; m_Wait = now + 1000; return 0;
+ }
+ if (m_Stage == 3)
+ {
+ if (!Inspect("Radio station", false)) return -1;
+ if (!Check(m_PreviousWidget == m_Presentation.ERR_ProbeRoot(), "rough to exact updates one existing widget")) return -1;
+ System.MakeScreenshot("$logs:err-objective-map-standard-exact");
+ Print("ERR OBJECTIVE CAPTURE READY: standard-exact");
+ m_Stage = 17; m_Wait = now + 1500; return 0;
+ }
+ if (m_Stage == 17)
+ {
+ float farX = 500; if (m_Point[0] < m_Map.GetMapSizeX() * 0.5) farX = m_Map.GetMapSizeX() - 500;
+ m_Map.ZoomPanSmooth(0.6, farX, 500, 0.1);
+ m_Stage = 4; m_Wait = now + 1000; return 0;
+ }
+ if (m_Stage == 4)
+ {
+ if (!Check(ReadyToInspect() && m_PreviousWidget == m_Presentation.ERR_ProbeRoot() && !m_PreviousWidget.IsVisible(), "native pan culls original objective without deleting or relocating its data")) return -1;
+ Center(0.3); m_Stage = 5; m_Wait = now + 1000; return 0;
+ }
+ if (m_Stage == 5)
+ {
+ if (!Inspect("Radio station", false)) return -1;
+ m_Marker.SetCustomText("Reported mine area (500 m); may still be active");
+ m_Stage = 22; m_Wait = now + 750; return 0;
+ }
+ if (m_Stage == 22)
+ {
+ if (!Inspect("Reported mine area (500 m); may still be active", false)) return -1;
+ System.MakeScreenshot("$logs:err-objective-map-standard-long-warning");
+ Print("ERR OBJECTIVE CAPTURE READY: standard-long-warning");
+ m_Stage = 23; m_Wait = now + 1500; return 0;
+ }
+ if (m_Stage == 23)
+ {
+ m_Marker.SetCustomText("Radio station");
+ if (!CloseStandard()) return -1;
+ m_Stage = 6; m_Wait = now + 500; return 0;
+ }
+ if (m_Stage == 6)
+ {
+ int closed = StandardClosed();
+ if (closed <= 0) return closed;
+ if (!OpenStandard()) return -1;
+ m_Stage = 7; m_Wait = now + 1000; return 0;
+ }
+ if (m_Stage == 8)
+ {
+ if (!Inspect("Radio station", false)) return -1;
+ System.MakeScreenshot("$logs:err-objective-map-standard-reopened");
+ Print("ERR OBJECTIVE CAPTURE READY: standard-reopened");
+ m_Stage = 18; m_Wait = now + 1500; return 0;
+ }
+ if (m_Stage == 18)
+ {
+ if (!CloseStandard()) return -1;
+ m_Stage = 15; m_Wait = now + 500; return 0;
+ }
+ if (m_Stage == 15)
+ {
+ int closed = StandardClosed();
+ if (closed <= 0) return closed;
+ m_SharedRoot = CreateSharedRoot();
+ if (!OpenShared(m_SharedRoot)) return -1;
+ m_Stage = 9; m_Wait = now + 1000; return 0;
+ }
+ if (m_Stage == 10)
+ {
+ if (!Inspect("Radio station", false)) return -1;
+ System.MakeScreenshot("$logs:err-objective-map-shared-root");
+ Print("ERR OBJECTIVE CAPTURE READY: shared-root");
+ m_Stage = 19; m_Wait = now + 1500; return 0;
+ }
+ if (m_Stage == 19)
+ {
+ m_Marker.SetCustomText("Reported mine area (500 m); may still be active");
+ m_Stage = 14; m_Wait = now + 750; return 0;
+ }
+ if (m_Stage == 14)
+ {
+ if (!Inspect("Reported mine area (500 m); may still be active", false)) return -1;
+ System.MakeScreenshot("$logs:err-objective-map-shared-long-warning");
+ Print("ERR OBJECTIVE CAPTURE READY: shared-long-warning");
+ m_Stage = 20; m_Wait = now + 1500; return 0;
+ }
+ if (m_Stage == 20)
+ {
+ MapConfiguration previousConfig = m_Config;
+ m_Map.CloseMap();
+ if (!Check(!m_Presentation.ERR_ProbeRoot(), "closing shared root clears only owned objective widget")) return -1;
+ m_ReusedRoot = CreateSharedRoot();
+ if (!OpenShared(m_ReusedRoot)) return -1;
+ if (!Check(m_Config == previousConfig && m_ReusedRoot != m_SharedRoot, "native plain config is reused with a distinct root")) return -1;
+ m_Stage = 11; m_Wait = now + 1000; return 0;
+ }
+ if (m_Stage == 12)
+ {
+ if (!Inspect("Reported mine area (500 m); may still be active", false)) return -1;
+ System.MakeScreenshot("$logs:err-objective-map-shared-reused-root");
+ Print("ERR OBJECTIVE CAPTURE READY: shared-reused-root");
+ m_Stage = 21; m_Wait = now + 1500; return 0;
+ }
+ if (!Check(m_Stage == 21, "only final captured stage can complete")) return -1;
+ m_Marker.OnDelete();
+ if (!Check(m_Presentation.ERR_ProbeStopped() && OwnsMap(), "objective teardown releases its widget while leaving native map open")) return -1;
+ m_Stage = 13;
+ Stop(); m_Done = true;
+ if (!Check(m_Annotations && m_Annotations.ERR_ProbeAnnotationCount() == m_AnnotationCount, "existing annotation registry count preserved; no privacy settings changed")) return -1;
+ Print("ERR SMOKE COMPLETE: objective overlay native standard/shared-root geometry, data, pan zoom culling reopen and teardown; not held-texture, manual input, restricted-platform or multiplayer proof");
+ return 1;
+ }
+ void Stop()
+ {
+ if (m_Stopped) return;
+ m_Stopped = true;
+ if (m_Marker) m_Marker.OnDelete();
+ if (m_Presentation) m_Presentation.Stop();
+ SCR_MapEntity.GetOnMapOpenComplete().Remove(Ready);
+ SCR_MapEntity.GetOnMapClose().Remove(Closed);
+ if (OwnsMap())
+ {
+ if (m_Menu)
+ {
+ if (!m_CloseRequested) GetGame().GetMenuManager().CloseMenu(m_Menu);
+ }
+ else m_Map.CloseMap();
+ }
+ if (m_SharedRoot) m_SharedRoot.RemoveFromHierarchy();
+ if (m_ReusedRoot) m_ReusedRoot.RemoveFromHierarchy();
+ m_Menu = null; m_SharedRoot = null; m_ReusedRoot = null; m_ExpectedRoot = null; m_Config = null; m_Ready = null;
+ }
+}
+modded class ERR_GameMode
+{
+ protected ref ERR_NativeObjectiveMapProbe m_ERRObjectiveMapProbe;
+ protected int m_ERRObjectiveMapDeadline;
+ protected override void EOnInit(IEntity owner)
+ {
+ super.EOnInit(owner);
+ if (SCR_Global.IsEditMode() || !Replication.IsServer() || !ERR_NativeObjectiveMapProbe.Enabled()) return;
+ m_ERRObjectiveMapDeadline = System.GetTickCount() + 180000;
+ OnStop().Insert(ERR_StopObjectiveMapProbe);
+ GetGame().GetCallqueue().CallLater(ERR_TickObjectiveMapProbe, 250, true);
+ }
+ protected void ERR_TickObjectiveMapProbe()
+ {
+ if (System.GetTickCount() >= m_ERRObjectiveMapDeadline || !PreparationError().IsEmpty())
+ {
+ Print("ERR SMOKE FAIL: objective overlay preparation/overall deadline", LogLevel.ERROR);
+ ERR_StopObjectiveMapProbe(); return;
+ }
+ if (!Active() || !GetGame().GetPlayerController() || !GetGame().GetPlayerController().GetControlledEntity()) return;
+ if (!m_ERRObjectiveMapProbe) m_ERRObjectiveMapProbe = new ERR_NativeObjectiveMapProbe(this);
+ if (m_ERRObjectiveMapProbe.Tick(GetWorld().GetWorldTime()) != 0) ERR_StopObjectiveMapProbe();
+ }
+ protected void ERR_StopObjectiveMapProbe()
+ {
+ if (GetGame()) GetGame().GetCallqueue().Remove(ERR_TickObjectiveMapProbe);
+ if (m_ERRObjectiveMapProbe) m_ERRObjectiveMapProbe.Stop();
+ OnStop().Remove(ERR_StopObjectiveMapProbe);
+ }
+ void ~ERR_GameMode() { ERR_StopObjectiveMapProbe(); }
+}
+#endif
--- /dev/null
+++ b/tests/test_native_objective_map_probe.py
@@ -0,0 +1,178 @@
+"""Actual fixture ownership/terminal cleanup plus explicit selector contracts."""
+import os,re
+from pathlib import Path
+from policy_harness import ROOT,body,run
+root=Path(os.environ.get('ERR_NATIVE_OBJECTIVE_MAP_ROOT',ROOT));p=root/'addons/core/Scripts/Game/ERR/00_Core/ERR_NativeObjectiveMapProbe.c'
+def adapt(s):
+ s=s.replace('protected ','').replace('null','nullptr').replace('SCR_MapEntity.','SCR_MapEntity::')
+ for name in ['m_Map','m_Config','m_Marker','m_Presentation','m_SharedRoot','m_ReusedRoot']:
+ s=s.replace(name+'.',name+'->')
+ for name in ['GetGame','GetMenuManager','GetOnMapOpenComplete','GetOnMapClose']:
+ s=s.replace(name+'().',name+'()->')
+ return s.replace('Remove(Ready)','Remove(this)').replace('Remove(Closed)','Remove(this)').replace('MapConfiguration config','Config* config')
+methods='\n'.join(adapt(body(p,s)) for s in ['protected bool OwnsMap(', 'protected bool CloseStandard(', 'protected int StandardClosed(', 'protected void Closed(', 'void Stop('])
+run('''#include <cassert>
+#include <set>
+#include <string>
+using string=std::string;
+struct Widget{int deletes=0;void RemoveFromHierarchy(){deletes++;}};
+struct Config{Widget*RootWidgetRef=nullptr;};
+struct Event{std::set<void*>owners;void Remove(void*p){owners.erase(p);}};
+struct SCR_MapEntity{static inline SCR_MapEntity*current=nullptr;static inline Event opened,closed;Config*config=nullptr;Widget*root=nullptr;bool open=true;int closes=0;static SCR_MapEntity*GetMapInstance(){return current;}static Event*GetOnMapOpenComplete(){return &opened;}static Event*GetOnMapClose(){return &closed;}bool IsOpen(){return open;}Config*GetMapConfig(){return config;}Widget*GetMapMenuRoot(){return root;}void CloseMap(){closes++;open=false;}};
+struct Marker{int deletes=0;void OnDelete(){deletes++;}};struct Presentation{int stops=0;Widget*root=nullptr;Widget*ERR_ProbeRoot(){return root;}void Stop(){stops++;root=nullptr;}};struct Menu{};
+struct Manager{int closes=0;bool asynchronous=false;bool CloseMenu(Menu*){closes++;if(!asynchronous&&SCR_MapEntity::current)SCR_MapEntity::current->CloseMap();return true;}};
+struct Game{Manager manager;Manager*GetMenuManager(){return &manager;}}game;Game*GetGame(){return &game;}
+struct Probe{bool m_Stopped=false,m_CloseRequested=false,m_CloseObserved=false;bool Check(bool ok,string){if(!ok)Stop();return ok;}SCR_MapEntity*m_Map=nullptr;Config*m_Config=nullptr,*m_Ready=nullptr;Widget*m_ExpectedRoot=nullptr,*m_SharedRoot=nullptr,*m_ReusedRoot=nullptr;Marker*m_Marker=nullptr;Presentation*m_Presentation=nullptr;Menu*m_Menu=nullptr;
+'''+methods+'''
+};
+int main(){SCR_MapEntity map;SCR_MapEntity::current=&map;Config cfg;Widget root,otherRoot,shared,reused;cfg.RootWidgetRef=&root;map.root=&root;map.config=&cfg;
+ Probe p;p.m_Map=&map;p.m_Config=&cfg;p.m_ExpectedRoot=&root;Marker marker;Presentation presentation;Menu menu;p.m_Marker=&marker;p.m_Presentation=&presentation;p.m_Menu=&menu;p.m_SharedRoot=&shared;p.m_ReusedRoot=&reused;
+ assert(p.OwnsMap());cfg.RootWidgetRef=&otherRoot;assert(!p.OwnsMap());cfg.RootWidgetRef=&root;map.root=&otherRoot;assert(!p.OwnsMap());map.root=&root;
+ SCR_MapEntity replacement;SCR_MapEntity::current=&replacement;assert(!p.OwnsMap());SCR_MapEntity::current=&map;
+ SCR_MapEntity::opened.owners.insert(&p);p.Stop();assert(p.m_Stopped&&marker.deletes==1&&presentation.stops==1&&game.manager.closes==1&&map.closes==1&&shared.deletes==1&&reused.deletes==1&&!SCR_MapEntity::opened.owners.count(&p));
+ p.Stop();assert(marker.deletes==1&&map.closes==1&&shared.deletes==1);
+ Probe interrupted;interrupted.m_Map=&map;interrupted.m_Config=&cfg;interrupted.m_ExpectedRoot=&root;interrupted.m_Menu=&menu;map.open=true;map.root=&otherRoot;interrupted.Stop();assert(map.open&&map.closes==1&&game.manager.closes==1);
+ Probe partial;partial.Stop();assert(partial.m_Stopped&&game.manager.closes==1);
+ // Native CloseMenu can return while MapMenu.OnMenuClose has not happened.
+ map.root=&root;map.open=true;game.manager.asynchronous=true;
+ Probe pending;pending.m_Map=&map;pending.m_Config=&cfg;pending.m_ExpectedRoot=&root;pending.m_Menu=&menu;pending.m_Presentation=&presentation;presentation.root=&shared;
+ assert(pending.CloseStandard()&&pending.m_Menu==&menu&&pending.m_CloseRequested&&map.open);assert(game.manager.closes==2);
+ assert(pending.StandardClosed()==0&&!pending.m_Stopped);assert(pending.StandardClosed()==0&&game.manager.closes==2);
+ Config foreign;pending.Closed(&foreign);assert(!pending.m_CloseObserved);
+ pending.Closed(&cfg);assert(pending.m_CloseObserved&&pending.StandardClosed()==0); // callback occurs before IsOpen clears
+ map.open=false;presentation.root=nullptr;assert(pending.StandardClosed()==1&&!pending.m_Menu&&!pending.m_CloseRequested);assert(game.manager.closes==2);
+ // Closed state alone cannot replace the observed exact native callback.
+ Probe noEvent;noEvent.m_Map=&map;noEvent.m_Config=&cfg;noEvent.m_ExpectedRoot=&root;noEvent.m_Menu=&menu;noEvent.m_Presentation=&presentation;map.open=true;assert(noEvent.CloseStandard());map.open=false;assert(noEvent.StandardClosed()==0&&noEvent.m_Menu==&menu);noEvent.Stop();
+ // A close request is not repeated by Stop or after foreign map adoption.
+ Probe cancel;cancel.m_Map=&map;cancel.m_Config=&cfg;cancel.m_ExpectedRoot=&root;cancel.m_Menu=&menu;map.open=true;assert(cancel.CloseStandard());int requests=game.manager.closes;cancel.Stop();assert(game.manager.closes==requests&&map.open);
+ Probe displaced;displaced.m_Map=&map;displaced.m_Config=&cfg;displaced.m_ExpectedRoot=&root;displaced.m_Menu=&menu;displaced.m_Presentation=&presentation;assert(displaced.CloseStandard());requests=game.manager.closes;map.root=&otherRoot;assert(displaced.StandardClosed()==-1&&displaced.m_Stopped&&map.open&&game.manager.closes==requests);
+ // Exact close callback with a leaked production widget still fails.
+ map.root=&root;Probe leak;leak.m_Map=&map;leak.m_Config=&cfg;leak.m_ExpectedRoot=&root;leak.m_Menu=&menu;leak.m_Presentation=&presentation;assert(leak.CloseStandard());leak.Closed(&cfg);map.open=false;presentation.root=&shared;assert(leak.StandardClosed()==-1&&leak.m_Stopped);
+
+}
+''')
+s=p.read_text();assert s.startswith('#ifdef WORKBENCH')
+assert 'System.GetTickCount() + 90000' in s and 'System.GetTickCount() + 180000' in s
+assert s.count('System.MakeScreenshot(')==7
+assert s.count('ERR OBJECTIVE CAPTURE READY: ')==7
+assert s.count('m_Wait = now + 1500; return 0;')==7
+assert 'm_Stage >= 16 && m_Stage <= 23 && !Check(ReadyToInspect()' in s
+assert 'm_Stage == 6' in s and 'm_Stage == 15' in s and s.count('int closed = StandardClosed();')==2
+assert 'SetBlocked(' not in s and 'SetPrivileged' not in s
+base=(root/'addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c').read_text()
+assert '"objective-map"' in base.split('ERR_NativeObjectiveProbe.BeginRound();')[0]
+runner=(root/'tools/headless_workbench.py').read_text()
+assert runner.index("objective-map requires --project core --rounds 1") < runner.index('steam, compat, profile =')
+# Actual invalid CLI must refuse before nonexistent prefix/profile processing.
+import subprocess,sys,tempfile
+with tempfile.TemporaryDirectory(prefix='err-objective-selector-') as temporary:
+ sandbox=Path(temporary); profile=sandbox/'must-not-exist'; prefix=sandbox/'absent-prefix'
+ environment=os.environ.copy(); environment['PYTHONPATH']=str(ROOT/'tools')
+ for project,rounds in [('chat','1'),('core','2')]:
+ result=subprocess.run([sys.executable,str(root/'tools/headless_workbench.py'),'--smoke','objective-map','--project',project,'--rounds',rounds,'--compat',str(prefix),'--profile',str(profile)],capture_output=True,text=True,env=environment)
+ assert result.returncode==2 and 'objective-map requires --project core --rounds 1' in result.stderr,result.stderr
+ assert not profile.exists() and not prefix.exists()
+print('PASS: actual native fixture root/config ownership and idempotent cleanup preserve foreign map, selector excludes ordinary route, fixed budgets/screenshots/UGC boundaries retained; native operations not executed')
+
+# Execute the complete actual Inspect method: parent identity and frame projection
+# are checked independently from native image-box coordinates. These are API
+# doubles, not proof of engine widget parenting, pixels or pointer transparency.
+inspect=body(p,'protected bool Inspect(').replace('protected ','')
+for t in ['Widget','ERR_ObjectiveMapElement','TextWidget','ImageWidget']:
+ inspect=re.sub(r'\b'+t+r' (?=\w)',t+'* ',inspect)
+for name in ['m_Presentation','m_Map','m_ExpectedRoot','m_Marker','root','element','container','nativeIcons','text','image']:
+ inspect=inspect.replace(name+'.',name+'->')
+for name in ['GetGame','GetWorkspace','GetMapWidget']:
+ inspect=inspect.replace(name+'().',name+'()->')
+for name in ['TextWidget','ImageWidget','ERR_DiscoveryMapRules','FrameSlot','Math']:
+ inspect=inspect.replace(name+'.',name+'::')
+run(r'''
+#include <cassert>
+#include <array>
+#include <cmath>
+#include <string>
+using string=std::string;using vector=std::array<float,3>;
+struct Widget{Widget*parent=nullptr,*container=nullptr,*overlay=nullptr,*text=nullptr,*image=nullptr;vector placed={0,0,0};float sx=0,sy=0,width=36,height=36;bool visible=true;virtual ~Widget()=default;Widget*GetParent(){return parent;}Widget*FindAnyWidget(string n){if(n=="UIIconsContainer")return container;if(n=="ERR_ObjectiveOverlay")return overlay;if(n=="ObjectiveText")return text;if(n=="Image")return image;return nullptr;}bool IsVisible(){return visible;}void GetScreenPos(float&x,float&y){x=sx;y=sy;}void GetScreenSize(float&w,float&h){w=width;h=height;}};
+struct TextWidget:Widget{string label;static TextWidget*Cast(Widget*w){return dynamic_cast<TextWidget*>(w);}string GetText(){return label;}};
+struct ImageWidget:Widget{static ImageWidget*Cast(Widget*w){return dynamic_cast<ImageWidget*>(w);}void GetImageSize(int,int&w,int&h){w=64;h=64;}};
+struct ERR_ObjectiveMapElement{vector pos;vector GetPos(){return pos;}};
+struct Presentation{Widget*root=nullptr,*canvas=nullptr,*container=nullptr;ERR_ObjectiveMapElement*element=nullptr;Widget*ERR_ProbeRoot(){return root;}Widget*ERR_ProbeCanvas(){return canvas;}Widget*ERR_ProbeContainer(){return container;}ERR_ObjectiveMapElement*ERR_ProbeElement(){return element;}};
+struct Map{Widget*canvas=nullptr;int x=240,y=180;Widget*GetMapWidget(){return canvas;}void WorldToScreen(int,int,int&ox,int&oy,bool pan){assert(pan);ox=x;oy=y;}};
+struct Marker{int x=6123,y=7027;void GetWorldPos(int p[2]){p[0]=x;p[1]=y;}};
+struct Workspace{float dpi=2;float DPIUnscale(float x){return x/dpi;}};
+struct Game{Workspace workspace;Workspace*GetWorkspace(){return &workspace;}}game;Game*GetGame(){return &game;}
+struct FrameSlot{static vector GetPos(Widget*w){return w->placed;}};
+struct Math{static float AbsFloat(float x){return std::fabs(x);}};
+struct ERR_DiscoveryMapRules{static int Coordinate(float x,int knowledge){return knowledge==1?int(std::floor(x/500))*500+250:int(x);}};
+template<class...T>void PrintFormat(T...){}
+struct Probe{Presentation*m_Presentation;Map*m_Map;Widget*m_ExpectedRoot;Marker*m_Marker;vector m_Point={6123,0,7027};int m_Stage=3;bool ready=true;bool ReadyToInspect(){return ready;}bool Check(bool b,string){return b;}
+'''+inspect+r'''
+};
+int main(){Widget menu,container,nativeIcons,canvas,root,foreign;TextWidget text;ImageWidget image;ERR_ObjectiveMapElement element;Presentation presentation;Map map;Marker marker;
+ menu.container=&nativeIcons;nativeIcons.parent=&container;root.parent=&container;root.text=&text;root.image=&image;text.label="Radio station";element.pos={6123,0,7027};
+ canvas.sx=100;canvas.sy=60;container.sx=24;container.sy=10;
+ presentation.root=&root;presentation.canvas=&canvas;presentation.container=&container;presentation.element=&element;map.canvas=&canvas;
+ // Canvas-relative projection240,180 -> screen340,240; supported parent has
+ // origin24,10, so slot(158,115) at DPI2, not slot(120,90).
+ root.placed={158,115,0};image.sx=322;image.sy=222;
+ Probe probe{&presentation,&map,&menu,&marker};assert(probe.Inspect(text.label,false));
+ root.placed={120,90,0};assert(!probe.Inspect(text.label,false));root.placed={158,115,0};
+ root.parent=&canvas;assert(!probe.Inspect(text.label,false));root.parent=&container;
+ nativeIcons.parent=&foreign;assert(!probe.Inspect(text.label,false));nativeIcons.parent=&container;
+ presentation.container=&foreign;assert(!probe.Inspect(text.label,false));presentation.container=&container;
+ presentation.canvas=&foreign;assert(!probe.Inspect(text.label,false));presentation.canvas=&canvas;
+ image.sx+=4;assert(!probe.Inspect(text.label,false));image.sx-=4;
+ element.pos[0]+=1;assert(!probe.Inspect(text.label,false));element.pos[0]-=1;
+ root.visible=false;assert(!probe.Inspect(text.label,false));root.visible=true;
+ probe.ready=false;assert(!probe.Inspect(text.label,false));probe.ready=true;
+ // Another valid parent origin and non-unit DPI cannot be compensated by
+ // dropping screen-origin offsets from the projection assertion.
+ container.sx=120;container.sy=110;game.workspace.dpi=1.25;root.placed={176,104,0};assert(probe.Inspect(text.label,false));
+ // Shared render roots choose their dedicated overlay instead of native icons.
+ menu.overlay=&container;nativeIcons.parent=&foreign;assert(probe.Inspect(text.label,false));
+ menu.overlay=&foreign;assert(!probe.Inspect(text.label,false));menu.overlay=&container;
+ marker.x=6250;marker.y=7250;element.pos={6250,0,7250};assert(probe.Inspect(text.label,true));
+}
+''')
+print('PASS: actual full Inspect rejects canvas parenting, wrong active container/canvas, omitted origin offset, wrong rendered box and stale coordinates; differing origins and DPI accepted')
+
+# Execute the added actual three phase blocks. Long-label capture must happen
+# before reset/close, and the radio label must be restored for later reopening.
+phases='\n'.join(body(p,'if (m_Stage == '+str(stage)+')') for stage in [5,22,23])
+phases=phases.replace('m_Marker.','m_Marker->').replace('System.','System::')
+run(r'''
+#include <cassert>
+#include <string>
+using string=std::string;
+struct Marker{string label="Radio station";void SetCustomText(string text){label=text;}};
+struct System{static inline int captures=0;static void MakeScreenshot(string path){assert(path=="$logs:err-objective-map-standard-long-warning");captures++;}};
+int checkpoints=0;void Print(string text){assert(text=="ERR OBJECTIVE CAPTURE READY: standard-long-warning");checkpoints++;}
+struct Probe{Marker*m_Marker;int m_Stage=5,closes=0,inspections=0;float m_Wait=0;bool Inspect(string text,bool rough){inspections++;return !rough&&m_Marker->label==text;}bool CloseStandard(){assert(m_Marker->label=="Radio station");closes++;return true;}
+ int Advance(float now){if(now<m_Wait)return 0;
+'''+phases+r'''
+ return 0;}
+};
+int main(){Marker marker;Probe p{&marker};assert(p.Advance(100)==0&&p.m_Stage==22&&p.m_Wait==850&&p.closes==0);assert(marker.label=="Reported mine area (500 m); may still be active");
+ p.Advance(849);assert(System::captures==0&&p.inspections==1);
+ p.Advance(850);assert(p.m_Stage==23&&p.m_Wait==2350&&p.inspections==2&&System::captures==1&&checkpoints==1&&p.closes==0);
+ p.Advance(2349);assert(marker.label=="Reported mine area (500 m); may still be active"&&p.closes==0);
+ p.Advance(2350);assert(p.m_Stage==6&&p.closes==1&&marker.label=="Radio station"&&System::captures==1);
+}
+''')
+assert 'GetOrigin() + "1037.25 0 1063.75"' in s
+print('PASS: actual long-warning phases retain visible label through1500ms checkpoint then restore radio before close; synthetic marker separated from unmoved player')
+
+# Actual Center method offsets the pan target, not the disclosed data or input.
+center=body(p,'protected void Center(').replace('protected ','').replace('m_Marker.','m_Marker->').replace('m_Map.','m_Map->')
+run(r'''
+#include <cassert>
+#include <cmath>
+#include <initializer_list>
+struct Marker{int x=6250,y=7250;void GetWorldPos(int p[2]){p[0]=x;p[1]=y;}};
+struct Map{float zoom=0,x=0,y=0,duration=0;int calls=0;void ZoomPanSmooth(float z,float px,float py,float t){zoom=z;x=px;y=py;duration=t;calls++;}};
+struct Probe{Marker*m_Marker;Map*m_Map;
+'''+center+r'''
+};
+int main(){Marker marker;Map map;Probe p{&marker,&map};for(float zoom:{0.3f,0.6f}){p.Center(zoom);assert(map.zoom==zoom&&map.y==7250&&std::abs(map.duration-0.1f)<0.001f);assert(std::abs((marker.x-map.x)*zoom+100.0f)<0.01f);assert(marker.x==6250&&marker.y==7250);}assert(map.calls==2);}
+''')
+print('PASS: actual Center places marker100screenpx left of pan center at both fixture zooms without altering disclosed coordinates')
--- a/tools/headless_workbench.py
+++ b/tools/headless_workbench.py
@@ -14,7 +14,7 @@
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=['normal-escape-route', 'escape-route', 'optional-site-skip', 'infantry-combat', 'medical-combat', 'hunter-faction', 'unconscious-camera', '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', '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', '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('--smoke', choices=['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', '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', '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('--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('--mine-stock', action='store_true', help='Read-only mounted-ammo check of exact callback-proved finite PMN4 cargo before and after natural radio delivery')
@@ -33,6 +33,7 @@
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 == '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:
@@ -139,7 +140,7 @@
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', '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', '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')
+ 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', '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', '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')