feat(orchestrator): S3 generated SVG faces whose mouths move while speaking

Requirement 001 phase 6, S3:

- faces.js derives a face from the agent's stable key (its session id,
  or name:<name> without a session), so a rename keeps the face: shape
  4, colour 7, eyes 5, brows 4, mouth 5, hair or headwear 6, accessory 5,
  84,000 combinations. It emits constant SVG tags and attributes only.
- The emoji avatar is gone: popup, Orbit and Chord nodes, the radio
  view, stream and palette draw the SVG face (the 3D views keep their
  marker).
- While the radio reads an agent's message, its faces carry the class
  speaking and the mouth runs the talk keyframes; a silent face does
  not move, and prefers-reduced-motion stops the mouth.

Evidence: make test 673 OK, ruff and markdownlint clean
(/tmp/s3-make-test-1.log); e2e 20 OK in Chromium and Firefox, none
skipped (/tmp/s3-e2e-3.log); screenshots
/tmp/orchestrator-phase4-shots/*-speaking.png; MEDUSA 13 files, 2
findings ("PLA" in "templates", the UI call openAgent) accepted after
an independent skeptic that found no markup sink or selector injection
in the face code.

🎯 Fix: 94 %
💥 Break: 3 %
💸 Cost: 100 %
⏱️ Time: –
🛡️ Security: 100 %
🏆 Score: 91
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Veen-BirkenbachandClaude Opus 5.5 committed 2026-11-08 22:06:31 +00:00
1 parent d89fb616b8
commit 4d88296e47
13 files changed
+262 -34

No files matched your search

+94
View File
@@ -0,0 +1,94 @@
from __future__ import annotations
import json
import math
import re
import subprocess
import unittest
from collections import Counter
from hub_support import FACES
FACES_PROBE = """
const F = require(process.argv[1]);
let seed = 7;
const hex = (digits) => Array.from({length: digits}, () => {
seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0;
return (seed >>> 28).toString(16);
}).join("");
const ids = Array.from({length: 5000}, () => `${hex(8)}-${hex(4)}-${hex(4)}-${hex(4)}-${hex(12)}`);
const fixed = ["s1", "s2", "name:Stage", "Operator", "", "名前"];
console.log(JSON.stringify({
size: F.SIZE,
counts: F.COUNTS,
combinations: F.COMBINATIONS,
picks: ids.map((id) => F.of(id).pick),
again: ids.slice(0, 200).every((id) => JSON.stringify(F.of(id)) === JSON.stringify(F.of(id))),
fixed: fixed.map((id) => F.of(id)),
parts: ids.slice(0, 400).map((id) => F.of(id).parts),
}));
"""
ATTRIBUTES = ["shape", "colour", "eyes", "brows", "mouth", "hair", "accessory"]
TAGS = {"ellipse", "rect", "path"}
COLOUR = re.compile(r"^#[0-9a-f]{3}(?:[0-9a-f]{3})?$")
def probe() -> dict:
done = subprocess.run(["node", "-e", FACES_PROBE, str(FACES)], capture_output=True, text=True, timeout=60, check=True)
return json.loads(done.stdout)
class TestFacesModule(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.data = probe()
cls.rerun = probe()
def test_seven_independent_attributes_multiply_to_at_least_1000_combinations(self):
counts = self.data["counts"]
self.assertEqual(list(counts), ATTRIBUTES)
self.assertTrue(all(count >= 2 for count in counts.values()), counts)
self.assertEqual(self.data["combinations"], math.prod(counts.values()))
self.assertGreaterEqual(self.data["combinations"], 1000)
def test_many_ids_reach_at_least_1000_distinct_faces(self):
distinct = {tuple(pick[name] for name in ATTRIBUTES) for pick in self.data["picks"]}
self.assertGreaterEqual(len(distinct), 1000)
self.assertGreater(len(distinct), len(self.data["picks"]) * 0.9)
def test_the_same_id_gets_the_same_face_in_every_call_and_every_run(self):
self.assertTrue(self.data["again"])
self.assertEqual(self.data["fixed"], self.rerun["fixed"])
self.assertEqual(self.data["picks"], self.rerun["picks"])
self.assertEqual(len({json.dumps(face["pick"]) for face in self.data["fixed"]}), len(self.data["fixed"]))
def test_different_ids_spread_evenly_over_every_variant_of_every_attribute(self):
total = len(self.data["picks"])
for name, count in self.data["counts"].items():
seen = Counter(pick[name] for pick in self.data["picks"])
with self.subTest(attribute=name):
self.assertEqual(sorted(seen), list(range(count)))
for variant, hits in seen.items():
self.assertTrue(0.6 < hits * count / total < 1.4, (variant, hits))
def test_the_svg_description_has_a_coloured_head_one_mouth_and_no_missing_values(self):
for parts in [*self.data["parts"], *(face["parts"] for face in self.data["fixed"])]:
with self.subTest(parts=parts[0]):
self.assertEqual({tag for tag, _ in parts} - TAGS, set())
self.assertRegex(parts[0][1]["fill"], COLOUR)
self.assertEqual([attrs.get("class") for _, attrs in parts].count("mouth"), 1)
for _, attrs in parts:
for key, value in attrs.items():
if isinstance(value, str):
self.assertTrue(value and "undefined" not in value and "NaN" not in value, (key, value))
else:
self.assertIsInstance(value, (int, float), (key, value))
self.assertTrue(math.isfinite(value) and -1 <= value <= self.data["size"] + 1, (key, value))
def test_faces_draw_no_circle_because_graph_node_styles_target_every_circle_in_a_node(self):
tags = {tag for parts in self.data["parts"] for tag, _ in parts}
self.assertNotIn("circle", tags)
if __name__ == "__main__":
unittest.main()