local load=function(n)return dofile(SOURCE..'/'..n..'.lua')end local policy=load('spatial_policy');local Z=load('zones');local N=load('native') local checks=0;local function C(v,msg)assert(v,msg);checks=checks+1 end local descriptors={} for _,d in ipairs(FIXTURE.mechs)do descriptors[d.entity]=d end for _,d in ipairs(FIXTURE.arms)do descriptors[d.entity]=d end local poses={};local unit_by_goid={};local hp={111,222,333,444,555,666} for i,d in ipairs(FIXTURE.mechs)do poses[d.entity]={(i-1)*30,0,0}end for i,d in ipairs(FIXTURE.arms)do poses[d.entity]={math.floor((i-1)/2)*30+(i%2==1 and -1 or 1),0,2}end for e,d in pairs(descriptors)do unit_by_goid[d.goid]={entity=e,goid=d.goid}end local ctl={session='s',peer=1,world='w',current=1,seated=true,exists=true,alive=true,roundtrip=true,graph_valid=true,roster=true,nodes=1} local stats={lookup=0,read=0,discover=0,old_update=0,forbidden=0,unit_inputs={}} local sr={Application={},Network={},GameSession={},UnitSynchronizer={},Unit={},World={},Vector3={},Gui={}} sr.Application.main_world=function()return ctl.world end;sr.Application.worlds=function()return {ctl.world,'ui'}end sr.Network.game_session=function()return ctl.session end;sr.Network.peer_id=function()return ctl.peer end sr.GameSession.unit_synchronizer=function(s)C(s==ctl.session);return 'sync'end sr.GameSession.game_object_exists=function(s,id)return ctl.exists end sr.UnitSynchronizer.game_object_id_to_unit=function(sync,id) C(sync=='sync');stats.lookup=stats.lookup+1;stats.unit_inputs[id]=true return not ctl.no_unit and unit_by_goid[id]or nil end sr.UnitSynchronizer.unit_to_game_object_id=function(sync,u)return ctl.roundtrip and u.goid or u.goid+1 end sr.Unit.alive=function(u)return ctl.alive end sr.Unit.num_scene_graph_items=function(u)return ctl.nodes end sr.Unit.world_position=function(u,node)C(node==1);return poses[u.entity]end sr.Vector3.to_elements=function(v)return v[1],v[2],v[3]end sr.Unit.id=function()stats.forbidden=stats.forbidden+1;error('editor id must not be used')end sr.World.unit_by_id=sr.Unit.id;sr.Unit.is_child=sr.Unit.id N.win={read=function()error('no real process access')end,begin_sample=function()end} N.graph=function() return {relation=function()return {status=ctl.seated and 'VEHICLE'or 'EMPTY',vehicle=FIXTURE.mechs[ctl.current]}end, root=function(_,name)return name end, roundtrip=function(_,net,d)C(N.same(descriptors[d.entity],d),'native descriptor identity')end, component=function(_,hm,e)return e,descriptors[e]end, validate=function()if not ctl.graph_valid then error('injected unstable graph')end end} end local function metadata()return {mech_descriptors=FIXTURE.mechs,candidate_descriptors=FIXTURE.arms,mechs=3,candidates=6}end local function context()return {session=ctl.session,peer=ctl.peer,world=ctl.world,avatar=20}end -- Match the actual t1 game export inventory, not names assumed by the mock. for _,ns in ipairs({'GameSession','UnitSynchronizer','Unit','Vector3'})do local available={};for _,name in ipairs(LIVE_API[ns])do available[name]=true end for name,value in pairs(sr[ns])do if type(value)=='function'and not available[name]then sr[ns][name]=nil end end end C(sr.Vector3.elements==nil and type(sr.Vector3.to_elements)=='function','live Vector3 export mismatch') local runtime=load('spatial_runtime')(sr,N,Z,policy) for k=1,3 do ctl.current=k;local list,why,info,stable=runtime.resolve(FIXTURE.mechs[k],metadata(),context()) C(list and #list==2 and why=='SPATIAL_HEURISTIC',why) C(list[1].entity==FIXTURE.arms[(k-1)*2+1].entity and list[2].entity==FIXTURE.arms[(k-1)*2+2].entity,'not current mech arms') C(info.heuristic and stable());C(#info.positions==9) end C(stats.forbidden==0) for _,d in pairs(descriptors)do C(stats.unit_inputs[d.goid]and not stats.unit_inputs[d.unit],'Unit bridge must use GOID, not native unit key')end local function refused(reason) local list,why,info=runtime.resolve(FIXTURE.mechs[ctl.current],metadata(),context());C(not list and why==reason,(why or 'nil')..':'..tostring(info.error));return info end ctl.current=1 local saved=poses[FIXTURE.mechs[2].entity];poses[FIXTURE.mechs[2].entity]={8,0,0};local near=runtime.resolve(FIXTURE.mechs[1],metadata(),context());C(near and #near==2,'separable 8m scene must not be blanket-hidden');poses[FIXTURE.mechs[2].entity]={2,0,0};refused('SPATIAL_COMPETING_MECH');poses[FIXTURE.mechs[2].entity]=saved ctl.roundtrip=false;C(refused('SPATIAL_UNAVAILABLE').error=='SPATIAL_UNIT_ROUNDTRIP');ctl.roundtrip=true ctl.no_unit=true;C(refused('SPATIAL_UNAVAILABLE').error:find('SPATIAL_NO_LIVE_UNIT',1,true));ctl.no_unit=false ctl.alive=false;refused('SPATIAL_UNAVAILABLE');ctl.alive=true ctl.exists=false;C(refused('SPATIAL_UNAVAILABLE').error=='SPATIAL_OBJECT_GONE');ctl.exists=true ctl.nodes=0;C(refused('SPATIAL_UNAVAILABLE').error=='SPATIAL_NODE_BOUND');ctl.nodes=1 ctl.graph_valid=false;refused('SPATIAL_UNAVAILABLE');ctl.graph_valid=true local sync=sr.UnitSynchronizer;sr.UnitSynchronizer=nil;C(refused('SPATIAL_UNAVAILABLE').error:find('SPATIAL_API_MISSING',1,true));sr.UnitSynchronizer=sync local vector_namespace=sr.Vector3 sr.Vector3=function(x,y,z)return {x,y,z}end local old_meta=debug.getmetatable(sr.Vector3) debug.setmetatable(sr.Vector3,{__index={to_elements=vector_namespace.to_elements}}) local valid_function_namespace=runtime.resolve(FIXTURE.mechs[1],metadata(),context());C(valid_function_namespace~=nil,'function namespace must be supported') debug.setmetatable(sr.Vector3,old_meta);sr.Vector3=vector_namespace local p=poses[FIXTURE.arms[1].entity];poses[FIXTURE.arms[1].entity]={0/0,0,0};refused('SPATIAL_UNAVAILABLE');poses[FIXTURE.arms[1].entity]=p local selected,reason,info,stable=runtime.resolve(FIXTURE.mechs[1],metadata(),context()) poses[FIXTURE.arms[1].entity]={p[1]+1,p[2],p[3]};local ok,why=stable();C(not ok and why=='SPATIAL_MOVED_DURING_SAMPLE');poses[FIXTURE.arms[1].entity]=p selected,reason,info,stable=runtime.resolve(FIXTURE.mechs[1],metadata(),context());ctl.current=2;ok,why=stable();C(not ok and why=='SPATIAL_SEAT_CHANGED');ctl.current=1 selected,reason,info,stable=runtime.resolve(FIXTURE.mechs[1],metadata(),context());ctl.peer=2;ok,why=stable();C(not ok and why=='SPATIAL_CONTEXT_CHANGED');ctl.peer=1 -- Pure geometry checks, independent of candidate ordering. local bodies,arms={},{} for _,d in ipairs(FIXTURE.mechs)do bodies[#bodies+1]={descriptor=d,position=poses[d.entity]}end for i=#FIXTURE.arms,1,-1 do local d=FIXTURE.arms[i];arms[#arms+1]={descriptor=d,position=poses[d.entity]}end local a=policy.select(FIXTURE.mechs[1],bodies,arms,Z.side_by_resource);C(a and a[1].entity==458 and a[2].entity==459) local duplicate={resource=FIXTURE.arms[1].resource,entity=900,goid=900,unit=900};arms[#arms+1]={descriptor=duplicate,position={2,0,2}} a,why=policy.select(FIXTURE.mechs[1],bodies,arms,Z.side_by_resource);C(not a and why=='SPATIAL_DUPLICATE_SIDE');arms[#arms]=nil local pose1=poses[458];poses[458]={100,0,0};for _,v in ipairs(arms)do if v.descriptor.entity==458 then v.position=poses[458]end end a=policy.select(FIXTURE.mechs[1],bodies,arms,Z.side_by_resource);C(a and #a==1 and a[1].entity==459,'missing arm must not be filled with another mech arm');poses[458]=pose1 -- Full HUD integration: real spatial module, mocked documented bridge, distinct HP per mech. sr.GameSession.in_session=function()return true end;sr.GameSession.objects_owned_by=function()return {10}end sr.GameSession.game_object_is_type=function()return true end;sr.GameSession.game_object_field=function()return 20 end sr.Gui.resolution=function()return 1920,1080 end local Reader={sample=function() if ctl.transient then return nil,'short read','TRANSIENT'end if not ctl.seated then return nil,'EMPTY','NOT_SEATED'end local d=FIXTURE.mechs[ctl.current] return {vehicle=d,model=Z.models[d.resource],hull=1800,hull_max=1800,hull_valid=true,zones={}} end} local Arm={discover=function()if ctl.discover_error then error('injected discovery error')end;stats.discover=stats.discover+1;return {},'AMBIGUOUS',metadata()end, read=function(g,d) stats.read=stats.read+1 for i,v in ipairs(FIXTURE.arms)do if N.same(v,d)then return {descriptor=d,hp=hp[i],main=hp[i],max=800,valid=true,side=Z.side_by_resource[d.resource],source='main',zones={}}end end error('foreign arm') end,verify=function()return ctl.roster end} local D={mode='MECH',clear=function()end,reset=function()end,ensure=function()return true end,draw=function(w,h,v)_G.LAST_VIEW=v end} CowboyBingusModLoader={api=1};update=function(...)stats.old_update=stats.old_update+1;return 'previous',nil,7 end local S=load('hud')(sr,N,Reader,function()return {}end,Z,function()return {debug=false,poll=.1,spatial=true}end,{},function()return D end,Arm,nil,runtime) local function tick(n)for i=1,n or 1 do local a,b,c=update(.25);C(a=='previous'and b==nil and c==7)end end local function shown(k) C(S.arms and #S.arms==2,'arms not shown');C(S.arms[1].hp==hp[(k-1)*2+1]and S.arms[2].hp==hp[(k-1)*2+2],'cross-mech HP') C(S.arm_association=='SPATIAL_HEURISTIC'and LAST_VIEW.arm_association=='SPATIAL_HEURISTIC','approximation not labelled') end tick();C(not S.arms,'warmup frame 1');tick();C(not S.arms,'warmup frame 2');tick();shown(1) for k=2,3 do ctl.current=k;tick();C(not S.arms,'old mech HP retained on switching');tick(2);shown(k)end local other=FIXTURE.mechs[2].entity;local pos_other=poses[other];poses[other]={55,0,0};tick();C(not S.arms and not S.arm_association and S.rows,'crowded scene must hide only arms immediately');poses[other]=pos_other tick(6);shown(3) ctl.roster=false;tick();C(not S.arms,'changed roster must refuse publishing');ctl.roster=true;tick(3);shown(3) local before=stats.lookup;ctl.seated=false;tick(4);C(not S.rows and not S.arms and stats.lookup==before,'no on-foot nearest-mech selection');ctl.seated=true;tick(3);shown(3) ctl.session='new_session';tick();C(not S.arms,'session invalidation');tick(2);shown(3) ctl.transient=true;tick();C(S.rows and not S.arms and not S.arm_association,'short hull read must not retain spatial arms');ctl.transient=false;tick(2);C(not S.arms);tick();shown(3) ctl.discover_error=true;tick();C(not S.arms);ctl.discover_error=false;tick(2);C(not S.arms);tick();shown(3) ctl.world='new_world';tick();C(not S.arms);tick(2);shown(3) ctl.peer=2;tick();C(not S.arms);tick(2);shown(3) -- Rejected API means no guessed arms, but hull still renders. sr.UnitSynchronizer=nil;tick();C(not S.arms and S.rows and S.hull==1800);sr.UnitSynchronizer=sync;tick(6);shown(3) C(stats.forbidden==0,'unverified engine functions invoked') return {checks=checks,spatial_policy_synthetic=true,bridge_mocked=true,real_game_api_invoked=false,in_game_verified=false,three_mech_switches=true,hp_distinct=true,approximation_label=true}