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.
124 lines
7.9 KiB
Python
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::current=↦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')
|