Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# Inventory and BMP checks prepared; complete regression suite restored
|
||||
|
||||
All **309 registered regression programs pass** on the exact unchanged inputs in `suite-inputs.json` (246.23 seconds). This includes the previously blocked packaging tests and the production inventory callback regression. The source-bundle preview passes. It does not compile or qualify engine behavior.
|
||||
|
||||
## Inventory callback check
|
||||
|
||||
A new explicit `--project core --smoke inventory-hitzone --rounds 1 --timeout 240` Workbench selector prepares one bounded cycle through the ordinary inventory and character-inspection menu. It pins the original conscious participant, mode and menu; waits for seven genuine native injury containers; emits one real blood-invoker notification with one argument; and requires each original native updater to receive it exactly once with explicit true. It then closes only its own menu, requires genuine detach cleanup, emits the notification again and checks that the retained old handlers receive no late update. Blood health must stay unchanged. The inner deadline is 30 seconds and never extends; native close refusal stays a failure even after one bounded cleanup attempt.
|
||||
|
||||
The focused actual-method harness passes and a missing-late-update-check mutation fails at runtime. UI, invoker and engine boundaries are doubled there. The installed SDK confirms the native menu signatures. This source preparation is not native compilation, rendered UI, natural injury, treatment, ordinary input or multiplayer proof. The production callback adapter from the preceding checkpoint is unchanged.
|
||||
|
||||
## BMP diagnostic refinement
|
||||
|
||||
The previous [failed native check](../native-2026-10-03-bmp-ui-callback/report.json) identified the exact empty armament's slot mismatch. The existing opt-in diagnostic now records missing item/null parent/different parent, structural slot identities and zero-muzzle rejection separately, once per reason. It preserves ordinary sampler behavior, custody/completeness rules, counts and caps. Ordinary and Workbench focused checks pass; the old source fails the newly required diagnostic assertion. Independent review binds exact hashes.
|
||||
|
||||
Read-only installed-resource review confirms the exact WCS empty armament declares a disabled inventory-item component and no muzzle. This supports a possible distinction between structural weapon mounting and inventory custody; it does not establish the live binding. No production exception has been added. Observe the prepared check before deciding whether a narrowly constrained zero-muzzle structural exclusion is justified.
|
||||
|
||||
## Native deferral and temporary storage
|
||||
|
||||
A read-only host preflight found an existing game, so no native process was started. The new checks still require native compilation and execution once the host is idle, serialized in the existing hidden and muted private environment. Inventory first, then the existing `--project chat --smoke mounted-ammo --mounted-bmp --rounds 1 --timeout 420` diagnostic. Do not change completeness or substitute another vehicle to obtain a pass.
|
||||
|
||||
The prior shared `/tmp` quota failure was resolved sufficiently for the complete suite by relocating only this task's inactive frozen-source duplicate to disk. All 1031 files were verified before and after copying; the original path remains linked to the identical files. `scratch-relocation.json` records every retained hash. Other tasks' files and all historical evidence were preserved. No production safety check was relaxed to accommodate a different temporary path.
|
||||
|
||||
Original 533 reference files, staged work, five sealed development candidates and the installed successor runtime are unchanged. This checkpoint adds no installed/package update, manual escape, native BMP radio proof, twelve-player multiplayer, physical support reliability, FPS or clean-release qualification.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"baseline.json": "bc9aa67de57421ebfb5de2f540015518b1012e93ac7afc48251b67a36afc771f",
|
||||
"suite-inputs.json": "a37a27713980a4aef97f55a6384ae6c79f80b3c2ec725f501de0913ffd4057ed",
|
||||
"suite-result.json": "15826804ef8dfbd3d405d4e23ac1ccff96009891ed42b6d65924ffa3cf63ffe9",
|
||||
"suite.log": "2f6455b81ab73aa5d6016e9b3d02bde25bd549abba0ed75df6a95a0ac596d697",
|
||||
"preservation.json": "3ebf5e70ede199542c91489a77351157e619f53006113eb42e4eb417abdff62a",
|
||||
"source-bundle-preview.json": "2fcb3167668c25c8da62c896a3ea6179977eb42c0075c5b826f4bc7e8a08c40c",
|
||||
"registered-check.py": "5cfe6ab8106fdd6d7ffb465da7145dcc995912466203c98e55d01679f11b6512",
|
||||
"scratch-relocation.json": "21cc568776e5df959f498e84a1dcbe8f886d24fc23cad842b2b43b552ef7cbcc",
|
||||
"native-deferral.json": "a565efca38d353101a89f428da28d897a1646923749afa37a8ccf7b6d9506ecc",
|
||||
"installed-preservation.json": "00d43b09720af162cb42ca2cfea569a878314e3ae31e808694568742862c798d",
|
||||
"README.md": "bfb5fc083f883be2553455b64c263289eeb7d02a972734939cf15940c8fcf8dd",
|
||||
"post-suite-doc-delta.json": "6e93d6c4d5cf262d49e8948e1d785d9b487141730ebff292de536cca5954ad19",
|
||||
"mounted/source-manifest.json": "e2e66871228b47738075e6d5faf1657c3c1c10fdbed2850de3c2a8d13b6883cf",
|
||||
"mounted/fix.patch": "442d71268cbdb2d3bb50fbe73bcda0203ab74e8fdd9c4469f840b2bb4413a378",
|
||||
"mounted/green.log": "427e7758df3b8746673da188ab74c12b409011eb4f4024b98fb709b7b5aea0a4",
|
||||
"mounted/baseline-red.log": "5719ad13bf68d29feb26bc053851949d086c1e2c22d48a879af1edda2e422c83",
|
||||
"mounted/independent-review.json": "db71bf614b0f20c1ab30fbe800dfd304e1f68ee7fdfc08179137637d8d51ad5c",
|
||||
"mounted/resource-review.json": "30e2c4c0bf293c39bb113efeca60ac3fdff55d3aaf016b2583b4ee4861cd83af",
|
||||
"inventory/source-manifest.json": "54d993dc6516f6b962633386693f50d5610568ea62337a88b6f601420e60bacc",
|
||||
"inventory/fix.patch": "d5b18a1716fa31fae10e51db703441ed660467c2abc7ee825ae19f3ea7a5495d",
|
||||
"inventory/green.log": "84c104970b68d9355726cf72b63ac726bf1e4ad6285c4ed66114519264125731",
|
||||
"inventory/late-update-mutation-red.log": "a89f6b30fb52ca00aa7e156a493c2af981461474aa3095382005469b5868f10d",
|
||||
"inventory/review.json": "fb2ffeebf96cdd31bb0bbae34fe4e3cedf37a2f2054fca40e1af8981f8d1a4e0",
|
||||
"inventory/independent-review.json": "367ec2874ef311e5d2399dffc2cd41ac2a93f4e33d811129b03a02bedba04801",
|
||||
"inventory/menu-api.json": "8dc9efb1f8c1753d5dfcc78635a3f4190160973443c6a61d6482a47eddc58870",
|
||||
"source/tests/test_hq_mounted_ammo_sampler.py": "f4adb94e5e688ff3843ef240c7a4232308979f232a53fe7a6a1d94d02591b960",
|
||||
"source/tests/test_native_inventory_hitzone.py": "bb67f3e2002661331b1f170955989df5bf30a60b48a294ff979bb6db335ca819",
|
||||
"source/tools/headless_workbench.py": "7ffa38822cee1df4248ea027234823a51c2535a8ce74a9e1a9e7756ee1a72d09",
|
||||
"source/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedAmmoSampler.c": "e8e36110e126de921d7635b8c319f923a48d84f700fa78e018bb40136d4819d1",
|
||||
"source/addons/core/Scripts/Game/ERR/00_Core/ERR_NativeInventoryHitZoneProbe.c": "604eacdc66cd52bc8a0115862ee7884a8b0f83bdb50609397de671eacd476543",
|
||||
"source/addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c": "d4318ac4f8567afde4cb9f9546f6022ca74e5cabd689355ee75d19f2cde25060"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"unchanged": true,
|
||||
"files": {
|
||||
"addon.gproj": "73e973a1f1c5910393e16f86a3a40d792031bb2fa3fc7f4dd87a06a16c3e34a4",
|
||||
"data.pak": "43fdaada75a7e4cc5916040b07e8385f5532513fbaf349efba26d70dbd587b27",
|
||||
"resourceDatabase.rdb": "06cb9d25394b496281748b609bef1cab81eebb1025c5a8db61e4a90bc9518c74"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
--- /dev/null
|
||||
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_NativeInventoryHitZoneProbe.c
|
||||
@@ -0,0 +1,166 @@
|
||||
+#ifdef WORKBENCH
|
||||
+// Read-only fixture access to native UI identities. No replacement widgets.
|
||||
+modded class SCR_InventoryMenuUI
|
||||
+{
|
||||
+ bool ERR_HitZoneProbeReady(IEntity actor)
|
||||
+ {
|
||||
+ return ERR_NativeInventoryHitZoneProbe.Enabled() && m_Player == actor && m_pStorageListUI
|
||||
+ && m_pStorageListUI.GetStorage() && m_PlayerRenderAttributes && m_wAttachmentPointsContainer;
|
||||
+ }
|
||||
+ bool ERR_HitZoneProbePoints(out array<ref SCR_InventoryHitZonePointContainerUI> points)
|
||||
+ {
|
||||
+ points.Clear();
|
||||
+ if (!ERR_NativeInventoryHitZoneProbe.Enabled() || m_aHitZonePoints.Count() != 7) return false;
|
||||
+ foreach (SCR_InventoryHitZonePointContainerUI point : m_aHitZonePoints) points.Insert(point);
|
||||
+ return true;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+modded class SCR_InventoryHitZonePointContainerUI
|
||||
+{
|
||||
+ protected SCR_HitZone m_ERRProbeBlood;
|
||||
+ protected int m_ERRProbeUpdates;
|
||||
+ protected bool m_ERRProbeFalseRefresh;
|
||||
+ protected override void UpdateHitZoneState(SCR_HitZone hz, bool updateAttachmentStorage = true)
|
||||
+ {
|
||||
+ super.UpdateHitZoneState(hz, updateAttachmentStorage);
|
||||
+ if (m_ERRProbeBlood && hz == m_ERRProbeBlood)
|
||||
+ {
|
||||
+ m_ERRProbeUpdates++;
|
||||
+ if (!updateAttachmentStorage) m_ERRProbeFalseRefresh = true;
|
||||
+ }
|
||||
+ }
|
||||
+ bool ERR_HitZoneProbeWatch(ERR_GameMode mode, SCR_HitZone blood)
|
||||
+ {
|
||||
+ if (!ERR_NativeInventoryHitZoneProbe.Enabled() || !mode || mode != m_ERRHitZoneMode
|
||||
+ || !blood || !m_ERRHitZoneSources.Contains(blood)) return false;
|
||||
+ m_ERRProbeBlood = blood; m_ERRProbeUpdates = 0; m_ERRProbeFalseRefresh = false;
|
||||
+ return true;
|
||||
+ }
|
||||
+ bool ERR_HitZoneProbeOnce() { return m_ERRProbeUpdates == 1 && !m_ERRProbeFalseRefresh; }
|
||||
+ bool ERR_HitZoneProbeDetached() { return !m_ERRHitZoneMode && m_ERRHitZoneSources.IsEmpty(); }
|
||||
+ void ERR_HitZoneProbeStop() { m_ERRProbeBlood = null; }
|
||||
+}
|
||||
+
|
||||
+// Emits one real one-argument damage-state notification without changing health.
|
||||
+// It proves callback/native UI integration, not a natural injury or manual UI use.
|
||||
+class ERR_NativeInventoryHitZoneProbe
|
||||
+{
|
||||
+ protected ERR_GameMode m_Mode;
|
||||
+ protected SCR_PlayerController m_Controller;
|
||||
+ protected IEntity m_Actor;
|
||||
+ protected SCR_InventoryStorageManagerComponent m_Inventory;
|
||||
+ protected SCR_CharacterBloodHitZone m_Blood;
|
||||
+ protected SCR_InventoryMenuUI m_Menu;
|
||||
+ protected ref array<ref SCR_InventoryHitZonePointContainerUI> m_Points = {};
|
||||
+ protected int m_Deadline;
|
||||
+ protected int m_Stage;
|
||||
+ protected int m_Result;
|
||||
+ protected bool m_Stopped;
|
||||
+ protected bool m_CloseRequested;
|
||||
+ protected float m_Health;
|
||||
+ static bool Enabled()
|
||||
+ {
|
||||
+ string enabled, outcome;
|
||||
+ return Replication.IsServer() && System.GetCLIParam("errBaseSmoke", enabled) && enabled == "1"
|
||||
+ && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "inventory-hitzone";
|
||||
+ }
|
||||
+ void ERR_NativeInventoryHitZoneProbe(ERR_GameMode mode, IEntity actor)
|
||||
+ {
|
||||
+ m_Mode = mode; m_Actor = actor; m_Deadline = System.GetTickCount() + 30000;
|
||||
+ m_Controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
||||
+ }
|
||||
+ protected bool Check(bool condition, string detail)
|
||||
+ {
|
||||
+ if (condition) { Print("ERR SMOKE PASS: inventory hitzone " + detail); return true; }
|
||||
+ Print("ERR SMOKE FAIL: inventory hitzone " + detail, LogLevel.ERROR);
|
||||
+ m_Result = -1; Stop(); return false;
|
||||
+ }
|
||||
+ protected bool Context()
|
||||
+ {
|
||||
+ return Enabled() && m_Mode && m_Mode == ERR_GameMode.Get() && m_Mode.Active()
|
||||
+ && m_Controller && m_Controller == GetGame().GetPlayerController() && m_Controller.GetControlledEntity() == m_Actor
|
||||
+ && m_Mode.Players() && m_Mode.Players().CurrentActor(m_Actor) && ERR_Actors.CanFight(m_Actor);
|
||||
+ }
|
||||
+ protected bool OwnsMenu()
|
||||
+ {
|
||||
+ return m_Menu && GetGame().GetMenuManager().FindMenuByPreset(ChimeraMenuPreset.Inventory20Menu) == m_Menu;
|
||||
+ }
|
||||
+ protected bool UnchangedHealth() { return m_Blood && m_Blood.GetHealth() == m_Health; }
|
||||
+ int Tick(float now)
|
||||
+ {
|
||||
+ if (m_Result != 0) return m_Result;
|
||||
+ if (m_Stopped) return -1;
|
||||
+ if (!Context()) { Check(false, "original conscious local round body changed"); return -1; }
|
||||
+ if (System.GetTickCount() >= m_Deadline) { Check(false, "fixed 30-second UI/callback deadline"); return -1; }
|
||||
+ if (m_Stage == 0)
|
||||
+ {
|
||||
+ if (!Check(!GetGame().GetMenuManager().GetTopMenu(), "no existing menu adopted")) return -1;
|
||||
+ m_Inventory = SCR_InventoryStorageManagerComponent.Cast(m_Actor.FindComponent(SCR_InventoryStorageManagerComponent));
|
||||
+ SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(m_Actor.FindComponent(SCR_CharacterDamageManagerComponent));
|
||||
+ if (damage) m_Blood = damage.GetBloodHitZone();
|
||||
+ if (!Check(m_Inventory && m_Blood, "original native inventory and blood event source present")) return -1;
|
||||
+ m_Health = m_Blood.GetHealth();
|
||||
+ m_Inventory.OpenInventory();
|
||||
+ m_Menu = SCR_InventoryMenuUI.Cast(GetGame().GetMenuManager().FindMenuByPreset(ChimeraMenuPreset.Inventory20Menu));
|
||||
+ if (!Check(OwnsMenu(), "ordinary native inventory opens without input automation")) return -1;
|
||||
+ m_Stage = 1; return 0;
|
||||
+ }
|
||||
+ if (m_Stage == 1)
|
||||
+ {
|
||||
+ if (!Check(OwnsMenu() && UnchangedHealth(), "owned inventory and original blood health retained")) return -1;
|
||||
+ if (!m_Menu.ERR_HitZoneProbeReady(m_Actor)) return 0;
|
||||
+ m_Menu.InspectItem(null); // Native character inspection creates its normal seven containers over several frames.
|
||||
+ m_Stage = 2; return 0;
|
||||
+ }
|
||||
+ if (m_Stage == 2)
|
||||
+ {
|
||||
+ if (!Check(OwnsMenu() && UnchangedHealth(), "native inspection retains original menu and health")) return -1;
|
||||
+ if (!m_Menu.ERR_HitZoneProbePoints(m_Points)) return 0;
|
||||
+ array<SCR_InventoryHitZonePointContainerUI> seen = {};
|
||||
+ foreach (SCR_InventoryHitZonePointContainerUI point : m_Points)
|
||||
+ {
|
||||
+ if (!Check(point && !seen.Contains(point) && point.ERR_HitZoneProbeWatch(m_Mode, m_Blood), "unique native point retains original blood subscription")) return -1;
|
||||
+ seen.Insert(point);
|
||||
+ }
|
||||
+ if (!Check(UnchangedHealth() && m_Blood.GetOnDamageStateChanged(false), "original blood event and health retained before notification")) return -1;
|
||||
+ m_Blood.GetOnDamageStateChanged(false).Invoke(m_Blood);
|
||||
+ foreach (SCR_InventoryHitZonePointContainerUI updated : m_Points)
|
||||
+ if (!Check(updated.ERR_HitZoneProbeOnce(), "one-argument event reached native updater once with explicit true")) return -1;
|
||||
+ if (!Check(UnchangedHealth() && OwnsMenu(), "notification changes no blood health or menu ownership")) return -1;
|
||||
+ bool closeAccepted = GetGame().GetMenuManager().CloseMenu(m_Menu);
|
||||
+ m_CloseRequested = closeAccepted;
|
||||
+ if (!Check(closeAccepted, "exact owned inventory close request accepted")) return -1;
|
||||
+ m_Stage = 3; return 0;
|
||||
+ }
|
||||
+ if (m_Stage == 3)
|
||||
+ {
|
||||
+ MenuBase current = GetGame().GetMenuManager().FindMenuByPreset(ChimeraMenuPreset.Inventory20Menu);
|
||||
+ if (current == m_Menu && m_Menu) return 0;
|
||||
+ if (!Check(!current, "native inventory closes without adopting a replacement")) return -1;
|
||||
+ foreach (SCR_InventoryHitZonePointContainerUI detached : m_Points)
|
||||
+ if (!Check(detached && detached.ERR_HitZoneProbeDetached(), "native detach cleared original point subscriptions")) return -1;
|
||||
+ if (!Check(UnchangedHealth() && m_Blood.GetOnDamageStateChanged(false), "original blood event and health retained before notification")) return -1;
|
||||
+ m_Blood.GetOnDamageStateChanged(false).Invoke(m_Blood);
|
||||
+ foreach (SCR_InventoryHitZonePointContainerUI oldPoint : m_Points)
|
||||
+ if (!Check(oldPoint.ERR_HitZoneProbeOnce(), "closed original point receives no late native update")) return -1;
|
||||
+ if (!Check(UnchangedHealth(), "blood health unchanged through complete callback proof")) return -1;
|
||||
+ m_Menu = null; m_Result = 1; Stop(); return 1;
|
||||
+ }
|
||||
+ Check(false, "unexpected fixture stage"); return -1;
|
||||
+ }
|
||||
+ void Stop()
|
||||
+ {
|
||||
+ if (m_Stopped) return;
|
||||
+ m_Stopped = true;
|
||||
+ foreach (SCR_InventoryHitZonePointContainerUI point : m_Points) if (point) point.ERR_HitZoneProbeStop();
|
||||
+ m_Points.Clear();
|
||||
+ if (GetGame() && GetGame().GetMenuManager() && !m_CloseRequested && OwnsMenu())
|
||||
+ {
|
||||
+ m_CloseRequested = true;
|
||||
+ GetGame().GetMenuManager().CloseMenu(m_Menu);
|
||||
+ }
|
||||
+ m_Menu = null; m_Blood = null; m_Inventory = null;
|
||||
+ }
|
||||
+}
|
||||
+#endif
|
||||
--- a/addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c
|
||||
@@ -20,6 +20,7 @@
|
||||
protected ref ERR_NativeLocalAdmissionProbe m_ERRLocalAdmissionProbe;
|
||||
protected ref ERR_NativePoweredFlightProbe m_ERRPoweredFlightProbe;
|
||||
protected ref ERR_NativePrisonBreachProbe m_ERRPrisonBreachProbe;
|
||||
+ protected ref ERR_NativeInventoryHitZoneProbe m_ERRInventoryHitZoneProbe;
|
||||
protected ref ERR_NativeItemStationTeardownProbe m_ERRItemStationTeardownProbe;
|
||||
protected ref ERR_NativeObjectiveLossProbe m_ERRObjectiveLossProbe;
|
||||
protected ref ERR_NativeMedicalProbe m_ERRBaseMedicalProbe;
|
||||
@@ -100,6 +101,7 @@
|
||||
if (m_ERRLocalAdmissionProbe) m_ERRLocalAdmissionProbe.Stop();
|
||||
if (m_ERRPoweredFlightProbe) m_ERRPoweredFlightProbe.Stop();
|
||||
if (m_ERRPrisonBreachProbe) m_ERRPrisonBreachProbe.Stop();
|
||||
+ if (m_ERRInventoryHitZoneProbe) m_ERRInventoryHitZoneProbe.Stop();
|
||||
if (m_ERRItemStationTeardownProbe) m_ERRItemStationTeardownProbe.Stop();
|
||||
if (m_ERRCasualtyServiceProbe) m_ERRCasualtyServiceProbe.Stop();
|
||||
if (m_ERRObjectiveLossProbe) m_ERRObjectiveLossProbe.Stop();
|
||||
@@ -172,6 +174,13 @@
|
||||
{
|
||||
if (!ERR_MissionRules.Terminal(current)) return;
|
||||
GetGame().GetCallqueue().Remove(ERR_BaseTick);
|
||||
+ if (m_ERRExtractionCase == "inventory-hitzone")
|
||||
+ {
|
||||
+ if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 2102, "native inventory callback fixture finished before explicit reset")) return;
|
||||
+ ERR_StopHealthProbes();
|
||||
+ Print("ERR SMOKE COMPLETE: real one-argument blood event reaches native inventory updater and detached points stay silent; synthetic notification, no injury/manual UI/multiplayer proof");
|
||||
+ return;
|
||||
+ }
|
||||
if (m_ERRExtractionCase == "station-teardown")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 2002, "native station teardown fixture finished before explicit reset")) return;
|
||||
@@ -336,6 +345,13 @@
|
||||
Print("ERR SMOKE COMPLETE: expected startup-failure");
|
||||
return;
|
||||
}
|
||||
+ if (m_ERRBaseStep == 2100)
|
||||
+ {
|
||||
+ int result = m_ERRInventoryHitZoneProbe.Tick(now);
|
||||
+ if (result < 0) { ERR_BaseCheck(false, "native inventory hitzone callback fixture"); return; }
|
||||
+ if (result == 1) { m_ERRBaseStep = 2102; Transition(ERR_Phase.DEFEAT); }
|
||||
+ return;
|
||||
+ }
|
||||
if (m_ERRBaseStep == 2000)
|
||||
{
|
||||
int result = m_ERRItemStationTeardownProbe.Tick(now);
|
||||
@@ -509,6 +525,12 @@
|
||||
{
|
||||
if (!ERR_NativeDiscoveryMapProbe.Run()) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
m_ERRDiscoveryMapChecked = true;
|
||||
+ }
|
||||
+ if (m_ERRExtractionCase == "inventory-hitzone")
|
||||
+ {
|
||||
+ m_ERRInventoryHitZoneProbe = new ERR_NativeInventoryHitZoneProbe(this, m_ERRBasePlayer);
|
||||
+ m_ERRBaseStep = 2100;
|
||||
+ return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "station-teardown")
|
||||
{
|
||||
--- 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=['crash-start', 'crash-geometry', 'normal-escape-route', 'escape-route', 'optional-site-skip', 'infantry-combat', 'medical-combat', 'hunter-faction', 'unconscious-camera', 'objective-map', 'town-garrisons', 'site-garrisons', 'radio-interaction', 'enemy-loadout', 'aggression', 'air-logistics', 'air-assault', 'air-recon', 'airfield-markers', 'opening-tools', 'mortar-station', 'generated-sites', 'hq-local-handoff', 'infantry-transfer', 'hq-body', 'convoy', 'supply-convoys', 'supply-travel', 'mounted-ammo', 'alarm-hearing', 'hq-memory', 'hq-awareness', 'aircraft-field', 'aircraft-route', 'native-end-active', 'native-end-preparing', 'aircraft-control', 'aircraft-cancel', 'aircraft-translate', 'map-ownership', 'mortar-refresh', 'mortar-salvo', 'mortar-courier', 'station-teardown', '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=['crash-start', 'crash-geometry', 'normal-escape-route', 'escape-route', 'optional-site-skip', 'infantry-combat', 'medical-combat', 'hunter-faction', 'unconscious-camera', 'objective-map', 'town-garrisons', 'site-garrisons', 'radio-interaction', 'enemy-loadout', 'aggression', 'air-logistics', 'air-assault', 'air-recon', 'airfield-markers', 'opening-tools', 'mortar-station', 'generated-sites', 'hq-local-handoff', 'infantry-transfer', 'hq-body', 'convoy', 'supply-convoys', 'supply-travel', 'mounted-ammo', 'alarm-hearing', 'hq-memory', 'hq-awareness', 'aircraft-field', 'aircraft-route', 'native-end-active', 'native-end-preparing', 'aircraft-control', 'aircraft-cancel', 'aircraft-translate', 'map-ownership', 'mortar-refresh', 'mortar-salvo', 'mortar-courier', 'station-teardown', 'inventory-hitzone', 'prison-breach', 'powered-flight', 'powered-translate', 'local-admission', 'mortar-expiry', 'mortar-transfer', 'mortar-fire', 'patrol-hq', 'victory', 'lost-helicopter', 'player-death', 'radio-network', 'hq-response', 'mortar-inventory', 'startup-failure', 'site-survey', 'cache-loss', 'extraction-capacity', 'early-helicopter-loss', 'extraction-startup-failure', 'extraction-boundary-failure', 'transport-loss', 'transport-survey', 'lifecycle', 'objective-ui', 'recovery', 'prisoner-kit', 'medical', 'casualty-load', 'casualty-direct', 'casualty-bench', 'casualty-remove', 'environment', 'environment-restart', 'patrols', 'radio-document-loss', 'radio-document-loss-partial', 'guard-cancel', 'casualty-service'], default='victory')
|
||||
p.add_argument('--trace-random-radius', action='store_true', help='Workbench-only read-only zero-radius caller traces; one normal Chat round, capped at sixteen records')
|
||||
p.add_argument('--maintenance-refuel', action='store_true', help='Opt-in normal-route check: original held Southern fuel can transfers finite fuel through its native UAZ action; one arranged tank depletion, no stock creation')
|
||||
p.add_argument('--mounted-swap-eligibility', action='store_true', help='Read-only mounted-ammo variant: one exact finite cargo/mounted magazine CanSwap call; no transfer or radio proof')
|
||||
@@ -36,6 +36,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 == 'inventory-hitzone' and (a.project != 'core' or a.rounds != 1 or a.timeout < 240): p.error('inventory-hitzone requires --project core --rounds 1 and timeout at least 240 seconds')
|
||||
if a.smoke == 'crash-start' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 240): p.error('crash-start requires Chat, one round and timeout at least 240 seconds')
|
||||
if a.smoke == 'crash-geometry' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 180): p.error('crash-geometry requires Chat, one round and timeout at least180 seconds')
|
||||
if a.trace_random_radius and (a.smoke != 'normal-escape-route' or a.project != 'chat' or a.rounds != 1 or a.timeout < 420): p.error('--trace-random-radius requires Chat normal-escape-route, one round and timeout at least 420 seconds')
|
||||
@@ -153,10 +154,10 @@
|
||||
if a.recon_takeover_only: command += ['-errReconTakeoverOnly', '1']
|
||||
if a.body_recovery_only: command += ['-errBodyRecoveryOnly', '1']
|
||||
if a.project == 'core':
|
||||
- if a.smoke not in {'optional-site-skip', 'infantry-combat', 'medical-combat', 'hunter-faction', 'unconscious-camera', 'objective-map', 'town-garrisons', 'site-garrisons', 'radio-interaction', 'enemy-loadout', 'aggression', 'air-logistics', 'air-assault', 'air-recon', 'airfield-markers', 'opening-tools', 'mortar-station', 'generated-sites', 'native-end-active', 'native-end-preparing', 'station-teardown', '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', 'inventory-hitzone', 'prison-breach', 'powered-flight', 'powered-translate', 'local-admission', 'victory', 'startup-failure', 'site-survey', 'cache-loss', 'extraction-capacity', 'early-helicopter-loss', 'extraction-startup-failure', 'extraction-boundary-failure', 'transport-loss', 'transport-survey', 'lifecycle', 'objective-ui', 'recovery', 'prisoner-kit', 'medical', 'casualty-load', 'casualty-direct', 'casualty-bench', 'casualty-remove', 'environment', 'environment-restart', 'patrols', 'radio-document-loss', 'radio-document-loss-partial', 'guard-cancel', 'casualty-service'}: p.error('Unsupported Core-only fixture')
|
||||
command[command.index('-plugin=ERR_ValidateWorldPlugin')] = '-plugin=ERR_ValidateBaseWorldPlugin'
|
||||
command[command.index('-errSmoke')] = '-errBaseSmoke'
|
||||
-if a.smoke in {'station-teardown', 'prison-breach', 'powered-flight', 'powered-translate', 'local-admission', 'cache-loss', 'extraction-capacity', 'early-helicopter-loss', 'extraction-startup-failure', 'extraction-boundary-failure', 'transport-loss', 'transport-survey', 'lifecycle', 'objective-ui', 'recovery', 'prisoner-kit', 'medical', 'casualty-load', 'casualty-direct', 'casualty-bench', 'casualty-remove', 'environment', 'environment-restart', 'patrols', 'radio-document-loss', 'radio-document-loss-partial', 'guard-cancel', 'casualty-service'} and a.project != 'core': p.error('This fixture requires --project core')
|
||||
+if a.smoke in {'station-teardown', 'inventory-hitzone', 'prison-breach', 'powered-flight', 'powered-translate', 'local-admission', 'cache-loss', 'extraction-capacity', 'early-helicopter-loss', 'extraction-startup-failure', 'extraction-boundary-failure', 'transport-loss', 'transport-survey', 'lifecycle', 'objective-ui', 'recovery', 'prisoner-kit', 'medical', 'casualty-load', 'casualty-direct', 'casualty-bench', 'casualty-remove', 'environment', 'environment-restart', 'patrols', 'radio-document-loss', 'radio-document-loss-partial', 'guard-cancel', 'casualty-service'} and a.project != 'core': p.error('This fixture requires --project core')
|
||||
if a.smoke in {'startup-failure', 'site-survey', 'transport-survey', 'extraction-startup-failure', 'extraction-boundary-failure'} and (a.project != 'core' or a.rounds != 1): p.error('This diagnostic requires --project core --rounds 1')
|
||||
env = os.environ.copy()
|
||||
env.update(STEAM_COMPAT_DATA_PATH=str(compat), STEAM_COMPAT_CLIENT_INSTALL_PATH=str(steam), WINEPREFIX=str(compat/'pfx'))
|
||||
--- /dev/null
|
||||
+++ b/tests/test_native_inventory_hitzone.py
|
||||
@@ -0,0 +1,87 @@
|
||||
+"""Actual probe state machine/counters; menu, actor and invoker boundaries doubled."""
|
||||
+import ast,os,re
|
||||
+from pathlib import Path
|
||||
+from types import SimpleNamespace
|
||||
+from policy_harness import ROOT,body,run
|
||||
+BASE=Path(os.environ.get('ERR_INVENTORY_PROBE_ROOT',ROOT))
|
||||
+p=Path(os.environ.get('ERR_NATIVE_INVENTORY_SOURCE',BASE/'addons/core/Scripts/Game/ERR/00_Core/ERR_NativeInventoryHitZoneProbe.c'))
|
||||
+s=p.read_text()
|
||||
+assert s.startswith('#ifdef WORKBENCH') and s.rstrip().endswith('#endif')
|
||||
+for forbidden in ['SetHealth(', 'FullHeal(', 'ForceUnconsciousness(', 'SetOrigin(', 'CreateWidgets(', 'InputManager', 'ERR_ClearHitZoneCallbacks(']: assert forbidden not in s
|
||||
+assert 'super.UpdateHitZoneState(hz, updateAttachmentStorage);' in s
|
||||
+assert 'm_Menu.InspectItem(null);' in s and 'm_Deadline = System.GetTickCount() + 30000' in s
|
||||
+assert 'out array<ref SCR_InventoryHitZonePointContainerUI> points' in s
|
||||
+
|
||||
+def cpp(s):
|
||||
+ for a,b in [('protected override ',''),('protected ',''),('override ',''),('super.UpdateHitZoneState','Native::UpdateHitZoneState'),('ERR_NativeInventoryHitZoneProbe','Probe'),('SCR_InventoryHitZonePointContainerUI','Point'),('SCR_CharacterDamageManagerComponent','Damage'),('SCR_InventoryStorageManagerComponent','Inventory'),('SCR_CharacterBloodHitZone','Zone'),('SCR_HitZone','Zone'),('ERR_GameMode','Mode'),('SCR_PlayerController','Controller'),('SCR_InventoryMenuUI','Menu'),('MenuBase','Menu'),('IEntity','Actor*'),('string ','string '),('null','nullptr'),('foreach (','for ('),('ref ',''),('array<Point> seen = {};','Array<Point*> seen;')]:s=s.replace(a,b)
|
||||
+ for name in ['mode','damage','point','updated','detached','oldPoint']:
|
||||
+ s=re.sub(r'\b'+name+r'\.',name+'->',s)
|
||||
+ for name in ['m_Mode','m_Controller','m_Actor','m_Inventory','m_Blood','m_Menu']:s=s.replace(name+'.',name+'->')
|
||||
+ for cls in ['Probe','Mode','Controller','Damage','Inventory','Menu','System','Replication','ERR_Actors','ChimeraMenuPreset']:s=s.replace(cls+'.',cls+'::')
|
||||
+ for cls,var in [('Mode','mode'),('Zone','hz'),('Zone','blood'),('Point','point'),('Point','updated'),('Point','detached'),('Point','oldPoint'),('Menu','current'),('Damage','damage')]:s=s.replace(cls+' '+var,cls+'* '+var)
|
||||
+ s=s.replace('GetGame().','GetGame()->').replace('GetMenuManager().','GetMenuManager()->').replace('Players().','Players()->').replace('GetOnDamageStateChanged(false).','GetOnDamageStateChanged(false)->')
|
||||
+ s=s.replace('FindComponent(Inventory)','FindComponent(1)').replace('FindComponent(Damage)','FindComponent(2)')
|
||||
+ s=s.replace('Print("ERR SMOKE FAIL: inventory hitzone " + detail, LogLevel.ERROR);','Print("ERR SMOKE FAIL: inventory hitzone " + detail);')
|
||||
+ return s
|
||||
+point='\n'.join(cpp(body(p,sig)) for sig in ['protected override void UpdateHitZoneState(','bool ERR_HitZoneProbeWatch(','bool ERR_HitZoneProbeOnce(','bool ERR_HitZoneProbeDetached(','void ERR_HitZoneProbeStop('])
|
||||
+probe='\n'.join(cpp(body(p,sig)) for sig in ['void ERR_NativeInventoryHitZoneProbe(','protected bool Check(','protected bool Context(','protected bool OwnsMenu(','protected bool UnchangedHealth(','int Tick(','void Stop('])
|
||||
+probe=probe.replace('void Probe(', 'Probe(')
|
||||
+run(r'''
|
||||
+#include <cassert>
|
||||
+#include <string>
|
||||
+#include <vector>
|
||||
+#include <algorithm>
|
||||
+#include <functional>
|
||||
+using string=std::string;template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;void Clear(){this->clear();}void Insert(T x){this->push_back(x);}bool Contains(T x){return std::find(this->begin(),this->end(),x)!=this->end();}bool IsEmpty(){return this->empty();}};
|
||||
+struct Actor;struct Mode;struct Point;struct Menu;struct Probe{static bool Enabled(){return enabled;}static inline bool enabled=true;};
|
||||
+namespace System{int now=0;int GetTickCount(){return now;}}
|
||||
+namespace ChimeraMenuPreset{int Inventory20Menu=1;}
|
||||
+void Print(string){}
|
||||
+struct Event{std::function<void()>fire;int calls=0;void Invoke(void*){calls++;if(fire)fire();}};
|
||||
+struct Zone{float health=100;Event event;float GetHealth(){return health;}Event*GetOnDamageStateChanged(bool create){assert(!create);return &event;}};
|
||||
+struct Damage{Zone*blood;Zone*GetBloodHitZone(){return blood;}static Damage*Cast(void*p){return static_cast<Damage*>(p);}};
|
||||
+struct Inventory{int opens=0;std::function<void()>opening;void OpenInventory(){opens++;if(opening)opening();}static Inventory*Cast(void*p){return static_cast<Inventory*>(p);}};
|
||||
+struct Actor{Inventory*inventory;Damage*damage;bool fit=true;void*FindComponent(int n){return n==1?static_cast<void*>(inventory):static_cast<void*>(damage);}};
|
||||
+namespace ERR_Actors{bool CanFight(Actor*a){return a&&a->fit;}}
|
||||
+struct Controller{Actor*actor;Actor*GetControlledEntity(){return actor;}static Controller*Cast(Controller*p){return p;}};
|
||||
+struct Roster{Actor*actor;bool CurrentActor(Actor*a){return a==actor;}};
|
||||
+struct Mode{static inline Mode*current=nullptr;Roster roster;bool active=true;static Mode*Get(){return current;}bool Active(){return active;}Roster*Players(){return &roster;}};
|
||||
+struct Native{int updates=0;void UpdateHitZoneState(Zone*,bool){updates++;}};
|
||||
+struct Point:Native{Zone*m_ERRProbeBlood=nullptr;int m_ERRProbeUpdates=0;bool m_ERRProbeFalseRefresh=false;Mode*m_ERRHitZoneMode=nullptr;Array<Zone*>m_ERRHitZoneSources;
|
||||
+POINT
|
||||
+};
|
||||
+struct Menu{Array<Point*>points;Actor*actor=nullptr;bool ready=true;int inspect=0;bool ERR_HitZoneProbeReady(Actor*a){return ready&&actor==a;}void InspectItem(void*){inspect++;}bool ERR_HitZoneProbePoints(Array<Point*>&out){out=points;return points.size()==7;}static Menu*Cast(Menu*m){return m;}};
|
||||
+struct Manager{Menu*current=nullptr;bool foreignTop=false,accept=true;int closes=0;void*GetTopMenu(){return foreignTop||current?this:nullptr;}Menu*FindMenuByPreset(int){return current;}bool CloseMenu(Menu*m){assert(m==current);closes++;return accept;}};
|
||||
+struct Game{Controller*controller;Manager manager;Controller*GetPlayerController(){return controller;}Manager*GetMenuManager(){return &manager;}};Game*game=nullptr;Game*GetGame(){return game;}
|
||||
+struct Actual:Probe{Mode*m_Mode;Controller*m_Controller;Actor*m_Actor;Inventory*m_Inventory=nullptr;Zone*m_Blood=nullptr;Menu*m_Menu=nullptr;Array<Point*>m_Points;int m_Deadline=0,m_Stage=0,m_Result=0;bool m_Stopped=false,m_CloseRequested=false;float m_Health=0;
|
||||
+PROBE
|
||||
+};
|
||||
+struct Fixture{Zone blood;Damage damage{&blood};Inventory inventory;Actor actor{&inventory,&damage};Controller controller{&actor};Mode mode;Game world{&controller,{}};Menu menu;Point points[7];Fixture(){System::now=0;Probe::enabled=true;game=&world;Mode::current=&mode;mode.roster.actor=&actor;menu.actor=&actor;inventory.opening=[&](){world.manager.current=&menu;};for(auto&p:points){p.m_ERRHitZoneMode=&mode;p.m_ERRHitZoneSources.Insert(&blood);menu.points.Insert(&p);}blood.event.fire=[&](){for(auto&p:points)if(!p.m_ERRHitZoneSources.IsEmpty())p.UpdateHitZoneState(&blood,true);};}
|
||||
+ void Close(){world.manager.current=nullptr;for(auto&p:points){p.m_ERRHitZoneMode=nullptr;p.m_ERRHitZoneSources.Clear();}}
|
||||
+};
|
||||
+int main(){
|
||||
+ {Fixture f;Actual p(&f.mode,&f.actor);assert(p.Tick(0)==0&&f.inventory.opens==1);assert(p.Tick(0)==0&&f.menu.inspect==1);assert(p.Tick(0)==0&&f.blood.event.calls==1&&f.world.manager.closes==1);assert(p.Tick(0)==0&&f.world.manager.closes==1);f.Close();assert(p.Tick(0)==1&&f.blood.event.calls==2&&f.blood.health==100);assert(p.Tick(0)==1);for(auto&x:f.points)assert(x.updates==1);}
|
||||
+ {Fixture f;Actual p(&f.mode,&f.actor);f.world.manager.foreignTop=true;assert(p.Tick(0)==-1&&f.inventory.opens==0&&f.world.manager.closes==0);}
|
||||
+ {Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);f.menu.ready=false;assert(p.Tick(0)==0&&f.menu.inspect==0);System::now=30000;assert(p.Tick(0)==-1&&f.world.manager.closes==1);}
|
||||
+ {Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);f.menu.points.pop_back();assert(p.Tick(0)==0&&f.blood.event.calls==0);p.Stop();assert(f.world.manager.closes==1);}
|
||||
+ {Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);f.menu.points[6]=f.menu.points[0];assert(p.Tick(0)==-1&&f.blood.event.calls==0);}
|
||||
+ {Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);f.blood.event.fire=[&](){for(auto&x:f.points)x.UpdateHitZoneState(&f.blood,false);};assert(p.Tick(0)==-1);}
|
||||
+ {Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);p.Tick(0);f.world.manager.current=nullptr;assert(p.Tick(0)==-1);}// missing real detach
|
||||
+ {Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);p.Tick(0);f.Close();f.blood.event.fire=[&](){f.points[0].UpdateHitZoneState(&f.blood,true);};assert(p.Tick(0)==-1);}// late retained callback
|
||||
+ {Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);f.world.manager.accept=false;assert(p.Tick(0)==-1&&f.world.manager.closes==2);}
|
||||
+ {Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);f.blood.health=99;assert(p.Tick(0)==-1);}
|
||||
+ {Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);Menu foreign;f.world.manager.current=&foreign;assert(p.Tick(0)==-1&&f.world.manager.closes==0);}
|
||||
+ {Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);Controller replacement{&f.actor};f.world.controller=&replacement;assert(p.Tick(0)==-1);}
|
||||
+ {Fixture f;Actual p(&f.mode,&f.actor);Probe::enabled=false;assert(p.Tick(0)==-1&&f.inventory.opens==0);}
|
||||
+}
|
||||
+'''.replace('POINT',point).replace('PROBE',probe.replace('Probe(', 'Actual(')))
|
||||
+base=(BASE/'addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c').read_text()
|
||||
+assert 'm_ERRInventoryHitZoneProbe.Tick(now)' in base and 'm_ERRInventoryHitZoneProbe.Stop()' in base
|
||||
+assert 'current == ERR_Phase.DEFEAT && m_ERRBaseStep == 2102' in base
|
||||
+assert 'if (result == 1) { m_ERRBaseStep = 2102; Transition(ERR_Phase.DEFEAT); }' in base
|
||||
+runner=(BASE/'tools/headless_workbench.py').read_text();tree=ast.parse(runner)
|
||||
+node=next(n for n in tree.body if isinstance(n,ast.If) and "a.smoke == 'inventory-hitzone'" in ast.unparse(n.test))
|
||||
+for project,rounds,timeout,bad in [('core',1,240,False),('chat',1,240,True),('core',2,240,True),('core',1,239,True)]:
|
||||
+ assert eval(compile(ast.Expression(node.test),'<selector>','eval'),{'a':SimpleNamespace(smoke='inventory-hitzone',project=project,rounds=rounds,timeout=timeout)})==bad
|
||||
+assert runner.count("'inventory-hitzone'")>=3
|
||||
+print('PASS: actual native-probe lifecycle/counters with doubled boundaries; fixed deadline, exact seven sources, true forwarding, async close, no late updates and selector wiring')
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual native-probe lifecycle/counters with doubled boundaries; fixed deadline, exact seven sources, true forwarding, async close, no late updates and selector wiring
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"complete": true,
|
||||
"source_review_approved": true,
|
||||
"blockers": [],
|
||||
"files": {
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeInventoryHitZoneProbe.c": "604eacdc66cd52bc8a0115862ee7884a8b0f83bdb50609397de671eacd476543",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c": "d4318ac4f8567afde4cb9f9546f6022ca74e5cabd689355ee75d19f2cde25060",
|
||||
"tools/headless_workbench.py": "7ffa38822cee1df4248ea027234823a51c2535a8ce74a9e1a9e7756ee1a72d09",
|
||||
"tests/test_native_inventory_hitzone.py": "bb67f3e2002661331b1f170955989df5bf30a60b48a294ff979bb6db335ca819"
|
||||
},
|
||||
"green_log_sha256": "84c104970b68d9355726cf72b63ac726bf1e4ad6285c4ed66114519264125731",
|
||||
"scope": "Read-only source/test/log review; no reviewer compiler, native or live writes.",
|
||||
"api": "SCR_InventoryMenuUI native fields, InspectItem(null), seven-point incremental InitHitZones; SCR_InventoryStorageManagerComponent.OpenInventory; damage.GetBloodHitZone and one-argument invoker; original protected updater override all match inspected installed SDK. CloseMenu(MenuBase) returns bool in exact installed GameLib generated MenuManager.c line32, independently read and hashed from core/data.pak.",
|
||||
"identity_and_proof": "Current mode, local controller, roster actor and consciousness checked each Tick. Opens only with no existing top menu; pins exact Inventory20Menu; seven unique original subscribed handlers held strongly. Instruments native updater after super and counts exact blood calls with explicit true. First real invoker notification must update each once, blood health unchanged. Exact menu closed; retained handlers must show actual adapter detach cleanup, and second notification must leave counters unchanged. No fixture direct clear of subscriptions.",
|
||||
"timing_and_cleanup": "30-second construction deadline never resets; one-way Stop closes only still-owned menu and never a replacement. NativeBaseSmoke routes only exact selector, stops probe on failure/terminal and completes only after result1 followed by explicit DEFEAT at step2102. Existing outer210-second cap and runnerCore/one-round/240-second minimum remain.",
|
||||
"test_review": "Actual probe and counter methods execute against explicit native/menu/actor/invoker doubles; tests retain false refresh, duplicate points, changed health/controller/menu, missing detach and late update rejection, asynchronous close and deadline. Menu readiness/getter and SDK invoker semantics still native validation targets.",
|
||||
"limits": [
|
||||
"No native compilation or real rendered UI/callback result yet.",
|
||||
"Synthetic Invoke(m_Blood) verifies dispatch wiring without injury; no natural damage, manual input or multiplayer claim.",
|
||||
"Inherited native health updater runs by design; health equality assertion concerns original blood health, not every possible native UI side effect."
|
||||
],
|
||||
"approved": true,
|
||||
"manifest_sha256": "54d993dc6516f6b962633386693f50d5610568ea62337a88b6f601420e60bacc",
|
||||
"patch_sha256": "d5b18a1716fa31fae10e51db703441ed660467c2abc7ee825ae19f3ea7a5495d",
|
||||
"logs": {
|
||||
"green.log": "84c104970b68d9355726cf72b63ac726bf1e4ad6285c4ed66114519264125731",
|
||||
"late-update-mutation-red.log": "a89f6b30fb52ca00aa7e156a493c2af981461474aa3095382005469b5868f10d"
|
||||
},
|
||||
"menu_api": {
|
||||
"archive": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/core/data.pak",
|
||||
"resource": "scripts/GameLib/generated/Menu/MenuManager.c",
|
||||
"sha256": "88bd64762866406a8b8283f82f1f34deef1925244b3148ac88edb7d8d72c664e",
|
||||
"signatures": {
|
||||
"18": "proto external MenuBase FindMenuByPreset(ScriptMenuPresetEnum preset);",
|
||||
"22": "proto external MenuBase GetTopMenu();",
|
||||
"32": "proto external bool CloseMenu(MenuBase menu);"
|
||||
},
|
||||
"method": "Read exact installed PAK entry with parse_tree, decompressed and hashed in memory; no asset copied."
|
||||
},
|
||||
"final_cleanup_delta": "Primary CloseMenu accepted flag is set from the actual returned bool. If it refuses, Check fails and one-way Stop can make exactly one further request for the still-exact owned menu; no retries thereafter, no replacement menu touched. Focused persistent-refusal control asserts result -1 and exactly two total requests. Persistent native refusal stays failure, not completed cleanup.",
|
||||
"mutation": "Late-update count-check omission compiled then runtime-failed the negative control; retained log hashed. Reviewer did not rerun."
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-182mkgob/check.cpp:156: int main(): Assertion `p.Tick(0)==-1' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-inventory-native-proposal/tests/test_native_inventory_hitzone.py", line 29, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<45 lines>...
|
||||
}
|
||||
^
|
||||
'''.replace('POINT',point).replace('PROBE',probe.replace('Probe(', 'Actual(')))
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/err-policy-182mkgob/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"archive": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/core/data.pak",
|
||||
"resource": "scripts/GameLib/generated/Menu/MenuManager.c",
|
||||
"sha256": "88bd64762866406a8b8283f82f1f34deef1925244b3148ac88edb7d8d72c664e",
|
||||
"signatures": {
|
||||
"18": "proto external MenuBase FindMenuByPreset(ScriptMenuPresetEnum preset);",
|
||||
"22": "proto external MenuBase GetTopMenu();",
|
||||
"32": "proto external bool CloseMenu(MenuBase menu);"
|
||||
},
|
||||
"method": "Read exact installed PAK entry with parse_tree, decompressed and hashed in memory; no asset copied."
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"scope": "Single WORKBENCH-only Core inventory-hitzone diagnostic; fixed30s after initial admission, ordinary menu+InspectItem,7original containers, real one-argument blood invoker, native updater once/true, unchangedhealth, asynchronous exact-ownedmenu close and no lateupdates.",
|
||||
"focused_test": "Actual probe Context/Tick/Stop/counters with doubled native UI/event boundaries green; omission of late-update count check compiles then runtime-fails negative control.",
|
||||
"test_registration_needed": "tests/test_native_inventory_hitzone.py",
|
||||
"native_execution": false,
|
||||
"native_compilation": false,
|
||||
"rendered_ui_qualified": false,
|
||||
"limits": [
|
||||
"No natural injury/treatment/manualinput/MP/FPS proof.",
|
||||
"If primary CloseMenu refuses, Stop makes one bounded cleanup request for the same owned menu; persistent native refusal stays a failure and session cleanup remains runner-owned.",
|
||||
"Invoker notification is deliberately synthetic; no health/damage methods invoked."
|
||||
],
|
||||
"runner": "--project core --smoke inventory-hitzone --rounds 1 --timeout 240 (existing disposable hidden/muted profile requirements retained)",
|
||||
"no_production_adapter_or_mounted_source_edits": true
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"files": {
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeInventoryHitZoneProbe.c": {
|
||||
"baseline_sha256": null,
|
||||
"proposal_sha256": "604eacdc66cd52bc8a0115862ee7884a8b0f83bdb50609397de671eacd476543",
|
||||
"index_sha256": null
|
||||
},
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c": {
|
||||
"baseline_sha256": "e811e985048e07365569acde6b5f045d57e9bbf13927f85b74e6e347fd2ef571",
|
||||
"proposal_sha256": "d4318ac4f8567afde4cb9f9546f6022ca74e5cabd689355ee75d19f2cde25060",
|
||||
"index_sha256": "ec319b070bd915d0326b9be1fd3fbc7aabc58956982a2ec69eb24d023a651815"
|
||||
},
|
||||
"tools/headless_workbench.py": {
|
||||
"baseline_sha256": "b0bbb87597fe5f116e8b4fd1d60d52983935d438d3e82b972c675991fddcc971",
|
||||
"proposal_sha256": "7ffa38822cee1df4248ea027234823a51c2535a8ce74a9e1a9e7756ee1a72d09",
|
||||
"index_sha256": "b82da94e227a45c87b8ad74cac55d1b48dfaed55b82a66fe867ca4f0e5d22190"
|
||||
},
|
||||
"tests/test_native_inventory_hitzone.py": {
|
||||
"baseline_sha256": null,
|
||||
"proposal_sha256": "bb67f3e2002661331b1f170955989df5bf30a60b48a294ff979bb6db335ca819",
|
||||
"index_sha256": null
|
||||
}
|
||||
},
|
||||
"patch_sha256": "d5b18a1716fa31fae10e51db703441ed660467c2abc7ee825ae19f3ea7a5495d",
|
||||
"logs": {
|
||||
"green.log": "84c104970b68d9355726cf72b63ac726bf1e4ad6285c4ed66114519264125731",
|
||||
"late-update-mutation-red.log": "a89f6b30fb52ca00aa7e156a493c2af981461474aa3095382005469b5868f10d"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
check: /tmp/err-policy-59xtcv14/check.cpp:358: int main(): Assertion `d->Detail.find(fault==0?"item=absent;":"item=present;")!=string::npos' failed.
|
||||
PASS: actual mounted hierarchy sampling, ownership exclusion, deduplication, completeness/caps, quarter threshold and detached validation; no runtime policy or ammunition mutation
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-mounted-slot-diagnostic/tests/test_hq_mounted_ammo_sampler.py", line 300, in <module>
|
||||
run('#define WORKBENCH\n'+program.replace('int main(){','int main(){'+cases))
|
||||
~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/err-policy-59xtcv14/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,153 @@
|
||||
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedAmmoSampler.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedAmmoSampler.c
|
||||
@@ -40,6 +40,51 @@
|
||||
string Detail;
|
||||
string FirstPrefab;
|
||||
int FirstPurpose = -1;
|
||||
+ // Called before Reject, once per reason in this explicit diagnostic sample.
|
||||
+ // These labels describe sampled relationships; they never authorize a weapon.
|
||||
+ void SlotEvidence(int reason, IEntity entity, WeaponSlotComponent mount, InventoryStorageSlot binding, InventoryItemComponent item, array<IEntity> allowed)
|
||||
+ {
|
||||
+ if ((Reasons & reason) != 0) return;
|
||||
+ if (reason == 64) Detail += "mount-slot-evidence:";
|
||||
+ else Detail += "zero-muzzle-slot-evidence:";
|
||||
+ if (item) Detail += "item=present;";
|
||||
+ else Detail += "item=absent;";
|
||||
+ InventoryStorageSlot parent = null;
|
||||
+ if (item) parent = item.GetParentSlot();
|
||||
+ if (!parent) Detail += "parent=null;";
|
||||
+ else if (parent == binding) Detail += "parent=same-binding;";
|
||||
+ else Detail += "parent=different-binding;";
|
||||
+ if (!binding) Detail += "binding=null;";
|
||||
+ else
|
||||
+ {
|
||||
+ if (binding.GetAttachedEntity() == entity) Detail += "binding-attached=exact;";
|
||||
+ else Detail += "binding-attached=other-or-null;";
|
||||
+ if (binding.GetParentContainer() == mount) Detail += "binding-container=exact-mount;";
|
||||
+ else Detail += "binding-container=other-or-null;";
|
||||
+ }
|
||||
+ IEntity owner = null;
|
||||
+ if (mount) owner = mount.GetOwner();
|
||||
+ if (owner && allowed.Contains(owner)) Detail += "mount-owner=allowed;";
|
||||
+ else Detail += "mount-owner=absent-or-foreign;";
|
||||
+ if (owner && owner.GetPrefabData()) Detail += "mount-prefab=" + owner.GetPrefabData().GetPrefabName() + ";";
|
||||
+ if (!parent) return;
|
||||
+ if (parent.GetAttachedEntity() == entity) Detail += "parent-attached=exact;";
|
||||
+ else Detail += "parent-attached=other-or-null;";
|
||||
+ WeaponSlotComponent parentMount = WeaponSlotComponent.Cast(parent.GetParentContainer());
|
||||
+ if (parent.GetParentContainer() == mount && mount) Detail += "parent-container=exact-mount;";
|
||||
+ else if (parentMount) Detail += "parent-container=other-mount;";
|
||||
+ else if (!parent.GetParentContainer()) Detail += "parent-container=null;";
|
||||
+ else Detail += "parent-container=non-mount;";
|
||||
+ if (!parentMount) return;
|
||||
+ IEntity parentOwner = parentMount.GetOwner();
|
||||
+ if (parentOwner && allowed.Contains(parentOwner)) Detail += "parent-mount-owner=allowed;";
|
||||
+ else Detail += "parent-mount-owner=absent-or-foreign;";
|
||||
+ if (parentOwner && parentOwner.GetPrefabData()) Detail += "parent-mount-prefab=" + parentOwner.GetPrefabData().GetPrefabName() + ";";
|
||||
+ if (parentMount.GetSlotInfo() == parent) Detail += "parent-mount-slot=exact;";
|
||||
+ else Detail += "parent-mount-slot=other-or-null;";
|
||||
+ if (parentMount.GetWeaponEntity() == entity) Detail += "parent-mount-weapon=exact;";
|
||||
+ else Detail += "parent-mount-weapon=other-or-null;";
|
||||
+ }
|
||||
void Reject(int reason, string detail, IEntity entity, int purpose = -1)
|
||||
{
|
||||
if ((Reasons & reason) == 0) Detail += detail + ";";
|
||||
@@ -104,7 +149,11 @@
|
||||
if (!mountedItem || mountedItem.GetParentSlot() != binding)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
- if (s_Diagnostic) s_Diagnostic.Reject(64, "mount-item-slot-mismatch", mountedEntity);
|
||||
+ if (s_Diagnostic)
|
||||
+ {
|
||||
+ s_Diagnostic.SlotEvidence(64, mountedEntity, mount, binding, mountedItem, allowed);
|
||||
+ s_Diagnostic.Reject(64, "mount-item-slot-mismatch", mountedEntity);
|
||||
+ }
|
||||
#endif
|
||||
result.Complete = false; return;
|
||||
}
|
||||
@@ -272,7 +321,23 @@
|
||||
if (muzzles.IsEmpty())
|
||||
{
|
||||
if (NonFiringPlaceholder(weapon, allowed)) result.Weapons--;
|
||||
- else result.Complete = false;
|
||||
+ else
|
||||
+ {
|
||||
+#ifdef WORKBENCH
|
||||
+ if (s_Diagnostic)
|
||||
+ {
|
||||
+ IEntity owner = weapon.GetOwner();
|
||||
+ InventoryItemComponent item = InventoryItemComponent.Cast(owner.FindComponent(InventoryItemComponent));
|
||||
+ WeaponSlotComponent mount = null;
|
||||
+ InventoryStorageSlot binding = null;
|
||||
+ if (item && item.GetParentSlot()) mount = WeaponSlotComponent.Cast(item.GetParentSlot().GetParentContainer());
|
||||
+ if (mount) binding = mount.GetSlotInfo();
|
||||
+ s_Diagnostic.SlotEvidence(4096, owner, mount, binding, item, allowed);
|
||||
+ s_Diagnostic.Reject(4096, "zero-muzzle-not-proven-placeholder", owner);
|
||||
+ }
|
||||
+#endif
|
||||
+ result.Complete = false;
|
||||
+ }
|
||||
continue;
|
||||
}
|
||||
if (muzzles.Count() > 8) { result.Complete = false; continue; }
|
||||
--- a/tests/test_hq_mounted_ammo_sampler.py
|
||||
+++ b/tests/test_hq_mounted_ammo_sampler.py
|
||||
@@ -15,6 +15,8 @@
|
||||
code=code.replace('InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent))','entity->item')
|
||||
code=code.replace('InventoryItemComponent.Cast(owner.FindComponent(InventoryItemComponent))','owner->item').replace('ResourceName resource', 'string resource')
|
||||
code=code.replace('WeaponSlotComponent.Cast(slot.GetParentContainer())','slot->mount').replace('BaseMuzzleComponent.Cast(slot.GetParentContainer())','slot->muzzle')
|
||||
+code=code.replace('WeaponSlotComponent.Cast(parent.GetParentContainer())','parent->mount').replace('WeaponSlotComponent.Cast(item.GetParentSlot().GetParentContainer())','item->GetParentSlot()->mount')
|
||||
+code=code.replace('parent.GetParentContainer()', 'parent->Container()')
|
||||
code=code.replace('EStoragePurpose.','EStoragePurpose::').replace('GetParentSlot().','GetParentSlot()->')
|
||||
code=code.replace('ChimeraCharacter.Cast(entity) != nullptr','entity->character').replace('ChimeraCharacter.Cast(entity) != null','entity->character')
|
||||
code=code.replace('ChimeraCharacter.Cast(root)','root->character').replace('ChimeraCharacter.Cast(entity)','entity->character')
|
||||
@@ -22,10 +24,11 @@
|
||||
code=re.sub(r'\bERR_HQMountedAmmoDiagnostic (?=\w)', 'ERR_HQMountedAmmoDiagnostic* ',code)
|
||||
code=code.replace('s_Diagnostic.', 's_Diagnostic->').replace('GetPrefabData().', 'GetPrefabData()->')
|
||||
code=re.sub(r'\bERR_HQMountedAmmoSnapshot (?=\w)','ERR_HQMountedAmmoSnapshot* ',code)
|
||||
-code=code.replace('array<','Array<').replace('foreach (','for (').replace('null','nullptr')
|
||||
+code=code.replace('array<','Array<').replace('foreach (','for (')
|
||||
+code=re.sub(r'"[^"]*"|\bnull\b',lambda m:m.group() if m.group().startswith(chr(34)) else 'nullptr',code)
|
||||
code=code.replace('Array<IEntity> allowed, Array<BaseWeaponComponent> weapons,','Array<IEntity>& allowed, Array<BaseWeaponComponent>& weapons,')
|
||||
code=code.replace('Array<IEntity> allowed, Array<BaseWeaponComponent> weapons)', 'Array<IEntity>& allowed, Array<BaseWeaponComponent>& weapons)')
|
||||
-for name in ('copy','result','weapon','root','entity','child','part','manager','mounted','muzzle','magazine','item','storage','mount','mountedEntity','mountedItem','owner'):
|
||||
+for name in ('copy','result','weapon','root','entity','child','part','manager','mounted','muzzle','magazine','item','storage','mount','mountedEntity','mountedItem','owner','parent','binding','parentMount','parentOwner'):
|
||||
code=re.sub(r'\b'+name+r'\.',name+'->',code)
|
||||
code=re.sub(r'\b(bool|int) (\w+);',r'\1 \2=0;',code).replace('\n}\n','\n};\n')
|
||||
program=(r'''
|
||||
@@ -41,7 +44,7 @@
|
||||
struct Entity;struct Weapon;struct Muzzle;struct Magazine;struct Manager;struct Mount;
|
||||
struct EStoragePurpose{static const int PURPOSE_UNSET=0,PURPOSE_DEPOSIT=1,PURPOSE_WEAPON_PROXY=2,PURPOSE_ATTACHMENT_PROXY=4,PURPOSE_ANY=127;};
|
||||
struct Storage{int purpose=EStoragePurpose::PURPOSE_DEPOSIT;int GetPurpose(){return purpose;}};
|
||||
-struct Slot{Mount* mount=nullptr;Muzzle* muzzle=nullptr;Storage* storage=nullptr;Storage* GetStorage(){return storage;}};
|
||||
+struct Slot{Mount* mount=nullptr;Muzzle* muzzle=nullptr;Storage* storage=nullptr;Entity* attached=nullptr;Entity* GetAttachedEntity(){return attached;}void* Container(){return mount?static_cast<void*>(mount):muzzle?static_cast<void*>(muzzle):static_cast<void*>(storage);}Storage* GetStorage(){return storage;}};
|
||||
struct Item{Slot* slot=nullptr;Slot* GetParentSlot(){return slot;}};
|
||||
using InventoryStorageSlot=Slot*;using WeaponSlotComponent=Mount*;
|
||||
using InventoryItemComponent=Item*;using BaseInventoryStorageComponent=Storage*;
|
||||
@@ -259,6 +262,26 @@
|
||||
# WORKBENCH records are opt-in. Re-run every original geometry/count assertion
|
||||
# in that build too, plus exact failure witnesses from the same traversal.
|
||||
cases=r'''
|
||||
+ // Exact reason64 distinguishes absent item, null slot and differing slot,
|
||||
+ // while actual Inspect remains incomplete and retains the same ammunition.
|
||||
+ for(int fault=0;fault<3;fault++){
|
||||
+ Rig r;Mount mount;Item item;Slot binding,other;Storage storage;storage.purpose=EStoragePurpose::PURPOSE_ATTACHMENT_PROXY;other.storage=&storage;
|
||||
+ mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&binding;binding.mount=&mount;binding.attached=&r.gun;
|
||||
+ r.manager.weapons={&mount};r.gun.item=fault==0?nullptr:&item;
|
||||
+ if(fault==2)item.slot=&other;
|
||||
+ other.mount=&mount;other.attached=&r.gun;
|
||||
+ ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();
|
||||
+ assert(!snap->Complete&&snap->Weapons==1&&snap->Rounds==25&&(d->Reasons&64));
|
||||
+ assert(d->Detail.find(fault==0?"item=absent;":"item=present;")!=string::npos);
|
||||
+ assert(d->Detail.find(fault==2?"parent=different-binding;":"parent=null;")!=string::npos);
|
||||
+ assert(d->Detail.find("binding-attached=exact;")!=string::npos&&d->Detail.find("binding-container=exact-mount;")!=string::npos);
|
||||
+ assert(d->Detail.find("mount-owner=allowed;")!=string::npos&&d->Detail.find("mount-prefab=fixture;")!=string::npos);
|
||||
+ if(fault==2)assert(d->Detail.find("parent-mount-slot=other-or-null;")!=string::npos);
|
||||
+ }
|
||||
+ {Rig r;r.weapon.muzzles={};ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();
|
||||
+ assert(!snap->Complete&&snap->Weapons==1&&snap->Rounds==0&&(d->Reasons&4096));
|
||||
+ assert(d->Detail.find("zero-muzzle-not-proven-placeholder;")!=string::npos&&d->Detail.find("zero-muzzle-slot-evidence:item=absent;parent=null;")!=string::npos);}
|
||||
+''' + r'''
|
||||
{Rig r;ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(snap->Complete&&d->Reasons==0&&d->Nodes==4&&d->Allowed==3&&d->Managers==1);assert(!ERR_HQMountedAmmoSampler::EndDiagnostic());}
|
||||
{Entity root;root.child=&root;ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=ERR_HQMountedAmmoSampler::Inspect(&root);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&d->Reasons==4&&d->Nodes==1&&d->Allowed==1&&root.reads==1);}
|
||||
{Entity root;std::vector<Entity> children(128);root.child=&children[0];for(int i=0;i<127;i++)children[i].sibling=&children[i+1];ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=ERR_HQMountedAmmoSampler::Inspect(&root);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&d->Reasons==1024&&d->Nodes==129&&d->Allowed==128&&d->Structural==129&&d->Cargo==0&&children[127].reads==0);}
|
||||
@@ -0,0 +1,2 @@
|
||||
PASS: actual mounted hierarchy sampling, ownership exclusion, deduplication, completeness/caps, quarter threshold and detached validation; no runtime policy or ammunition mutation
|
||||
PASS: WORKBENCH opt-in diagnostics distinguish cycles/cap/storage/manager/mount failures without repeated native traversal
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"complete": true,
|
||||
"approved": true,
|
||||
"blockers": [],
|
||||
"manifest_sha256": "e2e66871228b47738075e6d5faf1657c3c1c10fdbed2850de3c2a8d13b6883cf",
|
||||
"patch_sha256": "442d71268cbdb2d3bb50fbe73bcda0203ab74e8fdd9c4469f840b2bb4413a378",
|
||||
"files": {
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedAmmoSampler.c": {
|
||||
"baseline_sha256": "2f0db77ba999586e4290e440d112f1002da17ab5fd20bb4f5b7ff6df99f58a81",
|
||||
"proposal_sha256": "e8e36110e126de921d7635b8c319f923a48d84f700fa78e018bb40136d4819d1",
|
||||
"index_sha256": "88ea0fc0e13d7e3c22c7ce8b652816d1d137e4e6e37a72f48f0bdf515b936ae7"
|
||||
},
|
||||
"tests/test_hq_mounted_ammo_sampler.py": {
|
||||
"baseline_sha256": "a4d098adcd3d1f1156d00cf0753268f286cd63640bdae5aed81828f74a8b4633",
|
||||
"proposal_sha256": "f4adb94e5e688ff3843ef240c7a4232308979f232a53fe7a6a1d94d02591b960",
|
||||
"index_sha256": "b1c630909e5227d0f7bd0d69d7927403fcb0f33e7297d5fa52f96001720f8442"
|
||||
}
|
||||
},
|
||||
"scope": "Read-only source/test/log review, no compiler or native run. WORKBENCH class and s_Diagnostic gate only. SlotEvidence records once per reason, no additional loops, no state mutation or changed acceptance/count/caps. Original return and Complete=false preserved.",
|
||||
"api": "InventoryStorageSlot inherits GetAttachedEntity; GetParentContainer returns component; WeaponSlot GetOwner/GetSlotInfo/GetWeaponEntity and InventoryItem.GetParentSlot signatures match installed SDK. Null item/slot/mount guarded. allowed array is the established nonnull local collection.",
|
||||
"tests": "Actual whole-sampler tests add absent item/null parent/different parent distinction and zero-muzzle rejected-placeholder witness while preserving existing count assertions. Retained default and WORKBENCH green logs, baseline runtime red, and adapter failure logs hash-verified.",
|
||||
"limits": [
|
||||
"No native compile or observed reason64 binding state yet.",
|
||||
"First occurrence per reason cannot attribute every failing entity; labels are sampled evidence, not authorization.",
|
||||
"Zero-muzzle branch cannot rediscover missing reverse mount; records missing relation honestly."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Prefabs/Weapons/Core/Armament_Empty.et": {
|
||||
"archive": "/home/jakub/.local/share/Steam/steamapps/compatdata/1874880/pfx/drive_c/users/steamuser/Documents/My Games/ArmaReforger/addons/WCS_Armaments_629B2BA37EFFD577/data.pak",
|
||||
"sha256": "73b35bd524e30fe09f147f0dc5f197d49c341b46c660ec1ce402f143dc943731",
|
||||
"same_as_prior_extracted_source": true,
|
||||
"bytes": 1068
|
||||
},
|
||||
"Prefabs/Vehicles/Tracked/BMP1/VehParts/Turret/BMP1_Turret_Base.et": {
|
||||
"archive": "/home/jakub/.local/share/Steam/steamapps/compatdata/1874880/pfx/drive_c/users/steamuser/Documents/My Games/ArmaReforger/addons/BMP-1IFV_5E524E4FEECCA92B/data.pak",
|
||||
"sha256": "2fc39d7c2fc4bebe027efa5607ea71dd7dd0021daffb43f65332bf2698a52a4b",
|
||||
"same_as_prior_extracted_source": true,
|
||||
"bytes": 16134
|
||||
},
|
||||
"guid_identity": {
|
||||
"database": "/home/jakub/.local/share/Steam/steamapps/compatdata/1874880/pfx/drive_c/users/steamuser/Documents/My Games/ArmaReforger/addons/WCS_Armaments_629B2BA37EFFD577/resourceDatabase.rdb",
|
||||
"database_sha256": "7f38b7ed578b0b47864648a7b70288fe49dae4cc2556d5754377a1c28b2b6532",
|
||||
"exact_record_count": 1,
|
||||
"offset": 80834,
|
||||
"guid": "52120E4BA120661B"
|
||||
},
|
||||
"sdk_hashes": {
|
||||
"/tmp/erri-vanilla-stock/scripts/Game/generated/Weapon/WeaponSlotComponent.c": "f59ed140912e7ece862b1730af57cb8dcf2c65dbf40a27bba34eb83fa0d3bc58",
|
||||
"/tmp/erri-vanilla-stock/scripts/Game/generated/Components/InventorySystem/InventoryItemComponent.c": "3d812681816be5d34a578f33e208d7802b22a899be008073bf437b7a8ecb2251",
|
||||
"/tmp/erri-vanilla-stock/scripts/Game/generated/InventorySystem/InventoryStorageSlot.c": "45e584eb11f53e1b7374f375d57cdc58c8e757a7922c06cf5db9328ca3294cb5",
|
||||
"/tmp/erri-vanilla-stock/scripts/Game/generated/EntitySlotInfo.c": "90e41e28a14bc677d829f38faff01ad9f0568d3ec4850c496568c379f8301e50"
|
||||
},
|
||||
"findings": [
|
||||
"Exact standalone GenericEntity declares InventoryItemComponent with Enabled 0, WeaponComponent and no muzzle component; no inherited base prefab.",
|
||||
"Exact BMP turret slot index3 declares AttachType InventoryStorageSlot Gun and this WeaponTemplate.",
|
||||
"SDK WeaponSlotComponent exposes GetSlotInfo and GetWeaponEntity; InventoryStorageSlot exposes GetParentContainer and inherits EntitySlotInfo.GetAttachedEntity. SDK EntitySlotInfo.GetSlotInfo(entity) recognizes structural attachment through parent WeaponSlotComponent and GetAttachedEntity equality without requiring InventoryItemComponent.GetParentSlot.",
|
||||
"InventoryItemComponent.GetParentSlot explicitly returns null if none. Native construction and consequences of Enabled 0 are not specified in available script; a reciprocal inventory-item slot is not promised for this disabled component.",
|
||||
"Current reason64 conflates absent InventoryItemComponent with nonmatching/null parent slot. It follows successful allowed mounted entity, mount slot, and parent-container checks, but does not log which item-state branch failed."
|
||||
],
|
||||
"policy_candidate": "Only if native diagnostics establish the exact empty control structural binding: consider omission of an exact allowlisted zero-muzzle control via bounded structural slot proof (same allowed hierarchy, mount.GetWeaponEntity()==entity, mount.GetSlotInfo()==slot, slot.GetParentContainer()==mount, slot.GetAttachedEntity()==entity, exact physical weapon owner). Never relax inventory custody for real weapons/cargo or accept unknown/armed resources. Handle conflicting nonnull inventory binding fail-closed. This is a future candidate, not an approved implemented fix.",
|
||||
"next_observation": "Extend existing opt-in reason64 diagnostic to separate component missing / null parent slot / different parent slot, and record slot.GetAttachedEntity equality plus zero-muzzle count for exact prefab. No extra gameplay state mutation or repeated native run requested by this review.",
|
||||
"limits": [
|
||||
"Read-only source and installed archive/RDB examination only; no native or compiler execution.",
|
||||
"Enabled 0 supports a plausible explanation but is not runtime proof that null parent slot is expected; no source-only fix recommended."
|
||||
],
|
||||
"required_regressions_if_policy_adopted": [
|
||||
"Actual whole sampler: exact known zero-muzzle control with observed missing reverse inventory binding and full structural slot proof is omitted; real guns and ammunition remain counted exactly once.",
|
||||
"Refuse wrong GUID/path, nonzero muzzle list being omitted, unknown zero-muzzle resource, foreign hierarchy, missing/mismatched mount slot or parent container, different attached entity, and conflicting nonnull inventory parent slot.",
|
||||
"Manager and direct hierarchy duplicate discovery of the same control remains deduplicated without poisoning completeness; known controls alone produce zero armed weapons and cannot satisfy the armed BMP fixture.",
|
||||
"Retain existing real-weapon reciprocal custody, cargo/crew exclusion, node/weapon/muzzle bounds and fail-closed unknown behavior."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"files": {
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedAmmoSampler.c": {
|
||||
"baseline_sha256": "2f0db77ba999586e4290e440d112f1002da17ab5fd20bb4f5b7ff6df99f58a81",
|
||||
"proposal_sha256": "e8e36110e126de921d7635b8c319f923a48d84f700fa78e018bb40136d4819d1",
|
||||
"index_sha256": "88ea0fc0e13d7e3c22c7ce8b652816d1d137e4e6e37a72f48f0bdf515b936ae7"
|
||||
},
|
||||
"tests/test_hq_mounted_ammo_sampler.py": {
|
||||
"baseline_sha256": "a4d098adcd3d1f1156d00cf0753268f286cd63640bdae5aed81828f74a8b4633",
|
||||
"proposal_sha256": "f4adb94e5e688ff3843ef240c7a4232308979f232a53fe7a6a1d94d02591b960",
|
||||
"index_sha256": "b1c630909e5227d0f7bd0d69d7927403fcb0f33e7297d5fa52f96001720f8442"
|
||||
}
|
||||
},
|
||||
"patch_sha256": "442d71268cbdb2d3bb50fbe73bcda0203ab74e8fdd9c4469f840b2bb4413a378",
|
||||
"production_non_workbench_token_equivalent": true,
|
||||
"normalization": "Remove WORKBENCH sections, comments, whitespace and redundant braces around the single existing else assignment. All remaining tokens exactly match.",
|
||||
"tests": {
|
||||
"green_exit": 0,
|
||||
"baseline_exit": 1,
|
||||
"logs": {
|
||||
"fixture-failure.log": "53be342eb93454c82e26847fa799077ecce5e2daa4869ee89ec9a27d64b3af50",
|
||||
"baseline-red.log": "5719ad13bf68d29feb26bc053851949d086c1e2c22d48a879af1edda2e422c83",
|
||||
"adapter-string-failure.log": "715655dfd277f7414804c7b1ee06863da9660d1c3fc15aa06a94af01e56404c5",
|
||||
"adapter-failure.log": "d5b8d71945dfac7e85487ac9a5847976fbd986350112226b5097515c62d93dbf",
|
||||
"green.log": "427e7758df3b8746673da188ab74c12b409011eb4f4024b98fb709b7b5aea0a4"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"read_only_host_check": "existing Arma Reforger game PID3560867; private prefix clear",
|
||||
"native_started": false,
|
||||
"scope": "No game, window, input, desktop audio or user process was changed. Native checks await an idle host."
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"docs/current-status.md": {
|
||||
"before": "ef6ffdfb7c888772e9ea2502cf95c21276f2e101e7641d5184f0de36630d4f82",
|
||||
"after": "c0b6b7126cb5849a1c2042a1b40e851d53f61c8b06828a79db5ef2f5135fd444"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"complete": true,
|
||||
"original": {
|
||||
"missing": [],
|
||||
"changed": [],
|
||||
"count": 533,
|
||||
"unexpected": [],
|
||||
"manifest_sha256": "661c5ddf080c4ba8d4463aa4907f739d4d96acaa8694ac8d6f3d85bb94231226"
|
||||
},
|
||||
"candidates": {
|
||||
"crash-start": {
|
||||
"missing": [],
|
||||
"changed": [],
|
||||
"count": 446,
|
||||
"unexpected": [],
|
||||
"manifest_sha256": "8a594a8e340694efaf9874834c05713a40a6d223bbc08491fb42ae84bcce625f"
|
||||
},
|
||||
"depot-mines": {
|
||||
"missing": [],
|
||||
"changed": [],
|
||||
"count": 432,
|
||||
"unexpected": [],
|
||||
"manifest_sha256": "c91acda173e2914fea59fd3e264a451158ee21d85918fe1ec5c8312dd46f7074"
|
||||
},
|
||||
"ground-supply": {
|
||||
"missing": [],
|
||||
"changed": [],
|
||||
"count": 432,
|
||||
"unexpected": [],
|
||||
"manifest_sha256": "0b58fde4127486894917db58ce92b684f984233b341317418e336b1740cac107"
|
||||
},
|
||||
"mine-safety": {
|
||||
"missing": [],
|
||||
"changed": [],
|
||||
"count": 431,
|
||||
"unexpected": [],
|
||||
"manifest_sha256": "202fa0854cf60a6c08ace1f22b276d826abcb538c79b6e052b48e5e51f458a87"
|
||||
},
|
||||
"mounted-pair": {
|
||||
"missing": [],
|
||||
"changed": [],
|
||||
"count": 429,
|
||||
"unexpected": [],
|
||||
"manifest_sha256": "9fee715f8c9c324c465387ba1f88744c9c3748cb49668ce65e9c9500a38b14d7"
|
||||
}
|
||||
},
|
||||
"staged_diff_sha256": "670073db5d7531aca4466cc9d2827d10a46bfcfbd35f27ac97724de9d2fd3725"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"archive_bytes": 3164334,
|
||||
"archive_sha256": "d021d44588bbd63933566480bf994ae53fd580f6d1a35e601295b1037d60cd80",
|
||||
"kind": "DEVELOPMENT SOURCE BUNDLE",
|
||||
"payload_files": 441,
|
||||
"preview": true,
|
||||
"projects": {
|
||||
"core": {
|
||||
"dependencies": [
|
||||
"58D0FB3206B6F859",
|
||||
"595F2BF2F44836FB",
|
||||
"5E524E4FEECCA92B",
|
||||
"629B2BA37EFFD577",
|
||||
"65930CB4CD0237B2"
|
||||
],
|
||||
"guid": "2528466DE220D91C",
|
||||
"id": "ERR_Core"
|
||||
}
|
||||
}
|
||||
}
|
||||
+891
@@ -0,0 +1,891 @@
|
||||
// Explicit local engine fixture, excluded from game/server builds.
|
||||
#ifdef WORKBENCH
|
||||
modded class ERR_GameMode
|
||||
{
|
||||
protected ref ERR_NativeEnvironmentRestartProbe m_ERRBaseEnvironmentRestartProbe;
|
||||
protected override string SettingsPath()
|
||||
{
|
||||
string enabled, outcome;
|
||||
if (System.GetCLIParam("errBaseSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "environment-restart")
|
||||
{
|
||||
m_ERRBaseEnvironmentRestartProbe = new ERR_NativeEnvironmentRestartProbe(this);
|
||||
return ERR_NativeEnvironmentRestartProbe.PATH;
|
||||
}
|
||||
return super.SettingsPath();
|
||||
}
|
||||
|
||||
protected ref ERR_NativeCasualtyServiceProbe m_ERRCasualtyServiceProbe;
|
||||
protected ref ERR_NativePrisonerProbe m_ERRPrisonerProbe;
|
||||
protected ref ERR_NativeGuardCancelProbe m_ERRGuardCancelProbe;
|
||||
protected ref ERR_NativeLocalAdmissionProbe m_ERRLocalAdmissionProbe;
|
||||
protected ref ERR_NativePoweredFlightProbe m_ERRPoweredFlightProbe;
|
||||
protected ref ERR_NativePrisonBreachProbe m_ERRPrisonBreachProbe;
|
||||
protected ref ERR_NativeInventoryHitZoneProbe m_ERRInventoryHitZoneProbe;
|
||||
protected ref ERR_NativeItemStationTeardownProbe m_ERRItemStationTeardownProbe;
|
||||
protected ref ERR_NativeObjectiveLossProbe m_ERRObjectiveLossProbe;
|
||||
protected ref ERR_NativeMedicalProbe m_ERRBaseMedicalProbe;
|
||||
protected ref ERR_NativeCasualtyLoadProbe m_ERRCasualtyLoadProbe;
|
||||
protected ref ERR_NativeEnvironmentProbe m_ERRBaseEnvironmentProbe;
|
||||
protected bool m_ERRCatalogChecked;
|
||||
protected bool m_ERRDiscoveryMapChecked;
|
||||
protected ref ERR_NativePatrolProbe m_ERRPatrolEast;
|
||||
protected ref ERR_NativePatrolProbe m_ERRPatrolWest;
|
||||
protected int m_ERRBaseStep;
|
||||
protected bool m_ERRObjectiveCloseChecked;
|
||||
protected bool m_ERRStartupFailure;
|
||||
protected bool m_ERRCacheLoss;
|
||||
protected string m_ERRExtractionCase;
|
||||
protected ref ERR_NativeExtractionProbe m_ERRExtractionProbe;
|
||||
protected ref ERR_NativeTransportProbe m_ERRTransportProbe;
|
||||
protected ref ERR_NativeLifecycleProbe m_ERRLifecycleProbe;
|
||||
protected ref ERR_NativeRecoveryProbe m_ERRRecoveryProbe;
|
||||
protected ref ERR_NativeRadioDefenceProbe m_ERRBaseRadioProbe;
|
||||
protected string m_ERRActiveCacheId = "everon-supply-1";
|
||||
protected int m_ERRMedicalDiscoveries;
|
||||
protected IEntity m_ERRSightBlocker;
|
||||
protected float m_ERRBaseDeadline;
|
||||
protected float m_ERRBaseWait;
|
||||
protected IEntity m_ERRBasePlayer;
|
||||
protected ERR_LocationComponent m_ERRBaseRadio;
|
||||
protected ref array<IEntity> m_ERRBaseDocuments = {};
|
||||
|
||||
protected override void EOnInit(IEntity owner)
|
||||
{
|
||||
super.EOnInit(owner);
|
||||
string enabled;
|
||||
if (SCR_Global.IsEditMode() || !Replication.IsServer() || !System.GetCLIParam("errBaseSmoke", enabled) || enabled != "1") return;
|
||||
string nativeEndCase;
|
||||
array<string> independentFixtures = {"optional-site-skip", "infantry-combat", "medical-combat", "hunter-faction", "unconscious-camera", "objective-map", "town-garrisons", "site-garrisons", "radio-interaction", "enemy-loadout", "aggression", "native-end-active", "native-end-preparing", "generated-sites", "mortar-station", "opening-tools", "airfield-markers", "air-logistics", "air-assault", "air-recon"};
|
||||
if (System.GetCLIParam("errSmokeOutcome", nativeEndCase) && independentFixtures.Contains(nativeEndCase)) return;
|
||||
ERR_NativeObjectiveProbe.BeginRound();
|
||||
ERR_NativeProgressionIdentityProbe.BeginRound();
|
||||
string outcome;
|
||||
m_ERRStartupFailure = System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "startup-failure";
|
||||
m_ERRExtractionCase = outcome;
|
||||
if (outcome == "extraction-startup-failure" && m_Factions) m_Factions.ExtractionPrefab = "{259EE7B78C51B624}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469.et";
|
||||
if (outcome == "extraction-boundary-failure") m_EscapeRadius = 0;
|
||||
m_ERRCacheLoss = outcome == "cache-loss";
|
||||
if (m_ERRCacheLoss) m_ERRActiveCacheId = "everon-supply-2";
|
||||
OnLocationDiscovered().Insert(ERR_BaseDiscovered);
|
||||
OnStop().Insert(ERR_NativeObjectiveProbe.Stop);
|
||||
OnStop().Insert(ERR_StopRadioProbe);
|
||||
OnStop().Insert(ERR_StopHealthProbes);
|
||||
if (m_ERRStartupFailure && m_Factions) m_Factions.EnemyFactionKey = m_Factions.PlayerFactionKey;
|
||||
m_ERRBaseDeadline = GetWorld().GetWorldTime() + 210000;
|
||||
if (outcome == "patrols") m_ERRBaseDeadline = GetWorld().GetWorldTime() + 330000;
|
||||
OnPhaseChanged().Insert(ERR_BaseResult);
|
||||
GetGame().GetCallqueue().CallLater(ERR_BaseTick, 500, true);
|
||||
Print("ERR BASE SMOKE: core-only scripted escape; not manual or multiplayer acceptance");
|
||||
}
|
||||
protected bool ERR_BaseCheck(bool condition, string detail)
|
||||
{
|
||||
if (condition) { Print("ERR SMOKE PASS: base " + detail); return true; }
|
||||
ERR_NativeObjectiveProbe.Stop();
|
||||
ERR_StopRadioProbe();
|
||||
ERR_StopHealthProbes();
|
||||
Print("ERR SMOKE FAIL: base " + detail, LogLevel.ERROR);
|
||||
GetGame().GetCallqueue().Remove(ERR_BaseTick);
|
||||
return false;
|
||||
}
|
||||
protected void ERR_StopHealthProbes()
|
||||
{
|
||||
ERR_NativeDiscoveryMapProbe.Stop();
|
||||
ERR_NativeProgressionIdentityProbe.Stop();
|
||||
if (m_ERRTransportProbe) m_ERRTransportProbe.Stop();
|
||||
if (m_ERRPatrolEast) m_ERRPatrolEast.Stop();
|
||||
if (m_ERRPatrolWest) m_ERRPatrolWest.Stop();
|
||||
if (m_ERRBaseEnvironmentRestartProbe) m_ERRBaseEnvironmentRestartProbe.Stop();
|
||||
if (m_ERRBaseEnvironmentProbe) m_ERRBaseEnvironmentProbe.Stop();
|
||||
if (m_ERRPrisonerProbe) m_ERRPrisonerProbe.Stop();
|
||||
if (m_ERRGuardCancelProbe) m_ERRGuardCancelProbe.Stop();
|
||||
if (m_ERRLocalAdmissionProbe) m_ERRLocalAdmissionProbe.Stop();
|
||||
if (m_ERRPoweredFlightProbe) m_ERRPoweredFlightProbe.Stop();
|
||||
if (m_ERRPrisonBreachProbe) m_ERRPrisonBreachProbe.Stop();
|
||||
if (m_ERRInventoryHitZoneProbe) m_ERRInventoryHitZoneProbe.Stop();
|
||||
if (m_ERRItemStationTeardownProbe) m_ERRItemStationTeardownProbe.Stop();
|
||||
if (m_ERRCasualtyServiceProbe) m_ERRCasualtyServiceProbe.Stop();
|
||||
if (m_ERRObjectiveLossProbe) m_ERRObjectiveLossProbe.Stop();
|
||||
if (m_ERRBaseMedicalProbe) m_ERRBaseMedicalProbe.Stop();
|
||||
if (m_ERRCasualtyLoadProbe) m_ERRCasualtyLoadProbe.Cleanup();
|
||||
if (m_ERRRecoveryProbe) m_ERRRecoveryProbe.Stop();
|
||||
}
|
||||
protected void ERR_StopRadioProbe() { if (m_ERRBaseRadioProbe) m_ERRBaseRadioProbe.Stop(); }
|
||||
protected void ERR_BaseMove(vector position)
|
||||
{
|
||||
vector transform[4];
|
||||
m_ERRBasePlayer.GetWorldTransform(transform);
|
||||
transform[3] = ERR_Actors.Ground(position);
|
||||
ChimeraCharacter.Cast(m_ERRBasePlayer).Teleport(transform);
|
||||
}
|
||||
protected ERR_UnlockPrisonAction ERR_BaseLock(IEntity entity)
|
||||
{
|
||||
BaseActionsManagerComponent manager = BaseActionsManagerComponent.Cast(entity.FindComponent(BaseActionsManagerComponent));
|
||||
if (manager)
|
||||
{
|
||||
array<BaseUserAction> actions = {};
|
||||
manager.GetActionsList(actions);
|
||||
foreach (BaseUserAction action : actions)
|
||||
{
|
||||
ERR_UnlockPrisonAction lock = ERR_UnlockPrisonAction.Cast(action);
|
||||
if (lock) return lock;
|
||||
}
|
||||
}
|
||||
IEntity child = entity.GetChildren();
|
||||
while (child)
|
||||
{
|
||||
ERR_UnlockPrisonAction lock = ERR_BaseLock(child);
|
||||
if (lock) return lock;
|
||||
child = child.GetSibling();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
protected bool ERR_BaseCover()
|
||||
{
|
||||
int count;
|
||||
foreach (IEntity entity : m_OwnedEntities)
|
||||
{
|
||||
if (!entity || !entity.GetPrefabData() || entity.GetPrefabData().GetPrefabName() != "{4B6AC1D8701C35A1}Prefabs/ERR/FieldCover.et") continue;
|
||||
vector transform[4];
|
||||
entity.GetWorldTransform(transform);
|
||||
vector up = transform[1];
|
||||
if (!ERR_BaseCheck(up[1] > 0.99, "outpost barrier is upright")) return false;
|
||||
vector low, high;
|
||||
entity.GetWorldBounds(low, high);
|
||||
vector center = (low + high) * 0.5;
|
||||
vector across = "0 0 2";
|
||||
if (high[0] - low[0] < high[2] - low[2]) across = "2 0 0";
|
||||
TraceParam trace = new TraceParam();
|
||||
trace.Start = center + across;
|
||||
trace.End = center - across;
|
||||
trace.Flags = TraceFlags.ENTS;
|
||||
trace.LayerMask = TRACE_LAYER_CAMERA;
|
||||
float fraction = GetWorld().TraceMove(trace, null);
|
||||
PrintFormat("ERR COVER GEOMETRY: low=%1 high=%2 trace=%3", low, high, fraction);
|
||||
if (!ERR_BaseCheck(fraction < 1 && trace.TraceEnt == entity, "outpost barrier has physical collision")) return false;
|
||||
count++;
|
||||
}
|
||||
return ERR_BaseCheck(count == 3, "exactly three authored outpost barriers exist");
|
||||
}
|
||||
protected void ERR_BaseDiscovered(ERR_LocationComponent location, int previous, int current)
|
||||
{
|
||||
if (location.Id() == "everon-medical-1" && previous == 0 && current == 2) m_ERRMedicalDiscoveries++;
|
||||
}
|
||||
protected void ERR_BaseResult(ERR_Phase previous, ERR_Phase current)
|
||||
{
|
||||
if (!ERR_MissionRules.Terminal(current)) return;
|
||||
GetGame().GetCallqueue().Remove(ERR_BaseTick);
|
||||
if (m_ERRExtractionCase == "inventory-hitzone")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 2102, "native inventory callback fixture finished before explicit reset")) return;
|
||||
ERR_StopHealthProbes();
|
||||
Print("ERR SMOKE COMPLETE: real one-argument blood event reaches native inventory updater and detached points stay silent; synthetic notification, no injury/manual UI/multiplayer proof");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "station-teardown")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 2002, "native station teardown fixture finished before explicit reset")) return;
|
||||
ERR_StopHealthProbes();
|
||||
Print("ERR SMOKE COMPLETE: isolated off-world station catalog-expiry injection and native callback teardown; not restart reproduction");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "prison-breach")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1902, "native destroyed-gate crossing finished before explicit reset")) return;
|
||||
ERR_StopHealthProbes();
|
||||
Print("ERR SMOKE COMPLETE: actual gate destruction and sampled prison crossing; repositioned fixture, not walking or multiplayer");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "powered-flight" || m_ERRExtractionCase == "powered-translate")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1802, "native powered-flight fixture finished before explicit test reset")) return;
|
||||
ERR_StopHealthProbes();
|
||||
if (m_ERRExtractionCase == "powered-translate")
|
||||
Print("ERR SMOKE COMPLETE: native powered takeoff, bounded four-metre translation, target hold, contact landing and engine-off; isolated mechanism fixture reset");
|
||||
else Print("ERR SMOKE COMPLETE: native pilot-input powered takeoff, hover, contact landing and shutdown; isolated mechanism fixture reset");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "local-admission")
|
||||
{
|
||||
// Completion is emitted by the calling tick after synchronous teardown and
|
||||
// its post-transition invariants; a terminal event alone is insufficient.
|
||||
ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1700, "local admission fixture received its production fail-closed transition");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "early-helicopter-loss")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 201 && Players().HasSurvivor() && Extraction().State() == ERR_ExtractionState.LOST, "lost helicopter ends prison phase despite a living prisoner")) return;
|
||||
ERR_FailureGameOverScreenInfo result = new ERR_FailureGameOverScreenInfo();
|
||||
if (!ERR_BaseCheck(EndReason().Contains("helicopter was lost") && result.GetDebriefing(0, null) == EndReason(), "early loss has a specific replicated and debriefing reason")) return;
|
||||
Print("ERR SMOKE COMPLETE: early extraction vehicle loss");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "casualty-service")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1402, "casualty service fixture finished before explicit test reset")) return;
|
||||
Print("ERR SMOKE COMPLETE: native casualty service with helper-only identity adapter; reset requested by fixture");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "radio-document-loss" || m_ERRExtractionCase == "radio-document-loss-partial")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1302, "radio document-loss fixture finished before explicit test reset")) return;
|
||||
Print("ERR SMOKE COMPLETE: native " + m_ERRExtractionCase + "; reset requested by fixture");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ERRExtractionCase == "guard-cancel")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1502, "guard cancellation fixture finished before explicit test reset")) return;
|
||||
Print("ERR SMOKE COMPLETE: native guard-cancel; reset requested by fixture");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ERRExtractionCase == "prisoner-kit")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 702, "prisoner-kit fixture finished before explicit test reset")) return;
|
||||
Print("ERR SMOKE COMPLETE: native prisoner-kit; reset requested by fixture");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "medical")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 602, "medical fixture finished before explicit test reset")) return;
|
||||
Print("ERR SMOKE COMPLETE: native medical; reset requested by fixture");
|
||||
return;
|
||||
}
|
||||
if ((m_ERRExtractionCase == "casualty-load" || m_ERRExtractionCase == "casualty-direct" || m_ERRExtractionCase == "casualty-bench" || m_ERRExtractionCase == "casualty-remove"))
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 802, "casualty-load fixture finished before explicit test reset")) return;
|
||||
Print("ERR SMOKE COMPLETE: native " + m_ERRExtractionCase + "; reset requested by fixture");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "environment-restart")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1202, "environment restart round finished before explicit test reset")) return;
|
||||
PrintFormat("ERR SMOKE COMPLETE: environment restart round %1; reset requested by fixture", m_ERRBaseEnvironmentRestartProbe.Round());
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "environment")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1002, "environment fixture finished before explicit test reset")) return;
|
||||
Print("ERR SMOKE COMPLETE: native environment; reset requested by fixture");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "patrols")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1102, "both patrol routes finished before explicit test reset")) return;
|
||||
Print("ERR SMOKE COMPLETE: native finite patrol routes and cleanup; reset requested by fixture");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "recovery")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 502 && EndReason().Contains("All prisoners are unconscious"), "stalled all-unconscious team reaches automatic mission defeat")) return;
|
||||
if (!ERR_BaseCheck(!RecoveryStatus().IsEmpty() && !AbandonRound(), "terminal event observes active warning and rejects repeated abandonment")) return;
|
||||
Print("ERR SMOKE COMPLETE: player unconsciousness, native healing and automatic all-unconscious defeat");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "objective-ui")
|
||||
{
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 902, "objective UI fixture finished before explicit test reset")) return;
|
||||
Print("ERR SMOKE COMPLETE: native objective UI close, reopen and deleted-task cleanup; reset requested by fixture");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "lifecycle")
|
||||
{
|
||||
if (!ERR_BaseCheck(m_ERRBaseStep == 402, "lifecycle fixture finished before explicit test reset")) return;
|
||||
Print("ERR SMOKE COMPLETE: native lifecycle admission guards; reset requested by fixture");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "transport-loss")
|
||||
{
|
||||
if (!ERR_BaseCheck(m_ERRBaseStep == 302, "transport fixture finished before explicit test reset")) return;
|
||||
Print("ERR SMOKE COMPLETE: finite native ground transport; reset requested by fixture");
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "extraction-capacity")
|
||||
{
|
||||
if (!ERR_BaseCheck(m_ERRBaseStep == 202, "capacity fixture finished before its explicit test reset")) return;
|
||||
Print("ERR SMOKE COMPLETE: physical capacity and isolated extraction results; reset requested by fixture");
|
||||
return;
|
||||
}
|
||||
if (!ERR_BaseCheck(current == ERR_Phase.VICTORY && m_ERRBaseStep == 5, "escape and restart reached without companion services")) return;
|
||||
Print("ERR SMOKE COMPLETE: core-only escape");
|
||||
}
|
||||
protected void ERR_BaseTick()
|
||||
{
|
||||
float now = GetWorld().GetWorldTime();
|
||||
if (ERR_NativeProgressionIdentityProbe.Failed()) { ERR_BaseCheck(false, "native progression identity rejection fixture"); return; }
|
||||
if (now > m_ERRBaseDeadline) { ERR_BaseCheck(false, "timeout at " + m_ERRBaseStep.ToString()); return; }
|
||||
if (m_ERRExtractionCase == "extraction-startup-failure" || m_ERRExtractionCase == "extraction-boundary-failure")
|
||||
{
|
||||
if (PreparationError().IsEmpty()) return;
|
||||
if (!ERR_BaseCheck(Phase() == ERR_Phase.PREPARING && !Active() && m_Stopped, "invalid extraction setup never starts gameplay")) return;
|
||||
array<IEntity> players = {};
|
||||
ERR_Actors.Players(players);
|
||||
if (!ERR_BaseCheck(players.IsEmpty() && Sites() && Sites().Count() == 0 && m_OwnedEntities.IsEmpty() && !ExtractionVehicle(), "late startup failure rolls back all authored stock, scenery, groups and vehicle")) return;
|
||||
if (!ERR_BaseCheck(PreparationError().Contains("twelve") && !Transition(ERR_Phase.PRISON), "invalid extraction retains actionable diagnosis and blocks progression")) return;
|
||||
GetGame().GetCallqueue().Remove(ERR_BaseTick);
|
||||
Print("ERR SMOKE COMPLETE: expected late extraction startup failure");
|
||||
return;
|
||||
}
|
||||
if (m_ERRStartupFailure)
|
||||
{
|
||||
if (PreparationError().IsEmpty()) return;
|
||||
int presentation = ERR_NativePreparationStatusProbe.Tick(this, now);
|
||||
if (presentation < 0) { ERR_BaseCheck(false, "actual persistent startup-failure loading presentation within30seconds"); return; }
|
||||
if (presentation == 0) return;
|
||||
if (!ERR_BaseCheck(true, "actual failed-start loading widgets persist beyond17seconds with spinner hidden; diagnostic screenshot requested")) return;
|
||||
if (!ERR_BaseCheck(Phase() == ERR_Phase.PREPARING && !Active() && m_Stopped, "invalid faction setup stops services without starting the mission")) return;
|
||||
array<IEntity> players = {};
|
||||
ERR_Actors.Players(players);
|
||||
if (!ERR_BaseCheck(players.IsEmpty() && !Sites() && m_OwnedEntities.IsEmpty(), "failed setup spawns no players, sites or supplies")) return;
|
||||
array<string> replies = {};
|
||||
Admin().Execute(this, true, "status", replies);
|
||||
if (!ERR_BaseCheck(replies.Count() == 1 && replies[0].Contains("configured player and enemy factions"), "administrator receives retained startup failure cause")) return;
|
||||
if (!ERR_BaseCheck(!Transition(ERR_Phase.PRISON) && !Transition(ERR_Phase.EXTRACTION), "failed preparation cannot be advanced into gameplay")) return;
|
||||
GetGame().GetCallqueue().Remove(ERR_BaseTick);
|
||||
Print("ERR SMOKE COMPLETE: expected startup-failure");
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 2100)
|
||||
{
|
||||
int result = m_ERRInventoryHitZoneProbe.Tick(now);
|
||||
if (result < 0) { ERR_BaseCheck(false, "native inventory hitzone callback fixture"); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 2102; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 2000)
|
||||
{
|
||||
int result = m_ERRItemStationTeardownProbe.Tick(now);
|
||||
if (result < 0) { ERR_BaseCheck(false, "native isolated station teardown fixture"); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 2002; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 1900)
|
||||
{
|
||||
int result = m_ERRPrisonBreachProbe.Tick(now);
|
||||
if (result < 0) { ERR_BaseCheck(false, "native destroyed-gate progression fixture"); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 1902; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 1800)
|
||||
{
|
||||
int result = m_ERRPoweredFlightProbe.Tick(now);
|
||||
if (result < 0) { ERR_BaseCheck(false, "native powered-flight feasibility fixture"); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 1802; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 1700)
|
||||
{
|
||||
int result = m_ERRLocalAdmissionProbe.Tick(now);
|
||||
if (result < 0) { ERR_StopHealthProbes(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (result == 1)
|
||||
{
|
||||
m_ERRBaseStep = 1702;
|
||||
GetGame().GetCallqueue().Remove(ERR_BaseTick);
|
||||
Print("ERR SMOKE COMPLETE: native local admission callback replay and fail-closed method injection; not multiplayer evidence");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 1400)
|
||||
{
|
||||
int result = m_ERRCasualtyServiceProbe.Tick(now);
|
||||
if (result < 0) { ERR_BaseCheck(false, "casualty service fixture failed"); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 1402; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 1300)
|
||||
{
|
||||
int result = m_ERRObjectiveLossProbe.Tick(now);
|
||||
if (result < 0) { ERR_StopHealthProbes(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 1302; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ERRBaseStep == 1500)
|
||||
{
|
||||
int result = m_ERRGuardCancelProbe.Tick(now);
|
||||
if (result < 0) { ERR_StopHealthProbes(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 1502; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ERRBaseStep == 700)
|
||||
{
|
||||
int result = m_ERRPrisonerProbe.Tick(now);
|
||||
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 702; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 600)
|
||||
{
|
||||
int result = m_ERRBaseMedicalProbe.Tick(now);
|
||||
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 602; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 800)
|
||||
{
|
||||
int result = m_ERRCasualtyLoadProbe.Tick(now);
|
||||
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 802; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 1200)
|
||||
{
|
||||
int result = m_ERRBaseEnvironmentRestartProbe.Tick(now);
|
||||
if (result < 0) { ERR_BaseCheck(false, "environment restart fixture failed"); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 1202; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 1000)
|
||||
{
|
||||
int result = m_ERRBaseEnvironmentProbe.Tick(now);
|
||||
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 1002; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 1100)
|
||||
{
|
||||
int east = m_ERRPatrolEast.Tick(now);
|
||||
int west = m_ERRPatrolWest.Tick(now);
|
||||
if (east < 0 || west < 0) { ERR_StopHealthProbes(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (east == 1 && west == 1)
|
||||
{
|
||||
if (!ERR_NativePatrolProbe.StopChecks(this)) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
m_ERRBaseStep = 1102; Transition(ERR_Phase.DEFEAT);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 500)
|
||||
{
|
||||
int result = m_ERRRecoveryProbe.Tick(now);
|
||||
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (result == 1)
|
||||
{
|
||||
m_ERRBaseStep = 502;
|
||||
ERR_BaseCheck(Active() && ERR_Actors.Alive(m_ERRBasePlayer), "fixture leaves a living casualty for the production failure timer");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 400)
|
||||
{
|
||||
int result = m_ERRLifecycleProbe.Tick(now);
|
||||
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 402; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 300)
|
||||
{
|
||||
int result = m_ERRTransportProbe.Tick(now);
|
||||
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 302; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 200)
|
||||
{
|
||||
int result = m_ERRExtractionProbe.Tick(now);
|
||||
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 202; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 901)
|
||||
{
|
||||
int result = ERR_NativeObjectiveProbe.TickCloseReopen(this, now);
|
||||
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (result == 1)
|
||||
{
|
||||
if (m_ERRExtractionCase == "objective-ui")
|
||||
{
|
||||
if (!ERR_NativeObjectiveProbe.BeginDeletedTaskClose(this, now)) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
m_ERRBaseStep = 903;
|
||||
}
|
||||
else { m_ERRObjectiveCloseChecked = true; m_ERRBaseStep = 0; }
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_ERRBaseStep == 903)
|
||||
{
|
||||
int result = ERR_NativeObjectiveProbe.TickDeletedTaskClose(this, now);
|
||||
if (result < 0) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (result == 1) { m_ERRBaseStep = 902; Transition(ERR_Phase.DEFEAT); }
|
||||
return;
|
||||
}
|
||||
if (now < m_ERRBaseWait) return;
|
||||
if (m_ERRBaseStep == 0)
|
||||
{
|
||||
array<IEntity> players = {};
|
||||
ERR_Actors.Players(players, true);
|
||||
if (players.Count() != 1 || Phase() != ERR_Phase.PRISON) return;
|
||||
m_ERRBasePlayer = players[0];
|
||||
if (!m_ERRCatalogChecked)
|
||||
{
|
||||
if (!ERR_NativeFactionCatalogProbe.Run(this)) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
m_ERRCatalogChecked = true;
|
||||
}
|
||||
if (!m_ERRDiscoveryMapChecked)
|
||||
{
|
||||
if (!ERR_NativeDiscoveryMapProbe.Run()) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
m_ERRDiscoveryMapChecked = true;
|
||||
}
|
||||
if (m_ERRExtractionCase == "inventory-hitzone")
|
||||
{
|
||||
m_ERRInventoryHitZoneProbe = new ERR_NativeInventoryHitZoneProbe(this, m_ERRBasePlayer);
|
||||
m_ERRBaseStep = 2100;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "station-teardown")
|
||||
{
|
||||
m_ERRItemStationTeardownProbe = new ERR_NativeItemStationTeardownProbe();
|
||||
m_ERRBaseStep = 2000;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "prison-breach")
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRPrisonBreachProbe = new ERR_NativePrisonBreachProbe(this, m_ERRBasePlayer);
|
||||
m_ERRBaseStep = 1900;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "powered-flight" || m_ERRExtractionCase == "powered-translate")
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRPoweredFlightProbe = new ERR_NativePoweredFlightProbe(this);
|
||||
string autohoverMode;
|
||||
if (System.GetCLIParam("errFlightAutohover", autohoverMode) && autohoverMode == "1")
|
||||
if (!ERR_BaseCheck(m_ERRPoweredFlightProbe.SetAutohoverMode(true), "owned native autohover experiment selected before allocation")) return;
|
||||
m_ERRBaseDeadline = now + 250000;
|
||||
m_ERRBaseStep = 1800;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "local-admission")
|
||||
{
|
||||
m_ERRLocalAdmissionProbe = new ERR_NativeLocalAdmissionProbe(this);
|
||||
m_ERRBaseStep = 1700;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "casualty-service")
|
||||
{
|
||||
m_ERRCasualtyServiceProbe = new ERR_NativeCasualtyServiceProbe(this);
|
||||
m_ERRBaseStep = 1400;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "radio-document-loss" || m_ERRExtractionCase == "radio-document-loss-partial")
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRObjectiveLossProbe = new ERR_NativeObjectiveLossProbe(this, m_ERRBasePlayer, m_ERRExtractionCase == "radio-document-loss-partial");
|
||||
m_ERRBaseStep = 1300;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ERRExtractionCase == "guard-cancel")
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRGuardCancelProbe = new ERR_NativeGuardCancelProbe(this);
|
||||
m_ERRBaseStep = 1500;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ERRExtractionCase == "prisoner-kit")
|
||||
{
|
||||
int contentMenu = ERR_ProbeAdminMenu(now);
|
||||
if (contentMenu < 0) { ERR_BaseCheck(false, "content admin menu"); return; }
|
||||
if (contentMenu == 0) return;
|
||||
if (!ERR_NativeAdminSettingsProbe.Run(this)) { ERR_BaseCheck(false, "random settings storage and commands"); return; }
|
||||
if (!ERR_NativeContentProbe.Run(this)) { ERR_BaseCheck(false, "content registry and backpack"); return; }
|
||||
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRPrisonerProbe = new ERR_NativePrisonerProbe(this, m_ERRBasePlayer);
|
||||
m_ERRBaseStep = 700;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "medical")
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRBaseMedicalProbe = new ERR_NativeMedicalProbe(this);
|
||||
m_ERRBaseStep = 600;
|
||||
return;
|
||||
}
|
||||
if ((m_ERRExtractionCase == "casualty-load" || m_ERRExtractionCase == "casualty-direct" || m_ERRExtractionCase == "casualty-bench" || m_ERRExtractionCase == "casualty-remove"))
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
if (m_ERRExtractionCase == "casualty-remove") m_ERRCasualtyLoadProbe = new ERR_NativeCasualtyRemoveProbe(this);
|
||||
else m_ERRCasualtyLoadProbe = new ERR_NativeCasualtyLoadProbe(this, m_ERRExtractionCase != "casualty-load", m_ERRExtractionCase == "casualty-bench");
|
||||
m_ERRBaseStep = 800;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "environment-restart")
|
||||
{
|
||||
if (!ERR_BaseCheck(m_ERRBaseEnvironmentRestartProbe != null, "environment restart selected isolated settings before mode initialization")) return;
|
||||
m_ERRBaseStep = 1200;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "environment")
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRBaseEnvironmentProbe = new ERR_NativeEnvironmentProbe(this);
|
||||
m_ERRBaseStep = 1000;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "patrols")
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRPatrolEast = new ERR_NativePatrolProbe(this, "everon-outpost-1");
|
||||
m_ERRPatrolWest = new ERR_NativePatrolProbe(this, "everon-supply-2");
|
||||
m_ERRBaseStep = 1100;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "recovery")
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRRecoveryProbe = new ERR_NativeRecoveryProbe(this, m_ERRBasePlayer);
|
||||
m_ERRBaseStep = 500;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "lifecycle")
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRLifecycleProbe = new ERR_NativeLifecycleProbe(this, m_ERRBasePlayer);
|
||||
m_ERRBaseStep = 400;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "transport-survey")
|
||||
{
|
||||
ERR_NativeTransportSurvey.Run(this);
|
||||
GetGame().GetCallqueue().Remove(ERR_BaseTick);
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "transport-loss")
|
||||
{
|
||||
m_ERRTransportProbe = new ERR_NativeTransportProbe(this, m_ERRBasePlayer);
|
||||
m_ERRBaseStep = 300;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "early-helicopter-loss")
|
||||
{
|
||||
if (!ERR_BaseCheck(Extraction().Ready() && Players().HasSurvivor(), "ready helicopter and living prisoner before deliberate loss")) return;
|
||||
m_ERRBaseStep = 201;
|
||||
ERR_Actors.DeleteSpawned(ExtractionVehicle());
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "extraction-capacity")
|
||||
{
|
||||
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
m_ERRExtractionProbe = new ERR_NativeExtractionProbe(this);
|
||||
m_ERRBaseStep = 200;
|
||||
return;
|
||||
}
|
||||
string survey;
|
||||
if (System.GetCLIParam("errSmokeOutcome", survey) && survey == "site-survey")
|
||||
{
|
||||
ERR_NativeSiteSurvey probe = new ERR_NativeSiteSurvey();
|
||||
probe.Run();
|
||||
GetGame().GetCallqueue().Remove(ERR_BaseTick);
|
||||
return;
|
||||
}
|
||||
if (!m_ERRObjectiveCloseChecked)
|
||||
{
|
||||
if (!ERR_NativeObjectiveProbe.BeginCloseReopen(this, now)) { GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
m_ERRBaseStep = 901;
|
||||
return;
|
||||
}
|
||||
if (m_ERRExtractionCase == "victory")
|
||||
{
|
||||
int adminMenu = ERR_ProbeAdminMenu(now);
|
||||
if (adminMenu < 0) { ERR_BaseCheck(false, "Core admin menu integration"); return; }
|
||||
if (adminMenu == 0) return;
|
||||
}
|
||||
if (!ERR_NativeAdminSettingsProbe.Run(this) || !ERR_NativeDefinitionProbe.Run() || !ERR_NativeContractsProbe.Run(this, m_ERRBasePlayer) || !ERR_BaseCover() || !ERR_NativeTimeProbe.Run(this)) return;
|
||||
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
||||
if (!ERR_BaseCheck(Objectives() && Objectives().Current() && Objectives().Current().GetTaskName() == "Escape the prison", "native prison objective exists")) return;
|
||||
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
|
||||
ERR_SiteRuntime cache = Sites().Find(m_ERRActiveCacheId);
|
||||
if (!ERR_BaseCheck(Sites().Count() == 7 && cache && cache.Container && vector.DistanceXZ(cache.Container.GetOrigin(), PrisonPosition()) > 100, "authored supply cache is a separate scavenging location")) return;
|
||||
array<IEntity> stock = {};
|
||||
ERR_Inventory.Items(InventoryStorageManagerComponent.Cast(cache.Container.FindComponent(InventoryStorageManagerComponent)), stock);
|
||||
PrintFormat("ERR BASE STOCK: cache items=%1", stock.Count());
|
||||
int expectedStock;
|
||||
foreach (ERR_SiteStock entry : cache.Definition.Stock) expectedStock += entry.Count;
|
||||
if (!ERR_BaseCheck(stock.Count() >= expectedStock, "cache holds finite physical equipment and objective documents")) return;
|
||||
if (!ERR_BaseCheck(Command() == null, "no companion HQ or support controller loaded")) return;
|
||||
if (!ERR_BaseCheck(vector.DistanceXZ(m_ERRBasePlayer.GetOrigin(), PrisonPosition()) < 5, "prisoner spawned in authored prison")) return;
|
||||
foreach (ERR_LocationComponent location : m_Locations)
|
||||
if (location.Kind() == ERR_LocationKind.RADIO) m_ERRBaseRadio = location;
|
||||
ERR_UnlockPrisonAction lock;
|
||||
foreach (IEntity entity : m_OwnedEntities)
|
||||
{
|
||||
if (!entity) continue;
|
||||
if (!lock) lock = ERR_BaseLock(entity);
|
||||
}
|
||||
foreach (IEntity item : stock)
|
||||
if (item && item.GetPrefabData() && item.GetPrefabData().GetPrefabName() == ERR_Resources.ObjectiveDocument()) m_ERRBaseDocuments.Insert(item);
|
||||
if (m_ERRCacheLoss)
|
||||
{
|
||||
ERR_Actors.DeleteSpawned(Sites().Find("everon-supply-1").Container);
|
||||
if (!ERR_BaseCheck(!Sites().Find("everon-supply-1").Container, "primary cache lost before any clues are recovered")) return;
|
||||
}
|
||||
if (!ERR_BaseCheck(lock && m_ERRBaseRadio && m_ERRBaseDocuments.Count() == 2 && ERR_Actors.Alive(ExtractionVehicle()), "gate, clues, radio and helicopter created by core")) return;
|
||||
if (!ERR_NativeProgressionIdentityProbe.Begin(this, m_ERRBasePlayer)) { ERR_BaseCheck(false, "native progression identity gate baseline"); return; }
|
||||
ERR_BaseMove(lock.GetOwner().GetOrigin() + "1 0 1");
|
||||
lock.PerformAction(lock.GetOwner(), m_ERRBasePlayer);
|
||||
if (!ERR_BaseCheck(Phase() == ERR_Phase.RADIO_SEARCH, "gate action begins escape")) return;
|
||||
if (!ERR_BaseCheck(m_Settings.Set("radioDefenceSeconds", 30), "test defence duration accepted")) return;
|
||||
SCR_Task prisonTask = SCR_TaskSystem.GetTaskFromTaskID(ERR_Objectives.Id(ERR_Phase.PRISON), false);
|
||||
if (!ERR_BaseCheck(prisonTask && prisonTask.GetTaskState() == SCR_ETaskState.COMPLETED, "previous native objective completes on phase change")) return;
|
||||
if (!ERR_BaseCheck(Objectives().Current().GetTaskUIVisibility() == SCR_ETaskUIVisibility.LIST_ONLY, "search objective does not expose a hidden site")) return;
|
||||
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
|
||||
if (!ERR_BaseCheck(Sites().Find("everon-supply-1").Location.Knowledge() == 2 && Sites().Find("everon-supply-2").Location.Knowledge() == 2, "both named supply routes revealed after escape")) return;
|
||||
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
|
||||
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && Sites().Find("everon-outpost-1").Location.Knowledge() == 0, "optional sites remain hidden until approached")) return;
|
||||
ERR_BaseMove(medical.Location.Position() + "0 0 8");
|
||||
CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent)).SetUnconscious(true);
|
||||
m_ERRBaseStep = 10;
|
||||
m_ERRBaseWait = now + 6000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 10)
|
||||
{
|
||||
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
|
||||
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && !Sites().CanObserve(m_ERRBasePlayer, medical), "unconscious player cannot discover optional sites")) return;
|
||||
CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent)).SetUnconscious(false);
|
||||
ERR_BaseMove(medical.Location.Position() + "0 0 65");
|
||||
m_ERRBaseStep = 11;
|
||||
m_ERRBaseWait = now + 6000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 11)
|
||||
{
|
||||
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
|
||||
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && !Sites().CanObserve(m_ERRBasePlayer, medical), "distant player cannot discover optional sites")) return;
|
||||
ERR_BaseMove(medical.Location.Position() + "0 0 8");
|
||||
m_ERRSightBlocker = ERR_Actors.Spawn("{A67420C1FB91856E}Prefabs/ERR/PrisonWall.et", ERR_Actors.Ground(medical.Location.Position() + "-1.5 0 4"));
|
||||
if (!ERR_BaseCheck(m_ERRSightBlocker != null, "isolated visibility obstacle created")) return;
|
||||
Own(m_ERRSightBlocker);
|
||||
vector low, high;
|
||||
m_ERRSightBlocker.GetWorldBounds(low, high);
|
||||
PrintFormat("ERR VISIBILITY OBSTACLE: low=%1 high=%2 observer=%3 target=%4", low, high, m_ERRBasePlayer.GetOrigin(), medical.Location.Position());
|
||||
m_ERRBaseStep = 115;
|
||||
m_ERRBaseWait = now + 6000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 115)
|
||||
{
|
||||
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
|
||||
if (!ERR_BaseCheck(medical.Location.Knowledge() == 0 && !Sites().CanObserve(m_ERRBasePlayer, medical), "physical wall prevents nearby site discovery")) return;
|
||||
ERR_Actors.DeleteSpawned(m_ERRSightBlocker);
|
||||
m_ERRBaseStep = 12;
|
||||
m_ERRBaseWait = now + 6000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 12)
|
||||
{
|
||||
ERR_SiteRuntime medical = Sites().Find("everon-medical-1");
|
||||
if (!ERR_BaseCheck(medical.Location.Knowledge() == 2 && medical.Location.Label() == "Medical stash" && m_ERRMedicalDiscoveries == 1, "conscious nearby player visually discovers named medical stash once")) return;
|
||||
if (!ERR_BaseCheck(!Sites().CanObserve(m_ERRBasePlayer, Sites().Find("everon-radio-1")), "distant medical-site discovery does not reveal the radio")) return;
|
||||
m_ERRBaseStep = 1;
|
||||
}
|
||||
else if (m_ERRBaseStep == 1 || m_ERRBaseStep == 2)
|
||||
{
|
||||
ERR_BaseMove(Sites().Find(m_ERRActiveCacheId).Container.GetOrigin() + "0 0 2");
|
||||
SCR_InventoryStorageManagerComponent inventory = SCR_InventoryStorageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(SCR_InventoryStorageManagerComponent));
|
||||
InventoryStorageManagerComponent source = InventoryStorageManagerComponent.Cast(Sites().Find(m_ERRActiveCacheId).Container.FindComponent(InventoryStorageManagerComponent));
|
||||
BaseInventoryStorageComponent destination = inventory.FindStorageForItem(m_ERRBaseDocuments[m_ERRBaseStep - 1]);
|
||||
if (!ERR_BaseCheck(destination && source.TryMoveItemToStorage(m_ERRBaseDocuments[m_ERRBaseStep - 1], destination), "physical clue recovered")) return;
|
||||
m_ERRBaseStep++;
|
||||
m_ERRBaseWait = now + 2500;
|
||||
}
|
||||
else if (m_ERRBaseStep == 3)
|
||||
{
|
||||
if (!ERR_BaseCheck(Objectives().Current().GetTaskDescription().Contains("The radio station is identified on your map. Reach"), "objective guidance reacts to discovered radio")) return;
|
||||
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
|
||||
ERR_SiteRuntime cache = Sites().Find(m_ERRActiveCacheId);
|
||||
array<IEntity> stock = {};
|
||||
ERR_Inventory.Items(InventoryStorageManagerComponent.Cast(cache.Container.FindComponent(InventoryStorageManagerComponent)), stock);
|
||||
foreach (IEntity item : stock) if (item) SCR_EntityHelper.DeleteEntityAndChildren(item);
|
||||
if (!ERR_BaseCheck(m_ERRMedicalDiscoveries == 1, "discovery ticks do not repeat the location event")) return;
|
||||
if (m_ERRCacheLoss && !ERR_BaseCheck(!Sites().Find("everon-supply-1").Container, "lost primary cache stays absent while alternate documents identify the radio")) return;
|
||||
if (!ERR_BaseCheck(ERR_NativeProgressionIdentityProbe.DocumentPassed(), "actual held-document rejection and restored recovery were exercised this round")) return;
|
||||
if (!ERR_BaseCheck(m_ERRBaseRadio.Knowledge() == 2, "documents identify radio without Intel addon")) return;
|
||||
ERR_BaseMove(m_ERRBaseRadio.Position() + "2 0 0");
|
||||
m_ERRBaseStep = 30;
|
||||
m_ERRBaseWait = now + 1000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 30)
|
||||
{
|
||||
m_ERRBaseRadioProbe = new ERR_NativeRadioDefenceProbe(this, m_ERRBasePlayer, m_ERRBaseRadio);
|
||||
m_ERRBaseStep = 301;
|
||||
}
|
||||
else if (m_ERRBaseStep == 301)
|
||||
{
|
||||
int result = m_ERRBaseRadioProbe.Tick(now);
|
||||
if (result < 0) { ERR_StopRadioProbe(); GetGame().GetCallqueue().Remove(ERR_BaseTick); return; }
|
||||
if (result == 0) return;
|
||||
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.INACTIVE, "radio feedback remains inactive before tuning")) return;
|
||||
if (!ERR_BaseCheck(TuneRadio(m_ERRBasePlayer, m_ERRBaseRadio), "identified radio accepts transmission")) return;
|
||||
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.WAITING && ERR_ObjectiveText.Description(this, false).Contains("Waiting for"), "radio feedback publishes waiting before first occupancy sample")) return;
|
||||
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.WAITING && Objectives().Current() && Objectives().Current().GetTaskDescription().Contains("Waiting for a conscious defender") && !Objectives().Current().GetTaskDescription().Contains("interrupted"), "native radio task reports waiting before first occupancy sample")) return;
|
||||
m_ERRBaseStep = 31;
|
||||
m_ERRBaseWait = now + 5000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 31)
|
||||
{
|
||||
ERR_SiteRuntime cache = Sites().Find(m_ERRActiveCacheId);
|
||||
array<IEntity> stock = {};
|
||||
ERR_Inventory.Items(InventoryStorageManagerComponent.Cast(cache.Container.FindComponent(InventoryStorageManagerComponent)), stock);
|
||||
if (!ERR_BaseCheck(stock.IsEmpty() && Sites().Prepare() == ERR_PrepareResult.READY, "depleted stock stays empty when preparation is queried again")) return;
|
||||
ERR_Actors.DeleteSpawned(cache.Container);
|
||||
if (!ERR_BaseCheck(DefenceOccupied() && DefenceSecondsRemaining() > 0 && DefenceSecondsRemaining() < 30 && Objectives().Current().GetTaskDescription().Contains("seconds remaining"), "native objective reports advancing radio progress")) return;
|
||||
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.TRANSMITTING && ERR_ObjectiveText.Description(this, false).Contains("Transmission in progress"), "radio guide reports transmitting without querying a countdown")) return;
|
||||
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
|
||||
CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent)).SetUnconscious(true);
|
||||
m_ERRBaseStep = 310;
|
||||
m_ERRBaseWait = now + 5000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 310)
|
||||
{
|
||||
CharacterControllerComponent controller = CharacterControllerComponent.Cast(m_ERRBasePlayer.FindComponent(CharacterControllerComponent));
|
||||
if (!ERR_BaseCheck(controller.IsUnconscious() && ERR_Actors.Alive(m_ERRBasePlayer) && Players().HasSurvivor() && Phase() == ERR_Phase.RADIO_DEFENCE, "unconscious survivor remains revivable during the failure grace period")) return;
|
||||
if (!ERR_BaseCheck(!DefenceOccupied() && DefenceSecondsRemaining() == 30, "unconscious radio defender cannot advance transmission")) return;
|
||||
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.INTERRUPTED && ERR_ObjectiveText.Description(this, false).Contains("Progress has reset"), "radio guide reports interruption after unconsciousness")) return;
|
||||
SCR_PlayerController playerController = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
||||
if (!ERR_BaseCheck(playerController.GetControlledEntity() == m_ERRBasePlayer && playerController.ERR_ObserverState() == ERR_ObserverReason.NONE && !playerController.ERR_HasObserverCamera(), "unconscious player retains their body and camera")) return;
|
||||
controller.SetUnconscious(false);
|
||||
m_ERRBaseStep = 311;
|
||||
m_ERRBaseWait = now + 6000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 311)
|
||||
{
|
||||
if (!ERR_BaseCheck(ERR_Actors.CanFight(m_ERRBasePlayer) && DefenceOccupied() && DefenceSecondsRemaining() < 30, "regaining consciousness resumes transmission from zero")) return;
|
||||
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.TRANSMITTING && ERR_ObjectiveText.Description(this, false).Contains("Transmission in progress"), "radio guide returns to transmitting after recovery")) return;
|
||||
int playerId = GetGame().GetPlayerManager().GetPlayerIdFromControlledEntity(m_ERRBasePlayer);
|
||||
if (!ERR_BaseCheck(!Players().CanEnter(playerId, Phase()), "regaining consciousness does not grant another life")) return;
|
||||
ERR_BaseMove(m_ERRBaseRadio.Position() + "90 0 0");
|
||||
m_ERRBaseStep = 32;
|
||||
m_ERRBaseWait = now + 2500;
|
||||
}
|
||||
else if (m_ERRBaseStep == 32)
|
||||
{
|
||||
if (!ERR_BaseCheck(!Sites().Find(m_ERRActiveCacheId).Container, "lost supply cache is not replaced")) return;
|
||||
if (!ERR_BaseCheck(!DefenceOccupied() && DefenceSecondsRemaining() == 30 && Objectives().Current().GetTaskDescription().Contains("interrupted"), "objective reports interruption and reset")) return;
|
||||
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.INTERRUPTED && ERR_ObjectiveText.Description(this, false).Contains("Return a conscious survivor"), "radio guide reports interruption after leaving the area")) return;
|
||||
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
|
||||
ERR_BaseMove(m_ERRBaseRadio.Position() + "2 0 0");
|
||||
m_ERRBaseStep = 4;
|
||||
m_ERRBaseWait = now + 33000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 4)
|
||||
{
|
||||
if (!ERR_BaseCheck(Phase() == ERR_Phase.EXTRACTION, "radio defence reveals extraction") || !ERR_NativeTimeProbe.VerifyProgression(this)) return;
|
||||
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.INACTIVE, "radio feedback clears after completion")) return;
|
||||
if (!ERR_NativeObjectiveProbe.Observe(this)) return;
|
||||
CompartmentAccessComponent access = CompartmentAccessComponent.Cast(m_ERRBasePlayer.FindComponent(CompartmentAccessComponent));
|
||||
BaseCompartmentSlot slot = access.FindFreeCompartment(ExtractionVehicle(), ECompartmentType.PILOT);
|
||||
if (!ERR_BaseCheck(slot && access.GetInVehicle(ExtractionVehicle(), slot, true, -1, ECloseDoorAfterActions.INVALID, false), "player physically boards extraction helicopter")) return;
|
||||
m_ERRBaseStep = 5;
|
||||
m_ERRBaseWait = now + 3000;
|
||||
}
|
||||
else if (m_ERRBaseStep == 5)
|
||||
{
|
||||
if (!ERR_BaseCheck(CompartmentAccessComponent.GetVehicleIn(m_ERRBasePlayer) == ExtractionVehicle(), "extraction requires real occupancy")) return;
|
||||
vector transform[4];
|
||||
ExtractionVehicle().GetWorldTransform(transform);
|
||||
transform[3] = m_EscapeCenter + Vector(m_EscapeRadius + 100, 150, 0);
|
||||
ExtractionVehicle().SetWorldTransform(transform);
|
||||
m_ERRBaseWait = now + 4000;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
#ifdef WORKBENCH
|
||||
// Read-only fixture access to native UI identities. No replacement widgets.
|
||||
modded class SCR_InventoryMenuUI
|
||||
{
|
||||
bool ERR_HitZoneProbeReady(IEntity actor)
|
||||
{
|
||||
return ERR_NativeInventoryHitZoneProbe.Enabled() && m_Player == actor && m_pStorageListUI
|
||||
&& m_pStorageListUI.GetStorage() && m_PlayerRenderAttributes && m_wAttachmentPointsContainer;
|
||||
}
|
||||
bool ERR_HitZoneProbePoints(out array<ref SCR_InventoryHitZonePointContainerUI> points)
|
||||
{
|
||||
points.Clear();
|
||||
if (!ERR_NativeInventoryHitZoneProbe.Enabled() || m_aHitZonePoints.Count() != 7) return false;
|
||||
foreach (SCR_InventoryHitZonePointContainerUI point : m_aHitZonePoints) points.Insert(point);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
modded class SCR_InventoryHitZonePointContainerUI
|
||||
{
|
||||
protected SCR_HitZone m_ERRProbeBlood;
|
||||
protected int m_ERRProbeUpdates;
|
||||
protected bool m_ERRProbeFalseRefresh;
|
||||
protected override void UpdateHitZoneState(SCR_HitZone hz, bool updateAttachmentStorage = true)
|
||||
{
|
||||
super.UpdateHitZoneState(hz, updateAttachmentStorage);
|
||||
if (m_ERRProbeBlood && hz == m_ERRProbeBlood)
|
||||
{
|
||||
m_ERRProbeUpdates++;
|
||||
if (!updateAttachmentStorage) m_ERRProbeFalseRefresh = true;
|
||||
}
|
||||
}
|
||||
bool ERR_HitZoneProbeWatch(ERR_GameMode mode, SCR_HitZone blood)
|
||||
{
|
||||
if (!ERR_NativeInventoryHitZoneProbe.Enabled() || !mode || mode != m_ERRHitZoneMode
|
||||
|| !blood || !m_ERRHitZoneSources.Contains(blood)) return false;
|
||||
m_ERRProbeBlood = blood; m_ERRProbeUpdates = 0; m_ERRProbeFalseRefresh = false;
|
||||
return true;
|
||||
}
|
||||
bool ERR_HitZoneProbeOnce() { return m_ERRProbeUpdates == 1 && !m_ERRProbeFalseRefresh; }
|
||||
bool ERR_HitZoneProbeDetached() { return !m_ERRHitZoneMode && m_ERRHitZoneSources.IsEmpty(); }
|
||||
void ERR_HitZoneProbeStop() { m_ERRProbeBlood = null; }
|
||||
}
|
||||
|
||||
// Emits one real one-argument damage-state notification without changing health.
|
||||
// It proves callback/native UI integration, not a natural injury or manual UI use.
|
||||
class ERR_NativeInventoryHitZoneProbe
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected SCR_PlayerController m_Controller;
|
||||
protected IEntity m_Actor;
|
||||
protected SCR_InventoryStorageManagerComponent m_Inventory;
|
||||
protected SCR_CharacterBloodHitZone m_Blood;
|
||||
protected SCR_InventoryMenuUI m_Menu;
|
||||
protected ref array<ref SCR_InventoryHitZonePointContainerUI> m_Points = {};
|
||||
protected int m_Deadline;
|
||||
protected int m_Stage;
|
||||
protected int m_Result;
|
||||
protected bool m_Stopped;
|
||||
protected bool m_CloseRequested;
|
||||
protected float m_Health;
|
||||
static bool Enabled()
|
||||
{
|
||||
string enabled, outcome;
|
||||
return Replication.IsServer() && System.GetCLIParam("errBaseSmoke", enabled) && enabled == "1"
|
||||
&& System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "inventory-hitzone";
|
||||
}
|
||||
void ERR_NativeInventoryHitZoneProbe(ERR_GameMode mode, IEntity actor)
|
||||
{
|
||||
m_Mode = mode; m_Actor = actor; m_Deadline = System.GetTickCount() + 30000;
|
||||
m_Controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
||||
}
|
||||
protected bool Check(bool condition, string detail)
|
||||
{
|
||||
if (condition) { Print("ERR SMOKE PASS: inventory hitzone " + detail); return true; }
|
||||
Print("ERR SMOKE FAIL: inventory hitzone " + detail, LogLevel.ERROR);
|
||||
m_Result = -1; Stop(); return false;
|
||||
}
|
||||
protected bool Context()
|
||||
{
|
||||
return Enabled() && m_Mode && m_Mode == ERR_GameMode.Get() && m_Mode.Active()
|
||||
&& m_Controller && m_Controller == GetGame().GetPlayerController() && m_Controller.GetControlledEntity() == m_Actor
|
||||
&& m_Mode.Players() && m_Mode.Players().CurrentActor(m_Actor) && ERR_Actors.CanFight(m_Actor);
|
||||
}
|
||||
protected bool OwnsMenu()
|
||||
{
|
||||
return m_Menu && GetGame().GetMenuManager().FindMenuByPreset(ChimeraMenuPreset.Inventory20Menu) == m_Menu;
|
||||
}
|
||||
protected bool UnchangedHealth() { return m_Blood && m_Blood.GetHealth() == m_Health; }
|
||||
int Tick(float now)
|
||||
{
|
||||
if (m_Result != 0) return m_Result;
|
||||
if (m_Stopped) return -1;
|
||||
if (!Context()) { Check(false, "original conscious local round body changed"); return -1; }
|
||||
if (System.GetTickCount() >= m_Deadline) { Check(false, "fixed 30-second UI/callback deadline"); return -1; }
|
||||
if (m_Stage == 0)
|
||||
{
|
||||
if (!Check(!GetGame().GetMenuManager().GetTopMenu(), "no existing menu adopted")) return -1;
|
||||
m_Inventory = SCR_InventoryStorageManagerComponent.Cast(m_Actor.FindComponent(SCR_InventoryStorageManagerComponent));
|
||||
SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(m_Actor.FindComponent(SCR_CharacterDamageManagerComponent));
|
||||
if (damage) m_Blood = damage.GetBloodHitZone();
|
||||
if (!Check(m_Inventory && m_Blood, "original native inventory and blood event source present")) return -1;
|
||||
m_Health = m_Blood.GetHealth();
|
||||
m_Inventory.OpenInventory();
|
||||
m_Menu = SCR_InventoryMenuUI.Cast(GetGame().GetMenuManager().FindMenuByPreset(ChimeraMenuPreset.Inventory20Menu));
|
||||
if (!Check(OwnsMenu(), "ordinary native inventory opens without input automation")) return -1;
|
||||
m_Stage = 1; return 0;
|
||||
}
|
||||
if (m_Stage == 1)
|
||||
{
|
||||
if (!Check(OwnsMenu() && UnchangedHealth(), "owned inventory and original blood health retained")) return -1;
|
||||
if (!m_Menu.ERR_HitZoneProbeReady(m_Actor)) return 0;
|
||||
m_Menu.InspectItem(null); // Native character inspection creates its normal seven containers over several frames.
|
||||
m_Stage = 2; return 0;
|
||||
}
|
||||
if (m_Stage == 2)
|
||||
{
|
||||
if (!Check(OwnsMenu() && UnchangedHealth(), "native inspection retains original menu and health")) return -1;
|
||||
if (!m_Menu.ERR_HitZoneProbePoints(m_Points)) return 0;
|
||||
array<SCR_InventoryHitZonePointContainerUI> seen = {};
|
||||
foreach (SCR_InventoryHitZonePointContainerUI point : m_Points)
|
||||
{
|
||||
if (!Check(point && !seen.Contains(point) && point.ERR_HitZoneProbeWatch(m_Mode, m_Blood), "unique native point retains original blood subscription")) return -1;
|
||||
seen.Insert(point);
|
||||
}
|
||||
if (!Check(UnchangedHealth() && m_Blood.GetOnDamageStateChanged(false), "original blood event and health retained before notification")) return -1;
|
||||
m_Blood.GetOnDamageStateChanged(false).Invoke(m_Blood);
|
||||
foreach (SCR_InventoryHitZonePointContainerUI updated : m_Points)
|
||||
if (!Check(updated.ERR_HitZoneProbeOnce(), "one-argument event reached native updater once with explicit true")) return -1;
|
||||
if (!Check(UnchangedHealth() && OwnsMenu(), "notification changes no blood health or menu ownership")) return -1;
|
||||
bool closeAccepted = GetGame().GetMenuManager().CloseMenu(m_Menu);
|
||||
m_CloseRequested = closeAccepted;
|
||||
if (!Check(closeAccepted, "exact owned inventory close request accepted")) return -1;
|
||||
m_Stage = 3; return 0;
|
||||
}
|
||||
if (m_Stage == 3)
|
||||
{
|
||||
MenuBase current = GetGame().GetMenuManager().FindMenuByPreset(ChimeraMenuPreset.Inventory20Menu);
|
||||
if (current == m_Menu && m_Menu) return 0;
|
||||
if (!Check(!current, "native inventory closes without adopting a replacement")) return -1;
|
||||
foreach (SCR_InventoryHitZonePointContainerUI detached : m_Points)
|
||||
if (!Check(detached && detached.ERR_HitZoneProbeDetached(), "native detach cleared original point subscriptions")) return -1;
|
||||
if (!Check(UnchangedHealth() && m_Blood.GetOnDamageStateChanged(false), "original blood event and health retained before notification")) return -1;
|
||||
m_Blood.GetOnDamageStateChanged(false).Invoke(m_Blood);
|
||||
foreach (SCR_InventoryHitZonePointContainerUI oldPoint : m_Points)
|
||||
if (!Check(oldPoint.ERR_HitZoneProbeOnce(), "closed original point receives no late native update")) return -1;
|
||||
if (!Check(UnchangedHealth(), "blood health unchanged through complete callback proof")) return -1;
|
||||
m_Menu = null; m_Result = 1; Stop(); return 1;
|
||||
}
|
||||
Check(false, "unexpected fixture stage"); return -1;
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
if (m_Stopped) return;
|
||||
m_Stopped = true;
|
||||
foreach (SCR_InventoryHitZonePointContainerUI point : m_Points) if (point) point.ERR_HitZoneProbeStop();
|
||||
m_Points.Clear();
|
||||
if (GetGame() && GetGame().GetMenuManager() && !m_CloseRequested && OwnsMenu())
|
||||
{
|
||||
m_CloseRequested = true;
|
||||
GetGame().GetMenuManager().CloseMenu(m_Menu);
|
||||
}
|
||||
m_Menu = null; m_Blood = null; m_Inventory = null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+369
@@ -0,0 +1,369 @@
|
||||
// Detached mounted counts only. Carried and cargo ammunition never enter these totals.
|
||||
class ERR_HQMountedAmmoSnapshot
|
||||
{
|
||||
bool Complete;
|
||||
int Weapons;
|
||||
int Muzzles;
|
||||
int Magazines;
|
||||
int Rounds;
|
||||
int Capacity;
|
||||
int Chambers;
|
||||
int LowMagazines;
|
||||
ERR_HQMountedAmmoSnapshot Snapshot()
|
||||
{
|
||||
ERR_HQMountedAmmoSnapshot copy = new ERR_HQMountedAmmoSnapshot();
|
||||
copy.Complete = Complete; copy.Weapons = Weapons; copy.Muzzles = Muzzles;
|
||||
copy.Magazines = Magazines; copy.Rounds = Rounds; copy.Capacity = Capacity;
|
||||
copy.Chambers = Chambers; copy.LowMagazines = LowMagazines;
|
||||
return copy;
|
||||
}
|
||||
bool Valid()
|
||||
{
|
||||
if (Weapons < 0 || Weapons > 32 || Muzzles < 0 || Muzzles > 128 || Muzzles > Weapons * 8) return false;
|
||||
if (Magazines < 0 || Magazines > Muzzles || Magazines > 128) return false;
|
||||
if (Capacity < Magazines || Capacity > Magazines * 100000 || Rounds < 0 || Rounds > Capacity) return false;
|
||||
if (Chambers < 0 || Chambers > Muzzles * 32 || LowMagazines < 0 || LowMagazines > Magazines) return false;
|
||||
return !Complete || (Weapons == 0 || (Muzzles > 0 && Magazines > 0));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WORKBENCH
|
||||
// Ephemeral opt-in fixture diagnostics; never part of radio reports or policy.
|
||||
class ERR_HQMountedAmmoDiagnostic
|
||||
{
|
||||
int Reasons;
|
||||
int Nodes;
|
||||
int Structural;
|
||||
int Cargo;
|
||||
int Allowed;
|
||||
int Managers;
|
||||
string Detail;
|
||||
string FirstPrefab;
|
||||
int FirstPurpose = -1;
|
||||
// Called before Reject, once per reason in this explicit diagnostic sample.
|
||||
// These labels describe sampled relationships; they never authorize a weapon.
|
||||
void SlotEvidence(int reason, IEntity entity, WeaponSlotComponent mount, InventoryStorageSlot binding, InventoryItemComponent item, array<IEntity> allowed)
|
||||
{
|
||||
if ((Reasons & reason) != 0) return;
|
||||
if (reason == 64) Detail += "mount-slot-evidence:";
|
||||
else Detail += "zero-muzzle-slot-evidence:";
|
||||
if (item) Detail += "item=present;";
|
||||
else Detail += "item=absent;";
|
||||
InventoryStorageSlot parent = null;
|
||||
if (item) parent = item.GetParentSlot();
|
||||
if (!parent) Detail += "parent=null;";
|
||||
else if (parent == binding) Detail += "parent=same-binding;";
|
||||
else Detail += "parent=different-binding;";
|
||||
if (!binding) Detail += "binding=null;";
|
||||
else
|
||||
{
|
||||
if (binding.GetAttachedEntity() == entity) Detail += "binding-attached=exact;";
|
||||
else Detail += "binding-attached=other-or-null;";
|
||||
if (binding.GetParentContainer() == mount) Detail += "binding-container=exact-mount;";
|
||||
else Detail += "binding-container=other-or-null;";
|
||||
}
|
||||
IEntity owner = null;
|
||||
if (mount) owner = mount.GetOwner();
|
||||
if (owner && allowed.Contains(owner)) Detail += "mount-owner=allowed;";
|
||||
else Detail += "mount-owner=absent-or-foreign;";
|
||||
if (owner && owner.GetPrefabData()) Detail += "mount-prefab=" + owner.GetPrefabData().GetPrefabName() + ";";
|
||||
if (!parent) return;
|
||||
if (parent.GetAttachedEntity() == entity) Detail += "parent-attached=exact;";
|
||||
else Detail += "parent-attached=other-or-null;";
|
||||
WeaponSlotComponent parentMount = WeaponSlotComponent.Cast(parent.GetParentContainer());
|
||||
if (parent.GetParentContainer() == mount && mount) Detail += "parent-container=exact-mount;";
|
||||
else if (parentMount) Detail += "parent-container=other-mount;";
|
||||
else if (!parent.GetParentContainer()) Detail += "parent-container=null;";
|
||||
else Detail += "parent-container=non-mount;";
|
||||
if (!parentMount) return;
|
||||
IEntity parentOwner = parentMount.GetOwner();
|
||||
if (parentOwner && allowed.Contains(parentOwner)) Detail += "parent-mount-owner=allowed;";
|
||||
else Detail += "parent-mount-owner=absent-or-foreign;";
|
||||
if (parentOwner && parentOwner.GetPrefabData()) Detail += "parent-mount-prefab=" + parentOwner.GetPrefabData().GetPrefabName() + ";";
|
||||
if (parentMount.GetSlotInfo() == parent) Detail += "parent-mount-slot=exact;";
|
||||
else Detail += "parent-mount-slot=other-or-null;";
|
||||
if (parentMount.GetWeaponEntity() == entity) Detail += "parent-mount-weapon=exact;";
|
||||
else Detail += "parent-mount-weapon=other-or-null;";
|
||||
}
|
||||
void Reject(int reason, string detail, IEntity entity, int purpose = -1)
|
||||
{
|
||||
if ((Reasons & reason) == 0) Detail += detail + ";";
|
||||
if (Reasons == 0)
|
||||
{
|
||||
FirstPurpose = purpose;
|
||||
if (entity && entity.GetPrefabData()) FirstPrefab = entity.GetPrefabData().GetPrefabName();
|
||||
}
|
||||
Reasons = Reasons | reason;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
class ERR_HQMountedAmmoSampler
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
protected static ref ERR_HQMountedAmmoDiagnostic s_Diagnostic;
|
||||
static void BeginDiagnostic() { s_Diagnostic = new ERR_HQMountedAmmoDiagnostic(); }
|
||||
static ERR_HQMountedAmmoDiagnostic EndDiagnostic()
|
||||
{
|
||||
ref ERR_HQMountedAmmoDiagnostic result = s_Diagnostic; s_Diagnostic = null; return result;
|
||||
}
|
||||
#endif
|
||||
// Native mounted weapons and loaded magazines use InventoryStorageSlot too,
|
||||
// but their container is a weapon slot/muzzle rather than deposit storage.
|
||||
static bool Installed(IEntity entity, InventoryStorageSlot slot, array<IEntity> allowed)
|
||||
{
|
||||
if (!entity || !slot) return false;
|
||||
WeaponSlotComponent mount = WeaponSlotComponent.Cast(slot.GetParentContainer());
|
||||
if (mount && allowed.Contains(mount.GetOwner()) && mount.GetSlotInfo() == slot && mount.GetWeaponEntity() == entity) return true;
|
||||
BaseMuzzleComponent muzzle = BaseMuzzleComponent.Cast(slot.GetParentContainer());
|
||||
if (!muzzle || !allowed.Contains(muzzle.GetOwner())) return false;
|
||||
BaseMagazineComponent magazine = muzzle.GetMagazine();
|
||||
return magazine && magazine.GetOwner() == entity;
|
||||
}
|
||||
protected static void AddWeapon(BaseWeaponComponent weapon, array<IEntity> allowed, array<BaseWeaponComponent> weapons, ERR_HQMountedAmmoSnapshot result)
|
||||
{
|
||||
if (!weapon || !weapon.GetOwner() || !allowed.Contains(weapon.GetOwner()))
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
IEntity owner = null;
|
||||
if (weapon) owner = weapon.GetOwner();
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(16, "weapon-owner-not-allowed", owner);
|
||||
#endif
|
||||
result.Complete = false; return;
|
||||
}
|
||||
// Native managers can expose both a slot and its physical weapon. Normalize
|
||||
// one exact current installation before deduplication, never another slot.
|
||||
WeaponSlotComponent mount = WeaponSlotComponent.Cast(weapon);
|
||||
if (mount)
|
||||
{
|
||||
IEntity mountedEntity = mount.GetWeaponEntity();
|
||||
InventoryStorageSlot binding = mount.GetSlotInfo();
|
||||
if (!mountedEntity || !allowed.Contains(mountedEntity) || !binding || binding.GetParentContainer() != mount)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(32, "mount-binding-invalid", mountedEntity);
|
||||
#endif
|
||||
result.Complete = false; return;
|
||||
}
|
||||
InventoryItemComponent mountedItem = InventoryItemComponent.Cast(mountedEntity.FindComponent(InventoryItemComponent));
|
||||
if (!mountedItem || mountedItem.GetParentSlot() != binding)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic)
|
||||
{
|
||||
s_Diagnostic.SlotEvidence(64, mountedEntity, mount, binding, mountedItem, allowed);
|
||||
s_Diagnostic.Reject(64, "mount-item-slot-mismatch", mountedEntity);
|
||||
}
|
||||
#endif
|
||||
result.Complete = false; return;
|
||||
}
|
||||
weapon = BaseWeaponComponent.Cast(mountedEntity.FindComponent(BaseWeaponComponent));
|
||||
if (!weapon || WeaponSlotComponent.Cast(weapon) || weapon.GetOwner() != mountedEntity)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(128, "mount-weapon-invalid", mountedEntity);
|
||||
#endif
|
||||
result.Complete = false; return;
|
||||
}
|
||||
}
|
||||
if (weapons.Contains(weapon)) return;
|
||||
if (weapons.Count() >= 32)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(256, "weapon-limit32", weapon.GetOwner());
|
||||
#endif
|
||||
result.Complete = false; return;
|
||||
}
|
||||
weapons.Insert(weapon);
|
||||
}
|
||||
protected static void Collect(IEntity root, ERR_HQMountedAmmoSnapshot result, array<IEntity> allowed, array<BaseWeaponComponent> weapons)
|
||||
{
|
||||
if (!root || ChimeraCharacter.Cast(root))
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(1, "invalid-root", root);
|
||||
#endif
|
||||
result.Complete = false; return;
|
||||
}
|
||||
result.Complete = true;
|
||||
array<IEntity> nodes = {root};
|
||||
// Known deposit cargo must not consume the mounted hierarchy budget. Both
|
||||
// categories retain independent caps; the unique queue is capped at their sum.
|
||||
// Skipped crew/unknown nodes remain structural and cannot borrow cargo space.
|
||||
int structuralNodes = 0;
|
||||
int cargoNodes = 0;
|
||||
for (int cursor = 0; cursor < nodes.Count(); cursor++)
|
||||
{
|
||||
IEntity entity = nodes[cursor];
|
||||
bool character = ChimeraCharacter.Cast(entity) != null;
|
||||
bool excludedCargo;
|
||||
bool unknownStorage;
|
||||
// Exact installed slots are checked before deposit exclusion. Never walk
|
||||
// the children of known cargo or seated character roots.
|
||||
if (!character)
|
||||
{
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
|
||||
if (item && item.GetParentSlot() && !Installed(entity, item.GetParentSlot(), allowed))
|
||||
{
|
||||
BaseInventoryStorageComponent storage = item.GetParentSlot().GetStorage();
|
||||
if (!storage || storage.GetPurpose() == EStoragePurpose.PURPOSE_UNSET || storage.GetPurpose() == EStoragePurpose.PURPOSE_ANY)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
int purpose = -1;
|
||||
if (storage) purpose = storage.GetPurpose();
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(2, "ambiguous-storage", entity, purpose);
|
||||
#endif
|
||||
result.Complete = false; unknownStorage = true;
|
||||
}
|
||||
else excludedCargo = (storage.GetPurpose() & EStoragePurpose.PURPOSE_DEPOSIT) != 0;
|
||||
}
|
||||
}
|
||||
if (excludedCargo)
|
||||
{
|
||||
cargoNodes++;
|
||||
if (cargoNodes > 128)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(2048, "cargo-limit128", entity);
|
||||
#endif
|
||||
result.Complete = false; break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
structuralNodes++;
|
||||
if (structuralNodes > 128)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(1024, "structural-limit128", entity);
|
||||
#endif
|
||||
result.Complete = false; break;
|
||||
}
|
||||
if (character || unknownStorage) continue;
|
||||
allowed.Insert(entity);
|
||||
IEntity child = entity.GetChildren();
|
||||
while (child)
|
||||
{
|
||||
bool repeated = nodes.Contains(child);
|
||||
if (repeated || nodes.Count() >= 256)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic)
|
||||
{
|
||||
if (repeated) s_Diagnostic.Reject(4, "repeated-node", child);
|
||||
else s_Diagnostic.Reject(8, "queue-limit256", child);
|
||||
}
|
||||
#endif
|
||||
result.Complete = false; break;
|
||||
}
|
||||
nodes.Insert(child);
|
||||
child = child.GetSibling();
|
||||
}
|
||||
}
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic)
|
||||
{
|
||||
s_Diagnostic.Nodes = nodes.Count(); s_Diagnostic.Allowed = allowed.Count();
|
||||
s_Diagnostic.Structural = structuralNodes; s_Diagnostic.Cargo = cargoNodes;
|
||||
}
|
||||
#endif
|
||||
foreach (IEntity part : allowed)
|
||||
{
|
||||
BaseWeaponManagerComponent manager = BaseWeaponManagerComponent.Cast(part.FindComponent(BaseWeaponManagerComponent));
|
||||
if (manager)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Managers++;
|
||||
#endif
|
||||
array<BaseWeaponComponent> listed = {}; manager.GetWeapons(listed);
|
||||
if (listed.Count() > 8)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(512, "manager-limit8", part);
|
||||
#endif
|
||||
result.Complete = false;
|
||||
}
|
||||
else foreach (BaseWeaponComponent entry : listed) AddWeapon(entry, allowed, weapons, result);
|
||||
}
|
||||
BaseWeaponComponent mounted = BaseWeaponComponent.Cast(part.FindComponent(BaseWeaponComponent));
|
||||
if (mounted) AddWeapon(mounted, allowed, weapons, result);
|
||||
}
|
||||
}
|
||||
// These installed resources are inert selection/periscope controls, not guns.
|
||||
// Only omit them after the caller observed an empty native muzzle list and
|
||||
// reciprocal current weapon-slot ownership in this same sampled hierarchy.
|
||||
protected static bool NonFiringPlaceholder(BaseWeaponComponent weapon, array<IEntity> allowed)
|
||||
{
|
||||
if (!weapon || !weapon.GetOwner()) return false;
|
||||
IEntity owner = weapon.GetOwner();
|
||||
if (!allowed.Contains(owner) || !owner.GetPrefabData()) return false;
|
||||
ResourceName resource = owner.GetPrefabData().GetPrefabName();
|
||||
if (resource != "{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et"
|
||||
&& resource != "{7E1DD1CE4D6A23F4}Prefabs/Weapons/Core/VehiclePeriscope_DummyWeapon_Base.et") return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(owner.FindComponent(InventoryItemComponent));
|
||||
if (!item || !item.GetParentSlot()) return false;
|
||||
InventoryStorageSlot slot = item.GetParentSlot();
|
||||
WeaponSlotComponent mount = WeaponSlotComponent.Cast(slot.GetParentContainer());
|
||||
return mount && allowed.Contains(mount.GetOwner()) && mount.GetSlotInfo() == slot && mount.GetWeaponEntity() == owner;
|
||||
}
|
||||
static ERR_HQMountedAmmoSnapshot Inspect(IEntity root)
|
||||
{
|
||||
ERR_HQMountedAmmoSnapshot result = new ERR_HQMountedAmmoSnapshot();
|
||||
array<IEntity> allowed = {};
|
||||
array<BaseWeaponComponent> weapons = {};
|
||||
Collect(root, result, allowed, weapons);
|
||||
result.Weapons = weapons.Count();
|
||||
array<BaseMuzzleComponent> seenMuzzles = {};
|
||||
array<BaseMagazineComponent> seenMagazines = {};
|
||||
array<IEntity> magazineOwners = {};
|
||||
foreach (BaseWeaponComponent weapon : weapons)
|
||||
{
|
||||
array<BaseMuzzleComponent> muzzles = {}; weapon.GetMuzzlesList(muzzles);
|
||||
if (muzzles.IsEmpty())
|
||||
{
|
||||
if (NonFiringPlaceholder(weapon, allowed)) result.Weapons--;
|
||||
else
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic)
|
||||
{
|
||||
IEntity owner = weapon.GetOwner();
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(owner.FindComponent(InventoryItemComponent));
|
||||
WeaponSlotComponent mount = null;
|
||||
InventoryStorageSlot binding = null;
|
||||
if (item && item.GetParentSlot()) mount = WeaponSlotComponent.Cast(item.GetParentSlot().GetParentContainer());
|
||||
if (mount) binding = mount.GetSlotInfo();
|
||||
s_Diagnostic.SlotEvidence(4096, owner, mount, binding, item, allowed);
|
||||
s_Diagnostic.Reject(4096, "zero-muzzle-not-proven-placeholder", owner);
|
||||
}
|
||||
#endif
|
||||
result.Complete = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (muzzles.Count() > 8) { result.Complete = false; continue; }
|
||||
foreach (BaseMuzzleComponent muzzle : muzzles)
|
||||
{
|
||||
if (!muzzle || !muzzle.GetOwner() || !allowed.Contains(muzzle.GetOwner())) { result.Complete = false; continue; }
|
||||
if (seenMuzzles.Contains(muzzle)) continue;
|
||||
if (seenMuzzles.Count() >= 128) { result.Complete = false; continue; }
|
||||
seenMuzzles.Insert(muzzle); result.Muzzles++;
|
||||
int barrels = muzzle.GetBarrelsCount();
|
||||
if (barrels < 1 || barrels > 32) result.Complete = false;
|
||||
else for (int barrel = 0; barrel < barrels; barrel++)
|
||||
if (muzzle.IsBarrelChambered(barrel)) result.Chambers++;
|
||||
BaseMagazineComponent magazine = muzzle.GetMagazine();
|
||||
// Projectile-only weapons and absent magazines are unknown, never empty.
|
||||
if (!magazine || !magazine.GetOwner() || !allowed.Contains(magazine.GetOwner())) { result.Complete = false; continue; }
|
||||
if (seenMagazines.Contains(magazine)) continue;
|
||||
if (magazineOwners.Contains(magazine.GetOwner())) { result.Complete = false; continue; }
|
||||
seenMagazines.Insert(magazine); magazineOwners.Insert(magazine.GetOwner());
|
||||
int rounds = magazine.GetAmmoCount();
|
||||
int capacity = magazine.GetMaxAmmoCount();
|
||||
if (rounds < 0 || capacity < 1 || capacity > 100000 || rounds > capacity) { result.Complete = false; continue; }
|
||||
result.Magazines++; result.Rounds += rounds; result.Capacity += capacity;
|
||||
if (rounds <= capacity * 0.25) result.LowMagazines++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
"""Execute actual mounted sampler/snapshot methods against bounded native doubles."""
|
||||
import re,os
|
||||
from pathlib import Path
|
||||
from policy_harness import ROOT, run
|
||||
p=Path(os.environ.get('ERR_MOUNTED_AMMO_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedAmmoSampler.c'))
|
||||
s=p.read_text()
|
||||
for forbidden in ('SetAmmoCount','GetOrigin','GetWorld','Spawn','TryMove','DeleteEntity','GetAgents'):
|
||||
assert forbidden not in s, forbidden
|
||||
code=re.sub(r'//[^\n]*','',s).replace('protected ','').replace('class ','struct ')
|
||||
code=code.replace('BaseWeaponManagerComponent.Cast(part.FindComponent(BaseWeaponManagerComponent))','part->manager')
|
||||
code=code.replace('BaseWeaponComponent.Cast(part.FindComponent(BaseWeaponComponent))','part->weapon')
|
||||
code=code.replace('BaseWeaponComponent.Cast(mountedEntity.FindComponent(BaseWeaponComponent))','mountedEntity->weapon')
|
||||
code=code.replace('InventoryItemComponent.Cast(mountedEntity.FindComponent(InventoryItemComponent))','mountedEntity->item')
|
||||
code=code.replace('WeaponSlotComponent.Cast(weapon)','dynamic_cast<Mount*>(weapon)').replace('binding.GetParentContainer()','binding->mount')
|
||||
code=code.replace('InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent))','entity->item')
|
||||
code=code.replace('InventoryItemComponent.Cast(owner.FindComponent(InventoryItemComponent))','owner->item').replace('ResourceName resource', 'string resource')
|
||||
code=code.replace('WeaponSlotComponent.Cast(slot.GetParentContainer())','slot->mount').replace('BaseMuzzleComponent.Cast(slot.GetParentContainer())','slot->muzzle')
|
||||
code=code.replace('WeaponSlotComponent.Cast(parent.GetParentContainer())','parent->mount').replace('WeaponSlotComponent.Cast(item.GetParentSlot().GetParentContainer())','item->GetParentSlot()->mount')
|
||||
code=code.replace('parent.GetParentContainer()', 'parent->Container()')
|
||||
code=code.replace('EStoragePurpose.','EStoragePurpose::').replace('GetParentSlot().','GetParentSlot()->')
|
||||
code=code.replace('ChimeraCharacter.Cast(entity) != nullptr','entity->character').replace('ChimeraCharacter.Cast(entity) != null','entity->character')
|
||||
code=code.replace('ChimeraCharacter.Cast(root)','root->character').replace('ChimeraCharacter.Cast(entity)','entity->character')
|
||||
code=code.replace('protected static ref ', 'inline static ').replace('static ref ', 'inline static ').replace('ref ', '')
|
||||
code=re.sub(r'\bERR_HQMountedAmmoDiagnostic (?=\w)', 'ERR_HQMountedAmmoDiagnostic* ',code)
|
||||
code=code.replace('s_Diagnostic.', 's_Diagnostic->').replace('GetPrefabData().', 'GetPrefabData()->')
|
||||
code=re.sub(r'\bERR_HQMountedAmmoSnapshot (?=\w)','ERR_HQMountedAmmoSnapshot* ',code)
|
||||
code=code.replace('array<','Array<').replace('foreach (','for (')
|
||||
code=re.sub(r'"[^"]*"|\bnull\b',lambda m:m.group() if m.group().startswith(chr(34)) else 'nullptr',code)
|
||||
code=code.replace('Array<IEntity> allowed, Array<BaseWeaponComponent> weapons,','Array<IEntity>& allowed, Array<BaseWeaponComponent>& weapons,')
|
||||
code=code.replace('Array<IEntity> allowed, Array<BaseWeaponComponent> weapons)', 'Array<IEntity>& allowed, Array<BaseWeaponComponent>& weapons)')
|
||||
for name in ('copy','result','weapon','root','entity','child','part','manager','mounted','muzzle','magazine','item','storage','mount','mountedEntity','mountedItem','owner','parent','binding','parentMount','parentOwner'):
|
||||
code=re.sub(r'\b'+name+r'\.',name+'->',code)
|
||||
code=re.sub(r'\b(bool|int) (\w+);',r'\1 \2=0;',code).replace('\n}\n','\n};\n')
|
||||
program=(r'''
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
using string=std::string;
|
||||
struct Prefab{string name="fixture";string GetPrefabName(){return name;}};
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;
|
||||
int Count(){return this->size();}bool IsEmpty(){return this->empty();}
|
||||
void Insert(T n){this->push_back(n);}bool Contains(T n){return std::find(this->begin(),this->end(),n)!=this->end();}};
|
||||
struct Entity;struct Weapon;struct Muzzle;struct Magazine;struct Manager;struct Mount;
|
||||
struct EStoragePurpose{static const int PURPOSE_UNSET=0,PURPOSE_DEPOSIT=1,PURPOSE_WEAPON_PROXY=2,PURPOSE_ATTACHMENT_PROXY=4,PURPOSE_ANY=127;};
|
||||
struct Storage{int purpose=EStoragePurpose::PURPOSE_DEPOSIT;int GetPurpose(){return purpose;}};
|
||||
struct Slot{Mount* mount=nullptr;Muzzle* muzzle=nullptr;Storage* storage=nullptr;Entity* attached=nullptr;Entity* GetAttachedEntity(){return attached;}void* Container(){return mount?static_cast<void*>(mount):muzzle?static_cast<void*>(muzzle):static_cast<void*>(storage);}Storage* GetStorage(){return storage;}};
|
||||
struct Item{Slot* slot=nullptr;Slot* GetParentSlot(){return slot;}};
|
||||
using InventoryStorageSlot=Slot*;using WeaponSlotComponent=Mount*;
|
||||
using InventoryItemComponent=Item*;using BaseInventoryStorageComponent=Storage*;
|
||||
using IEntity=Entity*;using BaseWeaponComponent=Weapon*;using BaseMuzzleComponent=Muzzle*;
|
||||
using BaseMagazineComponent=Magazine*;using BaseWeaponManagerComponent=Manager*;
|
||||
struct Entity{Prefab prefab;Prefab*GetPrefabData(){return &prefab;}Item* item=nullptr;bool character=false;Entity* child=nullptr;Entity* sibling=nullptr;Manager* manager=nullptr;Weapon* weapon=nullptr;
|
||||
int reads=0;Entity* GetChildren(){reads++;assert(reads<=1);return child;}Entity* GetSibling(){return sibling;}};
|
||||
struct Magazine{IEntity owner=nullptr;int rounds=25,capacity=100,reads=0;
|
||||
IEntity GetOwner(){return owner;}int GetAmmoCount(){reads++;return rounds;}int GetMaxAmmoCount(){return capacity;}};
|
||||
struct Muzzle{IEntity owner=nullptr;Magazine* mag=nullptr;int barrels=1,chambered=1,reads=0;
|
||||
IEntity GetOwner(){return owner;}int GetBarrelsCount(){return barrels;}
|
||||
bool IsBarrelChambered(int b){reads++;return b<chambered;}Magazine* GetMagazine(){return mag;}};
|
||||
struct Weapon{virtual ~Weapon(){}IEntity owner=nullptr;Array<Muzzle*> muzzles;int reads=0;
|
||||
IEntity GetOwner(){return owner;}void GetMuzzlesList(Array<Muzzle*>& out){reads++;out=muzzles;}};
|
||||
struct Mount:Weapon{IEntity weapon=nullptr;Slot* slot=nullptr;
|
||||
Slot* GetSlotInfo(){return slot;}IEntity GetWeaponEntity(){return weapon;}};
|
||||
struct Manager{Array<Weapon*> weapons;int reads=0;void GetWeapons(Array<Weapon*>& out){reads++;out=weapons;}};
|
||||
'''+code+r'''
|
||||
struct Rig{Entity root,gun,magazine,crew,rifle;Manager manager,crewManager;Weapon weapon,carried;
|
||||
Muzzle muzzle,carriedMuzzle;Magazine mag,carriedMag;
|
||||
Rig(){root.child=&gun;gun.sibling=&magazine;magazine.sibling=&crew;crew.character=true;crew.child=&rifle;
|
||||
root.manager=&manager;manager.weapons={&weapon,&weapon};gun.weapon=&weapon;weapon.owner=&gun;weapon.muzzles={&muzzle,&muzzle};
|
||||
muzzle.owner=&gun;muzzle.mag=&mag;mag.owner=&magazine;
|
||||
crew.manager=&crewManager;crewManager.weapons={&carried};rifle.weapon=&carried;carried.owner=&rifle;
|
||||
carried.muzzles={&carriedMuzzle};carriedMuzzle.owner=&rifle;carriedMuzzle.mag=&carriedMag;carriedMag.owner=&rifle;}
|
||||
ERR_HQMountedAmmoSnapshot* Inspect(){return ERR_HQMountedAmmoSampler::Inspect(&root);}
|
||||
};
|
||||
int main(){
|
||||
// Installed BMP has three live gun components plus two inert native controls.
|
||||
// Exercise actual Inspect using exact documented resources and reciprocal slots.
|
||||
for(string name:{string("{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et"),string("{7E1DD1CE4D6A23F4}Prefabs/Weapons/Core/VehiclePeriscope_DummyWeapon_Base.et")}){
|
||||
Rig r;Entity dummy;Weapon inert;Mount mount;Item item;Slot slot;dummy.prefab.name=name;
|
||||
r.crew.sibling=&dummy;dummy.weapon=&inert;inert.owner=&dummy;dummy.item=&item;item.slot=&slot;slot.mount=&mount;mount.owner=&r.root;mount.slot=&slot;mount.weapon=&dummy;
|
||||
r.manager.weapons.Insert(&mount);r.manager.weapons.Insert(&inert);
|
||||
auto s=r.Inspect();assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Muzzles==1&&s->Magazines==1&&s->Rounds==25&&s->Capacity==100&&s->Chambers==1&&inert.reads==1);
|
||||
}
|
||||
// A root containing only a known installed inert control is completely unarmed.
|
||||
{Entity root,dummy;Weapon inert;Mount mount;Item item;Slot slot;root.child=&dummy;dummy.prefab.name="{7E1DD1CE4D6A23F4}Prefabs/Weapons/Core/VehiclePeriscope_DummyWeapon_Base.et";dummy.weapon=&inert;inert.owner=&dummy;dummy.item=&item;item.slot=&slot;slot.mount=&mount;mount.owner=&root;mount.slot=&slot;mount.weapon=&dummy;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(s->Complete&&s->Valid()&&s->Weapons==0&&s->Muzzles==0&&s->Magazines==0&&s->Rounds==0&&s->Capacity==0&&s->Chambers==0&&inert.reads==1);}
|
||||
// Both BMP controls coexist: decrement each exactly once while retaining the gun.
|
||||
{Rig r;Entity dummy[2];Weapon inert[2];Mount mount[2];Item item[2];Slot slot[2];r.crew.sibling=&dummy[0];dummy[0].sibling=&dummy[1];
|
||||
dummy[0].prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";dummy[1].prefab.name="{7E1DD1CE4D6A23F4}Prefabs/Weapons/Core/VehiclePeriscope_DummyWeapon_Base.et";
|
||||
for(int i=0;i<2;i++){dummy[i].weapon=&inert[i];inert[i].owner=&dummy[i];dummy[i].item=&item[i];item[i].slot=&slot[i];slot[i].mount=&mount[i];mount[i].owner=&r.root;mount[i].slot=&slot[i];mount[i].weapon=&dummy[i];r.manager.weapons.Insert(&mount[i]);r.manager.weapons.Insert(&inert[i]);}
|
||||
auto s=r.Inspect();assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Muzzles==1&&s->Magazines==1&&s->Rounds==25&&s->Capacity==100&&s->Chambers==1&&inert[0].reads==1&&inert[1].reads==1);}
|
||||
// Unknown no-muzzle weapons and broken/foreign installation remain incomplete.
|
||||
for(int bad=0;bad<6;bad++){
|
||||
Rig r;Entity dummy,foreign;Weapon inert;Mount mount;Item item;Slot slot,other;dummy.prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";
|
||||
r.crew.sibling=&dummy;dummy.weapon=&inert;inert.owner=&dummy;dummy.item=&item;item.slot=&slot;slot.mount=&mount;mount.owner=&r.root;mount.slot=&slot;mount.weapon=&dummy;
|
||||
if(bad==0)dummy.prefab.name="{0000000000000000}Prefabs/Weapons/Core/Armament_Empty.et";
|
||||
if(bad==1)dummy.item=nullptr;
|
||||
if(bad==2)item.slot=nullptr;
|
||||
if(bad==3)mount.owner=&foreign;
|
||||
if(bad==4)mount.slot=&other;
|
||||
if(bad==5)mount.weapon=&foreign;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Valid());
|
||||
}
|
||||
// A known resource carrying an actual muzzle is never silently omitted.
|
||||
{Rig r;r.gun.prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";auto s=r.Inspect();assert(s->Complete&&s->Weapons==1&&s->Rounds==25&&r.muzzle.reads==1);}
|
||||
// An excluded cargo subtree cannot turn a stored placeholder into mounted armament.
|
||||
{Rig r;Entity cargo;Weapon inert;Item item;Slot slot;Storage storage;r.crew.sibling=&cargo;cargo.prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";cargo.item=&item;item.slot=&slot;slot.storage=&storage;cargo.weapon=&inert;inert.owner=&cargo;auto s=r.Inspect();assert(s->Complete&&s->Weapons==1&&inert.reads==0);}
|
||||
{auto s=ERR_HQMountedAmmoSampler::Inspect(nullptr);assert(!s->Complete&&s->Valid());}
|
||||
{Entity root;root.character=true;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&root.reads==0);}
|
||||
{Entity root;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(s->Complete&&s->Valid()&&s->Weapons==0);}
|
||||
{Rig r;auto s=r.Inspect();assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Muzzles==1&&s->Magazines==1);
|
||||
assert(s->Rounds==25&&s->Capacity==100&&s->Chambers==1&&s->LowMagazines==1);
|
||||
assert(r.weapon.reads==1&&r.muzzle.reads==1&&r.mag.reads==1&&r.crew.reads==0&&r.crewManager.reads==0&&r.carried.reads==0);
|
||||
auto copy=s->Snapshot();s->Complete=false;s->Rounds=0;s->Capacity=0;s->LowMagazines=0;
|
||||
assert(copy->Complete&©->Rounds==25&©->Capacity==100&©->LowMagazines==1&©->Weapons==1&©->Muzzles==1&©->Magazines==1&©->Chambers==1);}
|
||||
for(int rounds:{0,24,25,26,100}){Rig r;r.mag.rounds=rounds;auto s=r.Inspect();assert(s->Complete&&s->Valid());assert(s->LowMagazines==(rounds<=25));}
|
||||
{Rig r;Muzzle second;second.owner=&r.gun;second.mag=&r.mag;second.chambered=0;r.weapon.muzzles.Insert(&second);
|
||||
auto s=r.Inspect();assert(s->Complete&&s->Muzzles==2&&s->Magazines==1&&s->Chambers==1&&s->Rounds==25&&r.mag.reads==1);}
|
||||
// Manager references into skipped characters, foreign roots, or nulls never count.
|
||||
for(int kind=0;kind<3;kind++){Rig r;Entity foreign;Weapon w;w.owner=&foreign;
|
||||
r.manager.weapons.Insert(kind==0?&r.carried:kind==1?&w:nullptr);auto s=r.Inspect();assert(!s->Complete&&s->Valid()&&s->Weapons==1);}
|
||||
for(int kind=0;kind<3;kind++){Rig r;Entity foreign;
|
||||
if(kind==0)r.muzzle.owner=&foreign;
|
||||
if(kind==1)r.mag.owner=&foreign;
|
||||
if(kind==2)r.mag.owner=nullptr;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Valid()&&s->Rounds==0&&s->Magazines==0);}
|
||||
// Deposited rifles and nested magazines are excluded; installed magazine slots remain admitted.
|
||||
{Rig r;Entity cargo,rifle,magEntity;Item item;Slot slot;Storage storage;Weapon rifleWeapon;
|
||||
r.crew.sibling=&cargo;cargo.child=&rifle;rifle.child=&magEntity;rifle.item=&item;item.slot=&slot;slot.storage=&storage;
|
||||
rifle.weapon=&rifleWeapon;rifleWeapon.owner=&rifle;auto s=r.Inspect();assert(s->Complete&&s->Weapons==1&&rifle.reads==0&&magEntity.reads==0);}
|
||||
{Rig r;Item item;Slot slot;Storage storage;r.magazine.item=&item;item.slot=&slot;slot.storage=&storage;
|
||||
storage.purpose=EStoragePurpose::PURPOSE_ATTACHMENT_PROXY;auto s=r.Inspect();assert(s->Complete&&s->Rounds==25&&s->Magazines==1);}
|
||||
for(int purpose:{0,127}){Rig r;Item item;Slot slot;Storage storage;r.gun.item=&item;item.slot=&slot;slot.storage=&storage;
|
||||
storage.purpose=purpose;auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Valid());}
|
||||
// Native purposes support combined flags; any deposit bit excludes the subtree.
|
||||
{Rig r;Item item;Slot slot;Storage storage;r.gun.item=&item;item.slot=&slot;slot.storage=&storage;
|
||||
storage.purpose=EStoragePurpose::PURPOSE_DEPOSIT|EStoragePurpose::PURPOSE_ATTACHMENT_PROXY;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Valid());}
|
||||
// Manager references cannot rescue a cargo weapon.
|
||||
{Rig r;Item item;Slot slot;Storage storage;r.gun.item=&item;item.slot=&slot;slot.storage=&storage;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Valid());}
|
||||
// Exact native weapon-slot and muzzle containers are not deposit storage.
|
||||
for(int purpose:{-1,0,127}){Rig r;Item gunItem,magItem;Slot gunSlot,magSlot;Storage storage;Mount mount;
|
||||
mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&gunSlot;gunSlot.mount=&mount;r.gun.item=&gunItem;gunItem.slot=&gunSlot;
|
||||
magSlot.muzzle=&r.muzzle;r.magazine.item=&magItem;magItem.slot=&magSlot;
|
||||
if(purpose>=0){storage.purpose=purpose;gunSlot.storage=&storage;magSlot.storage=&storage;}
|
||||
auto s=r.Inspect();assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Magazines==1&&s->Rounds==25&&s->Chambers==1);}
|
||||
// No generic missing-storage bypass: foreign container owners, stale slots and
|
||||
// substituted attached items fail closed, including foreign loaded magazines.
|
||||
for(int fault=0;fault<6;fault++){Rig r;Entity foreign;Item item;Slot slot,other;Mount mount;
|
||||
r.gun.item=&item;item.slot=&slot;slot.mount=&mount;mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&slot;
|
||||
if(fault==0)mount.owner=&foreign;
|
||||
if(fault==1)mount.owner=&r.crew;
|
||||
if(fault==2)mount.weapon=&foreign;
|
||||
if(fault==3)mount.slot=&other;
|
||||
if(fault==4)slot.mount=nullptr;
|
||||
if(fault==5)mount.owner=nullptr;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Rounds==0&&s->Valid());}
|
||||
for(int fault=0;fault<3;fault++){Rig r;Entity foreign;Item item;Slot slot;
|
||||
r.magazine.item=&item;item.slot=&slot;slot.muzzle=&r.muzzle;
|
||||
if(fault==0)r.muzzle.owner=&foreign;
|
||||
if(fault==1)r.muzzle.mag=nullptr;
|
||||
if(fault==2)r.mag.owner=&foreign;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Magazines==0&&s->Rounds==0&&s->Valid());}
|
||||
// An exact loaded magazine inside an excluded cargo rifle does not re-admit it.
|
||||
{Rig r;Item item,magItem;Slot slot,magSlot;Storage deposit;r.gun.item=&item;item.slot=&slot;slot.storage=&deposit;
|
||||
r.magazine.item=&magItem;magItem.slot=&magSlot;magSlot.muzzle=&r.muzzle;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Magazines==0&&s->Valid());}
|
||||
// Native manager/FindComponent expose the wrapper and the physical weapon.
|
||||
// A UAZ's single PKM must be one weapon even when all those paths overlap.
|
||||
{Rig r;Mount mount;Item item;Slot binding;mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&binding;
|
||||
binding.mount=&mount;item.slot=&binding;r.gun.item=&item;r.root.weapon=&mount;mount.muzzles=r.weapon.muzzles;
|
||||
r.manager.weapons={&mount,&r.weapon,&mount};auto s=r.Inspect();
|
||||
assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Muzzles==1&&s->Magazines==1&&s->Rounds==25&&s->Chambers==1);
|
||||
assert(mount.reads==0&&r.weapon.reads==1&&r.mag.reads==1);}
|
||||
// Unknown/foreign/empty/nested/stale slot endpoints never become real guns.
|
||||
for(int fault=0;fault<11;fault++){Rig r;Entity foreign;Mount mount,other;Item item;Slot binding,stale;
|
||||
mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&binding;binding.mount=&mount;item.slot=&binding;r.gun.item=&item;
|
||||
r.root.weapon=&mount;r.manager.weapons={&mount};
|
||||
if(fault==0)mount.weapon=nullptr;
|
||||
if(fault==1)mount.weapon=&foreign;
|
||||
if(fault==2)mount.weapon=&r.root; // nested/self wrapper
|
||||
if(fault==3)binding.mount=&other;
|
||||
if(fault==4)item.slot=&stale;
|
||||
if(fault==5)mount.slot=nullptr;
|
||||
if(fault==6)r.gun.item=nullptr;
|
||||
if(fault==7)mount.owner=&foreign;
|
||||
if(fault==8)r.gun.weapon=nullptr;
|
||||
if(fault==9){other.owner=&r.gun;r.gun.weapon=&other;} // valid first binding, nested wrapper endpoint
|
||||
if(fault==10)r.weapon.owner=&foreign; // component found on target belongs elsewhere
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Valid()&&s->Weapons<=1&&mount.reads==0&&other.reads==0);}
|
||||
// Thirty-two genuine installed guns still fit the weapon cap despite wrappers.
|
||||
{Entity root;Array<Entity> hosts(32),guns(32),mags(32);Array<Weapon> weapons(32);Array<Mount> mounts(32);
|
||||
Array<Muzzle> muzzles(32);Array<Magazine> magazines(32);Array<Item> items(32);Array<Slot> bindings(32);
|
||||
root.child=&hosts[0];for(int i=0;i<32;i++){
|
||||
if(i+1<32)hosts[i].sibling=&hosts[i+1];
|
||||
hosts[i].child=&guns[i];guns[i].child=&mags[i];hosts[i].weapon=&mounts[i];guns[i].weapon=&weapons[i];
|
||||
mounts[i].owner=&hosts[i];mounts[i].weapon=&guns[i];mounts[i].slot=&bindings[i];bindings[i].mount=&mounts[i];
|
||||
items[i].slot=&bindings[i];guns[i].item=&items[i];weapons[i].owner=&guns[i];weapons[i].muzzles={&muzzles[i]};
|
||||
mounts[i].muzzles=weapons[i].muzzles;muzzles[i].owner=&guns[i];muzzles[i].mag=&magazines[i];magazines[i].owner=&mags[i];}
|
||||
auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(s->Complete&&s->Valid()&&s->Weapons==32&&s->Muzzles==32&&s->Magazines==32&&s->Rounds==800);}
|
||||
// Missing/projectile-only magazines and unsupported native values stay incomplete.
|
||||
for(int kind=0;kind<9;kind++){Rig r;
|
||||
if(kind==0)r.muzzle.mag=nullptr;
|
||||
if(kind==1)r.weapon.muzzles={nullptr};
|
||||
if(kind==2)r.weapon.muzzles={};
|
||||
if(kind==3)r.muzzle.barrels=0;
|
||||
if(kind==4)r.muzzle.barrels=33;
|
||||
if(kind==5)r.mag.rounds=-1;
|
||||
if(kind==6)r.mag.capacity=0;
|
||||
if(kind==7)r.mag.capacity=100001;
|
||||
if(kind==8)r.mag.rounds=101;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Valid());}
|
||||
{Rig r;r.weapon.muzzles=Array<Muzzle*>(9,&r.muzzle);auto s=r.Inspect();assert(!s->Complete&&s->Muzzles==0&&r.muzzle.reads==0);}
|
||||
{Rig r;r.manager.weapons=Array<Weapon*>(9,&r.weapon);auto s=r.Inspect();assert(!s->Complete&&s->Weapons==1&&s->Valid());}
|
||||
// Entity budget also bounds enqueue and cycles, rather than only processed work.
|
||||
for(int count:{128,129,300}){std::vector<Entity> nodes(count);for(int i=0;i<count-1;i++)nodes[i].child=&nodes[i+1];
|
||||
auto s=ERR_HQMountedAmmoSampler::Inspect(&nodes[0]);assert(s->Complete==(count==128));assert(s->Valid());
|
||||
int reads=0;for(auto& e:nodes)reads+=e.reads;assert(reads==128);}
|
||||
{Entity root,child;root.child=&child;child.sibling=&child;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&s->Valid());}
|
||||
{Entity root,child;root.child=&child;child.child=&root;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&s->Valid());}
|
||||
// Maximum weapon and muzzle bounds remain valid detached counts on overflow.
|
||||
for(int count:{32,33}){Entity root;std::vector<Entity> nodes(count);std::vector<Weapon> weapons(count);
|
||||
root.child=&nodes[0];for(int i=0;i<count;i++){nodes[i].weapon=&weapons[i];weapons[i].owner=&nodes[i];if(i+1<count)nodes[i].sibling=&nodes[i+1];}
|
||||
auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&s->Weapons==32&&s->Valid());}
|
||||
{Entity root;std::vector<Entity> nodes(18);std::vector<Weapon> weapons(17);std::vector<Muzzle> muzzles(136);Magazine mag;mag.owner=&nodes[17];
|
||||
root.child=&nodes[0];for(int i=0;i<18;i++){if(i+1<18)nodes[i].sibling=&nodes[i+1];if(i==17)continue;
|
||||
nodes[i].weapon=&weapons[i];weapons[i].owner=&nodes[i];for(int j=0;j<8;j++){auto& m=muzzles[i*8+j];m.owner=&nodes[i];m.mag=&mag;weapons[i].muzzles.Insert(&m);}}
|
||||
auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&s->Muzzles==128&&s->Magazines==1&&s->Chambers==128&&s->Valid());}
|
||||
{Rig r;Magazine alias;alias.owner=&r.magazine;Muzzle second;second.owner=&r.gun;second.mag=&alias;r.weapon.muzzles.Insert(&second);
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Magazines==1&&s->Rounds==25&&alias.reads==0&&s->Valid());}
|
||||
|
||||
// Realistic production load: 128 known deposited items plus mounted root,
|
||||
// gun, loaded magazine and a pruned original crew root exceed128 in total.
|
||||
// Cargo-first and structure-first traversal both retain the same mounted counts.
|
||||
for(bool cargoFirst:{false,true}){Rig r;std::vector<Weapon> cargoWeapons(128);std::vector<Entity> cargo(128);std::vector<Item> items(128);std::vector<Slot> slots(128);Storage deposit;
|
||||
for(int i=0;i<128;i++){cargo[i].weapon=&cargoWeapons[i];cargoWeapons[i].owner=&cargo[i];cargo[i].item=&items[i];items[i].slot=&slots[i];slots[i].storage=&deposit;if(i<127)cargo[i].sibling=&cargo[i+1];}
|
||||
if(cargoFirst){r.root.child=&cargo[0];cargo.back().sibling=&r.gun;}else{r.crew.sibling=&cargo[0];}
|
||||
auto snapshot=r.Inspect();assert(snapshot->Complete&&snapshot->Weapons==1&&snapshot->Magazines==1&&snapshot->Rounds==25&&snapshot->Valid());
|
||||
for(auto& item:cargo){assert(item.reads==0);}for(auto& gun:cargoWeapons){assert(gun.reads==0);}assert(r.weapon.reads==1&&r.carried.reads==0);}
|
||||
// Independent128 structural/128 cargo limits, in either order and interleaved.
|
||||
for(int order:{0,1,2})for(int scenario:{0,1,2,3}){
|
||||
int structures=scenario==1?129:128;int cargoCount=scenario==2||scenario==3?129:(scenario==1?100:128);
|
||||
if(scenario==2)structures=4;
|
||||
std::vector<Entity> nodes(structures),cargo(cargoCount);std::vector<Item> items(cargoCount);std::vector<Slot> slots(cargoCount);Storage deposit;std::vector<Entity*> sequence;
|
||||
for(int i=0;i<cargoCount;i++){cargo[i].item=&items[i];items[i].slot=&slots[i];slots[i].storage=&deposit;}
|
||||
if(order==0){for(auto& e:cargo)sequence.push_back(&e);for(int i=1;i<structures;i++)sequence.push_back(&nodes[i]);}
|
||||
if(order==1){for(int i=1;i<structures;i++)sequence.push_back(&nodes[i]);for(auto& e:cargo)sequence.push_back(&e);}
|
||||
if(order==2){for(int i=0;i<std::max(structures-1,cargoCount);i++){if(i<cargoCount)sequence.push_back(&cargo[i]);if(i+1<structures)sequence.push_back(&nodes[i+1]);}}
|
||||
nodes[0].child=sequence[0];for(int i=0;i<int(sequence.size())-1;i++)sequence[i]->sibling=sequence[i+1];
|
||||
auto snapshot=ERR_HQMountedAmmoSampler::Inspect(&nodes[0]);assert(snapshot->Complete==(scenario==0)&&snapshot->Weapons==0&&snapshot->Valid());
|
||||
int reads=0;for(auto& e:nodes)reads+=e.reads;assert(reads<=128);for(auto& e:cargo)assert(e.reads==0);
|
||||
}
|
||||
// Wire validation rejects impossible or malicious counts.
|
||||
{ERR_HQMountedAmmoSnapshot s;assert(s.Valid());s.Weapons=33;assert(!s.Valid());s.Weapons=1;s.Muzzles=9;assert(!s.Valid());
|
||||
s.Muzzles=1;s.Magazines=2;assert(!s.Valid());s.Magazines=1;s.Capacity=100001;assert(!s.Valid());s.Capacity=100;s.Rounds=101;assert(!s.Valid());
|
||||
s.Rounds=25;s.Chambers=33;assert(!s.Valid());s.Chambers=1;s.LowMagazines=2;assert(!s.Valid());s.LowMagazines=1;s.Complete=true;assert(s.Valid());}
|
||||
}
|
||||
''')
|
||||
run(program)
|
||||
print('PASS: actual mounted hierarchy sampling, ownership exclusion, deduplication, completeness/caps, quarter threshold and detached validation; no runtime policy or ammunition mutation')
|
||||
|
||||
# WORKBENCH records are opt-in. Re-run every original geometry/count assertion
|
||||
# in that build too, plus exact failure witnesses from the same traversal.
|
||||
cases=r'''
|
||||
// Exact reason64 distinguishes absent item, null slot and differing slot,
|
||||
// while actual Inspect remains incomplete and retains the same ammunition.
|
||||
for(int fault=0;fault<3;fault++){
|
||||
Rig r;Mount mount;Item item;Slot binding,other;Storage storage;storage.purpose=EStoragePurpose::PURPOSE_ATTACHMENT_PROXY;other.storage=&storage;
|
||||
mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&binding;binding.mount=&mount;binding.attached=&r.gun;
|
||||
r.manager.weapons={&mount};r.gun.item=fault==0?nullptr:&item;
|
||||
if(fault==2)item.slot=&other;
|
||||
other.mount=&mount;other.attached=&r.gun;
|
||||
ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();
|
||||
assert(!snap->Complete&&snap->Weapons==1&&snap->Rounds==25&&(d->Reasons&64));
|
||||
assert(d->Detail.find(fault==0?"item=absent;":"item=present;")!=string::npos);
|
||||
assert(d->Detail.find(fault==2?"parent=different-binding;":"parent=null;")!=string::npos);
|
||||
assert(d->Detail.find("binding-attached=exact;")!=string::npos&&d->Detail.find("binding-container=exact-mount;")!=string::npos);
|
||||
assert(d->Detail.find("mount-owner=allowed;")!=string::npos&&d->Detail.find("mount-prefab=fixture;")!=string::npos);
|
||||
if(fault==2)assert(d->Detail.find("parent-mount-slot=other-or-null;")!=string::npos);
|
||||
}
|
||||
{Rig r;r.weapon.muzzles={};ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();
|
||||
assert(!snap->Complete&&snap->Weapons==1&&snap->Rounds==0&&(d->Reasons&4096));
|
||||
assert(d->Detail.find("zero-muzzle-not-proven-placeholder;")!=string::npos&&d->Detail.find("zero-muzzle-slot-evidence:item=absent;parent=null;")!=string::npos);}
|
||||
''' + r'''
|
||||
{Rig r;ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(snap->Complete&&d->Reasons==0&&d->Nodes==4&&d->Allowed==3&&d->Managers==1);assert(!ERR_HQMountedAmmoSampler::EndDiagnostic());}
|
||||
{Entity root;root.child=&root;ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=ERR_HQMountedAmmoSampler::Inspect(&root);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&d->Reasons==4&&d->Nodes==1&&d->Allowed==1&&root.reads==1);}
|
||||
{Entity root;std::vector<Entity> children(128);root.child=&children[0];for(int i=0;i<127;i++)children[i].sibling=&children[i+1];ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=ERR_HQMountedAmmoSampler::Inspect(&root);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&d->Reasons==1024&&d->Nodes==129&&d->Allowed==128&&d->Structural==129&&d->Cargo==0&&children[127].reads==0);}
|
||||
for(int purpose:{-1,0,127}){Rig r;Item item;Slot slot;Storage storage;r.gun.item=&item;item.slot=&slot;r.gun.prefab.name="first-gun";if(purpose>=0){slot.storage=&storage;storage.purpose=purpose;}ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&(d->Reasons&2)&&(d->Reasons&16)&&d->FirstPrefab=="first-gun"&&d->FirstPurpose==purpose&&d->Detail.find("ambiguous-storage;")!=string::npos);}
|
||||
{Entity root;Manager manager;root.manager=&manager;manager.weapons.resize(9,nullptr);ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=ERR_HQMountedAmmoSampler::Inspect(&root);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&d->Reasons==512&&d->Managers==1&&manager.reads==1);}
|
||||
{Rig r;Mount mount;mount.owner=&r.root;r.manager.weapons={&mount};ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&(d->Reasons&32));}
|
||||
{ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=ERR_HQMountedAmmoSampler::Inspect(nullptr);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&d->Reasons==1&&d->Nodes==0&&d->Allowed==0);}
|
||||
'''
|
||||
|
||||
cases+=r'''
|
||||
for(int which:{0,1}){Entity root;int cargoCount=which==0?129:256;std::vector<Entity> cargo(cargoCount);std::vector<Item> items(cargoCount);std::vector<Slot> slots(cargoCount);Storage storage;root.child=&cargo[0];
|
||||
for(int i=0;i<cargoCount;i++){cargo[i].item=&items[i];items[i].slot=&slots[i];slots[i].storage=&storage;if(i+1<cargoCount)cargo[i].sibling=&cargo[i+1];}
|
||||
ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snapshot=ERR_HQMountedAmmoSampler::Inspect(&root);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snapshot->Complete&&d->Cargo==129&&d->Structural==1&&(d->Reasons&2048));
|
||||
if(which==0){assert(d->Nodes==130&&!(d->Reasons&8));}else{assert(d->Nodes==256&&(d->Reasons&8));}}
|
||||
'''
|
||||
run('#define WORKBENCH\n'+program.replace('int main(){','int main(){'+cases))
|
||||
print('PASS: WORKBENCH opt-in diagnostics distinguish cycles/cap/storage/manager/mount failures without repeated native traversal')
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
"""Actual probe state machine/counters; menu, actor and invoker boundaries doubled."""
|
||||
import ast,os,re
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from policy_harness import ROOT,body,run
|
||||
BASE=Path(os.environ.get('ERR_INVENTORY_PROBE_ROOT',ROOT))
|
||||
p=Path(os.environ.get('ERR_NATIVE_INVENTORY_SOURCE',BASE/'addons/core/Scripts/Game/ERR/00_Core/ERR_NativeInventoryHitZoneProbe.c'))
|
||||
s=p.read_text()
|
||||
assert s.startswith('#ifdef WORKBENCH') and s.rstrip().endswith('#endif')
|
||||
for forbidden in ['SetHealth(', 'FullHeal(', 'ForceUnconsciousness(', 'SetOrigin(', 'CreateWidgets(', 'InputManager', 'ERR_ClearHitZoneCallbacks(']: assert forbidden not in s
|
||||
assert 'super.UpdateHitZoneState(hz, updateAttachmentStorage);' in s
|
||||
assert 'm_Menu.InspectItem(null);' in s and 'm_Deadline = System.GetTickCount() + 30000' in s
|
||||
assert 'out array<ref SCR_InventoryHitZonePointContainerUI> points' in s
|
||||
|
||||
def cpp(s):
|
||||
for a,b in [('protected override ',''),('protected ',''),('override ',''),('super.UpdateHitZoneState','Native::UpdateHitZoneState'),('ERR_NativeInventoryHitZoneProbe','Probe'),('SCR_InventoryHitZonePointContainerUI','Point'),('SCR_CharacterDamageManagerComponent','Damage'),('SCR_InventoryStorageManagerComponent','Inventory'),('SCR_CharacterBloodHitZone','Zone'),('SCR_HitZone','Zone'),('ERR_GameMode','Mode'),('SCR_PlayerController','Controller'),('SCR_InventoryMenuUI','Menu'),('MenuBase','Menu'),('IEntity','Actor*'),('string ','string '),('null','nullptr'),('foreach (','for ('),('ref ',''),('array<Point> seen = {};','Array<Point*> seen;')]:s=s.replace(a,b)
|
||||
for name in ['mode','damage','point','updated','detached','oldPoint']:
|
||||
s=re.sub(r'\b'+name+r'\.',name+'->',s)
|
||||
for name in ['m_Mode','m_Controller','m_Actor','m_Inventory','m_Blood','m_Menu']:s=s.replace(name+'.',name+'->')
|
||||
for cls in ['Probe','Mode','Controller','Damage','Inventory','Menu','System','Replication','ERR_Actors','ChimeraMenuPreset']:s=s.replace(cls+'.',cls+'::')
|
||||
for cls,var in [('Mode','mode'),('Zone','hz'),('Zone','blood'),('Point','point'),('Point','updated'),('Point','detached'),('Point','oldPoint'),('Menu','current'),('Damage','damage')]:s=s.replace(cls+' '+var,cls+'* '+var)
|
||||
s=s.replace('GetGame().','GetGame()->').replace('GetMenuManager().','GetMenuManager()->').replace('Players().','Players()->').replace('GetOnDamageStateChanged(false).','GetOnDamageStateChanged(false)->')
|
||||
s=s.replace('FindComponent(Inventory)','FindComponent(1)').replace('FindComponent(Damage)','FindComponent(2)')
|
||||
s=s.replace('Print("ERR SMOKE FAIL: inventory hitzone " + detail, LogLevel.ERROR);','Print("ERR SMOKE FAIL: inventory hitzone " + detail);')
|
||||
return s
|
||||
point='\n'.join(cpp(body(p,sig)) for sig in ['protected override void UpdateHitZoneState(','bool ERR_HitZoneProbeWatch(','bool ERR_HitZoneProbeOnce(','bool ERR_HitZoneProbeDetached(','void ERR_HitZoneProbeStop('])
|
||||
probe='\n'.join(cpp(body(p,sig)) for sig in ['void ERR_NativeInventoryHitZoneProbe(','protected bool Check(','protected bool Context(','protected bool OwnsMenu(','protected bool UnchangedHealth(','int Tick(','void Stop('])
|
||||
probe=probe.replace('void Probe(', 'Probe(')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
using string=std::string;template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;void Clear(){this->clear();}void Insert(T x){this->push_back(x);}bool Contains(T x){return std::find(this->begin(),this->end(),x)!=this->end();}bool IsEmpty(){return this->empty();}};
|
||||
struct Actor;struct Mode;struct Point;struct Menu;struct Probe{static bool Enabled(){return enabled;}static inline bool enabled=true;};
|
||||
namespace System{int now=0;int GetTickCount(){return now;}}
|
||||
namespace ChimeraMenuPreset{int Inventory20Menu=1;}
|
||||
void Print(string){}
|
||||
struct Event{std::function<void()>fire;int calls=0;void Invoke(void*){calls++;if(fire)fire();}};
|
||||
struct Zone{float health=100;Event event;float GetHealth(){return health;}Event*GetOnDamageStateChanged(bool create){assert(!create);return &event;}};
|
||||
struct Damage{Zone*blood;Zone*GetBloodHitZone(){return blood;}static Damage*Cast(void*p){return static_cast<Damage*>(p);}};
|
||||
struct Inventory{int opens=0;std::function<void()>opening;void OpenInventory(){opens++;if(opening)opening();}static Inventory*Cast(void*p){return static_cast<Inventory*>(p);}};
|
||||
struct Actor{Inventory*inventory;Damage*damage;bool fit=true;void*FindComponent(int n){return n==1?static_cast<void*>(inventory):static_cast<void*>(damage);}};
|
||||
namespace ERR_Actors{bool CanFight(Actor*a){return a&&a->fit;}}
|
||||
struct Controller{Actor*actor;Actor*GetControlledEntity(){return actor;}static Controller*Cast(Controller*p){return p;}};
|
||||
struct Roster{Actor*actor;bool CurrentActor(Actor*a){return a==actor;}};
|
||||
struct Mode{static inline Mode*current=nullptr;Roster roster;bool active=true;static Mode*Get(){return current;}bool Active(){return active;}Roster*Players(){return &roster;}};
|
||||
struct Native{int updates=0;void UpdateHitZoneState(Zone*,bool){updates++;}};
|
||||
struct Point:Native{Zone*m_ERRProbeBlood=nullptr;int m_ERRProbeUpdates=0;bool m_ERRProbeFalseRefresh=false;Mode*m_ERRHitZoneMode=nullptr;Array<Zone*>m_ERRHitZoneSources;
|
||||
POINT
|
||||
};
|
||||
struct Menu{Array<Point*>points;Actor*actor=nullptr;bool ready=true;int inspect=0;bool ERR_HitZoneProbeReady(Actor*a){return ready&&actor==a;}void InspectItem(void*){inspect++;}bool ERR_HitZoneProbePoints(Array<Point*>&out){out=points;return points.size()==7;}static Menu*Cast(Menu*m){return m;}};
|
||||
struct Manager{Menu*current=nullptr;bool foreignTop=false,accept=true;int closes=0;void*GetTopMenu(){return foreignTop||current?this:nullptr;}Menu*FindMenuByPreset(int){return current;}bool CloseMenu(Menu*m){assert(m==current);closes++;return accept;}};
|
||||
struct Game{Controller*controller;Manager manager;Controller*GetPlayerController(){return controller;}Manager*GetMenuManager(){return &manager;}};Game*game=nullptr;Game*GetGame(){return game;}
|
||||
struct Actual:Probe{Mode*m_Mode;Controller*m_Controller;Actor*m_Actor;Inventory*m_Inventory=nullptr;Zone*m_Blood=nullptr;Menu*m_Menu=nullptr;Array<Point*>m_Points;int m_Deadline=0,m_Stage=0,m_Result=0;bool m_Stopped=false,m_CloseRequested=false;float m_Health=0;
|
||||
PROBE
|
||||
};
|
||||
struct Fixture{Zone blood;Damage damage{&blood};Inventory inventory;Actor actor{&inventory,&damage};Controller controller{&actor};Mode mode;Game world{&controller,{}};Menu menu;Point points[7];Fixture(){System::now=0;Probe::enabled=true;game=&world;Mode::current=&mode;mode.roster.actor=&actor;menu.actor=&actor;inventory.opening=[&](){world.manager.current=&menu;};for(auto&p:points){p.m_ERRHitZoneMode=&mode;p.m_ERRHitZoneSources.Insert(&blood);menu.points.Insert(&p);}blood.event.fire=[&](){for(auto&p:points)if(!p.m_ERRHitZoneSources.IsEmpty())p.UpdateHitZoneState(&blood,true);};}
|
||||
void Close(){world.manager.current=nullptr;for(auto&p:points){p.m_ERRHitZoneMode=nullptr;p.m_ERRHitZoneSources.Clear();}}
|
||||
};
|
||||
int main(){
|
||||
{Fixture f;Actual p(&f.mode,&f.actor);assert(p.Tick(0)==0&&f.inventory.opens==1);assert(p.Tick(0)==0&&f.menu.inspect==1);assert(p.Tick(0)==0&&f.blood.event.calls==1&&f.world.manager.closes==1);assert(p.Tick(0)==0&&f.world.manager.closes==1);f.Close();assert(p.Tick(0)==1&&f.blood.event.calls==2&&f.blood.health==100);assert(p.Tick(0)==1);for(auto&x:f.points)assert(x.updates==1);}
|
||||
{Fixture f;Actual p(&f.mode,&f.actor);f.world.manager.foreignTop=true;assert(p.Tick(0)==-1&&f.inventory.opens==0&&f.world.manager.closes==0);}
|
||||
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);f.menu.ready=false;assert(p.Tick(0)==0&&f.menu.inspect==0);System::now=30000;assert(p.Tick(0)==-1&&f.world.manager.closes==1);}
|
||||
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);f.menu.points.pop_back();assert(p.Tick(0)==0&&f.blood.event.calls==0);p.Stop();assert(f.world.manager.closes==1);}
|
||||
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);f.menu.points[6]=f.menu.points[0];assert(p.Tick(0)==-1&&f.blood.event.calls==0);}
|
||||
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);f.blood.event.fire=[&](){for(auto&x:f.points)x.UpdateHitZoneState(&f.blood,false);};assert(p.Tick(0)==-1);}
|
||||
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);p.Tick(0);f.world.manager.current=nullptr;assert(p.Tick(0)==-1);}// missing real detach
|
||||
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);p.Tick(0);f.Close();f.blood.event.fire=[&](){f.points[0].UpdateHitZoneState(&f.blood,true);};assert(p.Tick(0)==-1);}// late retained callback
|
||||
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);p.Tick(0);f.world.manager.accept=false;assert(p.Tick(0)==-1&&f.world.manager.closes==2);}
|
||||
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);f.blood.health=99;assert(p.Tick(0)==-1);}
|
||||
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);Menu foreign;f.world.manager.current=&foreign;assert(p.Tick(0)==-1&&f.world.manager.closes==0);}
|
||||
{Fixture f;Actual p(&f.mode,&f.actor);p.Tick(0);Controller replacement{&f.actor};f.world.controller=&replacement;assert(p.Tick(0)==-1);}
|
||||
{Fixture f;Actual p(&f.mode,&f.actor);Probe::enabled=false;assert(p.Tick(0)==-1&&f.inventory.opens==0);}
|
||||
}
|
||||
'''.replace('POINT',point).replace('PROBE',probe.replace('Probe(', 'Actual(')))
|
||||
base=(BASE/'addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c').read_text()
|
||||
assert 'm_ERRInventoryHitZoneProbe.Tick(now)' in base and 'm_ERRInventoryHitZoneProbe.Stop()' in base
|
||||
assert 'current == ERR_Phase.DEFEAT && m_ERRBaseStep == 2102' in base
|
||||
assert 'if (result == 1) { m_ERRBaseStep = 2102; Transition(ERR_Phase.DEFEAT); }' in base
|
||||
runner=(BASE/'tools/headless_workbench.py').read_text();tree=ast.parse(runner)
|
||||
node=next(n for n in tree.body if isinstance(n,ast.If) and "a.smoke == 'inventory-hitzone'" in ast.unparse(n.test))
|
||||
for project,rounds,timeout,bad in [('core',1,240,False),('chat',1,240,True),('core',2,240,True),('core',1,239,True)]:
|
||||
assert eval(compile(ast.Expression(node.test),'<selector>','eval'),{'a':SimpleNamespace(smoke='inventory-hitzone',project=project,rounds=rounds,timeout=timeout)})==bad
|
||||
assert runner.count("'inventory-hitzone'")>=3
|
||||
print('PASS: actual native-probe lifecycle/counters with doubled boundaries; fixed deadline, exact seven sources, true forwarding, async close, no late updates and selector wiring')
|
||||
@@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run bounded native Workbench smoke tests inside a windowless Gamescope display.
|
||||
Requires Linux, gamescope, Proton, and a disposable COPY of a working Tools compat prefix.
|
||||
No desktop rules or existing Wine processes are changed. This is not a dedicated server.
|
||||
"""
|
||||
import argparse, fcntl, json, os, shutil, signal, subprocess, time
|
||||
from pathlib import Path
|
||||
from native_provenance import snapshot, persist, bindings, seal_run
|
||||
from headless_game import assert_prefix_idle, cleanup_owned_run
|
||||
from external_addons import policy as external_policy, prepare_profile, verify_profile
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--steam', type=Path, default=Path.home()/'.local/share/Steam')
|
||||
p.add_argument('--external-addons', type=Path, help='Installed RHS/WCS/BMP source root; expose only audited closure in a separate Chat profile')
|
||||
p.add_argument('--compat', type=Path, required=True, help='Disposable cloned Proton compatdata directory outside Steam')
|
||||
p.add_argument('--profile', type=Path, required=True, help='Separate disposable successor Workbench profile')
|
||||
p.add_argument('--smoke', choices=['crash-start', 'crash-geometry', 'normal-escape-route', 'escape-route', 'optional-site-skip', 'infantry-combat', 'medical-combat', 'hunter-faction', 'unconscious-camera', 'objective-map', 'town-garrisons', 'site-garrisons', 'radio-interaction', 'enemy-loadout', 'aggression', 'air-logistics', 'air-assault', 'air-recon', 'airfield-markers', 'opening-tools', 'mortar-station', 'generated-sites', 'hq-local-handoff', 'infantry-transfer', 'hq-body', 'convoy', 'supply-convoys', 'supply-travel', 'mounted-ammo', 'alarm-hearing', 'hq-memory', 'hq-awareness', 'aircraft-field', 'aircraft-route', 'native-end-active', 'native-end-preparing', 'aircraft-control', 'aircraft-cancel', 'aircraft-translate', 'map-ownership', 'mortar-refresh', 'mortar-salvo', 'mortar-courier', 'station-teardown', 'inventory-hitzone', 'prison-breach', 'powered-flight', 'powered-translate', 'local-admission', 'mortar-expiry', 'mortar-transfer', 'mortar-fire', 'patrol-hq', 'victory', 'lost-helicopter', 'player-death', 'radio-network', 'hq-response', 'mortar-inventory', 'startup-failure', 'site-survey', 'cache-loss', 'extraction-capacity', 'early-helicopter-loss', 'extraction-startup-failure', 'extraction-boundary-failure', 'transport-loss', 'transport-survey', 'lifecycle', 'objective-ui', 'recovery', 'prisoner-kit', 'medical', 'casualty-load', 'casualty-direct', 'casualty-bench', 'casualty-remove', 'environment', 'environment-restart', 'patrols', 'radio-document-loss', 'radio-document-loss-partial', 'guard-cancel', 'casualty-service'], default='victory')
|
||||
p.add_argument('--trace-random-radius', action='store_true', help='Workbench-only read-only zero-radius caller traces; one normal Chat round, capped at sixteen records')
|
||||
p.add_argument('--maintenance-refuel', action='store_true', help='Opt-in normal-route check: original held Southern fuel can transfers finite fuel through its native UAZ action; one arranged tank depletion, no stock creation')
|
||||
p.add_argument('--mounted-swap-eligibility', action='store_true', help='Read-only mounted-ammo variant: one exact finite cargo/mounted magazine CanSwap call; no transfer or radio proof')
|
||||
p.add_argument('--mounted-bmp', action='store_true', help='Read-only original BMP slot4 complete mounted counts and its natural HQ radio delivery; isolates unrelated opening hunters before unlock')
|
||||
p.add_argument('--mine-stock', action='store_true', help='Read-only mounted-ammo check of exact callback-proved finite PMN4 cargo before and after natural radio delivery')
|
||||
p.add_argument('--mounted-courier', action='store_true', help='Mounted-ammo variant: intentionally deplete one original installed magazine, then require natural HQ radio, finite pickup, native exchange and original-seat return')
|
||||
p.add_argument('--mounted-intercept', action='store_true', help='After convoy driving, two synthetic witness samples exercise real HQ radio/planning and original escort detour/hold/expiry/resume')
|
||||
p.add_argument('--convoy-cover', action='store_true', help='After convoy driving, arrange the existing prisoner to verify native sighting/radio HOLD and RESUME')
|
||||
p.add_argument('--field-terrain-audit', action='store_true', help='After a fixed aircraft-field survey refusal, log one read-only 1681-node coarse terrain grid; never selects or qualifies a corridor')
|
||||
p.add_argument('--field-service-cancel', action='store_true', help='Explicit aircraft-field variant: Stop the original retained service during measured cruise; requires physical cancellation landing')
|
||||
p.add_argument('--route-service-cancel', action='store_true', help='Explicit aircraft-route variant: stop the retained service after actual airborne translation and require controlled cancellation landing')
|
||||
p.add_argument('--station-teardown-diagnostics', action='store_true', help='Opt-in bounded native deletion diagnostics; preserves all validation errors')
|
||||
p.add_argument('--flight-autohover', action='store_true', help='Opt-in owned native helicopter autohover experiment; requires Core powered-flight and actor-local action inputs')
|
||||
p.add_argument('--flight-action-inputs', action='store_true', help='Experimental exact-AI ActionManager inputs; Core powered-flight fixture only')
|
||||
p.add_argument('--flight-input-diagnostics', action='store_true', help='Opt-in read-only powered-flight input/application and native rotor signal observations')
|
||||
p.add_argument('--recon-takeover-only', action='store_true', help='Air-recon fixture: only native faction handover/release and explicit aircraft deletion')
|
||||
p.add_argument('--body-recovery-only', action='store_true', help='Air-logistics fixture: only physical corpse inspection and returned document intelligence')
|
||||
p.add_argument('--rounds', type=int, default=2)
|
||||
p.add_argument('--timeout', type=int, default=360)
|
||||
p.add_argument('--project', choices=['core', 'chat'], default='chat', help='Diagnostic fixture family (both load the combined mod)')
|
||||
a = p.parse_args()
|
||||
if a.smoke == 'inventory-hitzone' and (a.project != 'core' or a.rounds != 1 or a.timeout < 240): p.error('inventory-hitzone requires --project core --rounds 1 and timeout at least 240 seconds')
|
||||
if a.smoke == 'crash-start' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 240): p.error('crash-start requires Chat, one round and timeout at least 240 seconds')
|
||||
if a.smoke == 'crash-geometry' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 180): p.error('crash-geometry requires Chat, one round and timeout at least180 seconds')
|
||||
if a.trace_random_radius and (a.smoke != 'normal-escape-route' or a.project != 'chat' or a.rounds != 1 or a.timeout < 420): p.error('--trace-random-radius requires Chat normal-escape-route, one round and timeout at least 420 seconds')
|
||||
if a.smoke == 'objective-map' and (a.project != 'core' or a.rounds != 1): p.error('objective-map requires --project core --rounds 1')
|
||||
if a.smoke == 'unconscious-camera' and a.project != 'core': p.error('unconscious-camera requires --project core to keep the general smoke fixture inactive')
|
||||
if a.mine_stock:
|
||||
if a.smoke != 'mounted-ammo' or a.project != 'chat' or a.rounds != 1 or a.timeout < 360:
|
||||
p.error('--mine-stock requires --smoke mounted-ammo --project chat --rounds 1 and timeout at least 360 seconds')
|
||||
if a.mounted_courier or a.mounted_swap_eligibility:
|
||||
p.error('--mine-stock cannot combine with --mounted-courier or --mounted-swap-eligibility')
|
||||
if a.maintenance_refuel and (a.smoke != 'normal-escape-route' or a.project != 'chat' or a.rounds != 1 or a.timeout < 420): p.error('--maintenance-refuel requires Chat normal-escape-route, one round and timeout at least 420 seconds')
|
||||
if a.body_recovery_only and (a.smoke != 'air-logistics' or a.project != 'core' or a.rounds != 1): p.error('--body-recovery-only requires Core air-logistics and one round')
|
||||
if a.recon_takeover_only and (a.smoke != 'air-recon' or a.project != 'core' or a.rounds != 1): p.error('--recon-takeover-only requires Core air-recon and one round')
|
||||
steam, compat, profile = a.steam.resolve(), a.compat.resolve(), a.profile.resolve()
|
||||
if not 1 <= a.rounds <= 10 or not 60 <= a.timeout <= 1800:
|
||||
p.error('Use 1..10 rounds and a 60..1800 second timeout')
|
||||
if a.field_terrain_audit and a.smoke != 'aircraft-field': p.error('--field-terrain-audit requires --smoke aircraft-field')
|
||||
if a.field_terrain_audit and a.field_service_cancel: p.error('--field-terrain-audit and --field-service-cancel are mutually exclusive')
|
||||
if a.field_service_cancel and a.smoke != 'aircraft-field': p.error('--field-service-cancel requires --smoke aircraft-field')
|
||||
if a.route_service_cancel and a.smoke != 'aircraft-route': p.error('--route-service-cancel requires --smoke aircraft-route')
|
||||
if a.smoke == 'mortar-courier' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 500):
|
||||
p.error('mortar-courier requires --project chat --rounds 1 --timeout at least 500')
|
||||
if a.smoke in {'mortar-refresh', 'mortar-salvo', 'mortar-expiry', 'mortar-transfer', 'mortar-fire'} and (a.project != 'chat' or a.rounds != 1 or a.timeout < 400):
|
||||
p.error('mortar-refresh/mortar-salvo/mortar-expiry/mortar-transfer/mortar-fire requires --project chat --rounds 1 --timeout at least 400')
|
||||
if a.smoke in {'aircraft-field', 'aircraft-route', 'aircraft-control', 'aircraft-cancel', 'aircraft-translate'} and (a.project != 'chat' or a.rounds != 1 or a.timeout < 420): p.error('aircraft-field/aircraft-route/aircraft-control/aircraft-cancel/aircraft-translate requires Chat, one round, and at least 420 seconds')
|
||||
if a.mounted_swap_eligibility and a.smoke != 'mounted-ammo': p.error('--mounted-swap-eligibility requires --smoke mounted-ammo')
|
||||
if a.mounted_courier and (a.smoke != 'mounted-ammo' or a.project != 'chat' or a.rounds != 1 or a.timeout < 780): p.error('--mounted-courier requires --smoke mounted-ammo --project chat --rounds 1 and timeout at least780 seconds')
|
||||
if a.mounted_courier and a.mounted_swap_eligibility: p.error('--mounted-courier and --mounted-swap-eligibility are mutually exclusive')
|
||||
if a.mounted_intercept and (a.smoke != 'convoy' or a.project != 'chat' or a.rounds != 1 or a.timeout < 900): p.error('--mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds')
|
||||
if a.mounted_intercept and a.convoy_cover: p.error('--mounted-intercept and --convoy-cover are mutually exclusive')
|
||||
if a.convoy_cover and a.smoke != 'convoy': p.error('--convoy-cover requires --smoke convoy')
|
||||
if a.convoy_cover and a.timeout < 900: p.error('--convoy-cover requires timeout at least900 seconds')
|
||||
if a.smoke == 'supply-travel' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 480): p.error('supply-travel requires Chat, one round, and at least480 seconds')
|
||||
if a.smoke == 'supply-convoys' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 900): p.error('supply-convoys requires Chat, one round, and at least900 seconds')
|
||||
if a.smoke == 'alarm-hearing' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 360): p.error('alarm-hearing requires Chat, exactly one round, and timeout at least360 seconds')
|
||||
if a.smoke == 'mounted-ammo' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 360): p.error('mounted-ammo requires Chat, exactly one round, and timeout at least360 seconds')
|
||||
if a.mounted_bmp and (a.smoke != 'mounted-ammo' or a.project != 'chat' or a.rounds != 1 or a.timeout < 360 or a.mine_stock or a.mounted_courier or a.mounted_swap_eligibility): p.error('--mounted-bmp requires Chat mounted-ammo, one round, timeout at least360 seconds, without mine/courier/swap flags')
|
||||
if a.smoke == 'convoy' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 600): p.error('convoy requires Chat, exactly one round, and timeout at least600 seconds')
|
||||
if a.smoke == 'infantry-transfer' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 360): p.error('infantry-transfer requires Chat, one round and timeout at least360 seconds')
|
||||
if a.smoke == 'hq-local-handoff' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 360): p.error('hq-local-handoff requires Chat, one round and timeout at least360 seconds')
|
||||
if a.smoke in {'hq-memory', 'hq-awareness', 'hq-body'} and (a.project != 'chat' or a.rounds != 1): p.error('hq-memory/hq-awareness/hq-body requires Chat and exactly one round')
|
||||
if a.smoke == 'map-ownership' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 180): p.error('map-ownership requires Chat, one round, and at least 180 seconds')
|
||||
if a.smoke == 'powered-translate' and (a.project != 'core' or not a.flight_action_inputs or not a.flight_autohover): p.error('powered-translate requires Core with flight-action-inputs and flight-autohover')
|
||||
if a.flight_autohover and (a.project != 'core' or a.smoke not in {'powered-flight', 'powered-translate'} or not a.flight_action_inputs): p.error('flight-autohover requires Core powered-flight with flight-action-inputs')
|
||||
if a.flight_action_inputs and (a.project != 'core' or a.smoke not in {'powered-flight', 'powered-translate'}): p.error('flight-action-inputs requires the Core powered-flight fixture')
|
||||
if a.smoke in {'powered-flight', 'powered-translate'} and (a.project != 'core' or a.rounds != 1 or a.timeout < 420): p.error('powered-flight requires Core, one round, and at least 420 seconds')
|
||||
if a.smoke == 'station-teardown' and (a.project != 'core' or a.rounds != 1): p.error('station-teardown requires --project core --rounds 1')
|
||||
if a.smoke in {'native-end-active', 'native-end-preparing'}:
|
||||
if a.project != 'core' or a.rounds != 2 or a.timeout < 120: p.error('native-end fixtures require Core, exactly two rounds and at least 120 seconds')
|
||||
fixture_names = ['erri-native-end-round.json', 'erri-native-end-round.json.tmp', 'erri-native-end-round.json.bak']
|
||||
if any((profile/name).exists() or (profile/name).is_symlink() for name in fixture_names): p.error('native-end fixture paths must not preexist; preserve prior evidence before retrying')
|
||||
if a.smoke == 'environment-restart':
|
||||
if a.project != 'core' or a.rounds != 2: p.error('environment-restart requires --project core --rounds 2')
|
||||
fixture_names = ['erri-environment-restart.json', 'erri-environment-restart.json.bak', 'erri-environment-restart.json.tmp', 'erri-environment-restart-round.json']
|
||||
if any((profile/name).exists() or (profile/name).is_symlink() for name in fixture_names): p.error('environment-restart fixture paths must not preexist; preserve or move prior evidence before retrying')
|
||||
if compat.is_relative_to(steam) or not (compat/'pfx/system.reg').is_file():
|
||||
p.error('--compat must be a disposable cloned prefix outside the Steam installation')
|
||||
if a.external_addons and profile == Path('/tmp/erri-workbench-profile'): p.error('Use a separate external-dependency profile; preserve the vanilla test profile')
|
||||
if not a.external_addons: p.error('The combined mod requires --external-addons pointing to installed authorized RHS/WCS/BMP dependencies')
|
||||
prefix_lock = (compat/'.erri-workbench.lock').open('a')
|
||||
try: fcntl.flock(prefix_lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except BlockingIOError: p.error('Another test runner owns this disposable Wine prefix')
|
||||
# The lock must be held before the preflight; never repair a busy prefix here.
|
||||
try: assert_prefix_idle(compat)
|
||||
except (OSError,ValueError) as error: p.error(str(error))
|
||||
if profile.is_relative_to(ROOT) or profile.is_relative_to(steam):
|
||||
p.error('--profile must be outside source and Steam directories')
|
||||
if not shutil.which('gamescope') or not shutil.which('pactl'): p.error('gamescope and pactl are required')
|
||||
wb = steam/'steamapps/common/Arma Reforger Tools/Workbench'
|
||||
proton = steam/'steamapps/common/Proton - Experimental/proton'
|
||||
vanilla = steam/'steamapps/common/Arma Reforger/addons/data'
|
||||
if not proton.is_file() or not (wb/'ArmaReforgerWorkbenchSteamDiag.exe').is_file():
|
||||
p.error('Stable Reforger Tools and Proton Experimental must already be installed')
|
||||
profile.mkdir(parents=True, exist_ok=True)
|
||||
addons = profile/'addons'
|
||||
addons.mkdir(exist_ok=True)
|
||||
link = addons/'ArmaReforger'
|
||||
if not link.exists(): link.symlink_to(vanilla, target_is_directory=True)
|
||||
external_inputs = None
|
||||
external_cache = profile/'external-source-cache.json'
|
||||
allowed_profile_addons = {'ArmaReforger', 'saves'}
|
||||
if a.external_addons:
|
||||
try:
|
||||
external_rules = external_policy(ROOT)
|
||||
external_inputs = prepare_profile(addons, a.external_addons, external_rules, external_cache)
|
||||
allowed_profile_addons.update('ERRI_'+guid for guid in external_inputs)
|
||||
except (OSError, ValueError) as error: p.error(str(error))
|
||||
if link.resolve() != vanilla.resolve() or any(x.name not in allowed_profile_addons for x in addons.iterdir()) or any((addons/'saves').rglob('*.gproj')):
|
||||
p.error('Test profile addons must contain only vanilla and the explicit audited external closure')
|
||||
profile_arg = 'Z:'+str(profile)
|
||||
# Refuse concurrency instead of terminating anybody else's editor.
|
||||
for proc in Path('/proc').iterdir():
|
||||
if not proc.name.isdigit(): continue
|
||||
try:
|
||||
args = (proc/'cmdline').read_bytes().split(b'\0')
|
||||
if profile_arg.encode() in args and any(x.endswith(b'ArmaReforgerWorkbenchSteamDiag.exe') for x in args):
|
||||
p.error('Another Workbench already uses this test profile')
|
||||
except (FileNotFoundError, PermissionError, ProcessLookupError): pass
|
||||
command = ['gamescope', '--backend', 'headless', '-W', '1280', '-H', '720', '-w', '1280', '-h', '720', '-r', '30', '--', str(proton), 'run', str(wb/'ArmaReforgerWorkbenchSteamDiag.exe'), '-gproj', 'Z:'+str(ROOT/'addons/core/addon.gproj'), '-addonsDir', 'Z:'+str(ROOT/'addons'), '-profile', profile_arg, '-logconsole', '-wbModule=WorldEditor', '-load', 'Worlds/ERR_Everon.ent', '-plugin=ERR_ValidateWorldPlugin', '-errPlay', '1', '-errSmoke', '1', '-errSmokeOutcome', a.smoke, '-noThrow', '-disableCrashReporter']
|
||||
if a.convoy_cover: command += ['-errConvoyCover', '1']
|
||||
if a.mounted_intercept: command += ['-errMountedIntercept', '1']
|
||||
if a.mounted_swap_eligibility: command += ['-errMountedSwapEligibility', '1']
|
||||
if a.mounted_courier: command += ['-errMountedCourier', '1']
|
||||
if a.mounted_bmp: command += ['-errMountedBMP', '1']
|
||||
if a.mine_stock: command += ['-errMineStock', '1']
|
||||
if a.maintenance_refuel: command += ['-errMaintenanceRefuel', '1']
|
||||
if a.trace_random_radius: command += ['-errTraceRandomRadius', '1']
|
||||
if a.field_service_cancel: command += ['-errSmokeFieldServiceCancel', '1']
|
||||
if a.field_terrain_audit: command += ['-errSmokeFieldTerrainAudit', '1']
|
||||
if a.route_service_cancel: command += ['-errSmokeRouteServiceCancel', '1']
|
||||
if a.station_teardown_diagnostics: command += ['-errStationTeardownDiag', '1']
|
||||
if a.flight_input_diagnostics: command += ['-errFlightInputDiag', '1']
|
||||
if a.flight_action_inputs: command += ['-errFlightActionInputs', '1']
|
||||
if a.flight_autohover: command += ['-errFlightAutohover', '1']
|
||||
if a.recon_takeover_only: command += ['-errReconTakeoverOnly', '1']
|
||||
if a.body_recovery_only: command += ['-errBodyRecoveryOnly', '1']
|
||||
if a.project == 'core':
|
||||
if a.smoke not in {'optional-site-skip', 'infantry-combat', 'medical-combat', 'hunter-faction', 'unconscious-camera', 'objective-map', 'town-garrisons', 'site-garrisons', 'radio-interaction', 'enemy-loadout', 'aggression', 'air-logistics', 'air-assault', 'air-recon', 'airfield-markers', 'opening-tools', 'mortar-station', 'generated-sites', 'native-end-active', 'native-end-preparing', 'station-teardown', 'inventory-hitzone', 'prison-breach', 'powered-flight', 'powered-translate', 'local-admission', 'victory', 'startup-failure', 'site-survey', 'cache-loss', 'extraction-capacity', 'early-helicopter-loss', 'extraction-startup-failure', 'extraction-boundary-failure', 'transport-loss', 'transport-survey', 'lifecycle', 'objective-ui', 'recovery', 'prisoner-kit', 'medical', 'casualty-load', 'casualty-direct', 'casualty-bench', 'casualty-remove', 'environment', 'environment-restart', 'patrols', 'radio-document-loss', 'radio-document-loss-partial', 'guard-cancel', 'casualty-service'}: p.error('Unsupported Core-only fixture')
|
||||
command[command.index('-plugin=ERR_ValidateWorldPlugin')] = '-plugin=ERR_ValidateBaseWorldPlugin'
|
||||
command[command.index('-errSmoke')] = '-errBaseSmoke'
|
||||
if a.smoke in {'station-teardown', 'inventory-hitzone', 'prison-breach', 'powered-flight', 'powered-translate', 'local-admission', 'cache-loss', 'extraction-capacity', 'early-helicopter-loss', 'extraction-startup-failure', 'extraction-boundary-failure', 'transport-loss', 'transport-survey', 'lifecycle', 'objective-ui', 'recovery', 'prisoner-kit', 'medical', 'casualty-load', 'casualty-direct', 'casualty-bench', 'casualty-remove', 'environment', 'environment-restart', 'patrols', 'radio-document-loss', 'radio-document-loss-partial', 'guard-cancel', 'casualty-service'} and a.project != 'core': p.error('This fixture requires --project core')
|
||||
if a.smoke in {'startup-failure', 'site-survey', 'transport-survey', 'extraction-startup-failure', 'extraction-boundary-failure'} and (a.project != 'core' or a.rounds != 1): p.error('This diagnostic requires --project core --rounds 1')
|
||||
env = os.environ.copy()
|
||||
env.update(STEAM_COMPAT_DATA_PATH=str(compat), STEAM_COMPAT_CLIENT_INSTALL_PATH=str(steam), WINEPREFIX=str(compat/'pfx'))
|
||||
# A test-only sink prevents startup sounds; a stream check also catches a saved
|
||||
# explicit Wine device selection. Never change the user's default sink.
|
||||
silent_sink = 'erri_tests_silent'
|
||||
sinks = json.loads(subprocess.check_output(['pactl', '-f', 'json', 'list', 'short', 'sinks'], text=True))
|
||||
if not any(sink['name'] == silent_sink for sink in sinks):
|
||||
subprocess.run(['pactl', 'load-module', 'module-null-sink', 'sink_name='+silent_sink, 'sink_properties=device.description=ERRI_Silent_Tests'], check=True, stdout=subprocess.DEVNULL)
|
||||
subprocess.run(['pactl', 'set-sink-mute', silent_sink, '1'], check=True)
|
||||
subprocess.run(['pactl', 'set-sink-volume', silent_sink, '0%'], check=True)
|
||||
sinks = json.loads(subprocess.check_output(['pactl', '-f', 'json', 'list', 'short', 'sinks'], text=True))
|
||||
silent_index = next(sink['index'] for sink in sinks if sink['name'] == silent_sink)
|
||||
env.update(PULSE_SINK=silent_sink, PULSE_PROP='application.id=org.escapistsrereintroduced.tests')
|
||||
muted_streams = set()
|
||||
def silence_test_streams():
|
||||
streams = json.loads(subprocess.check_output(['pactl', '-f', 'json', 'list', 'sink-inputs'], text=True))
|
||||
for stream in streams:
|
||||
props = stream.get('properties', {})
|
||||
owned = props.get('application.id') == 'org.escapistsrereintroduced.tests'
|
||||
pid = props.get('application.process.id', '')
|
||||
if not owned and pid.isdigit():
|
||||
try:
|
||||
target = str(compat/'pfx').encode()
|
||||
values = (Path('/proc')/pid/'environ').read_bytes().split(b'\0')
|
||||
owned = any(value.startswith(b'WINEPREFIX=') and value[len(b'WINEPREFIX='):].rstrip(b'/') == target for value in values)
|
||||
except (FileNotFoundError, PermissionError, ProcessLookupError): pass
|
||||
if not owned: continue
|
||||
index = str(stream['index'])
|
||||
if stream.get('sink') != silent_index:
|
||||
subprocess.run(['pactl', 'move-sink-input', index, silent_sink], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
if not stream.get('mute'):
|
||||
subprocess.run(['pactl', 'set-sink-input-mute', index, '1'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
if index not in muted_streams:
|
||||
muted_streams.add(index)
|
||||
print('Test audio routed to silent output and muted (stream '+index+')', flush=True)
|
||||
# Snapshot addon bytes before launching any engine process.
|
||||
captured_sources = snapshot(ROOT, command)
|
||||
recorded_sessions = set()
|
||||
started = time.time()
|
||||
log_root = profile/'logs'
|
||||
previous = set(log_root.glob('logs_*')) if log_root.exists() else set()
|
||||
result, session, complete_at = 'timeout', None, None
|
||||
runner_error = None
|
||||
cleanup_ok = True
|
||||
cleanup_report = None
|
||||
child = None
|
||||
with (profile/'headless-launch.log').open('w') as output:
|
||||
try:
|
||||
child = subprocess.Popen(command, cwd=wb, env=env, stdout=output, stderr=subprocess.STDOUT, start_new_session=True)
|
||||
while time.time()-started < a.timeout:
|
||||
silence_test_streams()
|
||||
candidates = [x for x in log_root.glob('logs_*') if x not in previous]
|
||||
if candidates:
|
||||
session = max(candidates, key=lambda x:x.stat().st_mtime)
|
||||
if session not in recorded_sessions:
|
||||
persist(session, captured_sources)
|
||||
if external_inputs:
|
||||
(session/'external-addons-inputs.json').write_text(json.dumps(external_inputs, indent=2)+'\n')
|
||||
recorded_sessions.add(session)
|
||||
console = session/'console.log'
|
||||
content = console.read_text(errors='replace') if console.exists() else ''
|
||||
if ('Reintroduced: scenario preparation failed' in content and a.smoke not in {'startup-failure', 'extraction-startup-failure', 'extraction-boundary-failure'}) or 'ERR SMOKE FAIL:' in content or 'Virtual Machine Exception' in content or 'Can\'t compile "Game"' in content:
|
||||
result = 'test-failed'; break
|
||||
if content.count('ERR SMOKE COMPLETE:') >= a.rounds:
|
||||
if complete_at is None: complete_at = time.time()
|
||||
if time.time()-complete_at >= 6: result = 'smoke-completed'; break
|
||||
if child.poll() is not None: result = 'process-exited'; break
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
result = 'interrupted'
|
||||
except (OSError,ValueError,subprocess.SubprocessError) as error:
|
||||
result = 'runner-failed'; runner_error = str(error)
|
||||
finally:
|
||||
result_before_cleanup = result
|
||||
if child is not None:
|
||||
cleanup_report = cleanup_owned_run(child,proton,env,compat)
|
||||
cleanup_ok = cleanup_report['cleanup_confirmed']
|
||||
if not cleanup_ok:
|
||||
result = 'cleanup-failed'
|
||||
cleanup_error = '; '.join(cleanup_report['errors'])
|
||||
runner_error = (runner_error + '; ' if runner_error else '') + cleanup_error
|
||||
external_unchanged = True
|
||||
if external_inputs:
|
||||
try: external_unchanged = verify_profile(external_inputs, a.external_addons, external_rules, external_cache, addons)
|
||||
except (OSError, ValueError): external_unchanged = False
|
||||
if not external_unchanged: result = 'external-inputs-changed'
|
||||
stopped_evidence = seal_run(ROOT, session, captured_sources) if cleanup_ok and session else {}
|
||||
if stopped_evidence and not stopped_evidence['source_unchanged_after_run']: result = 'source-changed'
|
||||
summary = {**stopped_evidence, **bindings(captured_sources), 'external_addons_unchanged_after_run':external_unchanged, 'external_addon_count':len(external_inputs or {}), 'external_addon_inputs':'external-addons-inputs.json' if external_inputs else None, 'started_unix':started, 'result':result, 'result_before_cleanup':result_before_cleanup, 'cleanup_confirmed':cleanup_ok, 'cleanup_report':cleanup_report, 'runner_error':runner_error, 'session':str(session) if session else None, 'rounds_required':a.rounds, 'project':a.project, 'display':'headless Gamescope 1280x720/30Hz', 'dedicated_server':False, 'audio':'silent sink and per-test-stream mute'}
|
||||
if session:
|
||||
(session/'runner.json').write_text(json.dumps(summary, indent=2)+'\n')
|
||||
shutil.copyfile(profile/'headless-launch.log', session/'headless-launch.log')
|
||||
print(json.dumps(summary, indent=2), flush=True)
|
||||
if result != 'smoke-completed': raise SystemExit(1)
|
||||
# All native errors still fail the report even when the scripted sequence completed.
|
||||
raise SystemExit(subprocess.call(['python3', str(ROOT/'tools/native_report.py'), str(session/'console.log'), '--require-completions', str(a.rounds)]))
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,756 @@
|
||||
PASS: shared config transaction rejects client writes, preserves first-save/prior/fallback state across open, write, backup, partial-copy, readback and mismatch failures
|
||||
PASS: actual Save/Commit preserves semantically valid backup across unrelated menu edits and repairs; unknown fields/default sections retained; actual terrain parser; other parsers, catalog and disk doubled
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 0.001s
|
||||
|
||||
OK
|
||||
PASS: actual objective overlay lifecycle preserves separate ownership, late readiness/reopen, data refresh, pan/zoom/culling, supported sibling/shared-frame parents, coordinate offsets, native task-clear isolation and terminal cleanup; native rendering remains unverified
|
||||
PASS: actual map element initializes each widget, retries missing widgets, updates position and changes only stale text/icon data; no native image-load claim
|
||||
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
|
||||
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
|
||||
PASS: actual long-warning phases retain visible label through1500ms checkpoint then restore radio before close; synthetic marker separated from unmoved player
|
||||
PASS: actual Center places marker100screenpx left of pan center at both fixture zooms without altering disclosed coordinates
|
||||
PASS: missing, child-only, malformed, zero and duplicate prefab root IDs rejected
|
||||
PASS: delayed scene/inventory arrival, deferred preload, identity checks, stale replies, timeout and observer cleanup
|
||||
PASS: actual inventory manifest, delayed/missing/wrong-owner item links, finite cap, consumed-stock refresh and preload ordering
|
||||
PASS: spawn scene remains held through final acknowledgement and nearby use; leaving, reassignment, disconnect and teardown release it
|
||||
PASS: production prison AI hold/release, repeat activation, authority/faction/player exclusion, dead actor and stop cleanup
|
||||
PASS: actual protected/capped damage plus exact-actor terminal fixture bypass through delayed native damage, fail-closed ownership and untouched ordinary actor; no native claim
|
||||
PASS: actual full planner selection/commit body, 360 equivalence scenarios, orders/reservations/cooldowns identical; one128-unit scan vs >10000 repeated unit visits; no FPS claim
|
||||
PASS: actual observer monitor/recovery/cleanup rejects retained old controller, preserves new camera/editor/hints/timer, restarts once and retains normal recovery; native effects modeled from SDK, no runtime claim
|
||||
PASS: former chat controller releases only its own callbacks and preserves replacement menu; current-owner stop/scope and repeated teardown remain intact
|
||||
PASS: wrong observer fixture selector rejected before runtime/profile setup; no engine launched
|
||||
PASS: actual depot mixed-roster worker/item selection, ownership/refusal and radio/planning/two-leg state transitions, fixed deadline, late proof quarantine, finite import and no adoption after Stop
|
||||
PASS: actual depot HQ lifecycle, existing planner priority, server/mode/owner gates, idempotent polling and owner replacement
|
||||
PASS: actual HQ mine/magazine claim separation, real demand methods, exact token cleanup and original authority gates; native availability doubled
|
||||
PASS: actual full magazine/inert-PMN4 depot leaf, exact custody, isolated callbacks, finite reservation, false/unknown and Stop quarantine, once-only delivery and caps; native movement unqualified
|
||||
PASS: actual mine/magazine provenance and separated finite mine imports; actual authored identity capture, READY-only detached provenance, same shared stock admission, completed exact two-leg proof, lifetime caps and Stop no late adoption
|
||||
PASS: actual crash geometry, detached definition setup, frozen server choice and guarded phase policy; integration/source ordering checked
|
||||
PASS: actual GameMode readiness admission and common full-footprint survey require crash authorization and both surveyed areas
|
||||
PASS: actual start defaults/parse/save, failure retention and authorized command wiring; current/next feedback, aliases and no random choice; native UI and core mode freezing are separate
|
||||
PASS: actual immutable measured footprint/root/attachment matching, bounds/transforms, moving geometry, seated-body and cargo reciprocity, finite hierarchy caps; native geometry doubled
|
||||
PASS: actual BMP singleton geometry and typed current order; one survey, full straight segment, width, known history, reservations, settlement/clearance and work caps; placement/footprint tested separately
|
||||
PASS: actual five random-radius diagnostic wrappers, disabled transparency, cached opt-in, exact zero and exclusion branch, native arguments/returns/outarg preservation, single superclass call and shared16-log cap
|
||||
PASS: zero-radius trace disabled by default, scoped before setup, exact opt-in emission; no native launch
|
||||
PASS: complete actual supply-travel probe, immutable 6+2 formations and 25 actors, admission/travel deadlines, six observed native-boundary moves, stop/ownership/partial-substitution refusals
|
||||
PASS: actual distinct supply-travel hook, fixed420s selector, original observer protection/restoration, failure/terminal cleanup and no legacy convoy setup
|
||||
PASS: actual supply-travel selector requires Chat, one round and480s; existing outcomes unaffected; no native launch
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {crash-start,crash-geometry,normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,supply-travel,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,inventory-hitzone,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--trace-random-radius]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mounted-bmp] [--mine-stock]
|
||||
[--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {crash-start,crash-geometry,normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,supply-travel,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,inventory-hitzone,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--trace-random-radius]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mounted-bmp] [--mine-stock]
|
||||
[--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {crash-start,crash-geometry,normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,supply-travel,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,inventory-hitzone,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--trace-random-radius]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mounted-bmp] [--mine-stock]
|
||||
[--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {crash-start,crash-geometry,normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,supply-travel,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,inventory-hitzone,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--trace-random-radius]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mounted-bmp] [--mine-stock]
|
||||
[--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {crash-start,crash-geometry,normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,supply-travel,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,inventory-hitzone,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--trace-random-radius]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mounted-bmp] [--mine-stock]
|
||||
[--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept and --convoy-cover are mutually exclusive
|
||||
PASS: actual native interception witness requires real radio cursor/lineage, original actor identities, fixed movement/hold/expiry/resume and immutable deadline; no movement/delivery injection
|
||||
PASS: actual CLI rejects wrong mode/project/rounds/899s/conflict before runtime; valid flag and terminal branch identify synthetic-observation qualification
|
||||
PASS: visible base hook and actual fixture override delegate once; delivery proof uses retained pre-super route only on successful production transition
|
||||
actual mounted HQ dispatch planning, radio, ownership and lifecycle checks passed
|
||||
PASS: actual mounted intercept eligibility/order chronology, fixed destinations, native road selection bounds, width, segment heading, clearance and settlement delegation; no native driving claim
|
||||
PASS: actual paired arc geometry,40m/80m spacing, separate original polylines, both-position validation and128road/4096segment budget; no native driving proof
|
||||
PASS: actual typed original BMP singleton, loss/footprint/custody guards and native cancellation, UAZ-only pair admission, rollback, native allocation/revocation boundaries, per-member ownership, hold and expiry
|
||||
PASS: actual Tick clock initialization admits fractional first ticks and expires without extending the original start
|
||||
PASS: actual finite conservation/deadline/callback policies; read-only selection, bounded once-only inventory submission and no can refill; native transfer not executed
|
||||
PASS: actual exact-context membership, bounded identity/reach gates, native start/cache/eligibility ordering and no duplicate/foreign action start; fuel manager identity and empty-can headroom guards checked
|
||||
PASS: actual detached node/value snapshot and conservation refuse mismatched lengths before indexed reads, retain original identities under native out-array aliasing and log bounded counts once; native cause remains unknown
|
||||
PASS: actual mine evidence sealing/recovery, historical coarse publication, caps, canonical ownership, marker join retry/owner-change/stop; native arming and replicated vector-array transport untested
|
||||
PASS: actual full document recovery retains authored/sealed targets, one shared spent ledger, retry/exhaustion semantics and canonical multi-player ownership alongside supplemental mine intel
|
||||
PASS: actual infantry search suspension, quiet grace, finite resume, expiry/replacement and defensive/ownership boundaries
|
||||
PASS: infantry combat gate precedes radio delivery and preserves non-task transports
|
||||
PASS: actual HQ contact transition wakes only fit squad members, cancels only task orders and logs changes once
|
||||
PASS: actual finite medicine grants, authored stock deduction, full-backpack refusal/pouch fallback, no loot replenishment, unresolved-operation quarantine and ownership/stop guards
|
||||
PASS: only recent native detected/identified threats suspend hunter travel; stale/disarmed/destroyed contacts cannot strand patrols
|
||||
PASS: actual hunter combat transition cancels only its travel once, holds through renewed contact and resumes after quiet; unregistered startup preserved
|
||||
PASS: captured sighting retention, immutable details, expiry, replay rejection, bounded sound uncertainty/cooldown, radio loss, priority and overdue-status fairness
|
||||
PASS: arrow press/release, held keys, opposing keys, disabled/menu input and firearm-only retention
|
||||
PASS: town trigger and hard 700m exclusion for all observers, empty/invalid positions and final-check crossings
|
||||
PASS: exact finite site squads, invalid roster/count rejection, committed-site spacing and safe fallback boundary
|
||||
PASS: mortar couriers use the mobile site team and refuse permanent-only garrisons
|
||||
PASS: omitted preferred mortar depot selects a surviving owned camp/mobile team; no camp, foreign cache and permanent-only sites are unavailable; historical priority retained
|
||||
PASS: real-session mortar deadline, failed-support convoy admission and hunter survivor/recovery gates
|
||||
PASS: native-route retry preserves delivered target, limits attempts and suspends during combat
|
||||
PASS: stale/deleted AI utility cannot receive Complete; mortar HQ preflight withholds unsafe orders; mounted convoy cover never creates infantry Defend
|
||||
PASS: flight crew affiliation and group precede native boarding; preparation remains bounded even while passengers wait
|
||||
PASS: factionless/deleted map targets do not abort perception; faction lookup tolerates teardown; deleting helicopter releases without capture/engine access
|
||||
PASS: shoot adapter waits for a physical round; bounded same-shell retries, interrupted/never-started loads, committed no-retry, order/owner changes and normal native delegation
|
||||
PASS: missing/invalid faction crew defers before any native spawn or allowance insertion; all registered RHS factions have explicit soldier and medic profiles
|
||||
PASS: vehicle radio issued once; pending, refusal and looting cannot duplicate or fabricate a physical radio link
|
||||
PASS: actual vehicle-radio link requires the original seated crew, physical manpack and live uncaptured mission vehicle; looting, dismount, release and teardown remove it
|
||||
PASS: actual hunter release preserves survivors/HQ, releases driving ownership once, delegates capture/teardown, and excludes prison/patrol/mortar from generic objective defence
|
||||
PASS: actual player-entry hook latches original hunter capture before native callbacks; both release layers cancel ownership without new entities/refund; bounded current-owner guards and prior-release policy
|
||||
PASS: real opening road geometry enforces wide azimuths; fresh delivered contact preempts patrol cooldown, respects radio failures and does not starve in-flight interception
|
||||
PASS: actual scoped 10% tighter REGULAR aim error and alert perception; preserves equipment multiplier and restores vanilla behavior when unbound
|
||||
PASS: physical inspection and parent-wreck retention; server-only searched/report delivery unlocks native cleanup once with a 300-second delay
|
||||
PASS: bounded same-group empty-spawn retries, no casualty refill, queue retirement, delivery-gated intel, prepared-team reuse, throttled diagnostics and LOD release
|
||||
PASS: production airborne memory confidence, persistence, ground coverage, confirmed losses, artillery veto, cooldown and invalid-input rules
|
||||
PASS: production cover retains the carried HQ target until its deadline; airport reload waits until the drop pass and cover have finished
|
||||
PASS: air logistics reservation preserves delivered search until radio handoff, excludes guards/combat/in-flight orders, and quarantines unresolved native loads/moves
|
||||
PASS: actual ammunition cargo selector handles empty rifle/machinegun/sniper magazines, preserves loaded variants, excludes auxiliary launchers and tolerates missing native metadata
|
||||
PASS: original 15-minute launch/loss cadence, active/captured timer disarm, saved delay reset, stop, 300m orbit and bounded close-contact standoff at 30/60/120 Hz
|
||||
PASS: actual aircraft-removal notification guards owner/server/active/stop and schedules one full delay without replay extension
|
||||
PASS: actual helicopter delay applies resolved Random values, preserves countdown on save failure, and leaves active flights running
|
||||
PASS: hill prominence/elevation, actual road edges/heights/bends and server-only finite scout admission, pending stock, timeout and no replacements/refills
|
||||
PASS: mortar duty scope excludes players, other crews, stopped/client/inactive states; only destruction releases an active assignment, while the gunner may approach the gun
|
||||
PASS: random mode restrictions, full numeric ranges, independent calendar dates and strict daylight boundaries
|
||||
PASS: actual killais eligibility includes the 1000m boundary, excludes players/retained bodies/dead/non-character entities and checks full 3D distance
|
||||
PASS: actual generation scheduler bounds each tick, enforces deadline/candidate ceiling, validates fallback, commits only accepted positions, skips only dispensable sites, preserves required objectives/intel, and resets the next search budget
|
||||
PASS: terrain placement samples world bounds, retains streaming candidates, bounds waits/work, rejects foreign fallbacks, preserves objectives/intel and validates completion
|
||||
PASS: actual navigation readiness supports static meshes, yields only for loadable streaming tiles and refuses unreachable positions
|
||||
PASS: real sampling handles absent airports and extraction clearance preserves geometry/navigation/flatness while selecting the correct aircraft survey
|
||||
PASS: universal connector, embedded/standalone exports, multiple terrains, stable IDs, native nav configs, missing-nav visibility, path validation and overwrite protection
|
||||
PASS: airfield detection accepts physical runways/towers and bounded authored markers, rejects unrelated props, water and out-of-world markers, and samples terrain height
|
||||
PASS: configured airport parsing, bounded readiness, stale admission rejection and cleanup wiring
|
||||
PASS: automatic default, explicit header/CLI opt-out, editor/authored-mode guards, optional airport override precedence and fresh-round saving guard
|
||||
PASS: singleton identity guards, paired faction deserialization and client world-cleanup wiring
|
||||
PASS: task/map/respawn getters survive delayed old destruction and preserve ordinary-mode behavior
|
||||
PASS: replication hierarchy refuses absent/aliased nodes and establishes delivery dependencies idempotently
|
||||
PASS: standalone old faction delegates collected once, with null/deleted owners ignored
|
||||
PASS: actual terrain takeover startup skips mode-less/menu worlds, always forwards super, retains eligible/unsupported/authored/client/editor semantics
|
||||
PASS: native name callback preserves authored modes, opt-outs and failures; terrain suffix follows world changes, cached resolution and confirmed takeover metadata restores on cleanup without changing scenario resource or overwriting later titles
|
||||
PASS: actual terrain parent traversal, Armenhof/Nogova names, Everon alias, missing-resource fallback, empty names and bounded cycles/depth
|
||||
PASS: saved terrain GUIDs, defaults, restart persistence, clearing, backup recovery, corrupt-file preservation, write failures, and client write denial
|
||||
PASS: startup terrain capture protects client/editor/menu worlds, default behavior, same-map loads and reload-loop recovery
|
||||
PASS: actual transition waits for native startup, disconnects without bans, requires an empty session, retains every loaded mod, bounds waiting and consumes requests once
|
||||
PASS: actual faction resolution reserves explicit roles, samples without replacement, substitutes disabled selections, and rejects duplicate/unknown/missing choices and insufficient pools
|
||||
PASS: Random all submits persistent random roles immediately; manual mixed choices, stale/unauthorized UI guards and debounce preserved
|
||||
PASS: actual content policy gates parent packs, individual factions, faction identity and finite group rotation with vanilla fallback
|
||||
PASS: actual delivered road eligibility, ahead projection without chords, reservations, stock road query bounds, terrain/water/obstruction refusal and clean outputs
|
||||
PASS: actual delivered-task lineage, expiry, old-order retention, native commit, release, road precision expiry and duplicate delivery
|
||||
PASS: actual original-roster local handoff,100m3D bound,busy carrier/budget exclusion,origin interruption including deletedsource,normal radio delivery and per-source replay history
|
||||
PASS: actual visual intake retains source/time/position/id, rejects replay, prefers usable own route, excludes simultaneous callers and conservatively resets negative-sweep dwell
|
||||
PASS: actual fixture local/radio gating, immutable source/time/position, interrupted-report exclusion and exact-owner cleanup
|
||||
PASS: local-handoff runner refuses wrong project/round count/insufficient deadline before runtime work
|
||||
PASS: actual fixture allocator terminal/unsubmitted guard, original-group lookup and both candidate gates
|
||||
PASS: actual report-derived waypoint insertion/order and tighter positive radii; no formation navigation claim
|
||||
PASS: actual Place/Stop waits native pose update, bounds four attempts, preserves original position, restores pending/settled placement and respects ownership/accepted-move quarantine
|
||||
PASS: actual fixture current/original recipient ownership, unresolved transfer cleanup, exact-token release and idempotent Stop
|
||||
PASS: actual fixture phased-cargo readiness, bounded callbacks, captured-stock mixed-caliber selection/continuation, reservation cleanup and component magazine counts
|
||||
PASS: actual inventory-fixture setup uses ordinary prison unlock before constructing its convoy-waiting probe; failed unlock cannot run fixture
|
||||
PASS: actual authored storage anchors, native trace flags, own-hierarchy exclusion, external wall/vehicle obstruction, range, unknown geometry and bounded context refusal
|
||||
PASS: actual Compatible and bounded stock preflight before report/claim consumption, later compatible truck, launcher controls, convoy binding, finite identities, disjoint tokens, shared report replay/cooldown, original recipients and HQ cleanup
|
||||
PASS: actual finite convoy supply pool; immutable slots, shared demand state, sparse/capped discovery and permanent shutdown
|
||||
PASS: actual HQ rotates finite donors, skips unavailable/contacted trucks, uses delivered reports and dispatches at most one trip per planning cadence
|
||||
PASS: actual original turret identity, native/canonical guards, scoped maximum-skill profile, reset, dismount/reboard, requested-skill preservation and restoration
|
||||
PASS: bounded HQ startup/memory windows and outcome-specific full-route deadlines
|
||||
PASS: explicit escape-route skips only administrator stress, preserving original full-fixture wait/failure paths
|
||||
PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged
|
||||
PASS: actual patrol fixture leaves prison before responder HQ proof, followed by separate permanent-patrol observation
|
||||
PASS: exact patrol-hq finite responder preparation and native search-radius receipt verification
|
||||
PASS: actual read migrates old valid settings, preserves explicit disabled state, rejects invalid data; next-round feedback and fixed menu remain honest
|
||||
PASS: actual automatic convoy timing/authority/admission/one-attempt/manual-task/stop policy; native deployment unproved
|
||||
PASS: late authoritative failure reason refreshes open result; detach, mode change and success subscriptions preserved
|
||||
infantry resupply actual rules, radio transitions, owned cleanup and unresolved-operation tests passed
|
||||
PASS: actual resupply Roster preserves permanent release, original slot mapping and bounded ownership
|
||||
PASS: actual designated zero/one original carrier selection, settled startup, shared finite claims, no refund after submission
|
||||
PASS: actual HQ startup grant readiness forwarding is authoritative and fail-closed
|
||||
PASS: actual finite PMN4 designated-zero/one entitlement, pinned original ownership, shared commit, physical custody, callback quarantine/deletion and no retry
|
||||
PASS: actual bounded empty-return and finite delivery; phase-isolated callbacks, exact ownership proof, stop quarantine and original actor/vehicle boundaries
|
||||
PASS: actual shared finite identity reservation; competing consumers, submission permanence, stale cleanup and96-item bound
|
||||
PASS: actual carried-mounted swap, shared committed claim, physical slot reach, exact two-way proof, uncertainty and terminal cleanup
|
||||
PASS: actual mounted pickup, exact finite ownership, pre-submission commit, physical custody, bounded callback and quarantine lifecycle; no native coverage claim
|
||||
PASS: actual mounted courier admission, radio, individual orders, two-stage transfer, reboarding, token cleanup and quarantine lifecycle
|
||||
PASS: actual scoped crew permit and native board completion/failure preserves foreign seats without teleport fallback
|
||||
PASS: actual shared mounted stock/history ownership, atomic exact HQ pair claim, and bounded stopped-owner proof polling
|
||||
PASS: actual carried/mounted early ammunition policy and status-first shared uplink; complete threshold, phases, cooldown, failures requested check-ins and non-urgent detached carried-mine status
|
||||
PASS: actual group radio startup preservation, finite native grants, callback quarantine, frozen selection/actor ownership and bounded observation
|
||||
PASS: actual maintenance native-stock check rejects missing/duplicate/wrong-identity/unowned/depleted/malformed stock and is read-only; native runtime not executed
|
||||
..................
|
||||
----------------------------------------------------------------------
|
||||
Ran 18 tests in 0.295s
|
||||
|
||||
OK
|
||||
PASS: actual radio node rejects current incapacitation, death, ownership and faction changes despite cached inventory; one-second capability refresh remains explicit
|
||||
PASS: Southern finite maintenance stock preserves weapons/clues and optional transport, historical Western fixture stays separate; native capacity/consumption untested
|
||||
PASS: optional transport deferred behind required readiness, bounded once-only spawn/omission, isolated cleanup, occupied-candidate retention and required-default preservation
|
||||
PASS: actual diagnostic fallback runs only on failed missing-context call, derives unique native pilot door, preserves stock eligibility and restores prior index on success/failure; diagnostics mutate no game state.
|
||||
PASS: actual native-prelude cleanup restores only original changed damage, refuses stale/incapacitated/aboard teleport; bounded five-seat/four-door fixture wiring checked. Native execution remains pending.
|
||||
PASS: actual CreateTransport requires successful scoped Configure after handbrake, preserves ordinary vehicles, tracks spawned entity before failures and never treats failed lock registration as ready.
|
||||
PASS: actual preparation Configure guards/idempotence/breach preservation, security eligibility/final current-body server mutation and all scoped action guards; all five seat/four door resource overrides. Native hold, prefab merge, replication and boarding remain unverified.
|
||||
PASS: production vehicle alarm single trigger/replica completion/no replay, fixed deadline/readiness wait, failed audio, dedicated server, moving exact handle, destruction/native end/deletion/clock and mission teardown. Native sound and multiplayer delivery require engine qualification.
|
||||
PASS: production alarm emitter/receiver original time/location, explicit native queue only, 32-entry cap, 50m local witness eligibility, bound perception, native-super preservation; actual engine broadcast/hearing remains unverified.
|
||||
PASS: actual optional candidate barrier, exact callback/custody proof and unchanged mine/primary snapshots; no native transfer claim
|
||||
Native finite mine-stock runner: parser, scope, exclusions and command pass
|
||||
PASS: actual bounded native mounted proof/detached payload/deadline/cleanup methods, production-only setup and isolated runner selector
|
||||
PASS: actual one-existing-convoy candidate readiness, sender-root identity, incomplete cargo refusal and fixed three-candidate bound
|
||||
PASS: actual BMP capture requires complete armed exact original resource; pinned delivery/slot checks and isolated CLI flag
|
||||
PASS: actual opt-in pre-unlock isolation refuses wrong mode/phase/flag and existing hunters; no baseline service changes
|
||||
PASS: actual once-only native dry query, exact before/after slots, native refusal, shutdown; shared bounded discovery and distinct opt-in verdict
|
||||
PASS: actual completion selector and pre-cleanup variant capture preserve separate radio and eligibility verdicts
|
||||
PASS: actual cargo diagnostic stages separate absent/foreign magazines, used/empty/invalid rounds, well mismatch and exact truck storage/attachment without mutation
|
||||
PASS: actual native courier witness rejects missing radio/custody/swap/reboard evidence; CLI opt-in bounded; native success remains unqualified
|
||||
PASS: actual courier constructor/Tick waits for preparation, returns before physical checks, preserves fixed deadline and sticky stop/failure; Enfusion startup awaits native verification
|
||||
PASS: actual arranged native alarm fixture deadline, single emit, natural-route observation, original timestamp/area/detail assertions, natural observer proximity/activation wait, failure cleanup and independent canonical-body restoration; native delivery itself remains unqualified.
|
||||
PASS: actual convoy cover observation/snapshot/uplink/HOLD/RESUME/cleanup transitions; native boundaries mocked, non-convoy runner opt-in refused
|
||||
Enemy optics actual-service ownership, preservation, callback, deadline and bounded-inspection policies passed
|
||||
PASS: actual delivered cover freshness/lineage/replay and canonical executor; owned waypoint-only hold, one resume at preserved route index, invalid ownership/crew/ledger refusal and fail-closed native recreation
|
||||
PASS: actual Stinger show/set/help state precise actor-roll effects; read-only help, bounded values, malformed syntax and failed-save preservation; no rerolls/gameplay edits and no native persistence claim
|
||||
PASS: new/retasked search and radio delivery ammo gates; active tasks and delivered release preserved
|
||||
HQ convoy delivered-report planning and downlink regressions passed
|
||||
PASS: actual own-inventory ammo inspection deduplicates loaded/carried/shared wells, separates chambers/launchers, freezes scalar snapshots and treats unsupported/capped/foreign inventory as unknown
|
||||
PASS: actual carried PMN4 inventory, original-owner guards, bounded deduplication, independent unknown state and detached counts
|
||||
PASS: actual mounted hierarchy sampling, ownership exclusion, deduplication, completeness/caps, quarter threshold and detached validation; no runtime policy or ammunition mutation
|
||||
PASS: WORKBENCH opt-in diagnostics distinguish cycles/cap/storage/manager/mount failures without repeated native traversal
|
||||
PASS: actual HQ original convoy-root admission rejects wrong force/server/group and never supplies replacement lookup
|
||||
PASS: production mounted structural seat scan ignores >128 carried/cargo descendants, shares exact native mount/magazine ownership, preserves attachment seats and exact occupants, rejects unknown/cyclic/oversize structures; native binding still requires qualification.
|
||||
PASS: native body fixture uses existing immutable actors, real production LOS/queued uplink, pre-delivery isolation, actual status overlap, bounded cleanup and no fabricated reports/spawns
|
||||
PASS: actual native fixture lookup selects exact BODY kind/identity/message from bounded evaluations, never sighting-only Get
|
||||
PASS: actual ammo snapshot comparison checks every scalar/category flag; native probe requires real roster rescan, loaded-round lower bound and separate immutable pending/Watch objects
|
||||
PASS: actual delivered slot-union bodies preserve overlap/disjoint evidence, replay idempotence, status subset integrity, full-wipe closure and formation-local bounds
|
||||
test_review_added_allowed_media_invalidates_snapshot (__main__.ReviewTests.test_review_added_allowed_media_invalidates_snapshot) ... ok
|
||||
test_review_added_source_directory_rejected (__main__.ReviewTests.test_review_added_source_directory_rejected) ... ok
|
||||
test_review_loose_root_resource_and_symlink_rejected (__main__.ReviewTests.test_review_loose_root_resource_and_symlink_rejected) ... ok
|
||||
test_review_metadata_media_and_sidecars_remain_allowed_and_bound (__main__.ReviewTests.test_review_metadata_media_and_sidecars_remain_allowed_and_bound) ... ok
|
||||
test_review_missing_link_rejected (__main__.ReviewTests.test_review_missing_link_rejected) ... ok
|
||||
test_review_package_guard_sees_new_scripts (__main__.ReviewTests.test_review_package_guard_sees_new_scripts) ... ok
|
||||
test_review_retargeted_private_link_rejected (__main__.ReviewTests.test_review_retargeted_private_link_rejected) ... ok
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 7 tests in 0.074s
|
||||
|
||||
OK
|
||||
PASS: actual report methods reproduce old starvation; status-first scheduler delivers check-ins and requires 30s without combat or actual visible contacts before negative reports
|
||||
PASS: actual actor group lookup retains controlled character membership when seated/inherited vehicle agents differ; missing control or parent refuses
|
||||
.................
|
||||
----------------------------------------------------------------------
|
||||
Ran 17 tests in 3.767s
|
||||
|
||||
OK
|
||||
PASS: actual convoy motion thresholds, native-only proof boundaries, physical cargo/crew ownership and pre-launch runner restrictions
|
||||
PASS: actual new measured ClearFootprint 25/441 sample grids, measured extents/height, water/invalid world/terrain/trace refusal plus legacy filled rotated-footprint TraceBox and nine terrain/water samples; broad tree/cable bounds alone accepted, actual collision/nonfinite trace refused, nonzero sweep and ENTS/OCEAN flags, bounded diagnostic query and reset
|
||||
PASS: actual road interpolation/projection/orientation, six35m starts,200m forward tail, preserved bends, short/far/degenerate/nonfinite/overdense/hairpin refusal and deterministic candidate ties
|
||||
PASS: actual authenticated convoy dispatch, active/ready admission gates, canonical receipt, safe unconsumed preflight retry, pool refusal, retained partial failure and consumed no-retry
|
||||
PASS: actual native readiness exact membership, one-time per-vehicle ledger admission/reservation, partial readiness retry without duplicate records and one-pass56-item cargo allocation without replenishment
|
||||
PASS: actual per-vehicle readiness reasons, native agent/activity counts, intended-seat diagnostics, five-second throttle and read-only observation
|
||||
PASS: actual convoy dispatch validates before receipt consumption, consumes once before native mutation, retains all12 partial-failure paths, accepts the selected non-USSR enemy, and allocates exact19 bodies without capacity shrinking
|
||||
PASS: own memberless prefab disables automatic spawn before initialization; actual admission rejects populated, immediate or authored-slot templates before finite crew creation
|
||||
PASS: actual native seated-group arrival outside35m, native waypoint removal, idempotent advance, and convoy waypoint idempotence/arrival/deadline, missing/dead/captured/reparented crew refusal, failed native waypoint release, original vehicle/member preservation and no replacement
|
||||
PASS: actual convoy/team Release retains original active survivors and HQ identity, rejects takeover/terminal/unknown roster, preserves exact logistics quarantine and normal eligibility
|
||||
PASS: actual convoy receipt identity/one-use, shared60-body and128-formation caps, outstanding/native-ledger accounting, exact19-body composition, source reuse, mode/client guards and no consumed receipt refunds
|
||||
PASS: actual supply route cycling/arrival/stops, finite startup bounds, contact freshness, dismount hold, quiet interval, interrupted boarding, timeout and lost-truck release
|
||||
PASS: moving-convoy perception boundaries and scoped individual/group abandonment priorities
|
||||
PASS: actual supply startup reasons expose unchanged body budget/requirement, journal only failure changes, preserve retry cadence/caps and report the true final refusal
|
||||
PASS: actual combat start releases only exact intercept claims before owning route/orders; foreign claim defers combat takeover
|
||||
PASS: actual StartOne plus actual pool preserve unconsumed receipt retry, reject consumed replay, skip impossible new-budget surveys, retain equality, retry caps and journal dedup; no FPS claim
|
||||
PASS: actual admission seeds use exact installed loaded prefab, reject replacement/foreign/projectile paths and preserve bounded immutable identity
|
||||
PASS: actual primary-before-mounted one-shot batch, shared96/128 caps, physical snapshot headroom, original ownership, callback refusal and admission pinning
|
||||
PASS: actual bounded HQ query validates only existing original binding without admission or replacement lookup
|
||||
PASS: exact optional PMN4 startup, primary callback independence, original owner,128 capacity/callback limits, terminal no-retry and callback-proven freeze
|
||||
PASS: production convoy spacing, physical arrival, stable follower targets, supply ownership, leader promotion and bounded stuck recovery
|
||||
PASS: convoy combat keeps native manoeuvres for unchanged contacts and refreshes when contact moves
|
||||
PASS: native convoy proof excludes pre-departure movement and ends via explicit fixture reset, not prohibited player death
|
||||
PASS: boarding retries only after approach/seat progress stops, capped at three, with delayed native reissue and completed-team preservation
|
||||
PASS: slow only owned vehicles near fit dismounted infantry; ignore passengers, casualties and released teams/vehicles
|
||||
PASS: restore missing native driving registration only for healthy owned seated crews; preserve existing association and refuse invalid/released vehicles
|
||||
PASS: actual body observer enforces owned physical roster, local death-only candidates, native trace gating, budget/cadence/caps, immutable body-only snapshots, radio retries and confirmed suppression
|
||||
PASS: own initial roster, explicit retained corpse evidence, unconscious/missing/ref transfer guards, fixed strength, no replacement adoption, detached samples, surviving radio sender, immutable convoy root/ledger, mounted capture release and lost-root unknown
|
||||
PASS: delivered-only status snapshots, two missed check-ins/reply deadline, last reported coordinates, episode/replay guards, confirmed-loss idempotence, detached/validated mounted payload replacement 256-record cap and detached carried-mine radio delivery/chronology
|
||||
PASS: actual failed-retask deadline rollback plus assignment cancellation/release preserves radio ownership, native waypoint lifecycle and failed-order retry boundaries
|
||||
PASS: actual force ledger, finite casualties/identity/cap, routes/arrival/elevation/overshoot, server and physical guards, staggered 256-record simulated hour without world queries
|
||||
PASS: actual equipment coordinator registration dedupe, startup delay, five member scans, finite component retries, unregister/stop terminal behavior and 256-group bound
|
||||
PASS: actual percentage selection, native/preferred launcher preservation, failure/removal fallback, pending-operation quarantine, capture, finite two-mine grants and no replenishment
|
||||
PASS: automatic inventory one-shot roll, exact item ownership, finite compatible ammo, rejection, capture/stop, deletion, loot and late callback quarantine
|
||||
PASS: enabled/valid catalog eligibility, category restrictions, content filtering, GUID deduplication across factions and bounded catalogs
|
||||
PASS: actual engineer native group-envelope admission and phase cancellation, preparation/deadline, original custody/identity, once-only callback removal, cancellation quarantine, withdrawal/clearance, native refusal and owned-action cleanup; native execution untested
|
||||
PASS: actual mine selector respects road and settlement flank safety; native boundaries doubled
|
||||
PASS: actual HQ and post ledger: second delivered post, one query/tick,2 active/32 jobs,4 consumed lifetime attempts, no-stock/query refusal consumption, pending old job and original identity constraints
|
||||
PASS: actual mine post ledger immutable history, strict chronology/evidence, aliases, >160m separation, detached sites and finite4/32 consumed attempts
|
||||
PASS: actual objective heading/threat/approach rules, persistent doctrine weight, fresh-contact coverage invalidation, stale-sweep rejection and atomic radio retask ownership
|
||||
PASS: actual bounded delivered-negative coverage, replay/freshness/expiry/invalid guards, oldest eviction, small-circle and nearest-unswept selection, reserved-cell exclusion and cleanup
|
||||
PASS: actual HQ active/pending reservations, interruption, committed replacement, bounded coverage and unchanged force counts
|
||||
PASS: actual bounded pursuit roles/objectives/scores, stale search degradation, physical/busy/home guards, finite lifetime and two-bearing escape gap; no hidden player reads or spawning
|
||||
PASS: actual finite45..110 radius and both ordinary search queue snapshot expressions
|
||||
PASS: actual HQ memory rules/service preserve delivered snapshots, chronology, replay bounds, decay, motion limits, identity and negative-search revisions
|
||||
..........
|
||||
----------------------------------------------------------------------
|
||||
Ran 10 tests in 0.183s
|
||||
|
||||
OK
|
||||
PASS: actual session Tick + journal router delivers each grant completion and new HQ line once, without repeats on empty/unavailable feeds or unauthorized ticks
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 0.417s
|
||||
|
||||
OK
|
||||
PASS: actual current-controller/admin/mode authorization, per-round anti-replay, token-rotation refusal, command/snapshot throttles and client scope; owner-only replies and player-tool cleanup wiring
|
||||
PASS: mission provider registration is idempotent, optional dispatch is ordered, status aggregates, journals preserve cursors, aliases stay with their addons and topic help is bounded
|
||||
PASS: actual menu command routing covers all sixteen settings, rejects unsupported keys, gates companions, and matches unique authored widgets
|
||||
PASS: explicit weapon retention buttons use the same ready/authorized server command path
|
||||
PASS: item editor, preset controls and dynamic item-row layout are structurally valid
|
||||
PASS: catalog requests use authenticated sessions, independent admin page selections, loaded-mod allowlisting, bounded pages and allowlisted item mutations
|
||||
PASS: actual item rules, shared-source conflicts, missing/returning mods, bulk reset, independent named presets, bounds, malformed data and failed-save state preservation
|
||||
PASS: actual summaries preserve queued/delivered meaning, clear counts and IDs; raw records, legacy display, cursor gaps and RPC limits remain intact
|
||||
PASS: actual admin body authorization, god restoration, vanilla item deposit, callback completion/failure, timeout quarantine and stop/revocation paths
|
||||
PASS: same-body revoke/regrant preserves pending insertions and timeout quarantine; new-body binding isolates late callbacks and never inherits god mode
|
||||
PASS: actual Intel commands distinguish status/start/failure, preserve authorization gate and reject malformed requests; spent ledger is idempotent, server-only and bounded
|
||||
PASS: actual reroll snapshot, authority, cancellation, ownership, accepted operations, quarantine, bounded work and stop methods; no native runtime claim
|
||||
PASS: production Intel feedback coalescing, rough secrecy, rate limit, bounded dedupe, local/remote delivery and round cleanup; native UI/RPC not exercised
|
||||
PASS: actual per-squad bounded report selector rotates visible targets, survives ordering/turnover, records only queued attempts, and preserves radio intake ordering
|
||||
PASS: actual action denial/clearing, server request failure, single submitted-result feedback, current rescuer identity and terminal/stop suppression; native UI/RPC pending
|
||||
PASS: actual physical denial precedence and transport-seat occupancy/reservation/access/lock checks
|
||||
PASS: actual initial admission exhaustion and ServerTick grace; new/pending/empty/controller/life guards, normal death preserved
|
||||
......................
|
||||
----------------------------------------------------------------------
|
||||
Ran 22 tests in 0.260s
|
||||
|
||||
OK
|
||||
......................
|
||||
----------------------------------------------------------------------
|
||||
Ran 22 tests in 24.068s
|
||||
|
||||
OK
|
||||
..................
|
||||
----------------------------------------------------------------------
|
||||
Ran 18 tests in 3.025s
|
||||
|
||||
OK
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 2.423s
|
||||
|
||||
OK
|
||||
...............
|
||||
----------------------------------------------------------------------
|
||||
Ran 15 tests in 1.211s
|
||||
|
||||
OK
|
||||
PASS: actual field result/braking observer and Chat dispatch rules; one finite fixed candidate, deferred callback failure and guarded cleanup; no native measurement
|
||||
PASS: actual opt-in terrain audit records one fixed1681-point grid, preserves original survey refusal/success and exact no-allocation scope; malformed heights stop without retry; no native data acquired
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 0.663s
|
||||
|
||||
OK
|
||||
PASS: actual field PI/ramp/vector caps, antiwindup, braking hypothesis, immutable admission limits and segment bounds; no native physics tested
|
||||
PASS: actual100m whole-field survey sampling/4096cap, rotated coverage, all-node terrain and native trace refusal; original certificate lineage retained; no native qualification
|
||||
PASS: actual QualifyField preserves original identity/footprint/time, orders complete surveys before issuance, and refuses failed stages/moved body/expired or changed world; native boundaries faked
|
||||
PASS: WORKBENCH first-refusal diagnostics preserve ordinary predicates, query order/count/budget and opt-in scope; no native logging run
|
||||
PASS: actual field startup delegation, immutable lease/target, braking/settling/cancel transitions, early abort, callback scope, physical terminal policy and shared landing demand; native boundaries faked
|
||||
PASS: actual inherited airborne pre-CLIMB cancellation enables verified native autohover once, refuses failed enable and never writes after capture
|
||||
PASS: actual Core maintenance/default/tick/stop methods; empty/populated authority, terminal reentry, no tactical resume or new scheduler; native boundaries substituted
|
||||
PASS: actual finite registration, unique claims/tokens, retention/cancel/stop, readiness gating, capture/expiry retirement and bounded cross-kind order history and typed field/route construction; native boundaries faked
|
||||
PASS: actual WORKBENCH route observer returns exact retained route subtype and refuses absent/field controllers
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 0.669s
|
||||
|
||||
OK
|
||||
......................
|
||||
----------------------------------------------------------------------
|
||||
Ran 22 tests in 0.183s
|
||||
|
||||
OK
|
||||
................
|
||||
----------------------------------------------------------------------
|
||||
Ran 16 tests in 10.714s
|
||||
|
||||
OK
|
||||
......
|
||||
----------------------------------------------------------------------
|
||||
Ran 6 tests in 0.021s
|
||||
|
||||
OK
|
||||
PASS: actual production bridge lookup, lease/world/mode/seat/capture guards, no allocation, native super ordering and read-only fixture hooks
|
||||
...............
|
||||
----------------------------------------------------------------------
|
||||
Ran 15 tests in 0.058s
|
||||
|
||||
OK
|
||||
.............
|
||||
----------------------------------------------------------------------
|
||||
Ran 13 tests in 5.102s
|
||||
|
||||
OK
|
||||
PASS: actual route gain/caps/braking, callback selection, old landing/cancellation control and matching read-only telemetry
|
||||
..............................
|
||||
----------------------------------------------------------------------
|
||||
Ran 30 tests in 0.428s
|
||||
|
||||
OK
|
||||
..............
|
||||
----------------------------------------------------------------------
|
||||
Ran 14 tests in 0.216s
|
||||
|
||||
OK
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 0.114s
|
||||
|
||||
OK
|
||||
PASS: actual gate observer binds native registered action/context and current life, reads accumulated progress, refuses stale bindings and never revives outside original bounds
|
||||
PASS: actual read-only look observer methods enforce explicit scope, pinned native camera/body/identity, normalized pose, terminal cancellation and original sample/time bounds
|
||||
...........
|
||||
----------------------------------------------------------------------
|
||||
Ran 11 tests in 0.072s
|
||||
|
||||
OK
|
||||
.......................
|
||||
----------------------------------------------------------------------
|
||||
Ran 23 tests in 0.037s
|
||||
|
||||
OK
|
||||
PASS: actual initial WAITING task/guide copy, configured duration, unchanged interruption and rounded progress; native task assertion pending
|
||||
........
|
||||
----------------------------------------------------------------------
|
||||
Ran 8 tests in 0.013s
|
||||
|
||||
OK
|
||||
PASS: actual inventory adapter scope, grid placement, sole compatible host, refresh reuse, rollback, foreign hierarchy refusal and exactly-once native delegation; native compatibility pending
|
||||
.................................................................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 81 tests in 0.552s
|
||||
|
||||
OK
|
||||
......
|
||||
----------------------------------------------------------------------
|
||||
Ran 6 tests in 0.077s
|
||||
|
||||
OK
|
||||
.................
|
||||
----------------------------------------------------------------------
|
||||
Ran 17 tests in 0.004s
|
||||
|
||||
OK
|
||||
...........
|
||||
----------------------------------------------------------------------
|
||||
Ran 11 tests in 0.087s
|
||||
|
||||
OK
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 0.365s
|
||||
|
||||
OK
|
||||
PASS: actual observer nonce, admission pins, baseline/menu sequencing, snapshots, bounded clocks/samples, cleanup and one-attempt latch; native untested
|
||||
PASS: actual corridor caps/height/hold and whole-rectangle survey bounds, terrain refusal, native trace refusal and exact certificate lineage; native route unqualified
|
||||
PASS: actual route entry, fixed corridor/target, hover hold, cancellation landing freeze, shared lease/capture/deadline gate and unchanged native startup/tick sequence
|
||||
PASS: finite opt-in route fixture, original native input order, meaningful physical-arrival evidence and guarded cleanup; no native launch
|
||||
PASS: actual Chat route step result dispatch and scoped completion/cleanup wiring; six real early-parser refusal paths leave no prefix/profile output
|
||||
PASS: actual retained-service fixture entry, immutable controller/token/deadline/count bindings, observer getter and cleanup ownership ordering; native route validation pending
|
||||
PASS: actual measured cancellation gate, one-shot retained service Stop, no premature drain, immutable lease, fresh real controller readback and separate physical cancellation result policy
|
||||
PASS: actual rough-intel objective selection, exact precedence, source grid dimensions and unchanged other-phase dispatch; native UI pending
|
||||
.................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 33 tests in 0.131s
|
||||
|
||||
OK
|
||||
...............
|
||||
----------------------------------------------------------------------
|
||||
Ran 15 tests in 0.035s
|
||||
|
||||
OK
|
||||
PASS: actual preparation status scope, persistent updates, spinner restoration, placeholder replacement/deletion/destruction, native observation persistence/timeout; rendering remains native-pending
|
||||
PASS: actual guide ownership and scoped timer cancellation, current-controller preservation/reacquisition, hint retry/dedup and observer boundaries; native UI not exercised
|
||||
PASS: actual native-end shutdown, existing terminal snapshots, reentrancy, unchanged native end data and stale preparation guards
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 1.412s
|
||||
|
||||
OK
|
||||
PASS: actual three-state result wording and UI refresh; stale winner IDs never manufacture escape
|
||||
...........
|
||||
----------------------------------------------------------------------
|
||||
Ran 11 tests in 0.117s
|
||||
|
||||
OK
|
||||
PASS: actual immutable target, bounded autohover demand, drift refusal, five-second hold and unchanged qualification/deadline
|
||||
PASS: actual admission attempt budget bounds blocked/missing prerequisites; preparation, spent life and async pending remain protected
|
||||
PASS: delayed admission retry retains controller ownership across same-account recycled connection IDs
|
||||
PASS: actual scene deadline overrides delayed native preload only for rejection; late completion cannot assign, ordinary verified admission preserved
|
||||
PASS: actual action permission matrix unchanged; useful phase/intel/consciousness denials, stale reason cleared, native door denial preserved; no native UI claim
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 0.236s
|
||||
|
||||
OK
|
||||
PASS: actual aircraft cancellation/stage rules, ground-only completion, expiry/capture faults, input ownership and no physical shortcuts
|
||||
PASS: actual exact-instance single-use qualification, native-clock staleness/movement refusal, footprint bounds and ownership-safe ground/fault cleanup
|
||||
PASS: actual optional screenshot helper flag/identity/world/life revalidation, one queued attempt, delayed capture and mission/world cleanup; no native image/presentation claim
|
||||
PASS: actual yard diagnostic exact registered prison ownership, eight native-bound corner projections, no root-sibling escape, bounded duplicate/deep/wide hierarchy traversal; native projection/mask still unmeasured
|
||||
PASS: actual CurrentActor, SpawnPosition and observer-selection methods reject pending/unregistered/replaced/recycled/foreign/dead bodies; canonical selection/cycling, unconscious following and prison/geometry fallback preserved; native boundaries adapted
|
||||
PASS: actual native-map fixture ownership, cleanup and callback evidence fail closed; native execution remains separate
|
||||
PASS: actual shared-map callbacks bind exact map/config/root, preserve replacement UI/camera, defer focus, and remove only owned delayed work
|
||||
PASS: actual Intel/HQ methods and one-shot qualified readiness handoff deliver late actors once; duplicate readiness, accepted spawn, pickup/drop, death and original negative rolls do not mint assignments
|
||||
PASS: Workbench-only read accessor rejects unknown/nonserver identities; Chat observes all distinct ready crew before existing shell-depletion mutation
|
||||
PASS: actual native-event latch survives deselection, requires exact activity/order, detaches before cleanup, preserves failure/timeout and never infers completion from physical shot count
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 0.118s
|
||||
|
||||
OK
|
||||
PASS: actual refresh-fixture gates pin immutable observations/actual downlinks/original activity, reject unaccepted or relabelled commits, stop queueing early and require all queued contacts claimed before replay
|
||||
..........................
|
||||
----------------------------------------------------------------------
|
||||
Ran 26 tests in 0.224s
|
||||
|
||||
OK
|
||||
PASS: actual refresh scheduler/delivery/cancellation/projectile methods reject interrupted or replayed reports, retain committed ownership, preserve immutable snapshots/aim geometry and isolate stopped or replaced orders; native radio/crew behavior remains separate
|
||||
.....................
|
||||
----------------------------------------------------------------------
|
||||
Ran 21 tests in 0.039s
|
||||
|
||||
OK
|
||||
...........
|
||||
----------------------------------------------------------------------
|
||||
Ran 11 tests in 0.035s
|
||||
|
||||
OK
|
||||
PASS: actual full-salvo gates require eight fresh committed physical launches, natural closure and exact finite inventory; reject partial/extra/replayed events
|
||||
PASS: actual reason/availability guidance and refresh matrix; no-target copy omits cycling, hint retry retained, existing command/admin paths unchanged; native UI pending
|
||||
PASS: actual client camera policy closes immediately on healing or mission end; late unconscious metadata cannot keep the view active
|
||||
PASS: actual input dispatch, one press per edge, menu-held suppression; actual revival camera handoff with editor close/retry, missing camera, replacement body and stopped round
|
||||
PASS: first-person pose survives missing samples without drift, follows body motion/rotation, resumes fresh pose and cannot leak between targets
|
||||
................usage: test_native_package.py [-h] [--project {core}] --session SESSION
|
||||
--compat COMPAT [--steam STEAM]
|
||||
[--timeout TIMEOUT]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
[--silent-workbench | --exit-after-init]
|
||||
[--continue-on-failure]
|
||||
test_native_package.py: error: argument --exit-after-init: not allowed with argument --silent-workbench
|
||||
................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 48 tests in 14.466s
|
||||
|
||||
OK
|
||||
.......
|
||||
----------------------------------------------------------------------
|
||||
Ran 7 tests in 0.259s
|
||||
|
||||
OK
|
||||
..............
|
||||
----------------------------------------------------------------------
|
||||
Ran 14 tests in 2.681s
|
||||
|
||||
OK
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 0.313s
|
||||
|
||||
OK
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 0.059s
|
||||
|
||||
OK
|
||||
PASS: actual finite-courier age, token, exact ownership/callback and delivered-status admission reject stale, replayed, captured, canceled and ambiguous work
|
||||
PASS: actual native-gun ownership gate rejects player/foreign/replaced/dead occupant, faction change, missing seat and missing registry
|
||||
PASS: four-battery courier selection preserves pending trip/report/transfer identity, skips unavailable crews, and selects the next depleted battery without new stock
|
||||
PASS: 64 bounded distinct storage candidates; native gates, five-store barrier and finite-stock ownership retained; native placement pending
|
||||
PASS: 128 coarse+80 local refinement+169 airport grid candidates retain all native clearance gates; real terrain untested
|
||||
PASS: actual prison registration/crossing policies reject missing/duplicate/destroyed initial gate, incomplete ownership/scan, outside-only body, jumps and stale samples; physical exit independent of later gate damage
|
||||
PASS: actual prison door execution blocks direct AI/UI bypass while locked and delegates unchanged native action once after unlock and permission approval
|
||||
..................
|
||||
----------------------------------------------------------------------
|
||||
Ran 18 tests in 2.062s
|
||||
|
||||
OK
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 1.219s
|
||||
|
||||
OK
|
||||
PASS: native local-admission snapshot rejects changed life/key/body/participant/pending state; authentication is not simulated
|
||||
PASS: actual mortar report ledger prevents duplicate response after cancellation, expiry and interleaved reports; native scheduler separate
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 0.014s
|
||||
|
||||
OK
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 0.168s
|
||||
|
||||
OK
|
||||
PASS: actual aim renewal requires fresh same-report solve and original-command equivalence; native firing untested
|
||||
........
|
||||
----------------------------------------------------------------------
|
||||
Ran 8 tests in 0.013s
|
||||
|
||||
OK
|
||||
PASS: actual startup/generation/ownership/timeout/callback policies and native-counter wait; integration preserves one-shot allocation and pending-crew cancellation
|
||||
PASS: rapid mortar cycle counts eight physical rounds once and waits for native completion without a ninth aim or shell
|
||||
PASS: actual probe binds native events to fresh immutable commits, permits delayed emission, rejects unrelated expiry closure and premature stop
|
||||
PASS: actual noSplash Load body requires both explicit diagnostic and noSplash, preserves vanilla otherwise, and handles repeated Load/reload
|
||||
PASS: actual fixture method permits one native resilience operation and restores either prior protection state, including failure paths
|
||||
PASS: actual safe-exit boundary policy, native action/door selection, stop/cache cleanup, separate old mechanism and opt-in runner contracts
|
||||
PASS: actual one-shot deadline/stability policies and preparation ownership, fuel, cleanup integration contracts
|
||||
PASS: pending-only cancellation retires copied instance intent, preserves physical/control actors, and stops once
|
||||
PASS: actual short-cast hit, upward support normal, no self support and no dynamic support
|
||||
PASS: actual committed-ticket structure preserves reservation after death, rejects missing bodies/seats and ends at observation expiry
|
||||
PASS: actual pending callback expires an empty-server reservation once, preserves its full lease, and cancels on completion/Stop
|
||||
PASS: rescue lease expiry/reordering, fresh five-second settling, independent rescue ranges and grounded upright aircraft motion boundaries
|
||||
PASS: requested guard population requires complete physical conscious membership before deadline
|
||||
......................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 38 tests in 0.021s
|
||||
|
||||
OK
|
||||
..........
|
||||
----------------------------------------------------------------------
|
||||
Ran 10 tests in 0.003s
|
||||
|
||||
OK
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 1.005s
|
||||
|
||||
OK
|
||||
PASS: authored radio/optional-site bounds and physical horizontal/vertical boundaries
|
||||
...
|
||||
----------------------------------------------------------------------
|
||||
Ran 3 tests in 0.001s
|
||||
|
||||
OK
|
||||
PASS: all phase transitions, terminal states, defence boundaries, stale/future observations, intel discovery
|
||||
PASS: Core document-discovery alias, companion-only alias exclusion, strict integer parsing and setting boundaries
|
||||
PASS: player admission phase/life/identity/controller matrix, preload displacement limits, pending reservation overlap and owner exclusion
|
||||
PASS: actual gate and pre-inventory document guards reject noncanonical/faction/life/disconnect/server mismatches; canonical reconnect and unconscious carried intel preserved
|
||||
PASS: WB-only native fault injection restores both body mutations before checks/returns and requires eligible held-document negative/positive proof; runtime pending
|
||||
PASS: actual discovery coordinates disclose stable 500m cell center until identified, then exact point, including negative/boundary coordinates
|
||||
PASS: actual local admission context/selection/latch, same-round late identity, replacement/mode failure, fresh round and untouched network path
|
||||
PASS: prison search packs 16 unique 3m-spaced inside-wall centers, prioritizes inner/edge positions and never bypasses clearance
|
||||
PASS: physical body occupancy rejects overlap, permits diagonal neighboring slots, and excludes own body and unrelated broad-phase bounds
|
||||
PASS: recovery state excludes dead bodies, preserves reservations, requires controlled rescuers and distinguishes empty/all-dead teams
|
||||
PASS: actual native-probe lifecycle/counters with doubled boundaries; fixed deadline, exact seven sources, true forwarding, async close, no late updates and selector wiring
|
||||
PASS: actual one-argument adapter, explicit refresh, native initialization flag, duplicate/blood ownership, rebind/detach/destruction and foreign-mode transparency
|
||||
PASS: authority-scoped slow recovery, bleeding pauses resilience, medicine unaffected, player-only lethal damage cap without healing
|
||||
PASS: bleeding severity survives low health, retains armour, causes no knockout and restores the actual damage/blood-loss cap
|
||||
PASS: viable passive recovery survives native delay; bleeding, disabled/full effects and low-blood resilience do not block defeat
|
||||
PASS: every minute round-trips, malformed clocks rejected, defaults limited to added keys, progression bounds and day duration aligned
|
||||
PASS: native settings policy matches calendar boundaries 1900-2050, date/profile parsing is strict, authored defaults preserve old worlds
|
||||
PASS: view-distance bounds, default, save failures, command parsing, temporary ownership, server/platform limits and lifecycle wiring
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 0.594s
|
||||
|
||||
OK
|
||||
.
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.386s
|
||||
|
||||
OK
|
||||
PASS: radio authorization/discovery/phase matrix, exact distance limits, defender handover, interruption, empty samples, clock regression and duration boundary
|
||||
PASS: actual radio feedback policy, initial wait, interruption persistence, resumption and terminal/new-round reset
|
||||
...............
|
||||
----------------------------------------------------------------------
|
||||
Ran 15 tests in 2.243s
|
||||
|
||||
OK
|
||||
..............................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 46 tests in 0.912s
|
||||
|
||||
OK
|
||||
PASS: legacy values preserved, unsupported fields reported, invalid data rejected, existing files protected, runtime bounds aligned
|
||||
PASS: failed initial settings promotion leaves defaults writable after restart; prior/fallback saves survive copy, readback and mismatch faults
|
||||
PASS: real-carrier shortest-hop paths, 500m boundary, three-hop cap, no-manpack failure, three-second originating-call interruption, unchanged downlink hop timing, completed-hop survival, interrupted final uplink, radio loss, capture, motion, timeout, reset and network bounds
|
||||
PASS: migrated aircraft availability policy (controller migration still pending)
|
||||
PASS: migrated salvo offsets are deterministic, spread and bounded (native firing and complete salvo qualification are separate)
|
||||
PASS: 37 prefabs have unique saved root entity IDs
|
||||
PASS: 299 original script entries tracked across 18 unfinished responsibilities
|
||||
PASS: 1 isolated addons, acyclic dependencies, 60 unique resources, exact vanilla/authorized external references
|
||||
All repository checks passed. Native compilation and multiplayer tests are separate requirements.
|
||||
Suite exit 0 programs 309 unchanged True
|
||||
Reference in New Issue
Block a user