Files
EscapistsReintroduced/docs/evidence/crash-start-2026-10-03/proposals/native-hold/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

88 lines
4.6 KiB
Diff

--- a/addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c
@@ -4,6 +4,10 @@
modded class SCR_ChimeraAIAgent
{
protected ERR_GameMode m_ERRPrisonMode;
+#ifdef WORKBENCH
+ // Observe only this mission hold; native distance LOD may independently deactivate AI.
+ bool ERR_PrisonHeldForTest() { return m_ERRPrisonMode != null; }
+#endif
void ERR_HoldForPrison()
{
--- a/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c
+++ b/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c
@@ -541,6 +541,29 @@
PrintFormat("ERR PRISON AI: checked=%1 active=%2", checked, active);
return checked > 0;
}
+ protected bool ERR_CrashPrisonHoldReleased()
+ {
+ if (!Forces() || Forces().Count() > 256) return false;
+ int checked, held, active, dormant;
+ for (int i = 0; i < Forces().Count(); i++)
+ {
+ ERR_ForceRecord record = Forces().At(i);
+ if (!record || !record.Group) continue;
+ array<AIAgent> agents = {};
+ record.Group.GetAgents(agents);
+ if (agents.Count() > 128) return false;
+ foreach (AIAgent original : agents)
+ {
+ SCR_ChimeraAIAgent agent = SCR_ChimeraAIAgent.Cast(original);
+ if (!agent || !agent.GetControlledEntity()) return false;
+ checked++;
+ if (agent.ERR_PrisonHeldForTest()) held++;
+ if (agent.IsAIActivated()) active++; else dormant++;
+ }
+ }
+ PrintFormat("ERR CRASH PRISON HOLD: checked=%1 held=%2 active=%3 dormant=%4; native activation is independent", checked, held, active, dormant);
+ return checked > 0 && held == 0;
+ }
protected void ERR_EndScreenshot() { System.MakeScreenshot("$logs:err-result"); }
protected void ERR_BeginProtectedDeathProbe(float now)
{
@@ -906,7 +929,7 @@
}
if (m_ERRTestCrashStart)
{
- if (!ERR_Check(!m_ERRCrashEnteredPrison && ERR_PrisonAIState(true), "crash start never enters prison and existing enemies are active")) return;
+ if (!ERR_Check(!m_ERRCrashEnteredPrison && ERR_CrashPrisonHoldReleased(), "crash start never enters prison and existing enemies have no prison hold")) return;
if (!ERR_NativeCrashStartProbe.Run(this, m_ERRSmokePlayer)) { m_ERRSmokeDone = true; GetGame().GetCallqueue().Remove(ERR_SmokeTick); return; }
}
else
--- a/tests/test_prison_ai.py
+++ b/tests/test_prison_ai.py
@@ -1,6 +1,8 @@
"""Run the production prison hold/release callbacks with engine boundaries stubbed."""
from policy_harness import ROOT, body, run
-source = ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c'
+from pathlib import Path
+import os
+source = Path(os.environ.get('ERR_PRISON_AI_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c'))
def cpp(signature):
s=body(source,signature).replace('protected ','')
for ns in ('Replication','SCR_Global','ERR_GameMode','ERR_Phase','ERR_Actors','SCR_FactionManager'):
@@ -32,16 +34,17 @@
struct Agent {ERR_GameMode* m_ERRPrisonMode=nullptr;Actor* actor=nullptr;bool pending=false,active=true;int starts=0;
Actor* GetControlledEntity(){return actor;}bool IsPlayerPending_S(){return pending;}
void DeactivateAI(){active=false;}void ActivateAI(){active=true;starts++;}
-'''+cpp('void ERR_HoldForPrison(')+cpp('protected void ERR_PrisonUnsubscribe(')+cpp('protected void ERR_PrisonPhaseChanged(')+cpp('protected void ERR_PrisonStopped(')+r'''
+'''+cpp('bool ERR_PrisonHeldForTest(')+cpp('void ERR_HoldForPrison(')+cpp('protected void ERR_PrisonUnsubscribe(')+cpp('protected void ERR_PrisonPhaseChanged(')+cpp('protected void ERR_PrisonStopped(')+r'''
};
int main(){
for(int phase=0;phase<=1;phase++){
ERR_GameMode mode;mode.phase=phase;ERR_GameMode::current=&mode;Actor actor;Agent a;a.actor=&actor;
- a.ERR_HoldForPrison();assert(!a.active&&mode.phases.callbacks.size()==1&&mode.stops.callbacks.size()==1);
+ a.ERR_HoldForPrison();assert(a.ERR_PrisonHeldForTest());assert(!a.active&&mode.phases.callbacks.size()==1&&mode.stops.callbacks.size()==1);
a.active=true;a.ERR_HoldForPrison();assert(!a.active&&mode.phases.inserted==1);
a.ERR_PrisonPhaseChanged(0,1);assert(!a.active&&a.m_ERRPrisonMode==&mode);
- mode.phase=2;a.ERR_PrisonPhaseChanged(1,2);assert(a.active&&a.starts==1&&!a.m_ERRPrisonMode&&mode.phases.callbacks.empty()&&mode.stops.callbacks.empty());
+ mode.phase=2;a.ERR_PrisonPhaseChanged(phase,2);assert(a.active&&a.starts==1&&!a.m_ERRPrisonMode&&mode.phases.callbacks.empty()&&mode.stops.callbacks.empty());
a.ERR_HoldForPrison();assert(a.active&&mode.phases.inserted==1);
+ assert(!a.ERR_PrisonHeldForTest());a.DeactivateAI();assert(!a.active&&!a.ERR_PrisonHeldForTest());
}
for(int fault=0;fault<8;fault++){
ERR_GameMode mode;ERR_GameMode::current=&mode;Actor actor;Agent a;a.actor=&actor;