|
51 | 51 | copyLink: document.getElementById("copyLinkButton"), |
52 | 52 | copyTrace: document.getElementById("copyTraceButton"), |
53 | 53 | copyStatus: document.getElementById("copyStatus"), |
| 54 | + beliefPanel: document.getElementById("beliefPanel"), |
54 | 55 | replay: document.getElementById("replaySlider"), |
55 | 56 | replayValue: document.getElementById("replayValue"), |
56 | 57 | comparePanel: document.getElementById("comparePanel"), |
|
186 | 187 | agentState: "parse_command", |
187 | 188 | target: "unresolved", |
188 | 189 | failure: "none", |
| 190 | + belief: ambiguousBelief(), |
189 | 191 | picked: null, |
190 | 192 | pickAt: null, |
191 | 193 | focus: null, |
|
205 | 207 | target: answer, |
206 | 208 | agentState: "update_goal_from_answer", |
207 | 209 | failure: "ambiguous_goal", |
| 210 | + belief: resolvedBelief(answer), |
208 | 211 | question: "Which block?", |
209 | 212 | answer, |
210 | 213 | focus: answer, |
|
219 | 222 | ...initial, |
220 | 223 | target: answer, |
221 | 224 | agentState: "target_confirmed", |
| 225 | + belief: resolvedBelief(answer), |
222 | 226 | focus: answer, |
223 | 227 | }, |
224 | 228 | }, |
|
231 | 235 | ...initial, |
232 | 236 | target: answer, |
233 | 237 | agentState: "done", |
| 238 | + belief: resolvedBelief(answer), |
234 | 239 | picked: answer, |
235 | 240 | pickAt: [targetX, 56], |
236 | 241 | focus: answer, |
|
310 | 315 | command: "put the block away", |
311 | 316 | robot: [7, 1], |
312 | 317 | target: "unresolved", |
| 318 | + belief: ambiguousBelief(), |
313 | 319 | held: null, |
314 | 320 | stored: null, |
315 | 321 | blocked: [], |
|
325 | 331 | command: "put the block away", |
326 | 332 | robot: [7, 1], |
327 | 333 | target: "unresolved", |
| 334 | + belief: ambiguousBelief(), |
328 | 335 | held: null, |
329 | 336 | stored: null, |
330 | 337 | blocked: [], |
|
343 | 350 | failure: "ambiguous_goal", |
344 | 351 | agentState: "update_goal_from_answer", |
345 | 352 | target: answer, |
| 353 | + belief: resolvedBelief(answer), |
346 | 354 | path: answer === "blue" ? blueRoute : redRoute, |
347 | 355 | }); |
348 | 356 |
|
|
513 | 521 | }); |
514 | 522 | } |
515 | 523 |
|
| 524 | + function ambiguousBelief() { |
| 525 | + const distribution = { red: 0.5, blue: 0.5 }; |
| 526 | + return { |
| 527 | + red: distribution.red, |
| 528 | + blue: distribution.blue, |
| 529 | + entropy: beliefEntropy(distribution), |
| 530 | + askGain: beliefEntropy(distribution), |
| 531 | + policy: "ask", |
| 532 | + }; |
| 533 | + } |
| 534 | + |
| 535 | + function resolvedBelief(answer) { |
| 536 | + const distribution = { |
| 537 | + red: answer === "red" ? 1 : 0, |
| 538 | + blue: answer === "blue" ? 1 : 0, |
| 539 | + }; |
| 540 | + return { |
| 541 | + red: distribution.red, |
| 542 | + blue: distribution.blue, |
| 543 | + entropy: beliefEntropy(distribution), |
| 544 | + askGain: 0, |
| 545 | + policy: "act", |
| 546 | + }; |
| 547 | + } |
| 548 | + |
| 549 | + function beliefEntropy(distribution) { |
| 550 | + return Object.values(distribution).reduce((total, probability) => { |
| 551 | + if (probability <= 0) { |
| 552 | + return total; |
| 553 | + } |
| 554 | + return total - probability * Math.log2(probability); |
| 555 | + }, 0); |
| 556 | + } |
| 557 | + |
516 | 558 | function snapshotFromContext(context) { |
517 | 559 | return { |
518 | 560 | command: context.command, |
519 | 561 | robot: context.robot.slice(), |
520 | 562 | target: context.target, |
| 563 | + belief: copyBelief(context.belief), |
521 | 564 | held: context.held, |
522 | 565 | stored: context.stored, |
523 | 566 | blocked: context.blocked.map((cell) => cell.slice()), |
|
530 | 573 | }; |
531 | 574 | } |
532 | 575 |
|
| 576 | + function copyBelief(belief) { |
| 577 | + return { |
| 578 | + red: belief.red, |
| 579 | + blue: belief.blue, |
| 580 | + entropy: belief.entropy, |
| 581 | + askGain: belief.askGain, |
| 582 | + policy: belief.policy, |
| 583 | + }; |
| 584 | + } |
| 585 | + |
533 | 586 | function trimPath(path, robot) { |
534 | 587 | if (!path || !path.length) { |
535 | 588 | return [robot]; |
|
577 | 630 | renderReplay(replayIndex); |
578 | 631 | renderCompare(); |
579 | 632 | renderTimeline(replayIndex); |
| 633 | + renderBelief(current); |
580 | 634 | renderScene(current); |
581 | 635 | renderTrace(replayIndex); |
582 | 636 | } |
|
652 | 706 | }); |
653 | 707 | } |
654 | 708 |
|
| 709 | + function renderBelief(snapshot) { |
| 710 | + const belief = snapshot.belief; |
| 711 | + elements.beliefPanel.textContent = ""; |
| 712 | + if (!belief) { |
| 713 | + elements.beliefPanel.hidden = true; |
| 714 | + return; |
| 715 | + } |
| 716 | + elements.beliefPanel.hidden = false; |
| 717 | + |
| 718 | + const bars = document.createElement("div"); |
| 719 | + bars.className = "belief-bars"; |
| 720 | + [ |
| 721 | + ["red", belief.red], |
| 722 | + ["blue", belief.blue], |
| 723 | + ].forEach(([label, probability]) => { |
| 724 | + bars.appendChild(renderBeliefRow(label, probability)); |
| 725 | + }); |
| 726 | + |
| 727 | + const metrics = document.createElement("div"); |
| 728 | + metrics.className = "belief-metrics"; |
| 729 | + [ |
| 730 | + ["entropy", formatBits(belief.entropy)], |
| 731 | + ["ask gain", "+" + formatBits(belief.askGain)], |
| 732 | + ["policy", belief.policy], |
| 733 | + ].forEach(([label, value]) => { |
| 734 | + const metric = document.createElement("span"); |
| 735 | + metric.textContent = label; |
| 736 | + const strong = document.createElement("strong"); |
| 737 | + strong.textContent = value; |
| 738 | + metric.appendChild(strong); |
| 739 | + metrics.appendChild(metric); |
| 740 | + }); |
| 741 | + |
| 742 | + elements.beliefPanel.appendChild(bars); |
| 743 | + elements.beliefPanel.appendChild(metrics); |
| 744 | + } |
| 745 | + |
| 746 | + function renderBeliefRow(label, probability) { |
| 747 | + const row = document.createElement("div"); |
| 748 | + row.className = "belief-row"; |
| 749 | + |
| 750 | + const name = document.createElement("span"); |
| 751 | + name.textContent = label; |
| 752 | + row.appendChild(name); |
| 753 | + |
| 754 | + const track = document.createElement("div"); |
| 755 | + track.className = "belief-track"; |
| 756 | + const fill = document.createElement("div"); |
| 757 | + fill.className = "belief-fill belief-" + label; |
| 758 | + fill.style.width = Math.round(probability * 100) + "%"; |
| 759 | + track.appendChild(fill); |
| 760 | + row.appendChild(track); |
| 761 | + |
| 762 | + const value = document.createElement("strong"); |
| 763 | + value.className = "belief-value"; |
| 764 | + value.textContent = Math.round(probability * 100) + "%"; |
| 765 | + row.appendChild(value); |
| 766 | + |
| 767 | + return row; |
| 768 | + } |
| 769 | + |
655 | 770 | function timelineClass(event) { |
656 | 771 | if (event.failure === "ambiguous_goal") return "timeline-ambiguous"; |
657 | 772 | if (event.failure === "unsafe_nominal_step") return "timeline-unsafe"; |
|
1106 | 1221 | function formatReward(value) { |
1107 | 1222 | return Number(value).toFixed(2); |
1108 | 1223 | } |
| 1224 | + |
| 1225 | + function formatBits(value) { |
| 1226 | + return Number(value).toFixed(2) + " bit"; |
| 1227 | + } |
1109 | 1228 | })(); |
0 commit comments