Wire production aircraft callbacks and diagnose native prison action selection

This commit is contained in:
2026-09-19 20:03:38 +02:00
parent 53eaf3894d
commit f870443b9b
58 changed files with 40247 additions and 27 deletions
+142
View File
@@ -0,0 +1,142 @@
"""Actual registry dispatch/ownership/lease guards; no engine or flight claim."""
from policy_harness import ROOT, body, run
import re
FLIGHT=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftFlight.c'
BRIDGE=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftControlBridge.c'
PROBES=[ROOT/'addons/ai/Scripts/Game/ERR'/name for name in ('ERR_NativeAircraftControlProbe.c','ERR_NativeAircraftRouteProbe.c')]
def cpp(text):
text=text.replace('protected ','').replace('ERR_AircraftFlight flight','ERR_AircraftFlight* flight')
for old,new in [('Vehicle.Cast(', 'CastVehicle('),('ERR_GameMode.Get()', 'Mode::Get()'),('ERR_Actors.Group(', 'Actors::GetGroup('),('SCR_FactionManager.SGetFaction(', 'Factions::Get('),('Replication.IsServer()', 'server'),('CompartmentAccessComponent.GetVehicleIn(', 'Compartment::GetVehicleIn(')]:text=text.replace(old,new)
for name in ('GetGame()','s_Owners','flight','controller','owner','m_Mode','m_Aircraft','m_Group','m_Pilot','m_Controller','m_Seat','m_Agent','m_Qualification'):
text=text.replace(name+'.',name+'->')
return text.replace('ERR_AircraftFlightState.RELINQUISHED','11')
# Preserve the actual callback admission and ControlLease boundary; substitute
# only the downstream native actuator work, exercised by existing flight tests.
prepare=body(FLIGHT,'void PrepareActions(')
prefix=prepare[:prepare.index(' if (!m_Readback')]
heli=body(FLIGHT,'void PrepareHelicopter(')
heli_prefix=heli[:heli.index(' controller.SetCollectiveMode')]
run(r'''
#include <cassert>
#include <string>
#include <map>
#include <vector>
using string=std::string;
bool server=true;
struct Entity;struct Group;struct Agent;struct Actions{};struct Controller;
using Vehicle=Entity*;using IEntity=Entity*;using ActionManager=Actions*;
using CharacterControllerComponent=Controller*;using HelicopterControllerComponent=Controller*;
struct Group {int faction=1;int GetFaction(){return faction;}};
struct Entity {bool vehicle=false;int world=1,faction=1;Entity* in=nullptr;Group* group=nullptr;int GetWorld(){return world;}};
Entity* CastVehicle(Entity* v){return v&&v->vehicle?v:nullptr;}
struct Seat {Entity* occupant=nullptr;Entity* GetOccupant(){return occupant;}};
struct Controller {Entity* owner=nullptr;Seat* seat=nullptr;Entity* GetOwner(){return owner;}Seat* GetPilotCompartmentSlot(){return seat;}};
struct Agent {Entity* actor=nullptr;Actions* actions=nullptr;Entity* GetControlledEntity(){return actor;}Actions* GetActionManager(){return actions;}};
struct Mode {bool active=true;int world=1;static Mode* current;bool Active(){return active;}int GetWorld(){return world;}int EnemyFaction(){return 1;}static Mode* Get(){return current;}};Mode* Mode::current=nullptr;
struct Game {int world=1;bool manager=true;Actions global;int GetWorld(){return world;}bool GetPlayerManager(){return manager;}Actions* GetInputManager(){return &global;}}game;Game* currentGame=&game;Game* GetGame(){return currentGame;}
struct Actors {static Group* GetGroup(Entity* p){return p->group;}};
struct Factions {static int Get(Entity* p){return p->faction;}};
struct Compartment {static Entity* GetVehicleIn(Entity* p){return p->in;}};
struct Certificate {Entity* aircraft=nullptr;Entity* pilot=nullptr;int token=7;bool ClaimedBy(Entity* a,Entity* p,int t){return aircraft==a&&pilot==p&&token==t;}};
struct ERR_AircraftFlight;
struct Registry {std::map<Entity*,ERR_AircraftFlight*> entries;ERR_AircraftFlight* Get(Entity* e){auto found=entries.find(e);return found==entries.end()?nullptr:found->second;}void Remove(Entity* e){entries.erase(e);}}registry;
struct ERR_AircraftFlight {
static Registry* s_Owners;
Mode* m_Mode=nullptr;Entity* m_Aircraft=nullptr;Group* m_Group=nullptr;Entity* m_Pilot=nullptr;
Controller* m_Controller=nullptr;Controller* m_Character=nullptr;Seat* m_Seat=nullptr;Agent* m_Agent=nullptr;Actions* m_Actions=nullptr;Certificate* m_Qualification=nullptr;
int m_State=1,m_Token=7,actionsWritten=0,helicopterWritten=0,releases=0;bool humans=false,control=true,restore=false;string m_Reason;
bool Finished(){return m_State>=8&&m_State<=11;}bool NoPlayers(){return !humans;}bool ControlLease(){return control;}
void Release(bool value){restore=value;releases++;s_Owners->Remove(m_Aircraft);}void RecordState(){}
''' + cpp(body(FLIGHT,'protected bool Owns(')) + cpp(body(FLIGHT,'protected bool Lease(')).replace('ERR_AircraftFlightState.IDLE','0') + cpp(body(FLIGHT,'protected void Relinquish(')) + cpp(body(FLIGHT,'static void DispatchActions(')) + cpp(body(FLIGHT,'static void DispatchHelicopter(')) + cpp(prefix) + ' actionsWritten++;\n }\n' + cpp(heli_prefix) + ' helicopterWritten++;\n }\n' + r'''
};
Registry* ERR_AircraftFlight::s_Owners=&registry;
struct Setup {
Mode mode;Entity aircraft,pilot,passenger;Group group;Seat seat;Controller heli,character;Actions actions;Agent agent;Certificate certificate;ERR_AircraftFlight flight;
Setup(){
registry.entries.clear();server=true;currentGame=&game;game.world=1;game.manager=true;Mode::current=&mode;
aircraft.vehicle=true;pilot.in=&aircraft;pilot.group=&group;passenger.in=&aircraft;
seat.occupant=&pilot;heli.owner=&aircraft;heli.seat=&seat;character.owner=&pilot;agent.actor=&pilot;agent.actions=&actions;certificate.aircraft=&aircraft;certificate.pilot=&pilot;
flight.m_Mode=&mode;flight.m_Aircraft=&aircraft;flight.m_Group=&group;flight.m_Pilot=&pilot;flight.m_Controller=&heli;flight.m_Character=&character;flight.m_Seat=&seat;flight.m_Agent=&agent;flight.m_Actions=&actions;flight.m_Qualification=&certificate;registry.entries[&aircraft]=&flight;
}
void Actor(bool player=false){ERR_AircraftFlight::DispatchActions(&character,&pilot,&actions,.05f,player);}
void Heli(){ERR_AircraftFlight::DispatchHelicopter(&heli);}
};
int main(){
{Setup x;x.Actor();x.Heli();assert(x.flight.actionsWritten==1&&x.flight.helicopterWritten==1&&x.flight.releases==0);}
for(int state:{1,2,3,4,5,6,7,12}){Setup x;x.flight.m_State=state;x.Actor();x.Heli();assert(x.flight.actionsWritten==1&&x.flight.helicopterWritten==1);}
for(int state:{0,8,9,10,11}){Setup x;x.flight.m_State=state;x.Actor();x.Heli();assert(x.flight.actionsWritten==0&&x.flight.helicopterWritten==0);}
{Setup x;registry.entries.clear();x.Actor();x.Heli();assert(x.flight.actionsWritten==0&&x.flight.helicopterWritten==0&&registry.entries.empty());}
{Setup x;server=false;x.Actor();x.Heli();assert(x.flight.actionsWritten==0&&x.flight.helicopterWritten==0&&x.flight.releases==0);}
{Setup x;x.Actor(true);assert(x.flight.actionsWritten==0);}
{Setup x;Actions wrong;ERR_AircraftFlight::DispatchActions(&x.character,&x.pilot,&wrong,.05f,false);ERR_AircraftFlight::DispatchActions(&x.character,&x.passenger,&x.actions,.05f,false);Controller wrongController;wrongController.owner=&x.aircraft;ERR_AircraftFlight::DispatchHelicopter(&wrongController);assert(x.flight.actionsWritten==0&&x.flight.helicopterWritten==0&&x.flight.releases==0);}
// Actual ownership predicates must revoke stale ownership before any actuator
// boundary, including a foreign passenger's native callback after capture.
for(int fault=0;fault<13;fault++){
Setup x;Mode replacement;Actions replacementActions;Entity other;Group otherGroup;Seat otherSeat;Agent otherAgent;
switch(fault){
case 0:Mode::current=&replacement;break;
case 1:game.world=2;break;
case 2:x.mode.active=false;break;
case 3:x.aircraft.world=2;break;
case 4:x.pilot.world=2;break;
case 5:x.seat.occupant=&other;break;
case 6:x.heli.seat=&otherSeat;break;
case 7:x.pilot.group=&otherGroup;otherGroup.faction=2;break;
case 8:x.aircraft.faction=2;break;
case 9:x.flight.humans=true;break;
case 10:x.agent.actions=&replacementActions;break;
case 11:x.agent.actions=&game.global;x.flight.m_Actions=&game.global;break;
case 12:x.pilot.in=nullptr;break;
}
x.Heli();assert(x.flight.actionsWritten==0&&x.flight.helicopterWritten==0&&x.flight.releases==1&&!x.flight.restore&&registry.entries.empty());
x.Actor();x.Heli();assert(x.flight.releases==1);
}
{Setup x;x.flight.humans=true;ERR_AircraftFlight::DispatchActions(&x.character,&x.passenger,&x.actions,.05f,false);assert(x.flight.releases==1&&x.flight.actionsWritten==0);}
for(int fault=0;fault<5;fault++){
Setup x;ERR_AircraftFlight other;
if(fault==0)x.flight.m_Token=0;
if(fault==1)x.certificate.token=8;
if(fault==2)x.flight.m_State=8;
if(fault==3)x.flight.m_Qualification=nullptr;
if(fault==4)registry.entries[&x.aircraft]=&other;
x.Actor();x.Heli();assert(x.flight.actionsWritten==0&&x.flight.helicopterWritten==0);
}
{Setup x;x.flight.control=false;x.Actor();x.Heli();assert(x.flight.actionsWritten==0&&x.flight.helicopterWritten==0);}
}
''')
# Both fixture layers still observe their hook, but cannot write or dispatch.
for path in PROBES:
for signature in ('void PrepareActions(', 'void PrepareHelicopter('):
method=body(path,signature)
assert 'm_Flight.Prepare' not in method and 'Dispatch' not in method
assert 'Hooks++' in method
source=BRIDGE.read_text()
assert '#ifdef' not in source and 'new ' not in source
actor=body(BRIDGE,'override void OnPrepareControls(')
heli=body(BRIDGE,'protected override event void OnPrepareControls(')
assert actor.count('ERR_AircraftFlight.DispatchActions(')==1 and actor.count('super.OnPrepareControls(')==1
assert actor.index('DispatchActions(')<actor.index('super.OnPrepareControls(')
assert heli.count('ERR_AircraftFlight.DispatchHelicopter(')==1 and heli.count('super.OnPrepareControls(')==1
assert heli.index('super.OnPrepareControls(')<heli.index('DispatchHelicopter(')
# Execute the actual native hook methods around a recording super boundary.
run(r'''
#include <cassert>
#include <vector>
using IEntity=int;using ActionManager=int;std::vector<int> events;
struct ERR_AircraftFlight {static void DispatchActions(void*,int,int,float,bool){events.push_back(1);}static void DispatchHelicopter(void*){events.push_back(3);}};
struct CharacterBase {void OnPrepareControls(int,int,float,bool){events.push_back(2);}};
struct HelicopterBase {void OnPrepareControls(){events.push_back(2);}};
struct Character:CharacterBase {
''' + actor.replace('override ','').replace('super.','CharacterBase::').replace('ERR_AircraftFlight.','ERR_AircraftFlight::') + r'''
};
struct Helicopter:HelicopterBase {
''' + heli.replace('protected ','').replace('override ','').replace('event ','').replace('super.','HelicopterBase::').replace('ERR_AircraftFlight.','ERR_AircraftFlight::') + r'''
};
int main(){Character c;c.OnPrepareControls(1,2,.05f,false);assert((events==std::vector<int>{1,2}));events.clear();Helicopter h;h.OnPrepareControls();assert((events==std::vector<int>{2,3}));}
''')
print('PASS: actual production bridge lookup, lease/world/mode/seat/capture guards, no allocation, native super ordering and read-only fixture hooks')
+21
View File
@@ -920,4 +920,25 @@ class AlignmentIntegrationTests(unittest.TestCase):
stop.assert_called_once();self.assertIsNone(failed.call_args.args[1])
class GateChoicesArguments(unittest.TestCase):
def test_exact_gate_choices_are_readonly_and_bound_in_wrapper_and_live_process(self):
args=SimpleNamespace(nonce='N',prefix=Path('/tmp/pfx'),profile=Path('/tmp/profile'),game_executable=Path('/tmp/game'),host_display=':0',report=Path('/tmp/input.json'),console=None,enable_private_look_proof=True,enable_private_approach_proof=True,
command=[sys.executable,str(Path(proof.display.__file__).resolve()),'placeholder'])
prefix=[str(Path.home()/'.local/share/Steam/steamapps/common/Proton - Experimental/proton'),'run',str(args.game_executable),'-errOrdinaryInputObserve','N','-errOrdinaryLookObserve','N','-errOrdinaryScenario','core','-profile','Z:/tmp/profile']
gate=['-errOrdinaryGateObserve','N'];choice=['-errOrdinaryGateChoices','N']
cases=[(gate,True,True),(gate+choice,True,True),(gate+choice,False,False),(gate,False,True),([],False,True),([],True,False),(choice,True,False),(gate+choice+choice,True,False),(gate+gate,True,False),(gate+['-errOrdinaryGateChoices','foreign'],True,False),(gate+['-errOrdinaryGateChoices'],True,False),(gate+['-errOrdinaryGateChoicesAlias','N'],True,False)]
for suffix,approach,allowed in cases:
with self.subTest(suffix=suffix,approach=approach):
args.enable_private_approach_proof=approach
native=prefix+suffix;owner={'pid':123,'process':{'pid':123}}
wrapped=SimpleNamespace(query_worker=False,prefix=args.prefix,profile=args.profile,game_executable=args.game_executable,host_display=args.host_display,report=Path('/tmp/display.json'),command=['--']+native)
with patch.object(proof.display,'parse_args',return_value=wrapped),patch.object(proof.display,'validate_paths'),patch.object(proof.display,'process',return_value={'args':native}),patch.object(proof.display,'identity',return_value=owner['process']):
if allowed:
self.assertEqual(proof.wrapper_command(args),args.command)
proof.game_arguments(owner,args)
else:
with self.assertRaises(proof.Refused):proof.wrapper_command(args)
with self.assertRaises(proof.Refused):proof.game_arguments(owner,args)
if __name__ == '__main__': unittest.main()
+56
View File
@@ -0,0 +1,56 @@
import copy
from pathlib import Path
import sys
import unittest
sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'tools'))
from ordinary_gate_choices import parse_choice_record,parse_choice_records,join_choice_snapshots,exact_prison_choices
from test_ordinary_gate_evidence import fixture,NONCE
def line(index=1,real=2100,selected='ERR_PrisonDoorAction:7',context=1,ordered='0=ERR_PrisonDoorAction:7:0,1=ERR_UnlockPrisonAction:8:1'):
return f'ERR GATE CHOICE: run={NONCE} sample={index} real={real} selected={selected} context={context} actions=2 permissions=2 shown=2 truncated=0 ordered=[{ordered}]'
class Choices(unittest.TestCase):
def test_exact_pair_and_selected_identity(self):
c=parse_choice_record(line(),NONCE);self.assertEqual(c['selected_id'],7)
self.assertEqual(exact_prison_choices({'choices':c}),c)
c['selected_class']='ERR_UnlockPrisonAction';c['selected_id']=8
self.assertEqual(exact_prison_choices({'choices':c})['selected_id'],8)
def test_empty_and_truncated_are_diagnostic_only(self):
empty=line(selected='none:-1',context=0,ordered='').replace('actions=2 permissions=2 shown=2','actions=0 permissions=0 shown=0')
self.assertEqual(parse_choice_record(empty,NONCE)['actions'],[])
with self.assertRaises(ValueError):exact_prison_choices({'choices':parse_choice_record(empty,NONCE)})
entries=','.join(f'{i}=Action:{i}:1' for i in range(8))
truncated=line(ordered=entries).replace('actions=2 permissions=2 shown=2 truncated=0','actions=9 permissions=9 shown=8 truncated=1')
self.assertTrue(parse_choice_record(truncated,NONCE)['truncated'])
def test_missing_permissions_do_not_become_false_permission(self):
text=line(ordered='0=ERR_PrisonDoorAction:7:0,1=ERR_UnlockPrisonAction:8:-1').replace('permissions=2','permissions=1')
c=parse_choice_record(text,NONCE);self.assertIsNone(c['actions'][1]['permitted'])
with self.assertRaises(ValueError):exact_prison_choices({'choices':c})
def test_malformed_ids_counts_nonce_and_sequence_refuse(self):
for text in [line().replace(NONCE,'foreign'),line().replace('shown=2','shown=1'),line().replace('truncated=0','truncated=1'),line().replace('Action:7','Action:-1'),line().replace('0=ERR','1=ERR'),line().replace(':8:1',':8:-1'),line().replace('context=1','context=3')]:
with self.subTest(text=text),self.assertRaises(ValueError):parse_choice_record(text,NONCE)
for text in [line()+'\n'+line(),line(2),line()+'\n'+line(2,2000)]:
with self.assertRaises(ValueError):parse_choice_records(text,NONCE)
def test_exact_pair_refuses_other_order_identity_or_permission(self):
c=parse_choice_record(line(),NONCE)
for fault in ('order','duplicate','permission','selected','class'):
d=copy.deepcopy(c)
if fault=='order':d['actions'].reverse()
elif fault=='duplicate':d['actions'][1]['id']=7
elif fault=='permission':d['actions'][0]['permitted']=True
elif fault=='selected':d['selected_id']=9
else:d['actions'][1]['class']='ForeignAction'
with self.subTest(fault=fault),self.assertRaises(ValueError):exact_prison_choices({'choices':d})
def test_real_native_schema_join_requires_exact_clock_and_selection(self):
from ordinary_gate_evidence import join_gate_snapshots
content=fixture();snapshots=join_gate_snapshots(content,NONCE)['snapshots']
choices=[]
for i,s in snapshots.items():
choices.append(line(i,s['real_ms'],selected='none:-1',context=0,ordered='').replace('actions=2 permissions=2 shown=2','actions=0 permissions=0 shown=0'))
raw=content+'\n'+'\n'.join(choices)
self.assertEqual(len(join_choice_snapshots(raw,NONCE)['snapshots']),len(snapshots))
for bad in [raw.replace('selected=none:-1','selected=ERR_UnlockPrisonAction:8',1),raw.replace('context=0 actions=0','context=1 actions=0',1),raw.replace(f'sample=1 real={snapshots[1]["real_ms"]} selected',f'sample=1 real={snapshots[1]["real_ms"]+1} selected',1)]:
with self.assertRaises(ValueError):join_choice_snapshots(bad,NONCE)
if __name__=='__main__':unittest.main()
+19 -5
View File
@@ -21,7 +21,7 @@ def cpp(s):
s=s.replace(getter+'().',getter+'()->')
return s.replace('null','nullptr').replace('string.Format(', 'Format(')
accessor=cpp(body(P,'bool ERR_GateReadProgress('))
methods='\n'.join(cpp(body(P,s)) for s in ('protected static bool Enabled(', 'static void Sample(', 'protected bool BindGate(', 'protected bool Context(', 'protected void Cancel(', 'protected bool CachedPermission(', 'protected static bool FiniteVector(', 'protected void Observe('))
methods='\n'.join(cpp(body(P,s)) for s in ('protected static bool Enabled(', 'static void Sample(', 'protected bool BindGate(', 'protected bool Context(', 'protected void Cancel(', 'protected bool CachedPermission(', 'protected static bool FiniteVector(', 'protected static string ChoiceIdentity(', 'protected void ObserveChoices(', 'protected void Observe('))
run(r'''
#include <cassert>
#include <cmath>
@@ -36,7 +36,8 @@ vector Vector(float x,float y,float z){return {{x,y,z}};}
enum {BaseActionsManagerComponentKey,BaseDoorComponentKey,SCR_InteractionHandlerComponentKey};
struct BaseWorld{};
struct IEntity {BaseWorld* world=nullptr;bool living=true,vehicle=false;void* components[3]={};vector origin=Vector(6090,210,7158);BaseWorld* GetWorld(){return world;}void* FindComponent(int key){return components[key];}vector forward=Vector(0,0,1.5);vector GetOrigin(){return origin;}void GetTransform(vector out[4]){out[2]=forward;out[3]=origin;}};
struct BaseUserAction {virtual ~BaseUserAction()=default;IEntity* owner=nullptr;int id=4;float duration=4;IEntity* GetOwner(){return owner;}int GetActionID(){return id;}float GetActionDuration(){return duration;}};
struct NativeType {string name;string ToString(){return name;}};
struct BaseUserAction {string className="BaseUserAction";NativeType Type(){return {className};}virtual ~BaseUserAction()=default;IEntity* owner=nullptr;int id=4;float duration=4;IEntity* GetOwner(){return owner;}int GetActionID(){return id;}float GetActionDuration(){return duration;}};
struct ERR_UnlockPrisonAction:BaseUserAction{};
struct UserActionContext {Array<BaseUserAction*> actions;vector origin=Vector(6092,212,7154);float radius=2;void GetActionsList(Array<BaseUserAction*>& out){out=actions;}vector GetOrigin(){return origin;}float GetRadius(){return radius;}};
struct BaseActionsManagerComponent {UserActionContext* context=nullptr;BaseUserAction* action=nullptr;UserActionContext* GetContext(string){return context;}BaseUserAction* FindAction(int id){return action&&action->id==id?action:nullptr;}};
@@ -61,12 +62,15 @@ namespace RplMode {enum {None,Listen,Dedicated};}namespace RplSession {int mode=
namespace ERR_OrdinaryInputObserverRules {bool Nonce(string n){return !n.empty()&&n.size()<=32&&n.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")==string::npos;}}
namespace ERR_Actors {bool CanFight(IEntity* actor){return actor&&actor->living;}}
namespace CompartmentAccessComponent {IEntity* GetVehicleIn(IEntity* actor){return actor->vehicle?actor:nullptr;}}
int headers=0,samples=0,geometries=0,cancels=0;string reason;bool lastSelected=false,lastContext=false,lastPermitted=false,lastBound=false,lastPerforming=false;float lastProgress=0;int lastPhase=0;
int headers=0,samples=0,geometries=0,cancels=0,choices=0;string lastChoice;string reason;bool lastSelected=false,lastContext=false,lastPermitted=false,lastBound=false,lastPerforming=false;float lastProgress=0;int lastPhase=0;
string Format(string,int phase,bool selected,bool context,bool permitted,bool bound,bool performing,float progress,float duration,float pressed){assert(duration==4);lastPhase=phase;lastSelected=selected;lastContext=context;lastPermitted=permitted;lastBound=bound;lastPerforming=performing;lastProgress=progress;return "SAMPLE";}
string Format(string,string name,int id){return name+":"+std::to_string(id);}
string Format(string,int index,string identity,int permission){return std::to_string(index)+"="+identity+":"+std::to_string(permission);}
string Format(string,string selected,bool context,int count,int permissions,int shown,bool truncated,string ordered){return "selected="+selected+" context="+std::to_string(context)+" actions="+std::to_string(count)+" permissions="+std::to_string(permissions)+" shown="+std::to_string(shown)+" truncated="+std::to_string(truncated)+" ordered=["+ordered+"]";}
vector lastPrison,lastGate,lastForward;
string Format(string,vector,vector,float,float,float,vector prison,vector gate,vector forward){lastPrison=prison;lastGate=gate;lastForward=forward;return "GEOMETRY";}
void PrintFormat(string,string,int,int){headers++;}
void PrintFormat(string format,string,int,int,string value){if(format.find("CANCELLED")!=string::npos){cancels++;reason=value;}else if(value=="SAMPLE")samples++;else if(value=="GEOMETRY")geometries++;else assert(false);}
void PrintFormat(string format,string,int,int,string value){if(format.find("ERR GATE CHOICE")!=string::npos){choices++;lastChoice=value;}else if(format.find("CANCELLED")!=string::npos){cancels++;reason=value;}else if(value=="SAMPLE")samples++;else if(value=="GEOMETRY")geometries++;else assert(false);}
class ERR_OrdinaryGateObserver {public:
static ERR_OrdinaryGateObserver* s_Request;static bool s_Attempted;
ERR_GameMode* m_Mode=nullptr;BaseWorld* m_World=nullptr;PlayerController* m_Controller=nullptr;IEntity* m_Actor=nullptr;ERR_PlayerLife* m_Life=nullptr;UUID m_Account;IEntity* m_Gate=nullptr;BaseActionsManagerComponent* m_Manager=nullptr;ERR_UnlockPrisonAction* m_Lock=nullptr;UserActionContext* m_Context=nullptr;BaseDoorComponent* m_Door=nullptr;SCR_InteractionHandlerComponent* m_Handler=nullptr;
@@ -75,10 +79,20 @@ class ERR_OrdinaryGateObserver {public:
};
ERR_OrdinaryGateObserver* ERR_OrdinaryGateObserver::s_Request=nullptr;bool ERR_OrdinaryGateObserver::s_Attempted=false;
struct Fixture {BaseWorld world,otherWorld;ERR_GameMode mode;IEntity actor,otherActor,gate,otherGate;PlayerController controller,otherController;PlayerManager players;Roster roster;ERR_PlayerLife life,otherLife;Prison prison;InputManager input;MenuManager menus;BaseActionsManagerComponent manager,otherManager;BaseDoorComponent door,otherDoor;UserActionContext context,otherContext;SCR_InteractionHandlerComponent handler,otherHandler;ERR_UnlockPrisonAction lock,otherLock;BaseUserAction foreignAction;
Fixture(){delete ERR_OrdinaryGateObserver::s_Request;ERR_OrdinaryGateObserver::s_Request=nullptr;ERR_OrdinaryGateObserver::s_Attempted=false;headers=samples=geometries=cancels=0;reason="";System::console=false;System::cli={{"errOrdinaryScenario","core"},{"errOrdinaryInputObserve","Run42"},{"errOrdinaryGateObserve","Run42"}};Replication::server=true;RplSession::mode=RplMode::None;activeGame=&game;ERR_GameMode::current=&mode;mode.world=&world;mode.roster=&roster;mode.prison=&prison;actor.world=gate.world=&world;controller.actor=&actor;controller.components[SCR_InteractionHandlerComponentKey]=&handler;players.controller=&controller;players.actor=&actor;roster.actor=&actor;roster.life=&life;prison.gate=&gate;gate.components[BaseActionsManagerComponentKey]=&manager;gate.components[BaseDoorComponentKey]=&door;manager.context=&context;manager.action=&lock;lock.owner=&gate;context.actions={&lock};handler.actor=&actor;handler.selected=handler.m_pLastUserAction=&lock;handler.current=handler.m_pLastContext=&context;handler.filtered={&lock};handler.permitted={true};game={&world,&controller,&players,&input,&menus};}
Fixture(){delete ERR_OrdinaryGateObserver::s_Request;ERR_OrdinaryGateObserver::s_Request=nullptr;ERR_OrdinaryGateObserver::s_Attempted=false;headers=samples=geometries=cancels=choices=0;lastChoice="";reason="";System::console=false;System::cli={{"errOrdinaryScenario","core"},{"errOrdinaryInputObserve","Run42"},{"errOrdinaryGateObserve","Run42"}};Replication::server=true;RplSession::mode=RplMode::None;activeGame=&game;ERR_GameMode::current=&mode;mode.world=&world;mode.roster=&roster;mode.prison=&prison;actor.world=gate.world=&world;controller.actor=&actor;controller.components[SCR_InteractionHandlerComponentKey]=&handler;players.controller=&controller;players.actor=&actor;roster.actor=&actor;roster.life=&life;prison.gate=&gate;gate.components[BaseActionsManagerComponentKey]=&manager;gate.components[BaseDoorComponentKey]=&door;manager.context=&context;manager.action=&lock;lock.owner=&gate;lock.className="ERR_UnlockPrisonAction";context.actions={&lock};handler.actor=&actor;handler.selected=handler.m_pLastUserAction=&lock;handler.current=handler.m_pLastContext=&context;handler.filtered={&lock};handler.permitted={true};game={&world,&controller,&players,&input,&menus};}
void sample(int index=1,int now=1100){ERR_OrdinaryGateObserver::Sample(&mode,&world,&controller,&actor,"Run42",1000,index,now);}
};
int main(){
// Diagnostics are an independent opt-in beneath the same validated observer.
{Fixture f;f.sample();assert(choices==0);System::cli["errOrdinaryGateChoices"]="foreign";f.sample(2,1200);assert(choices==0&&samples==2);}
// A disabled stock door can be selected while the exact lock is cached available.
{Fixture f;System::cli["errOrdinaryGateChoices"]="Run42";f.foreignAction.className="ERR_PrisonDoorAction";f.foreignAction.id=7;f.handler.filtered={&f.foreignAction,&f.lock};f.handler.permitted={false,true};f.handler.selected=&f.foreignAction;f.sample();assert(choices==1&&samples==1&&geometries==1);assert(!lastSelected&&lastContext&&lastPermitted);assert(lastChoice=="selected=ERR_PrisonDoorAction:7 context=1 actions=2 permissions=2 shown=2 truncated=0 ordered=[0=ERR_PrisonDoorAction:7:0,1=ERR_UnlockPrisonAction:4:1]");assert(f.handler.selected==&f.foreignAction&&f.handler.filtered[0]==&f.foreignAction);}
// Preserve native ordering and missing cached data without guessing authority.
{Fixture f;System::cli["errOrdinaryGateChoices"]="Run42";f.handler.selected=nullptr;f.handler.current=&f.otherContext;f.handler.filtered={nullptr,&f.lock};f.handler.permitted={false};f.sample();assert(choices==1&&!lastSelected&&!lastContext);assert(lastChoice=="selected=none:-1 context=0 actions=2 permissions=1 shown=2 truncated=0 ordered=[0=none:-1:0,1=ERR_UnlockPrisonAction:4:-1]");}
{Fixture f;System::cli["errOrdinaryGateChoices"]="Run42";f.handler.filtered.clear();f.handler.permitted.clear();for(int i=0;i<12;i++){f.handler.filtered.push_back(&f.lock);f.handler.permitted.push_back(true);}f.sample();assert(choices==1);assert(lastChoice.find("actions=12 permissions=12 shown=8 truncated=1")!=string::npos);assert(lastChoice.find("7=ERR_UnlockPrisonAction:4:1")!=string::npos&&lastChoice.find("8=ERR_")==string::npos);f.prison.gate=&f.otherGate;f.sample(2,1200);assert(choices==1&&cancels==1);}
// Direct diagnostic entry cannot bypass a stopped or replaced original binding.
{Fixture f;f.sample();System::cli["errOrdinaryGateChoices"]="Run42";auto request=ERR_OrdinaryGateObserver::s_Request;request->m_Stopped=true;request->ObserveChoices(2,1200);assert(choices==0);request->m_Stopped=false;f.roster.life=&f.otherLife;request->ObserveChoices(2,1200);assert(choices==0);}
{Fixture f;f.handler.m_bIsPerforming=true;f.handler.m_fCurrentProgress=2.25;f.input.pressed=1;f.sample();assert(headers==1&&samples==1&&geometries==1&&!cancels);assert(lastSelected&&lastContext&&lastPermitted&&lastBound&&lastPerforming&&lastProgress==2.25f);assert(lastPrison[2]==7160&&lastGate[2]==7158&&lastForward[2]==1);f.mode.phase=ERR_Phase::RADIO_SEARCH;f.handler.m_bIsPerforming=false;f.handler.m_fCurrentProgress=0;f.door.control=1;f.sample(2,1200);assert(samples==2&&headers==1&&!cancels&&lastPhase==ERR_Phase::RADIO_SEARCH);}
// Actual accessor never attributes accumulated progress from another selection/context.
for(int fault=0;fault<5;fault++){Fixture f;f.handler.m_bIsPerforming=true;f.handler.m_fCurrentProgress=3;BaseUserAction* expected=&f.lock;UserActionContext* ctx=&f.context;if(fault==0)expected=nullptr;if(fault==1)f.handler.selected=&f.foreignAction;if(fault==2)f.handler.current=&f.otherContext;if(fault==3)f.handler.m_pLastUserAction=&f.foreignAction;if(fault==4)f.handler.m_pLastContext=&f.otherContext;float progress=99;bool performing=true;assert(!f.handler.ERR_GateReadProgress(expected,ctx,progress,performing));assert(progress==0&&!performing);}
+37
View File
@@ -193,4 +193,41 @@ class AlignmentRunner(unittest.TestCase):
result,proof=r.input_exercise_result('game-started',context,session,observation,{'verified':True})
self.assertFalse(proof['verified']);self.assertIn('unchanged module sources',proof['error'])
class GateChoicesRunner(unittest.TestCase):
def test_readonly_choices_require_gate_and_preserve_default_signature(self):
base=['gamescope','--backend','headless','--','/proton','run','/game']
previous=r.input_observer_command(base,'chat',None,NONCE,True,True)
self.assertNotIn('-errOrdinaryGateChoices',previous)
choices=r.input_observer_command(base,'chat',None,NONCE,True,True,True)
self.assertEqual(choices,previous+['-errOrdinaryGateChoices',NONCE])
valid=dict(observe_prison_choices=True,observe_prison_gate=True,observe_player_input=True,project='chat',settle=50)
r.validate_input_observer(SimpleNamespace(**valid))
r.validate_input_exercise(SimpleNamespace(**valid)) # no input is requested
for missing in ('observe_prison_gate','observe_player_input'):
with self.assertRaises(ValueError):r.validate_input_observer(SimpleNamespace(**{**valid,missing:False}))
with self.assertRaises(ValueError):r.validate_input_exercise(SimpleNamespace(**valid,exercise_player_input=True))
for gate,choice in [(False,True),(True,1),(True,'true')]:
with self.assertRaises(ValueError):r.input_observer_command(base,'chat',None,NONCE,gate=gate,choices=choice)
for extra in (['-errOrdinaryGateChoices',NONCE],['-errOrdinaryGateChoices','foreign'],['-errOrdinaryGateChoicesAlias',NONCE]):
with self.assertRaises(ValueError):r.input_observer_command(base+extra,'chat',None,NONCE,gate=True,choices=True)
def test_input_wrapper_accepts_only_two_exact_gate_flags_without_extra_input_mode(self):
with tempfile.TemporaryDirectory() as folder:
root=Path(folder);(root/'tools').mkdir();profile=root/'profile';profile.mkdir()
for name in ('headless_input_proof.py','headless_display_probe.py','prison_approach_proof.py','ordinary_gate_evidence.py','ordinary_look_evidence.py','prison_approach_evidence.py'):(root/'tools'/name).write_text('# fake')
native=['gamescope','--backend','headless','--','/proton','run','/game','-errOrdinaryInputObserve',NONCE,'-errOrdinaryLookObserve',NONCE,'-errOrdinaryGateObserve',NONCE]
def wrap(value,approach=True):
wrapped,display=r.display_probe_command(value,root,Path('/compat'),profile,Path('/game'),NONCE,':0')
return r.input_exercise_command(wrapped,root,Path('/compat'),profile,Path('/game'),NONCE,':0',display,True,approach)
baseline,context=wrap(native)
command,newcontext=wrap(native+['-errOrdinaryGateChoices',NONCE])
self.assertEqual(command,baseline+['-errOrdinaryGateChoices',NONCE])
self.assertEqual({k:v for k,v in newcontext.items() if k!='command'},{k:v for k,v in context.items() if k!='command'})
self.assertEqual(newcontext['command'],context['command']+['-errOrdinaryGateChoices',NONCE])
for extra in (['-errOrdinaryGateChoices'],['-errOrdinaryGateChoices','foreign'],['-errOrdinaryGateChoices',NONCE]*2,['-errOrdinaryGateObserve',NONCE],['-errOrdinaryGateChoicesAlias',NONCE]):
with self.subTest(extra=extra),self.assertRaises(ValueError):wrap(native+extra)
with self.assertRaises(ValueError):wrap(native[:-2]+['-errOrdinaryGateChoices',NONCE])
with self.assertRaises(ValueError):wrap(native+['-errOrdinaryGateChoices',NONCE],False)
if __name__=='__main__':unittest.main()