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.
131 lines
8.2 KiB
Python
131 lines
8.2 KiB
Python
"""Execute actual DoSpawn_S ordering with native admission/geometry adapters.
|
|
No Enfusion scheduling, possession, collision or remote-client proof.
|
|
"""
|
|
import re
|
|
from policy_harness import ROOT, body, run
|
|
source = ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnLogic.c'
|
|
method = body(source, 'protected override void DoSpawn_S(')
|
|
method = method.replace('protected override ', '')
|
|
for old, new in [('ERR_GameMode', 'Mode'), ('SCR_PlayerController', 'Controller'),
|
|
('SCR_PlayerFactionAffiliationComponent', 'Affiliation'),
|
|
('SCR_RespawnComponent', 'Respawn'), ('ERR_PlayerLife', 'Life'),
|
|
('SCR_FreeSpawnData', 'Request'), ('ERR_ObserverReason', 'Reason')]:
|
|
method = method.replace(old, new)
|
|
for kind in ('Mode', 'Controller', 'Affiliation', 'Respawn', 'Faction', 'Life', 'Request'):
|
|
method = re.sub(r'\b'+kind+r' (\w+)\b', kind+r'* \1', method)
|
|
for var in ('controller', 'mode', 'affiliation', 'respawn', 'life'):
|
|
method = method.replace(var+'.', var+'->')
|
|
method = method.replace('Mode.Get()', 'Mode::Get()').replace('Controller.Cast(', 'Controller::Cast(')
|
|
method = method.replace('GetGame().GetPlayerManager().GetPlayerController(', 'GetGame()->GetPlayerManager()->GetPlayerController(')
|
|
method = method.replace('mode->Players().', 'mode->Players()->').replace('mode->Factions().', 'mode->Factions()->')
|
|
method = method.replace('ERR_MissionRules.Terminal(', 'Terminal(').replace('Reason.', 'Reason::')
|
|
method = method.replace('vector position;', 'int position=0;').replace('Reason reason =', 'int reason =')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <map>
|
|
#include <set>
|
|
#include <string>
|
|
#include <vector>
|
|
struct Text { bool empty=true; bool IsEmpty(){return empty;} };
|
|
struct Life {bool UsedLife=false;};
|
|
struct Roster {bool enter=true;Life life;bool CanEnter(int,int){return enter;}Life* Get(int){return &life;}};
|
|
struct Catalog {int PrisonerPrefab=1;};
|
|
struct Faction {} faction;
|
|
struct Controller;
|
|
struct Mode {int exhausted=0;void InitialAdmissionExhausted(int,Controller*){exhausted++;}bool active=true;int phase=1;Text error;Roster roster;Catalog catalog;Faction* factionPtr=&faction;
|
|
static Mode* current;static Mode* Get(){return current;}bool Active(){return active;}int Phase(){return phase;}
|
|
Text PreparationError(){return error;}Roster* Players(){return &roster;}Catalog* Factions(){return &catalog;}Faction* PlayerFaction(){return factionPtr;}};
|
|
Mode* Mode::current=nullptr;
|
|
struct Controller {bool controlled=false;static Controller* Cast(Controller* value){return value;}bool GetControlledEntity(){return controlled;}};
|
|
struct Manager {Controller* controller=nullptr;Controller* GetPlayerController(int){return controller;}};
|
|
struct Game {Manager manager;Manager* GetPlayerManager(){return &manager;}} game;
|
|
Game* GetGame(){return &game;}
|
|
struct Affiliation {int requests=0;void RequestFaction(Faction*){requests++;}};
|
|
struct Request {int prefab,position;Request(int a,int b):prefab(a),position(b){} };
|
|
struct Respawn {bool can=true,request=true;int requests=0,checks=0;std::vector<Request*> allocations;
|
|
bool CanSpawn(Request* value){allocations.push_back(value);checks++;return can;}
|
|
bool RequestSpawn(Request*){requests++;return request;}
|
|
~Respawn(){for(auto* value:allocations)delete value;}};
|
|
struct Values {std::map<int,int> values;bool Contains(int id){return values.count(id);}int Get(int id){return values.at(id);}void Set(int id,int value){values[id]=value;}};
|
|
struct IDs {std::set<int> values;bool Contains(int id){return values.count(id);}void Insert(int id){values.insert(id);}};
|
|
namespace Reason {enum {NONE,SPAWN_UNAVAILABLE,SPENT_LIFE,LATE_EXTRACTION};}
|
|
bool Terminal(int phase){return phase>=5;}
|
|
struct Logic {
|
|
Values m_Attempts,m_AuthorizedPositions;IDs m_Identities,m_Pending;bool m_AdmissionsStopped=false;
|
|
Affiliation affiliation;Respawn respawn;bool hasAffiliation=true,hasRespawn=true,context=true,clear=true;
|
|
int retries=0,observations=0,reason=Reason::NONE,placementChecks=0,observedBudget=-1;
|
|
Affiliation* GetPlayerFactionComponent_S(int){return hasAffiliation?&affiliation:nullptr;}
|
|
Respawn* GetPlayerRespawnComponent_S(int){return hasRespawn?&respawn:nullptr;}
|
|
bool CheckLocalAdmissionContext(){return context;}
|
|
bool SpawnPosition(Mode*,int& position){placementChecks++;position=42;return clear;}
|
|
void RetryLater(int){retries++;}
|
|
void Observe(int id,int why){observations++;reason=why;observedBudget=m_Attempts.Get(id);m_Attempts.values.erase(id);}
|
|
void OnPlayerSpawnFailed_S(int id){m_Pending.values.erase(id);m_AuthorizedPositions.values.erase(id);RetryLater(id);}
|
|
Logic(){m_Identities.Insert(1);m_Attempts.Set(1,0);}
|
|
'''+method+r'''
|
|
};
|
|
int main(){
|
|
Mode mode;Mode::current=&mode;Controller controller;game.manager.controller=&controller;
|
|
// Blocked geometry, missing affiliation, missing respawn and missing faction
|
|
// each consume exactly 30 eligible attempts, then expose the existing observer.
|
|
for(int fault=0;fault<5;fault++){
|
|
Logic logic;
|
|
if(fault==0)logic.clear=false;
|
|
if(fault==1)logic.hasAffiliation=false;
|
|
if(fault==2)logic.hasRespawn=false;
|
|
if(fault==3)mode.factionPtr=nullptr;
|
|
if(fault==4)logic.respawn.can=false;
|
|
for(int n=1;n<=30;n++){
|
|
logic.DoSpawn_S(1);assert(logic.m_Attempts.Get(1)==n);
|
|
assert(logic.retries==n&&logic.observations==0&&logic.respawn.requests==0);
|
|
assert(!logic.m_Pending.Contains(1)&&!logic.m_AuthorizedPositions.Contains(1));
|
|
assert(!mode.roster.life.UsedLife);
|
|
}
|
|
int previousExhausted=mode.exhausted;
|
|
logic.DoSpawn_S(1);
|
|
assert(mode.exhausted==previousExhausted+1);
|
|
assert(logic.observations==1&&logic.reason==Reason::SPAWN_UNAVAILABLE&&logic.retries==30&&logic.observedBudget==30);
|
|
logic.DoSpawn_S(1);assert(logic.observations==1); // removed attempt record
|
|
assert(!mode.roster.life.UsedLife);mode.factionPtr=&faction;
|
|
}
|
|
// Preparing may take arbitrarily many polls without consuming the budget.
|
|
{Logic logic;mode.active=false;for(int n=0;n<200;n++)logic.DoSpawn_S(1);
|
|
assert(logic.m_Attempts.Get(1)==0&&logic.retries==200&&logic.observations==0&&logic.placementChecks==0);
|
|
mode.active=true;logic.DoSpawn_S(1);assert(logic.m_Attempts.Get(1)==1&&logic.respawn.requests==1);}
|
|
// Temporarily blocked ground recovers normally; async pending calls are inert.
|
|
{Logic logic;logic.clear=false;for(int n=0;n<7;n++)logic.DoSpawn_S(1);
|
|
logic.clear=true;logic.DoSpawn_S(1);
|
|
assert(logic.m_Attempts.Get(1)==8&&logic.respawn.requests==1&&logic.m_Pending.Contains(1));
|
|
assert(logic.m_AuthorizedPositions.Get(1)==42&&!mode.roster.life.UsedLife);
|
|
for(int n=0;n<100;n++)logic.DoSpawn_S(1);
|
|
assert(logic.m_Attempts.Get(1)==8&&logic.respawn.requests==1&&logic.placementChecks==8&&logic.retries==7);}
|
|
// Request refusal does not double-charge; the next eligible attempt advances.
|
|
{Logic logic;logic.respawn.request=false;logic.DoSpawn_S(1);
|
|
assert(logic.m_Attempts.Get(1)==1&&logic.retries==1&&!logic.m_Pending.Contains(1));
|
|
logic.DoSpawn_S(1);assert(logic.m_Attempts.Get(1)==2&&logic.retries==2);}
|
|
// Admission refusal is before budget consumption and cannot spend a life.
|
|
for(bool spent: {false,true}){
|
|
Logic logic;mode.roster.enter=false;mode.roster.life.UsedLife=spent;
|
|
logic.DoSpawn_S(1);assert(logic.observations==1&&logic.placementChecks==0&&logic.retries==0&&logic.observedBudget==0);
|
|
assert(logic.reason==(spent?Reason::SPENT_LIFE:Reason::LATE_EXTRACTION));
|
|
assert(mode.roster.life.UsedLife==spent);mode.roster.enter=true;mode.roster.life.UsedLife=false;
|
|
}
|
|
// Existing body, invalid local context, stopped admission, terminal phase,
|
|
// preparation failure and missing controller cannot request or consume attempts.
|
|
for(int fault=0;fault<6;fault++){
|
|
Logic logic;
|
|
if(fault==0)controller.controlled=true;
|
|
if(fault==1)logic.context=false;
|
|
if(fault==2)logic.m_AdmissionsStopped=true;
|
|
if(fault==3)mode.phase=5;
|
|
if(fault==4)mode.error.empty=false;
|
|
if(fault==5)game.manager.controller=nullptr;
|
|
logic.DoSpawn_S(1);
|
|
assert(!logic.m_Attempts.Contains(1)||logic.m_Attempts.Get(1)==0);
|
|
assert(logic.placementChecks==0&&logic.respawn.requests==0&&logic.retries==0);
|
|
controller.controlled=false;mode.phase=1;mode.error.empty=true;game.manager.controller=&controller;
|
|
}
|
|
}
|
|
''')
|
|
print('PASS: actual admission attempt budget bounds blocked/missing prerequisites; preparation, spent life and async pending remain protected')
|