Show Escape mission text after terrain takeover; record Workshop 0.0.135
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-10-31 15:52:27 +01:00
parent 2c2158702d
commit 0e1e7790f9
10 changed files with 972 additions and 33 deletions
+34 -12
View File
@@ -3,10 +3,10 @@ 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(',
'void ERR_PresentTerrainTakeover(', 'void ERR_RefreshTerrainPresentation(', 'SCR_MissionHeader ERR_TerrainLoadingHeader(', 'string ERR_TerrainLoadingDescription(', '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 ERR_TerrainLoadingHeader', 'SCR_MissionHeader* ERR_TerrainLoadingHeader'), ('ERR_TerrainMissionText.Load', 'ERR_TerrainMissionText::Load'), ('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'),
@@ -16,6 +16,7 @@ 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;}};
struct ERR_TerrainMissionText {static inline bool available=true;static bool Load(string& description,string& details){if(!available)return false;description="Escape description";details="Escape details";return true;}};
int resolutions=0;string Resolve(string world){resolutions++;return world;}
namespace Stage {enum {INACTIVE,RUNNING,FAILED};}
bool enabled=true,editor=false,server=true;
@@ -24,6 +25,7 @@ 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 m_sDescription="Source description",m_sDetails="Source details";
string world="Nogova";string GetWorldResourceName(){return world;}
string resource="{469880DFE1B48F95}Missions/Nogova_TerrainTest.conf";
static SCR_MissionHeader* Cast(SCR_MissionHeader* p){return p;}
@@ -39,6 +41,8 @@ struct Base {World* world=nullptr;string loaded;World* GetWorld(){return world;}
};
struct Game:Base {
SCR_MissionHeader* m_ERRPresentedTerrainHeader=nullptr;
bool m_ERRTerrainTextApplied=false;
string m_ERROriginalTerrainDescription,m_ERROriginalTerrainDetails,m_ERRAppliedTerrainDescription,m_ERRAppliedTerrainDetails;
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;
@@ -61,9 +65,11 @@ int main(){
// 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.m_sDescription=="Escape description"&&h.m_sDetails=="Escape details");
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");
assert(h.m_sDescription=="Source description"&&h.m_sDetails=="Source details");
// 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";
@@ -89,9 +95,12 @@ int main(){
assert(g.Path(g.loaded)==g.loaded);
SCR_MissionHeader copy=h;copy.m_sName="Original Everon scenario";
copy.m_sDetails="Copied source details";
assert(g.ERR_TerrainLoadingTitle(&copy)==h.m_sName);
assert(g.ERR_TerrainLoadingDescription(&copy)=="Escape details");
copy.resource="different incoming scenario";copy.m_sName="Next map";
assert(g.ERR_TerrainLoadingTitle(&copy)=="Next map");
assert(g.ERR_TerrainLoadingDescription(&copy)=="Copied source details");
// 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");
@@ -99,10 +108,16 @@ int main(){
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);
h.m_sDescription="Original";h.m_sDetails="";
ERR_TerrainMissionText::available=false;g.ERR_PresentTerrainTakeover();
assert(h.m_sDescription=="Original"&&g.ERR_TerrainLoadingDescription(&h)=="Original");g.ERR_RestoreTerrainPresentation();
ERR_TerrainMissionText::available=true;g.ERR_PresentTerrainTakeover();h.m_sDetails="Later mod details";g.ERR_RestoreTerrainPresentation();
assert(h.m_sDescription=="Original"&&h.m_sDetails=="Later mod details");
assert(g.ERR_TerrainLoadingDescription(nullptr)=="");
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')
print('PASS: native name callback preserves authored modes, opt-outs and failures; terrain suffix follows world changes, cached resolution and confirmed takeover text restores on cleanup without changing scenario identity or overwriting later edits')
# 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('))
@@ -165,32 +180,39 @@ print('PASS: actual terrain parent traversal, Armenhof/Nogova names, Everon alia
# 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)
for a,b in [('override ',''),('protected ',''),('SCR_MissionHeader header','SCR_MissionHeader* header'),('super.','Base::'),('m_Widgets.','m_Widgets->'),('m_wTitle.','m_wTitle->'),('m_wDescription.','m_wDescription->'),('WidgetManager.Translate','WidgetManager::Translate'),('GetGame().','GetGame()->')]:loading=loading.replace(a,b)
run(r'''
#include <cassert>
#include <string>
using string=std::string;
struct SCR_MissionHeader{string title;};
struct string:std::string {using std::string::string;string(std::string v):std::string(v){}int Length(){return size();}string Substring(int a,int n){return substr(a,n);}};
struct WidgetManager{static string Translate(string s){return s;}};
const int MAX_DESCRIPTION_LENGTH=100;
struct SCR_MissionHeader{string title,description="Source description";};
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;}};
struct Widgets{Text*m_wTitle=nullptr;Text*m_wDescription=nullptr;};
struct Game{bool confirmed=false;string actual="Nogova",description="Escape details";string ERR_TerrainLoadingDescription(SCR_MissionHeader*h){return confirmed?description:h->description;}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;
struct Loading:Base{SCR_MissionHeader*m_ERRLoadingTerrainHeader=nullptr;string m_ERRLoadingTerrainTitle,m_ERRLoadingTerrainDescription;float m_ERRLoadingTerrainWait=0;
'''+loading+r'''
};int main(){
Loading screen;Widgets widgets;Text text;widgets.m_wTitle=&text;screen.m_Widgets=&widgets;
Loading screen;Widgets widgets;Text text,description;widgets.m_wTitle=&text;widgets.m_wDescription=&description;screen.m_Widgets=&widgets;
SCR_MissionHeader source{"Original Everon scenario"};screen.OnMissionDataRetrieved(&source);
assert(text.text==source.title&&screen.inits==1);
assert(description.text=="Source description");
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);
assert(description.text=="Escape details");
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);
assert(description.text=="Source description");
game.confirmed=true;game.description=string(150,'x');screen.Update(.6);assert(description.text==string(100,'x')+"...");
game.description="";screen.Update(.6);assert(description.text.empty());
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);
assert(screen.updates==10);
}
''')
print('PASS: actual loading title hooks follow late takeover/terrain changes, keep native initialization/progress updates, bound refreshes and tolerate missing headers/widgets/game')
print('PASS: actual loading title/description hooks follow late takeover, apply text limits and empty updates, retain native progress and tolerate missing headers/widgets/game')