Files
EscapistsReintroduced/docs/evidence/inventory-bmp-checks-2026-10-03/inventory/fix.patch
T
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

351 lines
30 KiB
Diff

--- /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')