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:
@@ -59,7 +59,7 @@ 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;
|
||||
struct Follow {bool m_LeftDown=false,m_RightDown=false,m_EnterDown=false,m_PlayerView=false;int m_InputResumeFrames=0;
|
||||
'''+input_body+r'''
|
||||
};
|
||||
int main(){
|
||||
@@ -71,10 +71,123 @@ int main(){
|
||||
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);
|
||||
// Inactive chat context masks held keys as zero; submission must not become
|
||||
// a new camera press when the context resumes (including its first zero frame).
|
||||
f.Input(false,false,false,false);
|
||||
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);
|
||||
int before=pc.calls;
|
||||
f.Input(false,false,false,false);
|
||||
f.Input(true,false,false,false);
|
||||
f.Input(true,true,false,false);f.Input(true,true,false,false);assert(pc.calls==before);
|
||||
f.Input(true,false,false,false);f.Input(true,true,false,false);assert(pc.calls==before+1);
|
||||
}
|
||||
''')
|
||||
# Execute the actual chat-manager state, capture helper, and pre-activation gate.
|
||||
# Chat contexts expire independently of widget focus; open panels are authoritative.
|
||||
def camera_ui_cpp(source):
|
||||
import re
|
||||
source=source.replace('override protected ', '').replace('super.', 'Base::').replace('null','nullptr')
|
||||
# Enforce initializes local booleans to false; C++ otherwise leaves them undefined.
|
||||
source=re.sub(r'\bbool (\w+);',r'bool \1=false;',source)
|
||||
source=source.replace('GetGame().', 'GetGame()->').replace('GetInputManager().', 'GetInputManager()->').replace('GetWorkspace().', 'GetWorkspace()->')
|
||||
for typ in ('SCR_ChatPanelManager','SCR_ChatPanel','EditBoxWidget','ERR_ObserverChatInput'):
|
||||
source=source.replace(typ+'.',typ+'::')
|
||||
source=source.replace('foreach (SCR_ChatPanel panel : m_aChatPanels)', 'for (SCR_ChatPanel panel : m_aChatPanels)')
|
||||
pointer_names=[]
|
||||
for typ in ('SCR_ChatPanelManager','SCR_ChatPanel','Widget','InputManager'):
|
||||
pointer_names+=re.findall(r'\b'+typ+r' (\w+)',source)
|
||||
source=re.sub(r'\b'+typ+r' (\w+)',typ+r'* \1',source)
|
||||
for name in pointer_names:
|
||||
source=re.sub(r'\b'+name+r'\.',name+'->',source)
|
||||
source=source.replace('FindHandler(SCR_ChatPanel)', 'FindHandler()')
|
||||
return source
|
||||
manager_method=camera_ui_cpp(body(p,'bool ERR_HasOpenChatPanel('))
|
||||
capture=camera_ui_cpp(body(p,'static bool Capture('))
|
||||
gate=camera_ui_cpp(body(p,'override protected bool IsDisabledByMenu('))
|
||||
ui_prelude=r'''#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
struct SCR_ChatPanel {bool open=false;bool IsOpen(){return open;}static SCR_ChatPanel*Cast(SCR_ChatPanel*p){return p;}};
|
||||
struct Widget {Widget*parent=nullptr;bool edit=false;SCR_ChatPanel*handler=nullptr;Widget*GetParent(){return parent;}SCR_ChatPanel*FindHandler(){return handler;}};
|
||||
struct EditBoxWidget {static Widget*Cast(Widget*w){return w&&w->edit?w:nullptr;}};
|
||||
struct InputManager {bool chat=false;int renewals=0;
|
||||
bool IsContextActive(const char*name){assert(std::string(name)=="ChatContext");return chat;}
|
||||
void ActivateContext(const char*name,int durationMs){assert(std::string(name)=="ChatContext"&&durationMs==1);chat=true;renewals++;}
|
||||
void Expire(){chat=false;}
|
||||
} inputs;
|
||||
struct Workspace {Widget*focus=nullptr;Widget*GetFocusedWidget(){return focus;}} workspace;
|
||||
struct Game {InputManager*GetInputManager(){return &inputs;}Workspace*GetWorkspace(){return &workspace;}} game;
|
||||
Game*GetGame(){return &game;}
|
||||
struct SCR_ChatPanelManager {static SCR_ChatPanelManager*current;std::vector<SCR_ChatPanel*>m_aChatPanels;
|
||||
static SCR_ChatPanelManager*GetInstance(){return current;}
|
||||
'''+manager_method+r'''
|
||||
} chatManager;
|
||||
SCR_ChatPanelManager*SCR_ChatPanelManager::current=&chatManager;
|
||||
struct ERR_ObserverChatInput {
|
||||
'''+capture+r'''
|
||||
};
|
||||
struct Base {bool menu=false;bool IsDisabledByMenu(){return menu;}};
|
||||
'''
|
||||
ui_tests=r'''
|
||||
int main(){
|
||||
Camera c;SCR_ChatPanel registered,unregistered;
|
||||
chatManager.m_aChatPanels={nullptr,®istered};
|
||||
assert(!chatManager.ERR_HasOpenChatPanel());assert(!c.IsDisabledByMenu());assert(inputs.renewals==0);
|
||||
// A dead observer can retain open chat after the native HUD stops renewing it.
|
||||
// No focus or previously active context is required for registered open chat.
|
||||
registered.open=true;assert(chatManager.ERR_HasOpenChatPanel());
|
||||
assert(c.IsDisabledByMenu());assert(inputs.chat&&inputs.renewals==1);
|
||||
inputs.Expire();assert(c.IsDisabledByMenu());assert(inputs.chat&&inputs.renewals==2);
|
||||
// Native menus retain priority; a camera must not activate input behind them.
|
||||
inputs.Expire();c.menu=true;assert(c.IsDisabledByMenu());assert(!inputs.chat&&inputs.renewals==2);c.menu=false;
|
||||
registered.open=false;assert(!chatManager.ERR_HasOpenChatPanel());assert(!c.IsDisabledByMenu());
|
||||
// An unregistered still-open panel is discoverable through focus ancestry.
|
||||
Widget panelWidget,child,edit,ordinary;panelWidget.handler=&unregistered;child.parent=&panelWidget;
|
||||
unregistered.open=true;workspace.focus=&child;
|
||||
assert(c.IsDisabledByMenu());assert(inputs.chat&&inputs.renewals==3);
|
||||
inputs.Expire();SCR_ChatPanelManager::current=nullptr;
|
||||
assert(c.IsDisabledByMenu());assert(inputs.chat&&inputs.renewals==4);
|
||||
// A closed panel cannot renew chat, even if it still owns focused widgets.
|
||||
unregistered.open=false;inputs.Expire();assert(!c.IsDisabledByMenu());assert(inputs.renewals==4);
|
||||
edit.edit=true;edit.parent=&panelWidget;workspace.focus=&edit;
|
||||
assert(c.IsDisabledByMenu());assert(!inputs.chat&&inputs.renewals==4);
|
||||
// Ordinary password/edit dialogs pause the camera without acquiring ChatContext.
|
||||
edit.parent=nullptr;child.parent=&edit;workspace.focus=&child;
|
||||
assert(c.IsDisabledByMenu());assert(!inputs.chat&&inputs.renewals==4);
|
||||
workspace.focus=&ordinary;assert(!c.IsDisabledByMenu());workspace.focus=nullptr;
|
||||
assert(!c.IsDisabledByMenu());SCR_ChatPanelManager::current=&chatManager;
|
||||
// A residual native chat context still blocks the camera until it expires.
|
||||
inputs.chat=true;assert(c.IsDisabledByMenu());assert(inputs.renewals==4);
|
||||
inputs.Expire();assert(!c.IsDisabledByMenu());
|
||||
// Integrate Capture with the production input edge/release state machine.
|
||||
Follow f;registered.open=true;f.Input(!c.IsDisabledByMenu(),false,false,false);
|
||||
registered.open=false;inputs.Expire();f.Input(!c.IsDisabledByMenu(),false,false,false);
|
||||
f.Input(!c.IsDisabledByMenu(),false,false,true);f.Input(!c.IsDisabledByMenu(),false,false,true);
|
||||
assert(!f.m_PlayerView); // submitting/holding Enter cannot become a camera toggle
|
||||
f.Input(!c.IsDisabledByMenu(),false,false,false);f.Input(!c.IsDisabledByMenu(),false,false,true);assert(f.m_PlayerView);
|
||||
registered.open=true;f.Input(!c.IsDisabledByMenu(),false,false,false);
|
||||
registered.open=false;inputs.Expire();f.Input(!c.IsDisabledByMenu(),false,false,false);
|
||||
f.Input(!c.IsDisabledByMenu(),true,false,false);assert(pc.calls==0);
|
||||
f.Input(!c.IsDisabledByMenu(),false,false,false);f.Input(!c.IsDisabledByMenu(),true,false,false);assert(pc.calls==1);
|
||||
}
|
||||
'''
|
||||
ui_follow=r'''
|
||||
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;int m_InputResumeFrames=0;
|
||||
'''+input_body+r'''
|
||||
};
|
||||
'''
|
||||
run(ui_prelude+'struct Camera:Base {\n'+gate+'\n};\n'+ui_follow+ui_tests)
|
||||
assert (ROOT/'addons/core/Prefabs/ERR/SpectatorCamera.et').read_text().startswith('ERR_ObserverCamera {')
|
||||
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')
|
||||
print('PASS: actual open-chat capture/context renewal and camera/menu priority, text-entry isolation, held-key release; actual revival camera handoff with editor close/retry, missing camera, replacement body and stopped round')
|
||||
|
||||
Reference in New Issue
Block a user