Fix faction ammunition resupply and settings recovery

Wait for both convoy cargo phases and use actual magazine stock. Resolve empty primary weapon ammunition from muzzle metadata and recover cleanly from failed initial settings saves.

Update native fixtures, scoped cleanup guards, packaging evidence and validation documentation. Make reviewed fixture tests checkout-relative. All repository checks pass against the isolated staged tree; native resupply completion and multiplayer qualification remain outstanding.
This commit is contained in:
2026-09-30 14:34:46 +02:00
parent c5629f7f4f
commit a951a96edc
158 changed files with 39131 additions and 69 deletions
+86
View File
@@ -54,3 +54,89 @@ for key,(_,lo,hi) in m.SCHEMA.items():
checks.append(f'assert(Valid("{key}",{value})=={str(expected).lower()});')
run('#include <cassert>\n#include <string>\n'+validator+'\nint main(){'+''.join(checks)+'assert(!Valid("unknown",1));}\n')
print('PASS: legacy values preserved, unsupported fields reported, invalid data rejected, existing files protected, runtime bounds aligned')
# Actual settings transaction: failures during final file promotion must not
# poison the next round when there was no previous settings file.
source = Path(os.environ.get('ERR_SETTINGS_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c'))
method = body(source, 'bool Set(')
method = (method.replace('string ', 'std::string ').replace('Replication.', 'Replication::')
.replace('FileIO.', 'FileIO::').replace('foreach (', 'for (')
.replace('JsonSaveContext json = new JsonSaveContext();', 'JsonSaveContext json;')
.replace('map<string, int> ', 'Values ').replace('map<std::string, int> ', 'Values '))
run(r'''
#include <cassert>
#include <map>
#include <string>
#include <vector>
using string = std::string;
struct Values {
std::map<string,int> data;
int Get(string key){return data.at(key);}
void Remove(string key){data.erase(key);}
};
struct File {bool valid=true;Values values;};
std::map<string,File> disk;
int fault=0;
namespace Replication {bool server=true;bool IsServer(){return server;}}
namespace FileIO {
bool FileExists(string path){return disk.count(path);}
bool DeleteFile(string path){return disk.erase(path);}
bool CopyFile(string from,string to){
if(!disk.count(from))return false;
disk[to]=disk[from];
if(from=="settings.tmp"){
if(fault==1){disk[to].valid=false;return false;} // partial copy reports failure
if(fault==2)disk[to].valid=false; // copy succeeds, readback fails
if(fault==3)disk[to].values.data["radio"]=31; // valid but incorrect contents
}
return true;
}
}
struct JsonSaveContext {
File pending;
void EnableTypeDiscriminator(bool value){}
void WriteValue(string key,int value){pending.values.data[key]=value;}
bool SaveToFile(string path){disk[path]=pending;return true;}
};
struct Settings {
bool m_ReadOnly=false;
string m_Path="settings";
Values m_Values{{{"radio",180},{"limit",4}}},m_Resolved{{{"radio",60}}};
std::vector<string> Keys(){return {"radio","limit"};}
bool ValidSelection(string key,int value){return (key=="radio"&&value>=30&&value<=1800)||(key=="limit"&&value>=0&&value<=8);}
int Get(string key){return m_Values.Get(key);}
bool Read(string path,Values& values){
if(!disk.count(path)||!disk[path].valid)return false;
values=disk[path].values;return true;
}
bool Restart(){
Values loaded;
if(Read(m_Path,loaded)||Read(m_Path+".bak",loaded))m_Values=loaded;
else if(FileIO::FileExists(m_Path)||FileIO::FileExists(m_Path+".bak"))m_ReadOnly=true;
return !m_ReadOnly;
}
'''+method+r'''
};
int main(){
for(int injected : {1,2,3}){
// A failed first save must not install a partial or rejected file for restart.
disk.clear();fault=injected;Settings initial;
assert(!initial.Set("radio",300));
assert(initial.Get("radio")==180&&initial.m_Resolved.Get("radio")==60);
assert(!FileIO::FileExists("settings"));
Settings restarted;assert(restarted.Restart());assert(restarted.Get("radio")==180);
fault=0;assert(restarted.Set("radio",450));assert(restarted.Get("radio")==450);
Settings persisted;assert(persisted.Restart()&&persisted.Get("radio")==450);
// An existing valid save is restored from the newly captured backup.
disk.clear();fault=0;Settings saved;assert(saved.Set("radio",600));
fault=injected;assert(!saved.Set("radio",900));assert(saved.Get("radio")==600);
Settings restored;assert(restored.Restart()&&restored.Get("radio")==600);
// A preexisting fallback backup remains authoritative when primary is absent.
disk.clear();disk["settings.bak"]={true,Values{{{"radio",750},{"limit",4}}}};
Settings fallback;assert(fallback.Restart()&&fallback.Get("radio")==750);
assert(!fallback.Set("radio",900));
Settings recovered;assert(recovered.Restart()&&recovered.Get("radio")==750);
}
}
''')
print('PASS: failed initial settings promotion leaves defaults writable after restart; prior/fallback saves survive copy, readback and mismatch faults')