1/** 2 * World Points of Interest 3 * Four named vantage points in the current arena footprint. 4 * Each POI has a floating label sprite and a proximity HUD panel (DOM). 5 * Enters "active" glow when live data is available nearby. 6 */ 7 8const POI_DEFS = [ 9 { 10 id: 'prediction_overlook', 11 label: 'PREDICTION OVERLOOK', 12 x: 0, 13 y: 3.5, 14 z: 58, 15 radius: 6, 16 hudTitle: 'Prediction Markets', 17 getContent: (state) => { 18 const pred = state.ui?.prediction; 19 const markets = pred?.markets ?? []; 20 if (markets.length === 0) return 'No open markets right now.'; 21 const m = markets[0]; 22 const yesP = ((m.yesPrice || 0) * 100).toFixed(0); 23 const noP = ((m.noPrice || 0) * 100).toFixed(0); 24 return `${m.question || 'Live market'}\nYES ${yesP}\u00A2 \u00B7 NO ${noP}\u00A2`; 25 } 26 }, 27 { 28 id: 'high_roller_corner', 29 label: 'HIGH-ROLLER CORNER', 30 x: 45, 31 y: 3.5, 32 z: -35, 33 radius: 6, 34 hudTitle: 'High-Roller Stats', 35 getContent: (state) => { 36 const feed = state.feedSummary; 37 if (!feed) return 'No big wins recorded yet today.'; 38 const parts = []; 39 if (feed.biggestWinToday) { 40 const { displayName, wager } = feed.biggestWinToday; 41 parts.push(`Top Win: ${displayName} +$${Number(wager || 0).toFixed(2)}`); 42 } 43 if (feed.hottestStreak) { 44 const { displayName, streak } = feed.hottestStreak; 45 parts.push(`Hot Streak: ${displayName} ${streak}W`); 46 } 47 return parts.length ? parts.join('\n') : 'Be the first on the board.'; 48 } 49 }, 50 { 51 id: 'bot_spectator_rail', 52 label: 'BOT SPECTATOR RAIL', 53 x: -50, 54 y: 3.5, 55 z: 0, 56 radius: 6, 57 hudTitle: 'Agent Activity', 58 getContent: (state) => { 59 const bots = [...(state.players?.values() ?? [])].filter( 60 (p) => p.role === 'agent' || p.actorClass === 'owner_bot' || p.actorClass === 'background_bot' 61 ); 62 if (bots.length === 0) return 'No agents currently online.'; 63 const lines = bots.slice(0, 4).map((b) => { 64 const cls = b.actorClass === 'owner_bot' ? 'OWNER' : 'AGENT'; 65 return `${b.displayName || b.id} [${cls}]`; 66 }); 67 if (bots.length > 4) lines.push(`+${bots.length - 4} more`); 68 return lines.join('\n'); 69 } 70 }, 71 { 72 id: 'cashier_overlook', 73 label: 'CASHIER OVERLOOK', 74 x: 42, 75 y: 3.5, 76 z: 25, 77 radius: 6, 78 hudTitle: 'Treasury Flow', 79 getContent: (state) => { 80 const feed = state.feedSummary; 81 if (!feed) return 'Treasury data loading...'; 82 if (feed.treasurySnapshot) { 83 const { lockedTotal, resolvedToday } = feed.treasurySnapshot; 84 return `Locked in escrow: $${Number(lockedTotal || 0).toFixed(2)}\nResolved today: $${Number(resolvedToday || 0).toFixed(2)}`; 85 } 86 return 'Cashier station: track all house flows here.'; 87 } 88 } 89]; 90 91function createPoiLabel(THREE, scene, poi) { 92 const canvas = document.createElement('canvas'); 93 canvas.width = 256; 94 canvas.height = 36; 95 const ctx = canvas.getContext('2d'); 96 const texture = new THREE.CanvasTexture(canvas); 97 texture.colorSpace = THREE.SRGBColorSpace; 98 99 function draw(active) { 100 if (!ctx) return; 101 ctx.clearRect(0, 0, canvas.width, canvas.height); 102 ctx.fillStyle = active ? 'rgba(47,143,94,0.85)' : 'rgba(30,28,20,0.82)'; 103 ctx.beginPath(); 104 ctx.roundRect(1, 1, canvas.width - 2, canvas.height - 2, canvas.height / 2); 105 ctx.fill(); 106 ctx.strokeStyle = active ? '#f2d27a' : 'rgba(47,143,94,0.35)'; 107 ctx.lineWidth = 1.2; 108 ctx.stroke(); 109 ctx.fillStyle = active ? '#1a1000' : '#2f8f5e'; 110 ctx.font = 'bold 11px "IBM Plex Mono", monospace'; 111 ctx.textAlign = 'center'; 112 ctx.textBaseline = 'middle'; 113 ctx.fillText(poi.label, canvas.width / 2, canvas.height / 2 + 0.5); 114 texture.needsUpdate = true; 115 } 116 117 draw(false); 118 119 const sprite = new THREE.Sprite( 120 new THREE.SpriteMaterial({ 121 map: texture, 122 transparent: true, 123 depthTest: false, 124 depthWrite: false 125 }) 126 ); 127 sprite.scale.set(3.2, 0.45, 1); 128 sprite.position.set(poi.x, poi.y + 1.2, poi.z); 129 scene.add(sprite); 130 131 return { redraw: draw }; 132} 133 134function createHudPanel(poi) { 135 const panel = document.createElement('div'); 136 panel.dataset.poiId = poi.id; 137 panel.style.cssText = [ 138 'position:fixed', 139 'left:50%', 140 'bottom:120px', 141 'transform:translateX(-50%)', 142 'background:rgba(18,16,10,0.92)', 143 'border:1px solid rgba(47,143,94,0.45)', 144 'border-radius:8px', 145 'padding:12px 20px', 146 'font-family:"IBM Plex Mono",monospace', 147 'font-size:12px', 148 'color:#2f8f5e', 149 'white-space:pre-line', 150 'pointer-events:none', 151 'z-index:800', 152 'display:none', 153 'max-width:340px', 154 'text-align:center' 155 ].join(';'); 156 157 const titleEl = document.createElement('strong'); 158 titleEl.style.cssText = 'color:#9ad4b4;display:block;margin-bottom:6px'; 159 titleEl.textContent = poi.hudTitle; 160 161 const bodyEl = document.createElement('span'); 162 163 panel.appendChild(titleEl); 164 panel.appendChild(bodyEl); 165 document.body.appendChild(panel); 166 167 return { 168 show(content) { 169 bodyEl.textContent = content; 170 panel.style.display = 'block'; 171 }, 172 hide() { 173 panel.style.display = 'none'; 174 } 175 }; 176} 177 178export function createWorldPoi({ THREE, scene }) { 179 const pois = POI_DEFS.map((def) => { 180 const label = createPoiLabel(THREE, scene, def);
181 const hud = createHudPanel(def); 182 return { def, label, hud, lastActive: false }; 183 }); 184 185 function update(state) { 186 const playerId = state.playerId; 187 const player = playerId ? state.players?.get(playerId) : null; 188 const px = player?.displayX ?? player?.x ?? 0; 189 const pz = player?.displayZ ?? player?.z ?? 0; 190 191 for (const poi of pois) { 192 const { def, label, hud } = poi; 193 const dx = px - def.x; 194 const dz = pz - def.z; 195 const dist = Math.sqrt(dx * dx + dz * dz); 196 const inRadius = dist < def.radius; 197 198 // Active glow when there is any live data 199 const hasLiveData = Boolean( 200 state.feedSummary || 201 (state.ui?.prediction?.markets?.length ?? 0) > 0 || 202 [...(state.players?.values() ?? [])].some((p) => p.role === 'agent') 203 ); 204 205 if (hasLiveData !== poi.lastActive) { 206 poi.lastActive = hasLiveData; 207 label.redraw(hasLiveData); 208 } 209 210 if (inRadius) { 211 hud.show(def.getContent(state)); 212 } else { 213 hud.hide(); 214 } 215 } 216 } 217 218 return { update }; 219}
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.