Files
EscapistsReintroduced/tests/test_native_shared_map_ownership.py
T
jakub cab998fb64 Combine Workshop addon and fix prison startup
Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
2026-09-20 16:42:52 +02:00

124 lines
7.9 KiB
Python

"""Execute fixture's actual ownership/cleanup and callback-evidence decisions.
Explicit native boundary doubles; this is not a native rendering/camera test.
"""
import re
from policy_harness import ROOT, body, run
SOURCE = ROOT / 'addons/core/Scripts/Game/ERR/30_Map/ERRM_NativeMapOwnershipProbe.c'
def method(signature):
text = body(SOURCE, signature).replace('protected ', '')
text = re.sub(r'^ Print\(string\.Format[^\n]+\n', '', text, flags=re.M)
text = text.replace('SCR_MapEntity.GetMapInstance()', 'Map::current')
text = text.replace('SCR_MapEntity.GetOnMapClose().Remove(OnMapClosed)', 'closeObserver.Remove(this)')
text = text.replace('SCR_MapEntity.GetOnMapOpenComplete().Remove(OnMapReady)', 'readyObserver.Remove(this)')
text = text.replace('SCR_EntityHelper.DeleteEntityAndChildren(', 'DeleteEntityAndChildren(')
for field in ['m_Map', 'm_ReplacementConfig', 'm_ReplacementRoot', 'm_Capture', 'm_Spare']:
text = text.replace(field + '.', field + '->')
return re.sub(r'\bnull\b', 'nullptr', text)
methods = '\n'.join(method(x) for x in [
'protected bool ReplacementIsCurrent(', 'void Stop()',
'protected void SaveCounters(', 'protected bool VerifyStaleAndStop(',
'protected bool DeletionComplete(', 'protected void RequestOwnedDeletion(', 'void ObserveDeletion('])
# The first Stop in this file is the fixture method; adapters are named distinctly.
run(r'''
#include <cassert>
#include <vector>
#include <algorithm>
#include <string>
struct Probe;
struct Widget { int removes=0;void RemoveFromHierarchy(){removes++;} };
struct Config { Widget* RootWidgetRef=nullptr; };
struct Entity { int deletes=0; };
void DeleteEntityAndChildren(Entity*& value){value->deletes++;value=nullptr;}
struct Map { static Map* current;bool opened=true;Config* config=nullptr;Widget* root=nullptr;
int closes=0;float pan=0,zoom=0;bool IsOpen(){return opened;}Config* GetMapConfig(){return config;}
Widget* GetMapMenuRoot(){return root;}void CloseMap(){closes++;opened=false;}
float GetCurrentPan(){return pan;}float GetTargetZoomPPU(){return zoom;}
};Map* Map::current=nullptr;
struct Capture {int stops=0;void ERRM_StopOwnershipCapture(){stops++;}};
struct Observer {std::vector<Probe*> owners;void Remove(Probe* p){owners.erase(std::remove(owners.begin(),owners.end(),p),owners.end());}};
Observer closeObserver,readyObserver;
struct Probe {
Map* m_Map=nullptr;Widget* m_ReplacementRoot=nullptr;Config* m_ReplacementConfig=nullptr;Config* m_ReadyConfig=nullptr;
Capture *m_Capture=nullptr,*m_Spare=nullptr;Entity *m_CaptureEntity=nullptr,*m_SpareEntity=nullptr,*m_Holder=nullptr;
bool m_DeleteRequested=false,m_DeleteStateFailed=false;int m_CaptureDeleted=0,m_SpareDeleted=0;
bool m_Stopped=false;int m_CloseEvents=0,m_RestoreCalls=0,m_RestoreAuthorized=0,m_Completions=0,m_CenterCalls=0,m_FinishCalls=0;
int m_CloseBaseline=0,m_RestoreBaseline=0,m_AuthorizedBaseline=0,m_CompletionBaseline=0,m_CenterBaseline=0,m_FinishBaseline=0;
float m_PanBaseline=0,m_ZoomBaseline=0;
bool Check(bool result,const std::string&){if(!result)Stop();return result;}
'''+methods+r'''
void Bind(Map& map,Config& cfg,Widget& root,Capture& c,Capture& s,Entity& ce,Entity& se){
m_Map=&map;Map::current=&map;m_ReplacementConfig=&cfg;m_ReplacementRoot=&root;
map.opened=true;map.config=&cfg;map.root=&root;cfg.RootWidgetRef=&root;
m_Capture=&c;m_Spare=&s;m_CaptureEntity=&ce;m_SpareEntity=&se;
closeObserver.owners.push_back(this);readyObserver.owners.push_back(this);
}
};
int main(){
Map map;Config cfg,foreignCfg;Widget root,foreignRoot;Capture capture,spare;Entity ce,se;
Probe foreign;foreign.Bind(map,cfg,root,capture,spare,ce,se);
Probe other;closeObserver.owners.push_back(&other);readyObserver.owners.push_back(&other);
// Same object reused with another native root must not be closed in cleanup.
cfg.RootWidgetRef=&foreignRoot;map.root=&foreignRoot;
assert(!foreign.ReplacementIsCurrent());foreign.Stop();
assert(map.closes==0&&map.opened&&root.removes==1&&foreignRoot.removes==0);
assert(ce.deletes==1&&se.deletes==1&&capture.stops==1&&spare.stops==1);
assert(closeObserver.owners.size()==1&&closeObserver.owners[0]==&other);
assert(readyObserver.owners.size()==1&&readyObserver.owners[0]==&other);
foreign.Stop();assert(root.removes==1&&ce.deletes==1); // no duplicate cleanup
Probe changed;changed.Bind(map,cfg,root,capture,spare,ce,se);map.config=&foreignCfg;
changed.Stop();assert(map.closes==0); // distinct active config also preserved
Probe oldWorld;oldWorld.Bind(map,cfg,root,capture,spare,ce,se);Map replacementWorld;Map::current=&replacementWorld;
oldWorld.Stop();assert(map.closes==0); // old singleton cannot be closed
Probe exact;exact.Bind(map,cfg,root,capture,spare,ce,se);exact.Stop();assert(map.closes==1&&!map.opened);
// A correct view without callbacks running must not pass native evidence.
Probe missed;missed.Bind(map,cfg,root,capture,spare,ce,se);missed.SaveCounters();missed.m_RestoreCalls++;
assert(!missed.VerifyStaleAndStop()&&missed.m_Stopped);
Probe observed;observed.Bind(map,cfg,root,capture,spare,ce,se);observed.SaveCounters();
observed.m_CenterCalls+=2;observed.m_FinishCalls++;observed.m_RestoreCalls++;
assert(observed.VerifyStaleAndStop()&&!observed.m_Stopped&&map.opened);
observed.Stop();
Probe moved;moved.Bind(map,cfg,root,capture,spare,ce,se);moved.SaveCounters();
moved.m_CenterCalls+=2;moved.m_FinishCalls++;moved.m_RestoreCalls++;map.pan=1;
assert(!moved.VerifyStaleAndStop()); // actual changed map pan invalidates evidence
map.pan=0;
Probe closed;closed.Bind(map,cfg,root,capture,spare,ce,se);closed.SaveCounters();
closed.m_CenterCalls+=2;closed.m_FinishCalls++;closed.m_RestoreCalls++;closed.m_CloseEvents++;
assert(!closed.VerifyStaleAndStop()); // even a close/reopen to same view fails
// Request return is not deletion completion. Native refs and superclass
// completion are separate observations, all required, without timeout waiver.
Probe delayed;delayed.Bind(map,cfg,root,capture,spare,ce,se);
assert(!delayed.DeletionComplete());delayed.RequestOwnedDeletion();
int ceDeletes=ce.deletes,seDeletes=se.deletes;delayed.RequestOwnedDeletion();
assert(ce.deletes==ceDeletes&&se.deletes==seDeletes); // exactly one request
assert(!delayed.m_CaptureEntity&&!delayed.m_SpareEntity&&!delayed.DeletionComplete());
delayed.ObserveDeletion(1,true);delayed.ObserveDeletion(2,true);
assert(!delayed.DeletionComplete()); // callbacks alone don't invalidate native refs
delayed.m_Capture=nullptr;delayed.m_Spare=nullptr; // explicit native-boundary simulation
assert(delayed.DeletionComplete());delayed.ObserveDeletion(2,true);
assert(!delayed.DeletionComplete()); // duplicate cleanup is never accepted
Probe unreleased;unreleased.m_DeleteRequested=true;
unreleased.ObserveDeletion(1,false);unreleased.ObserveDeletion(2,true);
assert(!unreleased.DeletionComplete()); // a post-super leftover field fails
Probe premature;premature.ObserveDeletion(1,true);premature.m_DeleteRequested=true;
premature.ObserveDeletion(2,true);assert(!premature.DeletionComplete());
}
''')
text = SOURCE.read_text()
assert text.startswith('#ifdef WORKBENCH\n') and text.rstrip().endswith('#endif')
for name in ('CenterMapOnHolder', 'FinishMapCapture', 'RestoreCharacterCamera', 'CompleteMapModeSwitch'):
assert 'super.' + name + '();' in body(SOURCE, 'protected override void ' + name + '(')
deletion = body(SOURCE, 'protected override void OnDelete(')
assert deletion.index('super.OnDelete(owner)') < deletion.index('probe.ObserveDeletion(')
assert 'now >= m_DeleteDeadline' in text and 'm_DeleteDeadline = now + 3000' in text
assert 'SetCharacterCameraRenderActive(' not in text # no guessed getter or explicit write
assert 'GetOnMapOpenComplete().Insert(OnMapReady)' in text
assert 'm_ReadyConfig == m_ReplacementConfig' in text
assert 'CallLater(CenterMapOnHolder, 100, false)' in text
assert 'CallLater(FinishMapCapture, 200, false)' in text
print('PASS: actual native-map fixture ownership, cleanup and callback evidence fail closed; native execution remains separate')