@@ -32,6 +32,7 @@ function renderProximityVizHtml() {
3232 canvas { width: 100%; height: 100%; display: block; }
3333 #side { border-left: 1px solid #1f2630; padding: 12px 14px; overflow-y: auto; }
3434 #side h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: #8b949e; margin: 0 0 8px; }
35+ #side h2:not(:first-child) { margin-top: 16px; }
3536 .adv { border: 1px solid #1f2630; border-radius: 8px; padding: 8px 10px; margin-bottom: 8px; }
3637 .adv.resolution { border-color: #b3402f; }
3738 .adv.advisory { border-color: #9a6700; }
@@ -41,8 +42,11 @@ function renderProximityVizHtml() {
4142 .adv .who { color: #c9d1d9; }
4243 .adv .act { color: #8b949e; font-size: 12px; margin-top: 3px; }
4344 .empty { color: #6e7681; }
45+ .agent-row { display: flex; gap: 8px; align-items: baseline; padding: 3px 0; font-size: 12px; }
46+ .agent-row .who { color: #c9d1d9; overflow-wrap: anywhere; }
47+ .agent-row .risk { margin-left: auto; color: #8b949e; white-space: nowrap; }
4448 #legend { position: absolute; left: 12px; bottom: 12px; font-size: 11px; color: #8b949e; background: rgba(11,14,20,.7); padding: 6px 8px; border-radius: 6px; }
45- .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; vertical-align: middle ; }
49+ .shape { display: inline-block; width: 12px; margin-right: 5px; text-align: center; font-weight: 700 ; }
4650</style>
4751</head>
4852<body>
@@ -53,16 +57,18 @@ function renderProximityVizHtml() {
5357 </header>
5458 <div id="wrap">
5559 <div id="stage">
56- <canvas id="c"> </canvas>
60+ <canvas id="c" role="img" aria-label="Agent airspace visualization. See the Agents panel for a text alternative.">Agent airspace visualization; see the Agents panel for per-agent risk. </canvas>
5761 <div id="legend">
58- <div><span class="dot " style="background :#3fb950"></span>clear</div>
59- <div><span class="dot " style="background :#e3b341"></span>traffic advisory (transmit)</div>
60- <div><span class="dot " style="background :#ff7b72"></span>resolution (steer)</div>
62+ <div><span class="shape " style="color :#3fb950">● </span>clear</div>
63+ <div><span class="shape " style="color :#e3b341">■ </span>traffic advisory (transmit)</div>
64+ <div><span class="shape " style="color :#ff7b72">▲ </span>resolution (steer)</div>
6165 </div>
6266 </div>
6367 <div id="side">
6468 <h2>Advisories</h2>
6569 <div id="advisories"><div class="empty">No advisories - airspace clear.</div></div>
70+ <h2>Agents</h2>
71+ <div id="agents"><div class="empty">No agents.</div></div>
6672 </div>
6773 </div>
6874<script>
@@ -81,12 +87,33 @@ function renderProximityVizHtml() {
8187 }
8288 window.addEventListener('resize', resize);
8389
90+ function riskLevel(risk) {
91+ if (risk >= 0.7) return 'resolution';
92+ if (risk >= 0.35) return 'advisory';
93+ return 'clear';
94+ }
95+
8496 function riskColor(risk) {
8597 if (risk >= 0.7) return '#ff7b72';
8698 if (risk >= 0.35) return '#e3b341';
8799 return '#3fb950';
88100 }
89101
102+ function drawRiskMarker(x, y, radius, level) {
103+ ctx.beginPath();
104+ if (level === 'resolution') {
105+ ctx.moveTo(x, y - radius);
106+ ctx.lineTo(x + radius, y + radius);
107+ ctx.lineTo(x - radius, y + radius);
108+ ctx.closePath();
109+ } else if (level === 'advisory') {
110+ ctx.rect(x - radius, y - radius, radius * 2, radius * 2);
111+ } else {
112+ ctx.arc(x, y, radius, 0, Math.PI * 2);
113+ }
114+ ctx.fill();
115+ }
116+
90117 // 3D to 2D: rotate around Y, simple perspective.
91118 function project(p, w, h) {
92119 var x = p[0], y = p[1] || 0, z = p[2] || 0;
@@ -122,8 +149,9 @@ function renderProximityVizHtml() {
122149 var ag = state.positions[k];
123150 var p = pos[ag.agentId];
124151 var radius = (6 + Math.sqrt(ag.fileCount || 1) * 3) * p[2];
125- ctx.fillStyle = riskColor(state.riskByAgent[ag.agentId] || 0);
126- ctx.beginPath(); ctx.arc(p[0], p[1], radius, 0, Math.PI * 2); ctx.fill();
152+ var agentRisk = state.riskByAgent[ag.agentId] || 0;
153+ ctx.fillStyle = riskColor(agentRisk);
154+ drawRiskMarker(p[0], p[1], radius, riskLevel(agentRisk));
127155 ctx.fillStyle = '#c9d1d9';
128156 ctx.font = '11px -apple-system, system-ui, sans-serif';
129157 ctx.fillText(String(ag.agentId).slice(0, 18), p[0] + radius + 4, p[1] + 3);
@@ -155,6 +183,25 @@ function renderProximityVizHtml() {
155183 });
156184 }
157185
186+ function renderAgents() {
187+ var box = document.getElementById('agents');
188+ box.textContent = '';
189+ if (!state.positions.length) {
190+ var empty = document.createElement('div'); empty.className = 'empty';
191+ empty.textContent = 'No agents.'; box.appendChild(empty); return;
192+ }
193+ state.positions.forEach(function (agent) {
194+ var risk = state.riskByAgent[agent.agentId] || 0;
195+ var row = document.createElement('div'); row.className = 'agent-row';
196+ var who = document.createElement('span'); who.className = 'who';
197+ who.textContent = String(agent.agentId); row.appendChild(who);
198+ var riskText = document.createElement('span'); riskText.className = 'risk';
199+ riskText.textContent = Math.round(risk * 100) + '% - ' + riskLevel(risk);
200+ row.appendChild(riskText);
201+ box.appendChild(row);
202+ });
203+ }
204+
158205 function applySnapshot(prox) {
159206 state.positions = prox.positions || [];
160207 state.links = prox.links || [];
@@ -166,7 +213,11 @@ function renderProximityVizHtml() {
166213 });
167214 state.riskByAgent = risk;
168215 renderAdvisories();
216+ renderAgents();
169217 var c = prox.counts || {};
218+ canvas.setAttribute('aria-label',
219+ (c.agents || state.positions.length) + ' agents in airspace, ' +
220+ (c.advisories || state.advisories.length) + ' advisories. See the Agents panel for per-agent risk.');
170221 document.getElementById('status').textContent =
171222 (c.agents || 0) + ' agents - ' + (c.advisories || 0) + ' advisories - ' + (c.resolutions || 0) + ' steering';
172223 }
0 commit comments