Fix convoy coordination, AI combat and spectator recovery

This commit is contained in:
2026-09-27 22:28:07 +02:00
parent 2551d171b3
commit c5629f7f4f
108 changed files with 4337 additions and 282 deletions
+80
View File
@@ -0,0 +1,80 @@
"""Execute production input dispatch and recovery with native camera/editor doubles."""
from policy_harness import ROOT,body,run
p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c'
s=body(p,'protected void ERR_UpdateRecoveryCamera(').replace('protected ','').replace('ERR_GameMode mode','ERR_GameMode* mode').replace('null','nullptr')
for name in ('ERR_Actors','SCR_EditorManagerEntity','LogLevel'):
s=s.replace(name+'.',name+'::')
for name in ('SCR_EditorManagerEntity','CameraManager','CameraBase'):
s=s.replace(name+' ',name+'* ')
for name in ('mode','editor','manager'):
s=s.replace(name+'.',name+'->')
s=s.replace('GetGame().','GetGame()->').replace('GetWorld().','GetWorld()->')
run(r'''#include <cassert>
struct Actor {bool alive=true,conscious=true;};using IEntity=Actor*;
struct ERR_Actors {static bool Alive(IEntity a){return a&&a->alive;}static bool CanFight(IEntity a){return Alive(a)&&a->conscious;}};
struct ERR_GameMode {bool active=true;bool Active(){return active;}};
struct SCR_EditorManagerEntity {bool opened=false,canClose=true;int closes=0;static SCR_EditorManagerEntity* current;static SCR_EditorManagerEntity* GetInstance(){return current;}bool IsOpened(){return opened;}void Close(bool){closes++;if(canClose)opened=false;}} editor;
SCR_EditorManagerEntity* SCR_EditorManagerEntity::current=&editor;
struct CameraBase{} player,other;
struct CameraManager {CameraBase* current=&other;int sets=0;void SetCamera(CameraBase* c){current=c;sets++;}CameraBase* CurrentCamera(){return current;}} manager;
struct World {float now=0;float GetWorldTime(){return now;}} world;
struct Game {World* GetWorld(){return &world;}CameraManager* GetCameraManager(){return &manager;}} game;
Game* GetGame(){return &game;}namespace LogLevel {enum{WARNING};}int warnings=0;void Print(const char*,int){warnings++;}
struct Controller {
IEntity controlled=nullptr,m_ERRRecoveryBody=nullptr;float m_ERRRecoveryDeadline=-1;CameraBase* camera=&player;
IEntity GetControlledEntity(){return controlled;}CameraBase* GetPlayerCamera(){return camera;}
'''+s+r'''
};
int main(){
ERR_GameMode mode;Actor body,replacement;Controller c;c.controlled=&body;
// Healthy admins retain their camera: restoration belongs only to recovery.
c.ERR_UpdateRecoveryCamera(&mode);assert(manager.sets==0&&editor.closes==0);
body.conscious=false;c.ERR_UpdateRecoveryCamera(&mode);assert(c.m_ERRRecoveryBody==&body&&manager.sets==0);
// Recovery works with NO observer-camera instance and a different active camera.
editor.opened=true;body.conscious=true;c.ERR_UpdateRecoveryCamera(&mode);
assert(editor.closes==1&&!editor.opened&&manager.current==&player&&!c.m_ERRRecoveryBody);
manager.current=&other;c.ERR_UpdateRecoveryCamera(&mode);assert(manager.current==&other); // no permanent camera lock
// An asynchronously closing editor retains the pending recovery; next tick completes.
body.conscious=false;c.ERR_UpdateRecoveryCamera(&mode);body.conscious=true;editor.opened=true;editor.canClose=false;
c.ERR_UpdateRecoveryCamera(&mode);assert(c.m_ERRRecoveryBody==&body);
editor.canClose=true;c.ERR_UpdateRecoveryCamera(&mode);assert(!c.m_ERRRecoveryBody&&manager.current==&player);
// Wait for late camera creation, then return once.
body.conscious=false;c.ERR_UpdateRecoveryCamera(&mode);body.conscious=true;c.camera=nullptr;
c.ERR_UpdateRecoveryCamera(&mode);assert(c.m_ERRRecoveryBody==&body);
c.camera=&player;c.ERR_UpdateRecoveryCamera(&mode);assert(!c.m_ERRRecoveryBody);
// Refused editor close is bounded, warning instead of permanently stealing input.
body.conscious=false;c.ERR_UpdateRecoveryCamera(&mode);body.conscious=true;editor.opened=true;editor.canClose=false;
c.ERR_UpdateRecoveryCamera(&mode);world.now=10001;c.ERR_UpdateRecoveryCamera(&mode);assert(!c.m_ERRRecoveryBody&&warnings==1);
editor.opened=false;editor.canClose=true;
// Changed possession / stopped round must not return a stale casualty's camera.
body.conscious=false;c.ERR_UpdateRecoveryCamera(&mode);c.controlled=&replacement;int before=manager.sets;
c.ERR_UpdateRecoveryCamera(&mode);assert(!c.m_ERRRecoveryBody&&manager.sets==before);
c.controlled=&body;c.ERR_UpdateRecoveryCamera(&mode);mode.active=false;body.conscious=true;
c.ERR_UpdateRecoveryCamera(&mode);assert(!c.m_ERRRecoveryBody&&manager.sets==before);
}
''')
input_body=body(p,'void Input(').replace('SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());','SCR_PlayerController* controller = &pc;').replace('controller.','controller->').replace('ERR_ObserverRules.','ERR_ObserverRules::')
run(r'''#include <cassert>
struct ERR_ObserverRules {
'''+body(p,'static int Cycle(')+body(p,'static bool Toggle(')+r'''
};
struct SCR_PlayerController {int calls=0,direction=0;void ERR_CycleObserver(int d){calls++;direction=d;}} pc;
struct Follow {bool m_LeftDown=false,m_RightDown=false,m_EnterDown=false,m_PlayerView=false;
'''+input_body+r'''
};
int main(){
Follow f;f.Input(true,true,false,false);assert(pc.calls==1&&pc.direction==-1);
f.Input(true,true,false,false);assert(pc.calls==1);
f.Input(true,false,false,false);f.Input(true,false,true,false);assert(pc.calls==2&&pc.direction==1);
f.Input(true,true,true,false);assert(pc.calls==2);
f.Input(false,false,false,true);f.Input(true,false,false,true);assert(!f.m_PlayerView); // menu-held Enter
f.Input(true,false,false,false);f.Input(true,false,false,true);assert(f.m_PlayerView);
f.Input(true,false,false,true);assert(f.m_PlayerView);
f.Input(true,false,false,false);f.Input(true,false,false,true);assert(!f.m_PlayerView);
}
''')
config=(ROOT/'addons/core/Configs/ERR/ObserverInput.conf').read_text()
for key in ('KC_LEFT','KC_RIGHT','KC_RETURN','KC_NUMPADENTER'): assert f'keyboard:{key}' in config
assert 'chimeraInputCommon.conf' in config and 'ActionContext/ManualCameraContext.conf' in config
assert 'Debug.KeyState' not in p.read_text()
print('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')