/** Read-only companion HUD for the pinned, unchanged Buster GAME.X. * Locate its relocated text using three independent instruction signatures. * Addresses below are link-relative symbols, never fixed guest load addresses. */ export type GuestMemory={peekByte(address:number):number;peekWord(address:number):number}; const signature=Uint8Array.from('302f00066c0470ff600a4a406f047001600270004e75'.match(/../g)!.map(x=>parseInt(x,16))); export class SwordMeter { base:number|null=null;private scan=0x10b12; constructor(private memory:GuestMemory){} private matches(address:number,bytes:Uint8Array){return bytes.every((b,i)=>this.memory.peekByte(address+i)===b);} locate(){ const end=Math.min(this.scan+0x10000,0x200000); for(;this.scan=0x200000)continue; const operand=this.memory.peekWord(base+0x206c)*65536+this.memory.peekWord(base+0x206e); if(operand!==base+0x1a060||this.memory.peekWord(base+0x2fb8)!==0x48e7||this.memory.peekWord(base+0x2062)!==0x4e56)continue; this.base=base;return true; } if(this.scan>=0x200000)this.scan=0x10b12; return false; } read(){ if(this.base===null&&!this.locate())return null; const base=this.base!,charge=this.memory.peekByte(base+0x1a060),state=this.memory.peekByte(base+0x1babc),latched=this.memory.peekByte(base+0x1bac6)!==0,lives=this.memory.peekByte(base+0x1a074); if(charge>40||state>4||!lives)return null; const swinging=state>=1&&state<=3; const text=charge<8?'CHARGING':swinging?'SWINGING':state!==0?'BUSY':latched?'RELEASE & PRESS':'READY'; return {base,charge,max:40,percent:Math.round(charge*2.5),state,lives,latched,ready:charge>=8&&state===0&&!latched,text}; } }