Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
219 lines
13 KiB
Diff
219 lines
13 KiB
Diff
--- /dev/null
|
|
+++ b/addons/core/Prefabs/ERR/CrashSite.et
|
|
@@ -0,0 +1,7 @@
|
|
+GenericEntity {
|
|
+ ID "46B3FAD3A89A4EA3"
|
|
+ components {
|
|
+ ERR_LocationComponent "{606DF66235E34CA9}" { m_Kind 0 }
|
|
+ RplComponent "{B6AB8FE43FE143B4}" { Streamable Disabled }
|
|
+ }
|
|
+}
|
|
--- /dev/null
|
|
+++ b/addons/core/Prefabs/ERR/CrashSite.et.meta
|
|
@@ -0,0 +1,7 @@
|
|
+MetaFileClass {
|
|
+ Name "{174211DDE2F6422B}Prefabs/ERR/CrashSite.et"
|
|
+ Configurations {
|
|
+ ResourceClass PC {}
|
|
+ ResourceClass HEADLESS : PC {}
|
|
+ }
|
|
+}
|
|
--- /dev/null
|
|
+++ b/addons/core/Prefabs/ERR/CrashWreck.et
|
|
@@ -0,0 +1,9 @@
|
|
+GenericEntity : "{74DCE1AEC6832E18}Prefabs/Props/Wrecks/UH1H_wreck/UH1H_wreck_static.et" {
|
|
+ ID "BDBAE020F10A41DF"
|
|
+ components {
|
|
+ Hierarchy "{BD920DE465D04435}" {}
|
|
+ RplComponent "{7E4852E4567E49D6}" { Streamable Disabled }
|
|
+ }
|
|
+ coords 0 0 0
|
|
+ angles 0 0 0
|
|
+}
|
|
--- /dev/null
|
|
+++ b/addons/core/Prefabs/ERR/CrashWreck.et.meta
|
|
@@ -0,0 +1,7 @@
|
|
+MetaFileClass {
|
|
+ Name "{486DDD0B811047EE}Prefabs/ERR/CrashWreck.et"
|
|
+ Configurations {
|
|
+ ResourceClass PC {}
|
|
+ ResourceClass HEADLESS : PC {}
|
|
+ }
|
|
+}
|
|
--- /dev/null
|
|
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_CrashSiteResources.c
|
|
@@ -0,0 +1,12 @@
|
|
+// Owned static aftermath; no aircraft, crew, flight or inventory systems.
|
|
+class ERR_CrashSiteResources
|
|
+{
|
|
+ static ResourceName SitePrefab() { return "{174211DDE2F6422B}Prefabs/ERR/CrashSite.et"; }
|
|
+ static ResourceName WreckPrefab() { return "{486DDD0B811047EE}Prefabs/ERR/CrashWreck.et"; }
|
|
+ static bool Bounds(out vector minimum, out vector maximum)
|
|
+ {
|
|
+ // Fail closed until the exact owned wrapper has native measured bounds.
|
|
+ minimum = vector.Zero; maximum = vector.Zero;
|
|
+ return false;
|
|
+ }
|
|
+}
|
|
--- /dev/null
|
|
+++ b/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeCrashGeometryProbe.c
|
|
@@ -0,0 +1,82 @@
|
|
+// Explicit WORKBENCH measurement only. Never moves or seats a player/AI.
|
|
+#ifdef WORKBENCH
|
|
+class ERR_NativeCrashGeometryProbe
|
|
+{
|
|
+ protected static bool Finite(vector value)
|
|
+ {
|
|
+ for (int axis = 0; axis < 3; axis++)
|
|
+ if (value[axis] != value[axis] || Math.AbsFloat(value[axis]) > 1000000) return false;
|
|
+ return true;
|
|
+ }
|
|
+ protected static bool Measure(IEntity root, out vector minimum, out vector maximum)
|
|
+ {
|
|
+ minimum = "1000000 1000000 1000000"; maximum = "-1000000 -1000000 -1000000";
|
|
+ if (!root || !root.GetVObject() || !root.FindComponent(RplComponent)) return false;
|
|
+ Physics physics = root.GetPhysics();
|
|
+ if (!physics || physics.IsDynamic()) return false;
|
|
+ array<IEntity> nodes = {root};
|
|
+ int meshes;
|
|
+ for (int index = 0; index < nodes.Count(); index++)
|
|
+ {
|
|
+ IEntity entity = nodes[index];
|
|
+ if (!entity) return false;
|
|
+ Physics childPhysics = entity.GetPhysics();
|
|
+ if (childPhysics && childPhysics.IsDynamic()) return false;
|
|
+ if (entity.GetVObject())
|
|
+ {
|
|
+ vector low, high; entity.GetBounds(low, high);
|
|
+ if (!Finite(low) || !Finite(high)) return false;
|
|
+ for (int axis = 0; axis < 3; axis++)
|
|
+ if (high[axis] <= low[axis] || high[axis] - low[axis] > 40) return false;
|
|
+ meshes++;
|
|
+ for (int x = 0; x < 2; x++)
|
|
+ {
|
|
+ for (int y = 0; y < 2; y++)
|
|
+ {
|
|
+ for (int z = 0; z < 2; z++)
|
|
+ {
|
|
+ vector corner = low;
|
|
+ if (x) corner[0] = high[0];
|
|
+ if (y) corner[1] = high[1];
|
|
+ if (z) corner[2] = high[2];
|
|
+ vector local = root.CoordToLocal(entity.CoordToParent(corner));
|
|
+ if (!Finite(local)) return false;
|
|
+ for (int component = 0; component < 3; component++)
|
|
+ {
|
|
+ minimum[component] = Math.Min(minimum[component], local[component]);
|
|
+ maximum[component] = Math.Max(maximum[component], local[component]);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ IEntity child = entity.GetChildren();
|
|
+ while (child)
|
|
+ {
|
|
+ if (nodes.Count() >= 32 || nodes.Contains(child)) return false;
|
|
+ nodes.Insert(child); child = child.GetSibling();
|
|
+ }
|
|
+ }
|
|
+ if (!meshes || !Finite(minimum) || !Finite(maximum)) return false;
|
|
+ for (int dimension = 0; dimension < 3; dimension++)
|
|
+ if (maximum[dimension] <= minimum[dimension] || maximum[dimension] - minimum[dimension] > 40) return false;
|
|
+ PrintFormat("ERR CRASH GEOMETRY: nodes=%1 meshes=%2 static=1 replicatedRoot=1 minX=%3 minY=%4 minZ=%5", nodes.Count(), meshes, minimum[0], minimum[1], minimum[2]);
|
|
+ PrintFormat("ERR CRASH GEOMETRY: maxX=%1 maxY=%2 maxZ=%3 resource=%4", maximum[0], maximum[1], maximum[2], ERR_CrashSiteResources.WreckPrefab());
|
|
+ return true;
|
|
+ }
|
|
+ static bool Run()
|
|
+ {
|
|
+ if (!Replication.IsServer()) return false;
|
|
+ // At high altitude, independent of the pending crash-start setup and terrain.
|
|
+ // This static prop is the only owned probe entity; every return after creation deletes it.
|
|
+ IEntity wreck = ERR_Actors.Spawn(ERR_CrashSiteResources.WreckPrefab(), "1000 2500 1000");
|
|
+ if (!wreck) return false;
|
|
+ vector minimum, maximum;
|
|
+ bool measured = Measure(wreck, minimum, maximum);
|
|
+ ERR_Actors.DeleteSpawned(wreck);
|
|
+ bool deleted = wreck == null;
|
|
+ PrintFormat("ERR CRASH GEOMETRY CLEANUP: measured=%1 deleted=%2; no terrain placement or multiplayer visibility claim", measured, deleted);
|
|
+ return measured && deleted;
|
|
+ }
|
|
+}
|
|
+#endif
|
|
--- a/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c
|
|
+++ b/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c
|
|
@@ -29,6 +29,7 @@
|
|
return camps > 0 && seen.Count() == camps * 2;
|
|
}
|
|
|
|
+ protected bool m_ERRTestCrashGeometry;
|
|
protected bool m_ERRTestPatrolHQ;
|
|
protected ref ERR_NativePatrolProbe m_ERRHQPatrolMovement;
|
|
protected ref ERR_NativePatrolHQProbe m_ERRHQPatrolOrder;
|
|
@@ -137,6 +138,7 @@
|
|
m_ERRTestMortarCourier = outcome == "mortar-courier";
|
|
m_ERRTestMortarSalvo = outcome == "mortar-salvo" || outcome == "mortar-refresh";
|
|
m_ERRTestMortarTransfer = outcome == "mortar-transfer";
|
|
+ m_ERRTestCrashGeometry = outcome == "crash-geometry";
|
|
m_ERRTestPatrolHQ = outcome == "patrol-hq";
|
|
m_ERRTestPlayerDeath = outcome == "player-death" || m_ERRTestRadioNetwork || m_ERRTestHQResponse || m_ERRTestMortar || m_ERRTestHQMemory || m_ERRTestHQBody || m_ERRTestConvoy || m_ERRTestInfantryTransfer;
|
|
if (m_ERRTestLocalHandoff || m_ERRTestMountedAmmo || m_ERRTestAlarmHearing) m_ERRTestPlayerDeath = true;
|
|
@@ -322,6 +324,14 @@
|
|
protected void ERR_SmokePhase(ERR_Phase previous, ERR_Phase current)
|
|
{
|
|
if (m_ERRSmokeDone || !ERR_MissionRules.Terminal(current)) return;
|
|
+ if (m_ERRTestCrashGeometry)
|
|
+ {
|
|
+ if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 3501, "static wreck geometry measured and probe deleted before explicit fixture reset")) return;
|
|
+ m_ERRSmokeDone = true;
|
|
+ GetGame().GetCallqueue().Remove(ERR_SmokeTick);
|
|
+ Print("ERR SMOKE COMPLETE: owned static crash wreck native bounds and local cleanup; terrain placement and multiplayer unqualified");
|
|
+ return;
|
|
+ }
|
|
if (m_ERRTestSupplyTravel)
|
|
{
|
|
if (!ERR_Check(current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 3201, "production supply travel checked before explicit fixture reset")) return;
|
|
@@ -673,6 +683,13 @@
|
|
if (!ERR_Check(CanStartSpawnPreload() && m_ERRSpawnPreloadRequested && !m_SpawnPreload, "prisoner admitted only after enabled native spawn preload finishes")) return;
|
|
m_ERRSmokePlayer = players[0];
|
|
if (!ERR_CheckRoster() || !ERR_NativeContractsProbe.Run(this, m_ERRSmokePlayer) || !ERR_CheckCompanionCommands()) return;
|
|
+ if (m_ERRTestCrashGeometry)
|
|
+ {
|
|
+ if (!ERR_Check(ERR_NativeCrashGeometryProbe.Run(), "owned static wreck finite aggregate bounds, static physics and local deletion")) return;
|
|
+ m_ERRSmokeStep = 3501;
|
|
+ Transition(ERR_Phase.DEFEAT);
|
|
+ return;
|
|
+ }
|
|
if (m_ERRTestSupplyTravel) { ERR_BeginSupplyTravel(now); return; }
|
|
if (m_ERRTestAircraftField)
|
|
{
|
|
--- a/tools/headless_workbench.py
|
|
+++ b/tools/headless_workbench.py
|
|
@@ -14,7 +14,7 @@
|
|
p.add_argument('--external-addons', type=Path, help='Installed RHS/WCS/BMP source root; expose only audited closure in a separate Chat profile')
|
|
p.add_argument('--compat', type=Path, required=True, help='Disposable cloned Proton compatdata directory outside Steam')
|
|
p.add_argument('--profile', type=Path, required=True, help='Separate disposable successor Workbench profile')
|
|
-p.add_argument('--smoke', choices=['normal-escape-route', 'escape-route', 'optional-site-skip', 'infantry-combat', 'medical-combat', 'hunter-faction', 'unconscious-camera', '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-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('--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')
|
|
@@ -35,6 +35,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 == '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')
|
|
--- a/docs/vanilla-resources.json
|
|
+++ b/docs/vanilla-resources.json
|
|
@@ -104,6 +104,7 @@
|
|
"Prefabs/Props/Furniture/Table_01.et": "53506DCB398C7EBE",
|
|
"Prefabs/Props/Military/Antennas/Antenna_FIA_02/Antenna_FIA_02_base.et": "B3949F6D215A9F78",
|
|
"Prefabs/Props/Military/Sandbags/Sandbag_01_short_burlap.et": "CFFEB23FBEA00322",
|
|
+ "Prefabs/Props/Wrecks/UH1H_wreck/UH1H_wreck_static.et": "74DCE1AEC6832E18",
|
|
"Prefabs/Structures/BuildingParts/Doors/DoorSet_Base.et": "87CADB5ADC989993",
|
|
"Prefabs/Structures/BuildingParts/Doors/Door_MetalGate_01_Short_LEFT_EXT_ST_B.et": "34B08027DBDE322A",
|
|
"Prefabs/Structures/BuildingParts/Doors/Door_Metal_A_RIGHT_EXT_COV_B.et": "67F17696FF278AE0",
|