"""Execute production incremental audio preparation with clock/file/audio doubles. These regressions prove bounded script work and lifecycle behavior, not native audio latency; the separate Workbench fixture measures native execution. """ import base64 import json import random import re from policy_harness import ROOT, body, run SOURCE = ROOT / 'addons/core/Scripts/Game/ERR/00_Core/ERR_WelcomeVoice.c' def cpp(text): for old, new in [ ('protected ', ''), ('override ', ''), ('ref ', ''), ('null', 'nullptr'), ('Math.Min', 'std::min'), ('Math.Max', 'std::max'), ('System.GetTickCount', 'System::GetTickCount'), ('array bytes', 'Array& bytes'), ]: text = text.replace(old, new) return text PREAMBLE = r''' #include #include #include #include #include #include namespace System { int now=0, increment=0; int GetTickCount(){int result=now;now+=increment;return result;} } struct string:std::string { using std::string::string; string(std::string value):std::string(value){} bool IsEmpty() const{return empty();} int Length() const{return size();} int ToAscii(int offset=0) const{return static_cast(at(offset));} string Substring(int offset,int count) const{return substr(offset,count);} int IndexOf(string value) const{auto p=find(value);return p==npos?-1:int(p);} }; templatestruct Array:std::vector { using std::vector::vector; int Count() const{return this->size();} bool IsEmpty() const{return this->empty();} void Clear(){this->clear();} void Insert(T value){this->push_back(value);} bool Contains(T value) const{return std::find(this->begin(),this->end(),value)!=this->end();} int Find(T value) const{auto p=std::find(this->begin(),this->end(),value);return p==this->end()?-1:int(p-this->begin());} // Native Enforce Remove replaces the removed slot with the last element. // Its explicitly ordered variants preserve FIFO order. void Remove(int index){(*this)[index]=this->back();this->pop_back();} void RemoveItem(T value){int index=Find(value);if(index>=0)Remove(index);} void RemoveOrdered(int index){this->erase(this->begin()+index);} void RemoveItemOrdered(T value){int index=Find(value);if(index>=0)RemoveOrdered(index);} }; #define Print(...) #define PrintFormat(...) ''' decoder = cpp(body(SOURCE, 'class ERR_WelcomeVoiceDecoder')).replace( 'class ERR_WelcomeVoiceDecoder', 'struct ERR_WelcomeVoiceDecoder') + ';' cases = [] for size in [44, 45, 46, 47, 255, 256, 257, 4094, 4095, 4096, 8192]: expected = random.Random(size).randbytes(size) encoded = base64.b64encode(expected).decode() cases.append('Check(' + json.dumps(encoded) + ', Array{' + ','.join(map(str, expected)) + '});') run(PREAMBLE + decoder + r''' Array DecodeAll(string text,int limit=5460) { ERR_WelcomeVoiceDecoder decoder;assert(decoder.Begin(text)); Array result,chunk;int slices=0,status=0; while(status==0){ status=decoder.Step(chunk,limit,2); assert(status>=0&&chunk.Count()<=4095); for(int value:chunk)result.Insert(value); assert(++slices<10000); } assert(status==1);return result; } void Check(string encoded,Array expected) { for(int limit:{4,1024,5460})assert(DecodeAll(encoded,limit)==expected); } int main(){ ''' + '\n'.join(cases) + r''' // Maximum PCM transport payload and both legal padding forms survive slicing. for(int size:{2097150,2097151,2097152}){ string encoded((size+2)/3*4,'A'); if(size%3==1){encoded[encoded.size()-2]='=';encoded.back()='=';} if(size%3==2)encoded.back()='='; auto bytes=DecodeAll(encoded);assert(bytes.Count()==size); for(int value:bytes)assert(value==0); } // This encoded length is allowed, but its unpadded result exceeds 2 MiB. ERR_WelcomeVoiceDecoder over;assert(over.Begin(string(2796204,'A'))); Array chunk;int status=0; while(status==0)status=over.Step(chunk); assert(status==-1); // Bad alphabet and padding are rejected even in a later slice/block. for(int offset:{0,1023,1024,5459,5460,7999}){ ERR_WelcomeVoiceDecoder bad;string encoded(8000,'A');encoded[offset]='?'; assert(bad.Begin(encoded));status=0; while(status==0)status=bad.Step(chunk); assert(status==-1); } for(string encoded:{string(59,'A'),string(61,'A'),string(2796208,'A'),string()}){ ERR_WelcomeVoiceDecoder bad;assert(!bad.Begin(encoded)); } for(string tail:{"AA=A","AB==","AAB=","=AAA","A=AA"}){ ERR_WelcomeVoiceDecoder bad;assert(bad.Begin(string(7996,'A')+tail));status=0; while(status==0)status=bad.Step(chunk); assert(status==-1); } {ERR_WelcomeVoiceDecoder bad;string encoded(8000,'A');encoded[1023]='='; assert(bad.Begin(encoded));status=0;while(status==0)status=bad.Step(chunk);assert(status==-1);} // The byte ceiling is independent of the clock. The elapsed-time guard also // yields when individual engine operations cost time. ERR_WelcomeVoiceDecoder slow;assert(slow.Begin(string(8000,'A'))); System::increment=1;status=slow.Step(chunk);System::increment=0; assert(status==0&&chunk.Count()>0&&chunk.Count()<4095); slow.Clear();assert(slow.Begin(string(60,'A')));assert(slow.Step(chunk)==1&&chunk.Count()==45); } ''') print('PASS: production incremental decoder preserves binary data, bounds each slice, yields on elapsed time and rejects malformed or oversized payloads') def client_cpp(text): text = cpp(text) for old, new in [ ('ERR_WelcomeVoiceRules.', 'Rules::'), ('ERR_GameMode.Get()', 'ERR_GameMode::Get()'), ('FileIO.', 'FileIO::'), ('AudioSystem.', 'AudioSystem::'), ('AudioHandle.Invalid', 'AudioHandle::Invalid'), ('FileMode.', 'FileMode::'), ('RestContext context', 'RestContext* context'), ('RestCallback callback', 'RestCallback* callback'), ('FileHandle file', 'File* file'), ('Array& bytes = {}', 'Array bytes'), ('GetGame().', 'GetGame()->'), ('GetRestApi().', 'GetRestApi()->'), ('m_Callback.SetOnSuccess(Downloaded)', 'm_Callback.SetOnSuccess(0)'), ('m_Callback.SetOnError(Failed)', 'm_Callback.SetOnError(0)'), ]: text = text.replace(old, new) for name in ['m_Connection', 'm_Callback', 'm_Decoder', 'm_Output', 'm_Mode', 'callback', 'context', 'file']: text = text.replace(name + '.', name + '->') return text client_methods = '\n'.join(client_cpp(body(SOURCE, signature)) for signature in [ 'protected bool Current()', 'bool PreparingDownload()', 'void Tick(', 'protected void Downloaded(', 'protected void CancelDownload()', 'bool PrepareDownload()', 'void Stop()', ]).replace('void Tick(', 'virtual void Tick(').replace('void Stop()', 'virtual void Stop()') soldier_source = ROOT / 'addons/core/Scripts/Game/ERR/10_AI/ERR_SoldierSpeech.c' soldier_methods = '\n'.join(client_cpp(body(soldier_source, signature)) for signature in [ 'override void Tick(', 'override void Stop()', ]).replace('super.Tick', 'Client::Tick').replace('super.Stop', 'Client::Stop') queue_methods = [] for signature in ['void ERR_QueueVoiceDownload(', 'void ERR_CancelVoiceDownload(', 'protected void ERR_PrepareVoiceDownloads()', 'protected void ERR_StopVoiceDownloads()']: method = cpp(body(SOURCE, signature)).replace('ERR_WelcomeVoiceClient client', 'Client* client').replace('client.', 'client->') method = re.sub(r'^(void) (ERR_\w+)', r'\1 Game::\2', method) queue_methods.append(method) connection = 'struct Connection { bool m_Attempted=false;' + '\n'.join( body(SOURCE, signature) for signature in ['void MainMenu()', 'bool Claim()', 'bool Attempted()']) + '};' # The native WAV parser remains outside this policy harness. A RIFF/WAVE byte # fixture exercises the production header/length checks and exact file output. wav = b'RIFF' + (9992).to_bytes(4, 'little') + b'WAVE' + random.Random(71).randbytes(9988) wav64 = json.dumps(base64.b64encode(wav).decode()) expected = 'Array{' + ','.join(map(str, wav)) + '}' run(PREAMBLE + decoder + connection + r''' namespace AudioHandle {const int Invalid=-1;} namespace AudioSystem {int stopped=0;void TerminateSound(int){stopped++;}} struct ERR_GameMode {static inline ERR_GameMode* live=nullptr;bool active=true; static ERR_GameMode* Get(){return live;}bool Active(){return active;}}; struct RestCallback {string data;void SetOnSuccess(int){}void SetOnError(int){}string GetData(){return data;}}; int requests=0; struct RestContext {void SetTimeout(int){}void GET(RestCallback*,string){requests++;}}; struct Api {RestContext context;RestContext* GetContext(string){return &context;}}; struct Rules {static bool HTTPReady(){return true;} ''' + body(SOURCE, 'static bool URL(') + r''' }; namespace FileMode {enum Type {WRITE,READ};} int writes=0,opens=0,deletes=0,closes=0,maxWrite=0; bool openWrite=true,openRead=true,shortWrite=false; int lengthOverride=-1; struct File { bool exists=false;Array data;int cursor=0; int WriteArray(Array& values,int elementSize){ assert(exists&&elementSize==1);writes++;maxWrite=std::max(maxWrite,values.Count()); int count=values.Count()-(shortWrite?1:0); for(int i=0;i=0?lengthOverride:data.Count();} void Read(string& result,int count){result="";for(int i=0;i files;std::map deleteCounts; void DeleteFile(string path){deletes++;deleteCounts[path]++;auto& file=files[path];file.exists=false;file.data.Clear();} File* OpenFile(string path,FileMode::Type mode){ opens++;auto& file=files[path]; if(mode==FileMode::WRITE){if(!openWrite)return nullptr;file.exists=true;file.data.Clear();} else if(!openRead||!file.exists)return nullptr; file.cursor=0;return &file; } } struct Client; struct Game { Api api;Array m_ERRVoiceDownloads;Api* GetRestApi(){return &api;} void ERR_QueueVoiceDownload(Client*);void ERR_CancelVoiceDownload(Client*); void ERR_PrepareVoiceDownloads();void ERR_StopVoiceDownloads(); } game; Game* GetGame(){return &game;} struct Client { ERR_GameMode* m_Mode=nullptr;string m_Path,m_Text;Connection connection;Connection* m_Connection=&connection; bool m_Stopped=false,m_Downloaded=false,m_Started=false;float m_Wait=0; RestCallback* m_Callback=nullptr;ERR_WelcomeVoiceDecoder* m_Decoder=nullptr;File* m_Output=nullptr; int m_PreparationBytes=0,m_PreparationFrames=0,m_PreparationLongest=0,m_Handle=AudioHandle::Invalid,plays=0; explicit Client(string path):m_Mode(ERR_GameMode::live),m_Path(path){} bool Permit(){return true;}virtual void Play(){plays++;} ''' + client_methods + r''' void Receive(string encoded){Tick(3,true,"http://voice/clip","hello");RestCallback response;response.data=encoded;Downloaded(&response);} }; ''' + '\n'.join(queue_methods) + r''' struct Soldier:Client { using Client::Client;bool Prepared=false;float m_Age=0;string GraphPath; void Play() override{plays++;Prepared=true;} // Native graph/audio boundary. ''' + soldier_methods + r''' }; void Drain(){int frames=0;while(!game.m_ERRVoiceDownloads.IsEmpty()){game.ERR_PrepareVoiceDownloads();assert(++frames<2000);}} int main(){ ERR_GameMode mode;ERR_GameMode::live=&mode; string wav=WAV64;Array expected=EXPECTED; // HTTP completion only enqueues; decoding and filesystem work wait for update. Client first("first"),second("second");first.Receive(wav);second.Receive(wav); assert(game.m_ERRVoiceDownloads.Count()==2&&writes==0&&opens==0); assert(first.PreparingDownload()&&second.PreparingDownload()); // Duplicate advertisements cannot cause multiple preparations in one frame. game.ERR_QueueVoiceDownload(&first);assert(game.m_ERRVoiceDownloads.Count()==2); game.ERR_PrepareVoiceDownloads();assert(writes==1&&first.m_PreparationFrames==1&&second.m_PreparationFrames==0); first.Tick(1,true,"","");assert(first.plays==0&&!first.m_Downloaded); game.ERR_PrepareVoiceDownloads();assert(writes==2&&first.m_PreparationFrames==1&&second.m_PreparationFrames==1); int beforeWrites=writes;game.ERR_PrepareVoiceDownloads();assert(writes-beforeWrites==1); Drain();assert(maxWrite<=4095&&FileIO::files["first"].data==expected&&FileIO::files["second"].data==expected); assert(first.m_Downloaded&&second.m_Downloaded&&first.PreparingDownload()&&first.plays==0); first.Tick(1,false,"","");assert(first.plays==0); first.Tick(1,true,"","");first.Tick(1,true,"","");assert(first.plays==1&&!first.PreparingDownload()); // Four simultaneous clips expose native unordered-removal behavior which a // two-client queue cannot distinguish. Every live client gets one turn before // any gets another, even after a middle client is cancelled. Client fairA("fairA"),fairB("fairB"),fairC("fairC"),fairD("fairD"); fairA.Receive(wav);fairB.Receive(wav);fairC.Receive(wav);fairD.Receive(wav); game.ERR_PrepareVoiceDownloads(); assert(fairA.m_PreparationFrames==1&&fairB.m_PreparationFrames==0&&fairC.m_PreparationFrames==0&&fairD.m_PreparationFrames==0); assert(game.m_ERRVoiceDownloads==Array({&fairB,&fairC,&fairD,&fairA})); fairC.Stop();assert(game.m_ERRVoiceDownloads==Array({&fairB,&fairD,&fairA})); game.ERR_PrepareVoiceDownloads();game.ERR_PrepareVoiceDownloads(); assert(fairA.m_PreparationFrames==1&&fairB.m_PreparationFrames==1&&fairC.m_PreparationFrames==0&&fairD.m_PreparationFrames==1); for(int i=0;i<3;i++)game.ERR_PrepareVoiceDownloads(); assert(fairA.m_PreparationFrames==2&&fairB.m_PreparationFrames==2&&fairD.m_PreparationFrames==2); assert(!fairA.m_Downloaded&&!fairB.m_Downloaded&&!fairD.m_Downloaded); Drain();assert(fairA.m_Downloaded&&fairB.m_Downloaded&&fairD.m_Downloaded&&!fairC.m_Downloaded); assert(FileIO::files["fairA"].data==expected&&FileIO::files["fairB"].data==expected&&FileIO::files["fairD"].data==expected); // Cancelling one job closes/deletes its partial file and preserves other jobs. Client stopped("stopped"),survivor("survivor");stopped.Receive(wav);survivor.Receive(wav); game.ERR_PrepareVoiceDownloads();int beforeCloses=closes;stopped.Stop(); assert(closes==beforeCloses+1&&!FileIO::files["stopped"].exists&&!stopped.PreparingDownload()); assert(game.m_ERRVoiceDownloads.Count()==1);beforeWrites=writes; stopped.Stop();RestCallback late;late.data=wav;stopped.Downloaded(&late); stopped.Tick(1,true,"","");assert(stopped.plays==0&&writes==beforeWrites&&game.m_ERRVoiceDownloads.Count()==1); Drain();assert(survivor.m_Downloaded); // Each failure path removes the partial file and never announces readiness. for(int failure=0;failure<7;failure++){ Client bad("bad"+std::to_string(failure));string input=wav; if(failure==0)input[input.size()-5]='?'; // malformed final slice if(failure==1)input[0]='A'; // decodes, but RIFF signature is invalid bad.Receive(input);game.ERR_PrepareVoiceDownloads(); if(failure==2)shortWrite=true; if(failure==3)openRead=false; if(failure==4)lengthOverride=43; if(failure==5)lengthOverride=2097153; if(failure==6)mode.active=false; Drain();bad.Tick(1,true,"",""); assert(!bad.m_Downloaded&&!bad.PreparingDownload()&&bad.plays==0&&!FileIO::files[bad.m_Path].exists); shortWrite=false;openRead=true;lengthOverride=-1;mode.active=true; } Client noFile("noFile");noFile.Receive(wav);openWrite=false;Drain();openWrite=true; assert(!noFile.m_Downloaded&&!noFile.PreparingDownload()&&!FileIO::files["noFile"].exists); Client invalidSize("invalidSize");invalidSize.Receive("AAAA");assert(!invalidSize.PreparingDownload()&&game.m_ERRVoiceDownloads.IsEmpty()); // World/main-menu teardown empties every queued job without preparing it. Client oldWorld("oldWorld"),otherOldWorld("otherOldWorld");oldWorld.Receive(wav);otherOldWorld.Receive(wav); game.ERR_PrepareVoiceDownloads();beforeWrites=writes;game.ERR_StopVoiceDownloads(); assert(game.m_ERRVoiceDownloads.IsEmpty()&&oldWorld.m_Stopped&&otherOldWorld.m_Stopped&&writes==beforeWrites); assert(!FileIO::files["oldWorld"].exists&&!FileIO::files["otherOldWorld"].exists); // A stale world is also rejected if its queue entry survives until a tick. Client stale("stale");stale.Receive(wav);ERR_GameMode::live=nullptr;Drain(); assert(!stale.PreparingDownload()&&!stale.m_Downloaded&&!FileIO::files["stale"].exists); ERR_GameMode::live=&mode; // A completed download may spend more than 30 seconds in the shared queue or // wait for playback readiness. Neither state is a stalled HTTP request. Soldier soldier("soldier");soldier.Receive(wav);soldier.Tick(31,true,"",""); assert(!soldier.m_Stopped&&soldier.PreparingDownload()&&!soldier.Prepared&&soldier.plays==0); Drain();assert(soldier.m_Downloaded); soldier.Tick(31,false,"","");assert(!soldier.m_Stopped&&soldier.m_Downloaded&&soldier.plays==0); soldier.Tick(1,true,"","");assert(soldier.Prepared&&soldier.plays==1&&!soldier.m_Stopped); soldier.Tick(120,true,"","");assert(soldier.Prepared&&!soldier.m_Stopped&&soldier.plays==1); // An HTTP request that never delivers and a failed preparation still expire. Soldier stalled("stalled");stalled.Tick(3,true,"http://voice/clip",""); assert(stalled.m_Started&&!stalled.PreparingDownload());stalled.Tick(28,true,"","");assert(stalled.m_Stopped); Soldier failed("failed");failed.Receive("AAAA");failed.Tick(28,true,"","");assert(failed.m_Stopped); // A subclass stop invoked through the queue closes both partial WAV and graph // exactly once; retained stopped clips are harmless on subsequent updates. Soldier stopping("stopping");stopping.GraphPath="stopping.acp"; FileIO::files[stopping.GraphPath].exists=true;stopping.Receive(wav);game.ERR_PrepareVoiceDownloads(); stopping.m_Handle=3;int beforeAudioStops=AudioSystem::stopped; game.ERR_StopVoiceDownloads();assert(stopping.m_Stopped&&!stopping.Prepared); assert(!FileIO::files[stopping.GraphPath].exists&&FileIO::deleteCounts[stopping.GraphPath]==1); int beforeDeletes=deletes;stopping.Stop();stopping.Tick(60,true,"",""); assert(deletes==beforeDeletes&&AudioSystem::stopped==beforeAudioStops+1&&game.m_ERRVoiceDownloads.IsEmpty()); } '''.replace('WAV64', wav64).replace('EXPECTED', expected)) print('PASS: production client callbacks only queue; shared updates prepare one bounded slice, preserve bytes, handle file failures and cancel safely before playback') print('PASS: production soldier timeout preserves queued/completed preparation, expires stalled requests and removes stopped graphs only once')