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:
@@ -0,0 +1,191 @@
|
||||
"""Execute the native name callback and reversible runtime header presentation."""
|
||||
from policy_harness import ROOT, body, run
|
||||
p = ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverPresentation.c'
|
||||
s = '\n'.join(body(p, signature) for signature in [
|
||||
'static string Path(', 'string ERR_TerrainMissionTitle(', 'override string GetMissionName(',
|
||||
'void ERR_PresentTerrainTakeover(', 'void ERR_RefreshTerrainPresentation(', 'string ERR_TerrainLoadingTitle(', 'override void OnUpdate(', 'protected void ERR_RestoreTerrainPresentation(',
|
||||
'override protected void OnBeforeWorldCleanup('])
|
||||
for a, b in [('override ', ''), ('protected ', ''),
|
||||
('SCR_MissionHeader header', 'SCR_MissionHeader* header'), ('SCR_MissionHeader requested','SCR_MissionHeader* requested'), ('BaseWorld world','World* world'), ('requested.','requested->'),
|
||||
('SCR_MissionHeader.Cast', 'SCR_MissionHeader::Cast'),
|
||||
('ResourceName ', 'string '), ('GetWorld().', 'GetWorld()->'), ('ERR_TerrainDisplayName.Resolve', 'Resolve'), ('ERR_TerrainDisplayName.Path', 'Path'), ('header.', 'header->'), ('m_ERRPresentedTerrainHeader.', 'm_ERRPresentedTerrainHeader->'),
|
||||
('ERR_TerrainTakeoverStage.', 'Stage::'), ('ERR_TerrainTakeoverSettings.Enabled', 'Enabled'),
|
||||
('SCR_Global.IsEditMode', 'EditMode'), ('Replication.IsServer', 'Server'), ('super.', 'Base::'), ('null', 'nullptr')]:
|
||||
s = s.replace(a, b)
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
struct string:std::string {using std::string::string;string(std::string v):std::string(v){}bool IsEmpty(){return empty();}int Length(){return size();}string Substring(int a,int n){return substr(a,n);}string GetPath(){assert(false && "native resource lookup in title hot path");return *this;}};
|
||||
int resolutions=0;string Resolve(string world){resolutions++;return world;}
|
||||
namespace Stage {enum {INACTIVE,RUNNING,FAILED};}
|
||||
bool enabled=true,editor=false,server=true;
|
||||
bool Server(){return server;}
|
||||
bool Enabled(){return enabled&&!editor;}bool EditMode(){return editor;}
|
||||
template<class... T>void PrintFormat(T...){}
|
||||
struct SCR_MissionHeader {
|
||||
string m_sName="Nogova — Exploration Alpha",m_sGameMode="Terrain exploration";
|
||||
string world="Nogova";string GetWorldResourceName(){return world;}
|
||||
string resource="{469880DFE1B48F95}Missions/Nogova_TerrainTest.conf";
|
||||
static SCR_MissionHeader* Cast(SCR_MissionHeader* p){return p;}
|
||||
string GetHeaderResourceName(){return resource;}
|
||||
};
|
||||
struct World {string root="Armenhof";int count=1;int GetNumSubScenes(){return count;}string GetSubSceneName(int){return root;}};
|
||||
struct Base {World* world=nullptr;string loaded;World* GetWorld(){return world;}string GetWorldFile(){return loaded;}
|
||||
SCR_MissionHeader* header=nullptr;int cleanup=0;
|
||||
SCR_MissionHeader* GetMissionHeader(){return header;}
|
||||
string GetMissionName(){return header?header->m_sName:"";}
|
||||
int updates=0;void OnUpdate(World*,float){updates++;}
|
||||
void OnBeforeWorldCleanup(){cleanup++;}
|
||||
};
|
||||
struct Game:Base {
|
||||
SCR_MissionHeader* m_ERRPresentedTerrainHeader=nullptr;
|
||||
string m_ERROriginalTerrainTitle,m_ERROriginalTerrainGameMode,m_ERRAppliedTerrainTitle,m_ERRTitleWorld,m_ERRTerrainLabel;
|
||||
World* m_ERRPresentedTerrainWorld=nullptr;bool m_ERRTitleLoaded=false;float m_ERRTerrainPresentationWait=0;
|
||||
int m_ERRTerrainStage=Stage::INACTIVE;
|
||||
''' + s + r'''
|
||||
};
|
||||
int main(){
|
||||
Game g;SCR_MissionHeader h;g.header=&h;
|
||||
// Native DS request occurs before the original mode has been replaced.
|
||||
assert(g.GetMissionName()=="Co10 Escape Reintroduced - Nogova");
|
||||
assert(h.m_sName=="Nogova — Exploration Alpha");
|
||||
assert(g.GetMissionName()=="Co10 Escape Reintroduced - Nogova"&&resolutions==1);
|
||||
h.world="Armenhof";assert(g.GetMissionName()=="Co10 Escape Reintroduced - Armenhof");
|
||||
h.world="Nogova";
|
||||
World loaded;g.world=&loaded;g.loaded="different";assert(g.GetMissionName()=="Co10 Escape Reintroduced - Nogova");
|
||||
g.loaded=h.world;assert(g.GetMissionName()=="Co10 Escape Reintroduced - Armenhof");g.world=nullptr;
|
||||
server=false;assert(g.GetMissionName()==h.m_sName);server=true;
|
||||
enabled=false;assert(g.GetMissionName()==h.m_sName);enabled=true;
|
||||
editor=true;assert(g.GetMissionName()==h.m_sName);g.ERR_PresentTerrainTakeover();assert(!g.m_ERRPresentedTerrainHeader);editor=false;
|
||||
g.m_ERRTerrainStage=Stage::FAILED;assert(g.GetMissionName()==h.m_sName);g.m_ERRTerrainStage=Stage::RUNNING;
|
||||
// Confirmed takeover changes display metadata, never the terrain selection.
|
||||
g.ERR_PresentTerrainTakeover();g.ERR_PresentTerrainTakeover();
|
||||
assert(h.m_sName=="Co10 Escape Reintroduced - Nogova"&&h.m_sGameMode=="Escape");
|
||||
assert(h.resource=="{469880DFE1B48F95}Missions/Nogova_TerrainTest.conf");
|
||||
g.OnBeforeWorldCleanup();assert(g.cleanup==1&&!g.m_ERRPresentedTerrainHeader);
|
||||
assert(h.m_sName=="Nogova — Exploration Alpha"&&h.m_sGameMode=="Terrain exploration");
|
||||
// A reused header cannot keep Escape's name after an opt-out/restart.
|
||||
enabled=false;assert(g.GetMissionName()==h.m_sName);enabled=true;
|
||||
SCR_MissionHeader authored;authored.m_sName="Escape — Authored island";authored.m_sGameMode="Escape";
|
||||
g.header=&authored;assert(g.GetMissionName()==authored.m_sName);
|
||||
g.header=&h;g.ERR_PresentTerrainTakeover();h.m_sName="Another system's title";
|
||||
g.ERR_RestoreTerrainPresentation();assert(h.m_sName=="Another system's title"&&h.m_sGameMode=="Terrain exploration");
|
||||
h.world="";g.ERR_RestoreTerrainPresentation();assert(g.GetMissionName()=="Co10 Escape Reintroduced");
|
||||
// A confirmed running terrain wins over a stale source header, on server and client.
|
||||
g.OnBeforeWorldCleanup();h.m_sName="Original Everon scenario";h.world="Everon";g.header=&h;
|
||||
World actual;actual.root="Nogova";actual.count=0;g.world=&actual;g.loaded="Nogova";server=false;
|
||||
assert(g.ERR_TerrainLoadingTitle(&h)=="Original Everon scenario");
|
||||
g.ERR_PresentTerrainTakeover();assert(h.m_sName=="Co10 Escape Reintroduced - Nogova");
|
||||
// A late root resolution must refresh the SAME header even after GameMode became Escape.
|
||||
actual.count=1;actual.root="Armenhof";g.OnUpdate(&actual,.6);
|
||||
assert(h.m_sName=="Co10 Escape Reintroduced - Armenhof"&&g.updates==1);
|
||||
assert(g.GetMissionName()==h.m_sName);server=true;assert(g.GetMissionName()==h.m_sName);
|
||||
// Raw addon-qualified paths must never reach the native GetPath resolver.
|
||||
g.loaded="$ChernarusMinus:Worlds/GM_Cherno/GM_Cherno.ent";actual.root="$ChernarusMinus:Worlds/ChernarusMinusBeta.ent";
|
||||
g.ERR_RefreshTerrainPresentation();int warm=resolutions;
|
||||
for(int i=0;i<120;i++){g.OnUpdate(&actual,.5);g.GetMissionName();g.ERR_TerrainLoadingTitle(&h);}
|
||||
assert(resolutions==warm&&h.m_sName=="Co10 Escape Reintroduced - $ChernarusMinus:Worlds/ChernarusMinusBeta.ent");
|
||||
assert(g.Path("{0123456789ABCDEF}Worlds/Island.ent")=="Worlds/Island.ent");
|
||||
assert(g.Path(g.loaded)==g.loaded);
|
||||
|
||||
SCR_MissionHeader copy=h;copy.m_sName="Original Everon scenario";
|
||||
assert(g.ERR_TerrainLoadingTitle(©)==h.m_sName);
|
||||
copy.resource="different incoming scenario";copy.m_sName="Next map";
|
||||
assert(g.ERR_TerrainLoadingTitle(©)=="Next map");
|
||||
// A different current header cannot inherit the outgoing world's confirmed title.
|
||||
SCR_MissionHeader incoming;incoming.world="New island";g.header=&incoming;
|
||||
assert(g.GetMissionName()=="Co10 Escape Reintroduced - New island");
|
||||
g.header=&h;h.m_sName="Later mod title";actual.root="Other";g.ERR_PresentTerrainTakeover();
|
||||
assert(h.m_sName=="Later mod title");g.ERR_RestoreTerrainPresentation();assert(h.m_sName=="Later mod title");
|
||||
h.m_sName="Original Everon scenario";g.ERR_PresentTerrainTakeover();g.OnBeforeWorldCleanup();
|
||||
assert(h.m_sName=="Original Everon scenario"&&!g.m_ERRPresentedTerrainWorld);
|
||||
g.header=nullptr;assert(g.GetMissionName().empty());g.ERR_PresentTerrainTakeover();g.OnBeforeWorldCleanup();
|
||||
}
|
||||
''')
|
||||
print('PASS: native name callback preserves authored modes, opt-outs and failures; terrain suffix follows world changes, cached resolution and confirmed takeover metadata restores on cleanup without changing scenario resource or overwriting later titles')
|
||||
|
||||
# Resolve actual parent chains without requiring a loaded game world.
|
||||
resolver='\n'.join(body(p,signature) for signature in ('static string Path(', 'static string Label(', 'static string Resolve('))
|
||||
for a,b in [('foreach (','for ('),('ResourceName','string'),('array<string>','Array<string>'),('Resource resource','Resource* resource'),('BaseContainer container','Container* container'),('BaseContainerTools.LoadContainer','Load'),('resource.','resource->'),('container.','container->'),('GetResource().','GetResource()->'),('FilePath.StripPath','StripPath'),('container->Get("Parent", parent)','container->Get(parent)')]:resolver=resolver.replace(a,b)
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
struct string:std::string {
|
||||
using std::string::string;string(std::string v):std::string(v){}bool IsEmpty(){return empty();}int Length(){return size();}
|
||||
int IndexOf(string x){auto n=find(x);return n==npos?-1:int(n);}string Substring(int a,int n){return substr(a,n);}
|
||||
void ToLower(){std::transform(begin(),end(),begin(),[](unsigned char c){return std::tolower(c);});}
|
||||
void ToUpper(){std::transform(begin(),end(),begin(),[](unsigned char c){return std::toupper(c);});}
|
||||
void Replace(string a,string b){size_t n=0;while((n=find(a,n))!=npos){replace(n,a.size(),b);n+=b.size();}}
|
||||
string Trim(){auto a=find_first_not_of(" \r\n");if(a==npos)return "";return substr(a,find_last_not_of(" \r\n")-a+1);}
|
||||
string GetPath(){auto n=find('}');return n==npos?*this:string(substr(n+1));}
|
||||
};
|
||||
string StripPath(string s){auto n=s.find_last_of('/');return n==s.npos?s:string(s.substr(n+1));}
|
||||
template<class T>struct Array:std::vector<T>{void Insert(T v){this->push_back(v);}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}};
|
||||
struct Container{string parent;bool Get(string& p){p=parent;return !p.IsEmpty();}};
|
||||
struct Resource{Container container;bool valid=true;bool IsValid(){return valid;}Resource* GetResource(){return this;}Container* ToBaseContainer(){return &container;}};
|
||||
std::map<string,Resource> resources;int calls=0;
|
||||
Resource* Load(string world){calls++;auto it=resources.find(world);return it==resources.end()?nullptr:&it->second;}
|
||||
struct Names {
|
||||
'''+resolver+r'''
|
||||
};
|
||||
int main(){
|
||||
resources["Worlds/GM/Conflict.ent"].container.parent="Worlds/Armenhof/Armenhof.ent";
|
||||
resources["Worlds/Armenhof/Armenhof.ent"];
|
||||
assert(Names::Resolve("Worlds/GM/Conflict.ent")=="Armenhof");
|
||||
resources["Worlds/Nogova/Nogova_Test.ent"].container.parent="Worlds/Nogova/Nogova.ent";
|
||||
resources["Worlds/Nogova/Nogova.ent"];
|
||||
assert(Names::Resolve("Worlds/Nogova/Nogova_Test.ent")=="Nogova");
|
||||
assert(Names::Label("{853E92315D1D9EFE}worlds/Eden/Eden.ent")=="Everon");
|
||||
assert(Names::Resolve("Worlds/New_Island.ent")=="New Island");
|
||||
assert(Names::Label("Worlds/GameMaster/GM_Armenhof.ent")=="Armenhof");
|
||||
assert(Names::Label("Worlds/Nogova/Nogova_Test.ent")=="Nogova");
|
||||
assert(Names::Label("Worlds/ERR_Everon.ent")=="Everon");
|
||||
assert(Names::Label("Worlds/armenhof.ent")=="Armenhof");
|
||||
assert(Names::Resolve("")=="");
|
||||
assert(Names::Path("$ChernarusMinus:Worlds/GM_Cherno/GM_Cherno.ent")=="$ChernarusMinus:Worlds/GM_Cherno/GM_Cherno.ent");
|
||||
assert(Names::Path("{0123456789ABCDEF}Worlds/Island.ent")=="Worlds/Island.ent");
|
||||
assert(Names::Path("{bad}Worlds/Island.ent")=="{bad}Worlds/Island.ent");
|
||||
assert(Names::Label("$NogovaReforger:Worlds/Nogova/Nogova_Test.ent")=="Nogova");
|
||||
|
||||
resources["a.ent"].container.parent="b.ent";resources["b.ent"].container.parent="a.ent";
|
||||
calls=0;assert(Names::Resolve("a.ent")=="B"&&calls==2);
|
||||
for(int i=0;i<30;i++)resources[std::to_string(i)+".ent"].container.parent=std::to_string(i+1)+".ent";
|
||||
calls=0;assert(Names::Resolve("0.ent")=="16"&&calls==16);
|
||||
}
|
||||
''')
|
||||
print('PASS: actual terrain parent traversal, Armenhof/Nogova names, Everon alias, missing-resource fallback, empty names and bounded cycles/depth')
|
||||
|
||||
# Run the real loading component hooks with widget/native-super boundaries.
|
||||
loading='\n'.join(body(p,x) for x in ('override void OnMissionDataRetrieved(', 'protected void ERR_RefreshLoadingTerrainTitle(', 'override void Update(float timeSlice'))
|
||||
for a,b in [('override ',''),('protected ',''),('SCR_MissionHeader header','SCR_MissionHeader* header'),('super.','Base::'),('m_Widgets.','m_Widgets->'),('m_wTitle.','m_wTitle->'),('GetGame().','GetGame()->')]:loading=loading.replace(a,b)
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
using string=std::string;
|
||||
struct SCR_MissionHeader{string title;};
|
||||
struct Text{string text;int writes=0;void SetText(string t){text=t;writes++;}};
|
||||
struct Widgets{Text*m_wTitle=nullptr;};
|
||||
struct Game{bool confirmed=false;string actual="Nogova";string ERR_TerrainLoadingTitle(SCR_MissionHeader*h){return confirmed?"Co10 Escape Reintroduced - "+actual:h->title;}};
|
||||
Game game;Game*current=&game;Game*GetGame(){return current;}
|
||||
struct Base{Widgets*m_Widgets=nullptr;int inits=0,updates=0;void OnMissionDataRetrieved(SCR_MissionHeader*h){inits++;if(h&&m_Widgets&&m_Widgets->m_wTitle)m_Widgets->m_wTitle->SetText(h->title);}void Update(float,float,float){updates++;}};
|
||||
struct Loading:Base{SCR_MissionHeader*m_ERRLoadingTerrainHeader=nullptr;string m_ERRLoadingTerrainTitle;float m_ERRLoadingTerrainWait=0;
|
||||
'''+loading+r'''
|
||||
};int main(){
|
||||
Loading screen;Widgets widgets;Text text;widgets.m_wTitle=&text;screen.m_Widgets=&widgets;
|
||||
SCR_MissionHeader source{"Original Everon scenario"};screen.OnMissionDataRetrieved(&source);
|
||||
assert(text.text==source.title&&screen.inits==1);
|
||||
game.confirmed=true;screen.Update(.25);assert(text.text==source.title);
|
||||
screen.Update(.25);assert(text.text=="Co10 Escape Reintroduced - Nogova"&&screen.inits==1&&screen.updates==2);
|
||||
int writes=text.writes;screen.Update(.6);assert(text.writes==writes); // unchanged titles don't churn widgets
|
||||
game.actual="Armenhof";screen.Update(.6);assert(text.text=="Co10 Escape Reintroduced - Armenhof");
|
||||
game.confirmed=false;screen.Update(.6);assert(text.text==source.title);
|
||||
screen.OnMissionDataRetrieved(nullptr);screen.Update(.6);assert(screen.inits==2);
|
||||
screen.m_Widgets=nullptr;screen.OnMissionDataRetrieved(&source);screen.Update(.6); // layout not attached yet
|
||||
screen.m_Widgets=&widgets;current=nullptr;screen.Update(.6); // game teardown
|
||||
assert(screen.updates==8);
|
||||
}
|
||||
''')
|
||||
print('PASS: actual loading title hooks follow late takeover/terrain changes, keep native initialization/progress updates, bound refreshes and tolerate missing headers/widgets/game')
|
||||
Reference in New Issue
Block a user