124 lines
24 KiB
C++
124 lines
24 KiB
C++
#include "irons/gameFramework/Game.hpp"
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <fstream>
|
|
#include <functional>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <vector>
|
|
using namespace irons::gameFramework;
|
|
namespace {
|
|
struct Memory : IProfileStorage {
|
|
std::string bytes;bool failWrite=false;int writes=0,failAt=-1;Result load(std::string& out)override{if(bytes.empty())return Result::fail(Code::Missing);out=bytes;return {};}
|
|
Result commitAtomically(std::string_view data)override{writes++;if(failWrite||writes==failAt)return Result::fail(Code::SaveFailed);bytes=data;return {};}
|
|
};
|
|
struct Ids : IIdGenerator {int n=0;std::string next()override{std::ostringstream s;s<<std::hex<<std::setfill('0')<<std::setw(32)<<++n;return s.str();}};
|
|
void require(bool ok,const char* message="expectation failed"){if(!ok)throw std::runtime_error(message);}
|
|
void ok(Result r){require(static_cast<bool>(r),r.detail.empty()?"command failed":r.detail.c_str());}
|
|
struct Fixture {
|
|
Memory memory;Ids ids;Session session{memory,ids};
|
|
Fixture(){ok(session.load());}
|
|
Item item(std::string id,int count=1){Item i;i.uid=ids.next();i.itemId=id;i.count=count;return i;}
|
|
std::string uid(std::string_view id)const{for(const auto& i:session.state().warehouse)if(i.itemId==id)return i.uid;throw std::runtime_error("item missing");}
|
|
void stock(){ok(session.seedAcceptanceStock());}
|
|
void patch(const std::function<void(Profile&)>& change){auto p=session.state();change(p);memory.bytes=encode(p);ok(session.load());}
|
|
};
|
|
int passed=0,failed=0;std::vector<std::pair<std::string,bool>> results;
|
|
void test(const std::string& name,const std::function<void()>& action){try{action();passed++;results.push_back({name,true});}catch(const std::exception& e){failed++;results.push_back({name,false});std::cerr<<"FAIL "<<name<<": "<<e.what()<<'\n';}}
|
|
}
|
|
int main(int argc,char** argv){
|
|
test("Starter ownership and V1",[]{Fixture f;require(f.session.state().version==1&&f.session.state().warehouse.size()==4&&f.session.state().sorties==0);ok(validate(f.session.state()));});
|
|
test("Source definition catalog",[]{auto c=Catalog::defaults();require(c.equipment.size()==9&&c.chassis.size()==3&&c.chassis[0].speed==8&&c.part("coil")->slot==0);});
|
|
for(auto id:{"rapid","coil","siege","guided","pulse","efficient","vector","composite_guard","reactive_guard"})test(std::string("Grid rotation ")+id,[id]{auto a=Grid::size(id),b=Grid::size(id,true);require(a.first==b.second&&a.second==b.first&&Grid::slot(id)>=0);});
|
|
test("Grid occupancy rejects overlap",[]{std::vector<Item> v;Item a{"a","coil"},b{"b","coil"};require(Grid::place(v,4,4,a,0,0,false));require(!Grid::place(v,4,4,b,1,1,false));require(v.size()==1);});
|
|
test("Grid rejects negative and out of bounds",[]{std::vector<Item> v;Item a{"a","siege"};require(!Grid::place(v,4,4,a,-1,0,false)&&!Grid::place(v,4,4,a,3,0,false));});
|
|
test("Pod can pack differently oriented shapes",[]{std::vector<Item> v={Item{"a","guided"},Item{"b","efficient"}};require(Grid::podFits(v));});
|
|
test("Pod rejects 2x3 weapon",[]{require(!Grid::podFits({Item{"a","coil"}}));});
|
|
test("Pod rejects empty selection",[]{require(!Grid::podFits({}));});
|
|
test("Invalid UID uniqueness",[]{Fixture f;auto p=f.session.state();p.warehouse.push_back(p.warehouse.front());require(!validate(p));});
|
|
test("Invalid unknown equipment",[]{Fixture f;auto p=f.session.state();p.warehouse.front().itemId="unknown";require(!validate(p));});
|
|
test("Invalid material stack",[]{Fixture f;auto p=f.session.state();p.recoveryInbox.push_back({"test","alloy",11});require(!validate(p));});
|
|
test("Invalid borrowed warehouse item",[]{Fixture f;auto p=f.session.state();p.warehouse.front().borrowed=true;require(!validate(p));});
|
|
test("Invalid duplicate receipts",[]{Fixture f;auto p=f.session.state();p.receipts={"same","same"};require(!validate(p));});
|
|
test("V1 codec roundtrip",[]{Fixture f;f.stock();Profile p;ok(decode(encode(f.session.state()),p));require(encode(p)==encode(f.session.state()));});
|
|
test("Reject older schema",[]{Profile p;require(!decode("{\"version\":1}",p));});
|
|
test("Reject unsupported version",[]{Fixture f;auto p=f.session.state();p.version=2;Profile out;require(decode(encode(p),out).code==Code::Unsupported);});
|
|
test("Malformed JSON does not reset a profile",[]{Fixture f;auto before=encode(f.session.state());f.memory.bytes="{bad";require(!f.session.load());require(before==encode(f.session.state()));});
|
|
test("Reject NaN vitals",[]{Fixture f;auto p=f.session.state();p.run.armor=std::nanf("");require(!validate(p));});
|
|
test("Prepare consumes no stock or sortie",[]{Fixture f;f.stock();auto before=f.memory.bytes;auto uid=f.uid("coil");ok(f.session.prepare(uid,Hold::Cargo));require(before==f.memory.bytes&&f.session.state().sorties==0);});
|
|
test("Preparation preserves selected rotation",[]{Fixture f;f.stock();ok(f.session.prepare(f.uid("coil"),Hold::Cargo,true));require(f.session.draft().items[0].rotated);});
|
|
test("Safe case rejects oversized gear",[]{Fixture f;f.stock();require(f.session.prepare(f.uid("siege"),Hold::SafeCase).code==Code::NoSpace);});
|
|
test("Equipped and staged ownership mutually exclusive",[]{Fixture f;f.stock();auto uid=f.uid("coil");ok(f.session.equip(uid));require(!f.session.prepare(uid,Hold::Cargo));});
|
|
test("Equip removes carry reservation",[]{Fixture f;f.stock();auto uid=f.uid("coil");ok(f.session.prepare(uid,Hold::Cargo));ok(f.session.equip(uid));require(f.session.draft().items.empty());});
|
|
test("Borrowing selects baseline model",[]{Fixture f;f.stock();ok(f.session.equip(f.uid("coil")));ok(f.session.borrow(0));require(f.session.draft().equipment[0]==0&&f.session.draft().selected[0]=="@borrowed");});
|
|
test("Invalid chassis selection",[]{Fixture f;require(!f.session.selectChassis(3));});
|
|
test("Light chassis rejects overloaded configuration",[]{Fixture f;f.stock();ok(f.session.selectChassis(0));ok(f.session.equip(f.uid("siege")));ok(f.session.equip(f.uid("vector")));ok(f.session.equip(f.uid("reactive_guard")));require(!f.session.begin(1));});
|
|
test("Deployment commits equipped and carried UID once",[]{Fixture f;f.stock();auto uid=f.uid("coil");ok(f.session.prepare(uid,Hold::Cargo,true));ok(f.session.begin(12));require(!find(f.session.state().warehouse,uid)&&find(f.session.state().run.cargo,uid));require(f.session.state().sorties==1);require(!f.session.begin(12));require(f.session.state().sorties==1);});
|
|
test("Deployment write failure keeps entire stock",[]{Fixture f;auto before=encode(f.session.state());f.memory.failWrite=true;require(!f.session.begin(1));require(before==encode(f.session.state()));});
|
|
test("Active run locks base modifications",[]{Fixture f;ok(f.session.begin(1));require(!f.session.selectChassis(0)&&!f.session.borrow(0)&&!f.session.sortWarehouse()&&!f.session.discard("bad"));});
|
|
test("Material splitting creates distinct UID",[]{Fixture f;f.stock();auto uid=f.uid("alloy");auto before=f.session.state().warehouse.size();ok(f.session.moveWarehouse(uid,8,12,false,3));require(f.session.state().warehouse.size()==before+1);require(find(f.session.state().warehouse,uid)->count==7);ok(validate(f.session.state()));});
|
|
test("Material split conserves amount",[]{Fixture f;f.stock();auto uid=f.uid("alloy");ok(f.session.moveWarehouse(uid,8,12,false,3));require(total(f.session.state().warehouse,"alloy")==30&&find(f.session.state().warehouse,uid)->count==7);ok(validate(f.session.state()));});
|
|
test("Invalid move rolls back serialized state",[]{Fixture f;auto before=encode(f.session.state());auto uid=f.uid("rapid");require(!f.session.moveWarehouse(uid,-1,0,false));require(before==encode(f.session.state()));});
|
|
test("Sort retains UIDs and counts",[]{Fixture f;f.stock();auto uid=f.uid("siege");ok(f.session.sortWarehouse());require(find(f.session.state().warehouse,uid)&&total(f.session.state().warehouse,"alloy")==30);});
|
|
test("Research cost and persistent unlock",[]{Fixture f;f.stock();ok(f.session.research("blueprint_coil_capacitor"));require(total(f.session.state().warehouse,"alloy")==28&&f.session.state().unlocked.size()==1);});
|
|
test("Research cannot charge twice",[]{Fixture f;f.stock();ok(f.session.research("blueprint_coil_capacitor"));auto before=f.memory.bytes;require(!f.session.research("blueprint_coil_capacitor"));require(before==f.memory.bytes);});
|
|
test("Manufacture consumes physical materials",[]{Fixture f;f.stock();ok(f.session.research("blueprint_coil_capacitor"));ok(f.session.manufacture("blueprint_coil_capacitor"));require(total(f.session.state().warehouse,"alloy")==24&&total(f.session.state().warehouse,"cells")==17);});
|
|
test("Manufacture requires recipe",[]{Fixture f;f.stock();auto before=f.memory.bytes;require(!f.session.manufacture("blueprint_coil_capacitor"));require(before==f.memory.bytes);});
|
|
test("Reserved material cannot be spent",[]{Fixture f;f.stock();for(const auto& i:f.session.state().warehouse)if(i.itemId=="alloy")ok(f.session.prepare(i.uid,Hold::Cargo));require(f.session.available("alloy")==0);require(!f.session.research("blueprint_coil_capacitor"));});
|
|
test("Recycled known blueprint deposits material",[]{Fixture f;f.stock();auto uid=f.uid("blueprint_coil_capacitor");ok(f.session.research("blueprint_coil_capacitor"));ok(f.session.recycle(uid));require(!find(f.session.state().warehouse,uid)&&total(f.session.state().warehouse,"alloy")==30);});
|
|
test("Loot source key prevents duplicate recovery",[]{Fixture f;ok(f.session.begin(1));ok(f.session.addLoot("alloy",4,"crate"));require(f.session.addLoot("alloy",4,"crate").code==Code::Duplicate);require(total(f.session.state().run.cargo,"alloy")==4);});
|
|
test("Loot write failure retains searchable source",[]{Fixture f;ok(f.session.begin(1));f.memory.failWrite=true;require(!f.session.addLoot("alloy",4,"crate"));require(f.session.state().run.searched.empty()&&f.session.state().run.cargo.empty());});
|
|
test("Loot material stack limit",[]{Fixture f;ok(f.session.begin(1));require(!f.session.addLoot("alloy",11));});
|
|
test("Safe case retains UID",[]{Fixture f;ok(f.session.begin(1));ok(f.session.addLoot("vector",1));auto uid=f.session.state().run.cargo[0].uid;ok(f.session.moveItem(uid,Hold::Cargo,Hold::SafeCase));require(find(f.session.state().run.safeCase,uid));});
|
|
test("Blocked safe case move is atomic",[]{Fixture f;ok(f.session.begin(1));ok(f.session.addLoot("coil",1));auto before=f.memory.bytes;require(!f.session.moveItem(f.session.state().run.cargo[0].uid,Hold::Cargo,Hold::SafeCase));require(before==f.memory.bytes);});
|
|
test("Pod commits removal, receipt, bank and used flag",[]{Fixture f;ok(f.session.begin(1));ok(f.session.addLoot("alloy",4));auto uid=f.session.state().run.cargo[0].uid;ok(f.session.recoverPod({uid}));require(f.session.state().run.podUsed&&f.session.state().run.cargo.empty()&&total(f.session.state().warehouse,"alloy")==4);require(f.session.state().receipts[0].ends_with(":cargo"));});
|
|
test("Pod cannot be reused",[]{Fixture f;ok(f.session.begin(1));ok(f.session.addLoot("alloy",4));ok(f.session.recoverPod({f.session.state().run.cargo[0].uid}));ok(f.session.addLoot("cells",1));require(!f.session.recoverPod({f.session.state().run.cargo[0].uid}));});
|
|
test("Pod failure publishes no receipt",[]{Fixture f;ok(f.session.begin(1));ok(f.session.addLoot("alloy",4));auto uid=f.session.state().run.cargo[0].uid;f.memory.failWrite=true;require(!f.session.recoverPod({uid}));require(!f.session.state().run.podUsed&&f.session.state().receipts.empty()&&find(f.session.state().run.cargo,uid));});
|
|
test("Pod rejects duplicate UIDs",[]{Fixture f;ok(f.session.begin(1));ok(f.session.addLoot("alloy",4));auto uid=f.session.state().run.cargo[0].uid;require(!f.session.recoverPod({uid,uid}));});
|
|
test("Death retains safe case and loses external cargo",[]{Fixture f;ok(f.session.begin(1));ok(f.session.addLoot("vector",1));auto uid=f.session.state().run.cargo[0].uid;ok(f.session.moveItem(uid,Hold::Cargo,Hold::SafeCase));ok(f.session.addLoot("alloy",5));ok(f.session.finish(true));require(find(f.session.state().warehouse,uid)&&total(f.session.state().warehouse,"alloy")==0&&!f.session.state().run.active);});
|
|
test("Death writes separate secure eject and return receipts",[]{Fixture f;ok(f.session.begin(1));ok(f.session.finish(true));require(f.session.state().receipts.size()==2&&f.session.state().receipts[0].ends_with(":secure_eject"));});
|
|
test("Pending death survives finalization failure",[]{Fixture f;ok(f.session.begin(1));ok(f.session.addLoot("alloy",4));auto uid=f.session.state().run.cargo[0].uid;ok(f.session.moveItem(uid,Hold::Cargo,Hold::SafeCase));f.memory.failAt=f.memory.writes+2;require(!f.session.finish(true));require(f.session.state().run.destructionPending&&f.session.state().run.armor==0);Profile disk;ok(decode(f.memory.bytes,disk));require(disk.run.destructionPending);f.memory.failAt=-1;ok(f.session.load());ok(f.session.finish(true));require(total(f.session.state().warehouse,"alloy")==4);});
|
|
test("Pending death blocks new transactions",[]{Fixture f;ok(f.session.begin(1));f.memory.failAt=f.memory.writes+2;require(!f.session.finish(true));require(!f.session.addLoot("alloy",1)&&!f.session.objective());});
|
|
test("Normal return banks owned loadout and cargo",[]{Fixture f;ok(f.session.begin(1));ok(f.session.addLoot("alloy",6));ok(f.session.finish(false));require(f.session.state().warehouse.size()==5&&f.session.state().extractions==1);});
|
|
test("Borrowed parts never become permanent property",[]{Fixture f;for(int s=0;s<4;s++)ok(f.session.borrow(s));ok(f.session.begin(1));ok(f.session.finish(false));require(f.session.state().warehouse.size()==4);});
|
|
test("Repeated finish cannot duplicate stock",[]{Fixture f;ok(f.session.begin(1));ok(f.session.finish(false));auto before=f.memory.bytes;require(!f.session.finish(false));require(before==f.memory.bytes);});
|
|
test("Return failure keeps all active ownership",[]{Fixture f;ok(f.session.begin(1));auto before=encode(f.session.state());f.memory.failWrite=true;require(!f.session.finish(false));require(before==encode(f.session.state()));});
|
|
test("Full warehouse banks return into inbox",[]{Fixture f;f.patch([&](Profile& p){p.warehouse.clear();p.warehouseWidth=p.warehouseHeight=1;p.warehouse.push_back(f.item("alloy",10));});for(int s=0;s<4;s++)ok(f.session.borrow(s));ok(f.session.begin(1));ok(f.session.addLoot("cells",2));ok(f.session.finish(false));require(f.session.state().recoveryInbox.size()==1);require(!f.session.begin(2));});
|
|
test("Inbox claim and capacity gate",[]{Fixture f;f.patch([&](Profile& p){p.recoveryInbox.push_back(f.item("alloy",5));});require(!f.session.begin(1));ok(f.session.claim());require(f.session.state().recoveryInbox.empty()&&total(f.session.state().warehouse,"alloy")==5);});
|
|
test("Stage transition preserves safe case and pod use",[]{Fixture f;ok(f.session.begin(1));ok(f.session.addLoot("alloy",2));ok(f.session.recoverPod({f.session.state().run.cargo[0].uid}));ok(f.session.addLoot("vector",1));auto uid=f.session.state().run.cargo[0].uid;ok(f.session.moveItem(uid,Hold::Cargo,Hold::SafeCase));ok(f.session.objective());ok(f.session.nextStage(1));require(f.session.state().run.stage==1&&f.session.state().run.podUsed&&find(f.session.state().run.safeCase,uid));});
|
|
test("Stage locked before objective",[]{Fixture f;ok(f.session.begin(1));require(!f.session.nextStage(0));});
|
|
test("Route completion counts once and does not return loot",[]{Fixture f;ok(f.session.begin(1));ok(f.session.addLoot("alloy",2));ok(f.session.completeRoute());require(f.session.state().completed==1&&f.session.state().run.active&&f.session.state().run.cargo.size()==1);require(!f.session.completeRoute());});
|
|
test("Core upgrades reset for new sortie",[]{Fixture f;ok(f.session.begin(1));ok(f.session.upgrade(1));ok(f.session.finish(false));ok(f.session.begin(2));require(f.session.state().run.upgrades==0);});
|
|
test("Uninstall produces actual empty weapon slot",[]{Fixture f;ok(f.session.begin(1));ok(f.session.uninstall(0,Hold::Cargo,0,0));require(f.session.state().run.equipment[0]==-1);Combat c;c.apply(f.session.catalog(),f.session.state().run);require(c.rounds==0);});
|
|
test("Reinstall preserves weapon UID",[]{Fixture f;ok(f.session.begin(1));auto uid=f.session.state().run.installed[0].uid;ok(f.session.uninstall(0,Hold::Cargo,0,0));ok(f.session.install(uid,Hold::Cargo));require(find(f.session.state().run.installed,uid)&&f.session.state().run.equipment[0]==0);});
|
|
test("Weapon needs fresh trigger after menu",[]{Fixture f;ok(f.session.begin(1));Combat c;c.apply(f.session.catalog(),f.session.state().run);require(!f.session.fire(c));f.session.tick(.1f,c,true);require(f.session.fire(c));});
|
|
test("Ammo cooldown enforced",[]{Fixture f;ok(f.session.begin(1));Combat c;f.session.tick(.1f,c,true);require(f.session.fire(c));int rounds=c.rounds;require(!f.session.fire(c)&&c.rounds==rounds);});
|
|
test("Reload completes through portable clock",[]{Fixture f;ok(f.session.begin(1));Combat c;f.session.tick(.1f,c,true);require(f.session.fire(c));require(f.session.reload(c));f.session.tick(10,c,true);require(c.rounds==f.session.catalog().part("rapid")->magazineSize);});
|
|
test("Boost energy and cooldown",[]{Fixture f;ok(f.session.begin(1));Combat c;require(f.session.boost(c));require(f.session.state().run.energy==82&&!f.session.boost(c));});
|
|
test("Guard mitigates damage",[]{Fixture f;ok(f.session.begin(1));Combat c;require(f.session.guard(c));f.session.takeDamage(c,40);require(f.session.state().run.armor==170);});
|
|
test("Secondary spends portable energy",[]{Fixture f;ok(f.session.begin(1));Combat c;require(f.session.fire(c,true));require(f.session.state().run.energy==85&&!f.session.fire(c,true));});
|
|
test("Core enemy damage and death event exactly once",[]{Fixture f;ok(f.session.begin(1));f.session.registerEnemy("enemy",0);require(f.session.damageEnemy("enemy",200));require(!f.session.damageEnemy("enemy",200));auto events=f.session.takeEvents();require(std::count_if(events.begin(),events.end(),[](const DomainEvent& e){return e.key=="enemy.destroyed";})==1);});
|
|
test("Enemy shot requires perception fact",[]{Fixture f;ok(f.session.begin(1));f.session.registerEnemy("enemy",0);require(!f.session.stepEnemy("enemy",2,10,false).fire);require(f.session.stepEnemy("enemy",.1f,10,true).fire);require(!f.session.stepEnemy("enemy",.1f,10,true).fire);});
|
|
test("Independent stores isolate range from campaign",[]{Fixture a,b;ok(a.session.begin(1));ok(b.session.begin(2));ok(b.session.addLoot("alloy",5));ok(b.session.finish(false));require(total(a.session.state().warehouse,"alloy")==0&&a.session.state().run.active);});
|
|
test("Branch choice locks at second sector",[]{Fixture f;ok(f.session.begin(1));ok(f.session.objective());ok(f.session.nextStage(0));ok(f.session.objective());ok(f.session.nextStage(1));ok(f.session.objective());ok(f.session.nextStage(0));require(f.session.state().run.branch==1&&f.session.state().run.biome==0&&f.session.state().run.upgrades==3);});
|
|
test("Upgrade source cannot be farmed",[]{Fixture f;ok(f.session.begin(1));ok(f.session.upgrade(0));auto bytes=f.memory.bytes;require(!f.session.upgrade(0)&&f.memory.bytes==bytes);});
|
|
test("Pending death disables combat resources",[]{Fixture f;ok(f.session.begin(1));f.memory.failAt=f.memory.writes+2;require(!f.session.finish(true));Combat c;require(!f.session.boost(c)&&!f.session.guard(c)&&!f.session.fire(c,true));});
|
|
test("Training gate order and portable clock",[]{Fixture f;ok(f.session.begin(1));Combat c;f.session.rangeGate(1);require(f.session.range().gates==0);f.session.rangeGate(0);f.session.tick(3,c,true);f.session.rangeGate(1);f.session.tick(2,c,true);f.session.rangeGate(2);require(f.session.range().gates==3&&f.session.range().seconds==5);f.session.rangeReset();require(f.session.range().gates==0);});
|
|
test("Training targets count hits without enemy death",[]{Fixture f;ok(f.session.begin(1));f.session.registerEnemy("target",4);require(!f.session.damageEnemy("target",1000));require(f.session.range().hits==1&&f.session.enemy("target")->health>0&&!f.session.stepEnemy("target",2,4,true).fire);});
|
|
test("Extraction uses portable fifteen second clock",[]{Fixture f;ok(f.session.begin(1));Combat c;ok(f.session.callExtraction());f.session.tick(14,c,true);require(!f.session.field().ready&&!f.session.boardExtraction());f.session.tick(1,c,true);require(f.session.field().ready&&f.session.state().run.active);ok(f.session.boardExtraction());require(!f.session.state().run.active&&f.session.state().extractions==1);});
|
|
test("Repeated extraction call cannot reset timer",[]{Fixture f;ok(f.session.begin(1));Combat c;ok(f.session.callExtraction());f.session.tick(5,c,true);require(!f.session.callExtraction()&&f.session.field().extraction==10);});
|
|
test("Ready boarding rolls back on storage failure",[]{Fixture f;ok(f.session.begin(1));Combat c;ok(f.session.callExtraction());f.session.tick(15,c,true);f.memory.failWrite=true;require(!f.session.boardExtraction()&&f.session.state().run.active&&f.session.field().ready);f.memory.failWrite=false;ok(f.session.boardExtraction());});
|
|
test("Pod channel requires three stable seconds",[]{Fixture f;ok(f.session.begin(1));ok(f.session.addLoot("alloy",2));std::vector<std::string> picked={f.session.state().run.cargo[0].uid};ok(f.session.advanceRecovery(2,true,true,picked));require(!f.session.state().run.podUsed);ok(f.session.advanceRecovery(1,true,true,picked));require(f.session.state().run.podUsed);});
|
|
test("Pod motion interruption resets portable progress",[]{Fixture f;ok(f.session.begin(1));ok(f.session.addLoot("alloy",2));std::vector<std::string> picked={f.session.state().run.cargo[0].uid};ok(f.session.advanceRecovery(2,true,true,picked));require(!f.session.advanceRecovery(.1f,true,false,picked)&&f.session.field().podProgress==0);ok(f.session.advanceRecovery(2,true,true,picked));require(!f.session.state().run.podUsed);});
|
|
test("New sortie resets field timers",[]{Fixture f;ok(f.session.begin(1));ok(f.session.callExtraction());ok(f.session.finish(false));ok(f.session.begin(2));require(f.session.field().extraction<0&&!f.session.field().ready);});
|
|
test("Blast falloff and bounds are engine independent",[]{require(radialDamage(100,0,5)==100&&radialDamage(100,2.5f,5)==77.5f&&radialDamage(100,5,5)==0&&radialDamage(100,-1,5)==0);});
|
|
for(int branch=0;branch<2;branch++)test("Five sector route branch "+std::to_string(branch),[branch]{Fixture f;ok(f.session.begin(19));for(int stage=0;stage<4;stage++){ok(f.session.objective());ok(f.session.nextStage(stage==1?branch:0));}require(f.session.state().run.stage==4&&f.session.state().run.branch==branch);if(branch==0){f.session.registerEnemy("boss",3,true);require(f.session.damageEnemy("boss",1000));}else ok(f.session.completeRoute());require(f.session.state().completed==1&&f.session.state().run.active);ok(f.session.callExtraction());Combat c;f.session.tick(15,c,true);ok(f.session.boardExtraction());require(f.session.state().extractions==1&&!f.session.state().run.active);});
|
|
test("Boss completion retries failed durable commit",[]{Fixture f;ok(f.session.begin(1));f.session.registerEnemy("boss",3,true);f.memory.failWrite=true;require(f.session.damageEnemy("boss",1000)&&!f.session.state().run.routeCompleted);f.memory.failWrite=false;f.session.stepEnemy("boss",1,10,true);require(f.session.state().run.routeCompleted&&f.session.state().completed==1);f.session.stepEnemy("boss",1,10,true);require(f.session.state().completed==1);});
|
|
test("Escort damage bounds belong to core",[]{Fixture f;ok(f.session.begin(1));f.session.damageEscort(75);require(f.session.state().run.escortArmor==175);f.session.damageEscort(1000);require(f.session.state().run.escortArmor==0);f.session.damageEscort(-1);require(f.session.state().run.escortArmor==0);});
|
|
std::cout<<"gameFramework: "<<passed<<" passed, "<<failed<<" failed\n";
|
|
if(argc>1){std::ofstream out(argv[1]);out<<"{\"passed\":"<<(failed==0?"true":"false")<<",\"total\":"<<results.size()<<",\"passedCount\":"<<passed<<",\"failedCount\":"<<failed<<",\"checks\":[";bool first=true;for(const auto& [name,value]:results){if(!first)out<<',';first=false;out<<"{\"name\":\""<<name<<"\",\"passed\":"<<(value?"true":"false")<<'}';}out<<"]}\n";}
|
|
return failed?1:0;
|
|
}
|