Files
EscapistsReintroduced/tests/test_journal_readability.py
T
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

82 lines
6.5 KiB
Python

"""Actual readable journal formatter and retained-record transport semantics."""
import re
from policy_harness import ROOT,body,run
p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_JournalText.c'
formatter=p.read_text()[p.read_text().index('class ERR_JournalText'):]
formatter=formatter.replace('class ERR_JournalText', 'struct ERR_JournalText').replace('\n}\n','\n};\n')
formatter=re.sub(r'(sequence|messageId)\.ToString\(\)',r'string(std::to_string(\1))',formatter)
journal=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQJournal.c'
write=body(journal,'void Write(').replace('Replication.IsServer()', 'Replication::server').replace('GetGame().GetWorld().GetWorldTime() / 1000','12.7f').replace('Math.Round(seconds * 10)', 'std::round(seconds * 10)').replace('string.Format', 'string::Format').replace('ERR_JournalText.Entry','ERR_JournalText::Entry').replace('SCR_DateTimeHelper.GetTimeLocal()', 'SCR_DateTimeHelper::GetTimeLocal()')
read=body(journal,'void Read(').replace('out array<string> lines','Array<string>& lines').replace('out int cursor','int& cursor').replace('Math.Clamp','Math::Clamp').replace('string.Format','string::Format').replace('int characters;','int characters=0;')
run(r'''
#include <cassert>
#include <algorithm>
#include <cmath>
#include <sstream>
#include <string>
#include <vector>
struct string:std::string{
using std::string::string;string(std::string s):std::string(s){}
int Length()const{return size();}bool IsEmpty()const{return empty();}
int IndexOf(string s)const{auto i=find(s);return i==npos?-1:int(i);}
string Substring(int p,int n)const{return substr(p,n);}bool Contains(string s)const{return find(s)!=npos;}
void Replace(string a,string b){size_t i=0;while((i=find(a,i))!=npos){replace(i,a.size(),b);i+=b.size();}}
void ToLower(){for(char& c:*this)c=std::tolower((unsigned char)c);}
template<class T>static string Convert(T v){std::ostringstream out;out<<v;return out.str();}
template<class...T>static string Format(string fmt,T...args){std::vector<string> values{Convert(args)...};for(int i=values.size();i>0;i--)fmt.Replace("%"+std::to_string(i),values[i-1]);return fmt;}
};
template<class T>struct Array:std::vector<T>{int Count()const{return this->size();}bool IsEmpty()const{return this->empty();}void Insert(T v){this->push_back(v);}void Remove(int i){this->erase(this->begin()+i);}void Clear(){this->clear();}};
namespace Replication{bool server=true;}
namespace SCR_DateTimeHelper{string clock="09:05:07";string GetTimeLocal(){return clock;}}
namespace Math{int Clamp(int v,int a,int b){return std::clamp(v,a,b);}}
'''+formatter+r'''
struct Journal{
Array<string> m_Recent,m_Pending;int m_Sequence=0,m_Lost=0;string m_Session="fixture";
string Path(){return "test.log";}
'''+write+read+r'''
};
int main(){
assert(ERR_JournalText::Field("destination=<10, 20, 30>; foo=bar","destination")=="<10, 20, 30>");
assert(ERR_JournalText::Field("foo=bar; baz=","baz")=="unknown");
assert(ERR_JournalText::Field("no fields","crew")=="unknown");
auto ended=ERR_JournalText::Description("BODY RECOVERY END","job=1 stage=0 delivered=0; preparation deadline");
assert(ended.Contains("without an inspection report"));
assert(ERR_JournalText::Description("BODY RECOVERY END","job=1 stage=3 delivered=1;").Contains("HQ received"));
assert(ERR_JournalText::Description("BODY RECOVERY TEAM","job=1").Contains("preparation may still be pending"));
auto queued=ERR_JournalText::Description("ORDER QUEUED","group 4 search");
assert(queued.Contains("not received")&&!queued.Contains("order delivered"));
assert(ERR_JournalText::Description("ORDER DELIVERED","group 4 search").Contains("order delivered"));
assert(ERR_JournalText::Description("CONTACT LOCAL","group=3 observed=<1,2,3>").Contains("still needs to reach HQ"));
assert(ERR_JournalText::Description("LOST","caller died").Contains("failed"));
auto spawned=ERR_JournalText::Description("SPAWN MEMBER","group=3 actor=Character<0x123> prefab={ABC}asset position=<10,20,30>");
assert(spawned.Contains("Group 3")&&spawned.Contains("<10,20,30>")&&!spawned.Contains("0x")&&!spawned.Contains("prefab"));
auto escalation=ERR_JournalText::Description("AGGRESSION","bodies=9 aggression=3/30 hunter allowance=5/15");
assert(escalation.Contains("9 bodies")&&escalation.Contains("3/30")&&escalation.Contains("5/15"));
auto mortar=ERR_JournalText::Description("MORTAR SITE STATUS","site=2 operational=0 fighting=4 inventory HE=27 radio connected=1");
assert(mortar.Contains("operational: no")&&mortar.Contains("27")&&mortar.Contains("connected: yes"));
auto mine=ERR_JournalText::Description("MINE EQUIPMENT","actor=Character<0x123> finite two-mine allowance issued");
assert(mine.Contains("up to two")&&!mine.Contains("0x"));
assert(ERR_JournalText::Description("FUTURE_EVENT","something happened")=="future event: something happened");
Journal journal;Replication::server=false;journal.Write("REGISTER","group=1");assert(journal.m_Sequence==0);Replication::server=true;
journal.Write("ORDER QUEUED","group 4 search",7);
string full=journal.m_Recent[0];assert(full.Contains("#1 t=")&&full.Contains("M7 ORDER QUEUED group 4 search [session=fixture]"));
assert(journal.m_Pending[0]==full&&full.Contains("not received")&&full.find("[09:05:07] ")==0);
SCR_DateTimeHelper::clock="00:00:01"; // later viewing must not restamp the stored event
string summary=ERR_JournalText::Menu(full,false),details=ERR_JournalText::Menu(full,true);
assert(!summary.Contains("session=")&&summary.Contains("event 1, message 7"));
assert(details.Contains("session=")&&details.Contains("No. 1")&&details.Contains(summary));
assert(journal.m_Recent[0]==full&&summary.find("[09:05:07] ")==0); // retained event time survives later rendering
Journal midnight;midnight.Write("REPORT","after midnight");
assert(midnight.m_Recent[0].find("[00:00:01] ")==0);
assert(ERR_JournalText::Menu("#4 legacy",false)=="No. 4 legacy");
for(int i=0;i<304;i++)journal.Write("REPORT","position known",i);
Array<string> lines;int cursor; journal.Read(0,lines,cursor,8);
assert(journal.m_Recent.Count()==300&&lines.Count()==8&&lines[0].Contains("5 older entries")&&cursor==12);
int before=cursor;journal.Read(cursor,lines,cursor,8);assert(lines.Count()==8&&cursor==before+8);
Journal large;string huge(9000,'x');large.Write("UNKNOWN",huge);large.Write("UNKNOWN",huge);
large.Read(0,lines,cursor,40);assert(lines.Count()==1&&cursor==1);
large.Read(cursor,lines,cursor,40);assert(lines.Count()==1&&cursor==2); // unread event not skipped at RPC limit
}
''')
print('PASS: actual summaries preserve queued/delivered meaning, clear counts and IDs; raw records, legacy display, cursor gaps and RPC limits remain intact')