Bundle mission speech with an admin mute option and fix live audio downloads

This commit is contained in:
2026-10-15 16:53:39 +02:00
parent 07c4d70de5
commit 009b2fb591
181 changed files with 19248 additions and 103 deletions
+41 -7
View File
@@ -135,7 +135,7 @@ print('PASS: production incremental decoder preserves binary data, bounds each s
def client_cpp(text):
text = cpp(text)
for old, new in [
('ERR_WelcomeVoiceRules.', 'Rules::'), ('ERR_GameMode.Get()', 'ERR_GameMode::Get()'),
('ResourceName', 'string'), ('ERR_BakedSpeech.', 'ERR_BakedSpeech::'), ('ERR_WelcomeVoiceRules.', 'Rules::'), ('ERR_GameMode.Get()', 'ERR_GameMode::Get()'),
('FileIO.', 'FileIO::'), ('AudioSystem.', 'AudioSystem::'),
('AudioHandle.Invalid', 'AudioHandle::Invalid'),
('FileMode.', 'FileMode::'), ('RestContext context', 'RestContext* context'),
@@ -154,7 +154,7 @@ def client_cpp(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()',
'protected void BeginPreparation(', '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'
@@ -175,16 +175,48 @@ connection = 'struct Connection { bool m_Attempted=false;' + '\n'.join(
wav = b'RIFF' + (9992).to_bytes(4, 'little') + b'WAVE' + random.Random(71).randbytes(9988)
wav64 = json.dumps(base64.b64encode(wav).decode())
expected = 'Array<int>{' + ','.join(map(str, wav)) + '}'
run(PREAMBLE + decoder + connection + r'''
transfer = cpp(body(SOURCE, 'class ERR_VoiceTransfer')).replace('class ERR_VoiceTransfer', 'struct ERR_VoiceTransfer').replace('int m_Total;', 'int m_Total=0;') + ';'
run(PREAMBLE + transfer + r'''
int main(){
for(int size:{60,262140,262144,262148,1600060,2796204}){
ERR_VoiceTransfer transfer;string expected(size, 'A');int offset=0,requests=0;
while(offset<size){
assert(transfer.Part()==offset/262144);
int length=std::min(262144,size-offset);
int result=transfer.Accept(offset,size,expected.substr(offset,length));
offset+=length;requests++;
assert(result==(offset==size?1:0));assert(requests<=11);
}
assert(transfer.Data()==expected);
assert(transfer.Accept(offset,size,"AAAA")==-1);
transfer.Clear();assert(transfer.Data().IsEmpty()&&transfer.Part()==0);
}
for(int total:{0,59,61,2796208}){
ERR_VoiceTransfer bad;assert(bad.Accept(0,total,"AAAA")==-1);
assert(bad.Data().IsEmpty());
}
ERR_VoiceTransfer partial;assert(partial.Accept(0,524288,string(262144,'A'))==0);
assert(partial.Accept(0,524288,string(262144,'A'))==-1); // duplicate
assert(partial.Accept(524288,786432,string(262144,'A'))==-1); // gap
assert(partial.Accept(262144,524292,string(262144,'A'))==-1); // changed size
assert(partial.Accept(262144,524288,string(262140,'A'))==-1); // truncated
assert(partial.Accept(262144,524288,string(262148,'A'))==-1); // oversized
assert(partial.Data().Length()==262144);
assert(partial.Accept(262144,524288,string(262144,'A'))==1);
}
''')
print('PASS: production speech transfer checks order, exact part sizes and stable bounded totals without changing audio')
run(PREAMBLE + decoder + transfer + 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;
struct ERR_GameMode {static inline ERR_GameMode* live=nullptr;bool active=true;bool ERR_RecordingsEnabled(){return 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;}
struct ERR_BakedSpeech {static string Graph(string){return "";}};
struct Rules {static bool HTTPReady(){return true;}static bool Source(string value){return URL(value);}
''' + body(SOURCE, 'static bool URL(') + r'''
};
namespace FileMode {enum Type {WRITE,READ};}
@@ -222,6 +254,8 @@ struct Game {
} game;
Game* GetGame(){return &game;}
struct Client {
ERR_VoiceTransfer* m_Transfer=nullptr;string m_DownloadURL,m_BakedGraph;
void RequestPart(){requests++;}
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;
@@ -229,7 +263,7 @@ struct Client {
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);}
void Receive(string encoded){Tick(3,true,"http://voice/clip","hello");BeginPreparation(encoded);}
};
''' + '\n'.join(queue_methods) + r'''
struct Soldier:Client {
@@ -276,7 +310,7 @@ int main(){
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.Stop();stopped.BeginPreparation(wav);
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.