1// Temporary live diagnostic: reads the current visit; never rolls or places a robot. 2export function isRobotMapShortcut(event){ 3 return !event.defaultPrevented&&!event.repeat&&!event.altKey&&!event.ctrlKey&&!event.metaKey&&!event.isComposing&&(event.key==='~'||event.key==='`')&&!event.target?.closest?.('input,textarea,select,[contenteditable]:not([contenteditable="false"]),[role="textbox"]'); 4} 5export function robotMapStatuses(model,world,activation){ 6 const service=world?.residentService,state=world?.residents,identity=world?.data?.layout||world?.data?.plan; 7 const cache=service?.cache.get(identity),diagnostics=state?.diagnostics||cache?.diagnostics; 8 return new Map(model.rooms.map(room=>{ 9 let info; 10 if(activation.status==='disabled'||service?.enabled===false)info={status:'disabled',label:'Disabled',detail:'Robot master switch is OFF.'}; 11 else if(activation.status==='failure')info={status:'failure',label:'Loading failure',detail:activation.message}; 12 else if(!world)info={status:'failure',label:'Loading failure',detail:'3D Gallery did not initialize; robots cannot be displayed in the exhibit list.'}; 13 else if(!service)info={status:'failure',label:'Loading failure',detail:'Robot service is unavailable.'}; 14 else if(state?.disposed&&service.enabled)info={status:'failure',label:'Loading failure',detail:'Robot scene initialization or activation failed.'}; 15 else { 16 const diagnostic=diagnostics?.get(room.index); 17 if(!diagnostic)info={status:'pending',label:'Not evaluated',detail:'This room has not yet been generated in this visit. Walk closer to evaluate it.'}; 18 else if(diagnostic.status==='placed'){ 19 const d=diagnostic.descriptor,number=String(d.design+1).padStart(2,'0'),loaded=state?.entries.some(e=>e.descriptor.roomIndex===room.index); 20 info={status:'placed',label:'Placed · Robot '+number,descriptor:d,detail:`Robot ${number} · ${d.kind==='stair'?'under stairs':'room floor'} · x ${(d.x-room.x).toFixed(2)}, z ${d.z.toFixed(2)} m from room center · ${loaded?'loaded in current scene':'saved placement; outside current scene'}.`}; 21 }else if(diagnostic.status==='skipped')info={status:'skipped',label:'Randomly skipped',detail:`Roll ${(diagnostic.roll*100).toFixed(2)}% did not pass the 40% threshold.`}; 22 else if(diagnostic.status==='failure')info={status:'failure',label:'Loading failure',detail:diagnostic.message}; 23 else {const reasons=Object.entries(diagnostic.reasons||{}).sort((a,b)=>b[1]-a[1]);info={status:'no-space',label:'No suitable space',detail:[diagnostic.tested!=null?`${diagnostic.tested} robot positions checked${diagnostic.greetings!=null?', '+diagnostic.greetings+' greeting positions tested':''}.`:'',...reasons.map(([why,n])=>`${why}: ${n}.`),diagnostic.limit||''].filter(Boolean).join(' '),shortReason:reasons[0]?.[0]||diagnostic.limit||'No valid candidate'};} 24 } 25 return [room.index,info]; 26 })); 27} 28const colors={placed:'#166a8a',skipped:'#646765','no-space':'#935500',failure:'#a82929',pending:'#69636b',disabled:'#69636b'}; 29const ns='http://www.w3.org/2000/svg'; 30function node(tag,attrs={},text=null){const n=document.createElementNS(ns,tag);for(const [key,value]of Object.entries(attrs))n.setAttribute(key,String(value));if(text!==null)n.textContent=text;return n;} 31export function drawRobotStatuses(svg,model,statuses,detail,list){ 32 const font=Math.max(.7,model.bounds.depth/45,model.bounds.width/100); 33 const show=(room,info)=>{detail.textContent=`Room ${room.index+1} · ${info.label}. ${info.detail}`;}; 34 for(const room of model.rooms){ 35 const info=statuses.get(room.index),group=node('g',{class:'map-robot-room',tabindex:0,role:'button','aria-label':`Room ${room.index+1} · ${info.label}. ${info.detail}`}); 36 const lines=info.status==='placed'?['Placed',info.label.split(' · ')[1]]:info.label==='No suitable space'?['No suitable','space']:info.label.split(' '); 37 const size=Math.min(font,room.width/(Math.max(...lines.map(s=>s.length))*.65+1)); 38 group.append(node('title',{},`${info.label}. ${info.detail}`)); 39 lines.forEach((line,i)=>group.append(node('text',{x:room.x,y:font*1.5+i*size*1.2,'font-size':size,'text-anchor':'middle',fill:colors[info.status],'font-weight':700},line))); 40 group.addEventListener('pointerenter',()=>show(room,info));group.addEventListener('focus',()=>show(room,info));group.addEventListener('click',()=>show(room,info));group.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();show(room,info);}});svg.append(group); 41 } 42 // Draw markers last, including robots under stairs, in actual world coordinates. 43 for(const room of model.rooms){const info=statuses.get(room.index),d=info.descriptor;if(!d)continue; 44 const g=node('g',{'aria-label':info.label,transform:`translate(${d.x} ${d.z})`,class:'map-robot-marker'}); 45 g.append(node('circle',{r:font*.8,fill:colors.placed,stroke:'#fff','stroke-width':font*.12}),node('text',{y:font*.23,'font-size':font*.66,'text-anchor':'middle',fill:'#fff','font-weight':700},String(d.design+1).padStart(2,'0')));svg.append(g); 46 } 47 list.replaceChildren(); 48 for(const room of model.rooms){const info=statuses.get(room.index),row=document.createElement('div'),title=document.createElement('strong'),body=document.createElement('p');row.className='robot-map-row';title.style.color=colors[info.status];title.textContent=`Room ${room.index+1} · ${info.label}`;body.textContent=info.detail;row.append(title,body);list.append(row);} 49 detail.textContent='Current visit only. Counts show rejected robot positions and greeting attempts; alternate greetings can produce multiple rejections per position. Opening this map does not reroll placements.'; 50}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.