-- Arm-weapon discovery + reading (v1.2). Read-only. -- Arm weapons are separate Health owners (resources from the resource table: -- combat_walker_shield/shotgun etc). Discovery is bounded (per poll for spatial); every -- displayed value is re-verified each poll via network round-trip + Health -- component owner match, like the mech itself. return function(N,Z) local A={cache=nil,next_discovery=0,dump_done=false};local rosters=setmetatable({},{__mode='k'}) local function bulk(read,p,n) local parts={};local off=0 while off196608 or limit.calls>8192 then error('arm discovery budget',0)end local b=N.win.read(p,n) if capture and type(b)=='string'and #b==n then if trace_bytes+n<=131072 and #trace<1024 then trace[#trace+1]={address=string.format('%.0f',p),hex=(b:gsub('.',function(c)return string.format('%02x',c:byte())end))};trace_bytes=trace_bytes+n else trace_truncated=true end end return b end local g=N.graph(read,N.base) local hm=g:root('health');local net=g:root('network') local t=g:table(hm+0x1030) if t.capacity==0 or t.capacity>4096 then error('Health table size',0) end local raw=bulk(read,t.entries,t.capacity*8) local descs=N.ptr(g:watch(hm+0x1048,8),0) local maxj=-1;local rows={} for pos=0,#raw-8,8 do local e,j=N.u32(raw,pos),N.u32(raw,pos+4) if e~=t.empty and j~=4294967295 then if j>=65536 then error('Health dense index bound',0) end rows[#rows+1]={e,j};if j>maxj then maxj=j end end end if maxj<0 then return {} end local ptrs=bulk(read,descs,(maxj+1)*8) local found,mechs,near={},0,{};local mech_descriptors={} for _,r in ipairs(rows) do local p=N.ptr(ptrs,r[2]*8) if p~=0 then local b=read(p,24) if type(b)=='string' and #b==24 then local d={address=p,resource=N.hex64(b,0),entity=N.u32(b,8),unit=N.u32(b,12),goid=N.u32(b,16),flags=N.u32(b,20)} if d.entity==r[1] then if Z.models[d.resource]then if #mech_descriptors>=64 then error('mech descriptor bound',0)end mech_descriptors[#mech_descriptors+1]=d end if d.resource==mech.resource then mechs=mechs+1 end if d.resource==mech.resource or model.arms[d.resource]then if g:watch(p,24)~=b then error('arm discovery descriptor changed',0)end end if model.arms[d.resource] then found[#found+1]=d elseif d.resource~=mech.resource and not Z.not_arm[d.resource] and math.abs(d.entity-mech.entity)<=3 then near[#near+1]=d end end end end end -- Link rule: identical native unit key to the mech (strong), otherwise the -- mission must contain exactly one mech of this model and at most the -- expected number of arm owners (sole). Anything else stays unlinked. if #found>64 then error('arm candidate bound',0)end local usable_unit=type(mech.unit)=='number'and mech.unit>0 and mech.unit<4294967295 local strong={};for _,d in ipairs(found) do if usable_unit and d.unit==mech.unit then strong[#strong+1]=d end end local out,link if #strong>0 then out,link=strong,'SAME_UNIT' elseif mechs==1 and #found>0 and #found<=model.arm_count then out,link=found,'SOLE_MECH' else out,link={},(#found==0 and 'NONE' or 'AMBIGUOUS') end -- Do not promote neighbouring entity numbers into a proven parent link. table.sort(out,function(a,b)return a.entity196608 then error('arm verify budget',0)end;return N.win.read(p,n)end local ok,valid=pcall(function() if N.ptr(read(N.base+N.roots.health,8),0)~=s.hm then return false end if read(s.hm+0x1030,20)~=s.header or N.ptr(read(s.hm+0x1048,8),0)~=s.descs then return false end return bulk(read,s.entries,#s.raw)==s.raw and bulk(read,s.descs,#s.ptrs)==s.ptrs end) return ok and valid==true end local function config_address(g,net,hm,d) local j=g:lookup(g:table(hm+0x1070),d.entity) if j~=nil then return N.ptr(g:watch(hm+0x10B0,8),0)+j*0x5650 end local t=N.ptr(g:watch(net+0xF12B78,8),0);local start=N.mod64hex(d.resource,1002) for step=0,63 do local row=g:watch(t+((start+step)%1002)*16,16);local key=N.hex64(row,0) if key==d.resource then local k=N.u32(row,8);if k>=1002 then error('Health settings index',0) end return t+0x3EA0+k*0x5650 elseif key=='0000000000000000' then break end end error('no arm Health configuration',0) end -- Read one arm owner inside the caller's graph (validated together). function A.read(g,d) local net,hm=g:root('network'),g:root('health') g:roundtrip(net,d) local hi,hd=g:component(hm,d.entity,0x1030,0x1048) if hi==nil or not N.same(hd,d) then error('arm Health owner changed',0) end local data=g:read(N.ptr(g:watch(hm+0x1058,8),0)+hi*0x1B8,0x1B8) local cfgp=config_address(g,net,hm,d) local cfg=g:read(cfgp,0x208+4*0x228) local mx=N.i32(cfg,0);local hp=N.i32(data,0x14) if hp<0 and hp>-1000000 then hp=0 end if mx<=0 or mx>1000000 then error('invalid arm maximum',0) end if (d.guessed or Z.arm_probation[d.resource]) and (mx<400 or mx>3000) then return {skip=true,descriptor=d,max=mx} end local zones={};local shown,src=hp,'main' for i=0,3 do local z=0x208+i*0x228;local h=string.format('%08x',N.u32(cfg,z+0x60)) if h=='00000000' then break end local zm,zh=N.i32(cfg,z+0xE8),N.i32(data,0xF8+4*i) zones[#zones+1]={hash=h,max=zm,hp=zh} if (zm==mx or zm==-1) and zh>=-1000000 and zh<=mx then if zh<0 then zh=0 end if zh=0 and shown<=mx,zones=zones, side=Z.arm_side(d.resource,zones),guessed=d.guessed} end return A end