From d4859b33c6a671850568fbaf6cf9c7ba884b6813 Mon Sep 17 00:00:00 2001 From: Kartios Date: Tue, 11 Aug 2026 22:38:56 -0700 Subject: [PATCH] feat: add ephemeral webcam faces and adaptive screens --- BUILD_PLAN.md | 18 ++- deploy/STATIC.md | 31 ++++- index.html | 1 + src/engine/scene.ts | 6 + src/main.ts | 127 ++++++++++++++++++- src/media/index.ts | 4 + src/media/remoteMedia.ts | 131 ++++++++++++++++++- src/profile/editor.ts | 12 +- src/profile/index.ts | 16 +++ src/profile/webcamCapture.ts | 116 +++++++++++++++++ src/profile/webcamPanel.ts | 190 ++++++++++++++++++++++++++++ src/test/actorOfficeHandoff.test.ts | 36 ++++++ src/test/mediaPrivacyPolicy.test.ts | 33 +++++ src/test/profileEditor.test.ts | 22 ++++ src/test/remoteMedia.test.ts | 136 +++++++++++++++++++- src/test/webcamCapture.test.ts | 94 ++++++++++++++ src/test/webcamPanel.test.ts | 145 +++++++++++++++++++++ 17 files changed, 1107 insertions(+), 11 deletions(-) create mode 100644 src/profile/webcamCapture.ts create mode 100644 src/profile/webcamPanel.ts create mode 100644 src/test/mediaPrivacyPolicy.test.ts create mode 100644 src/test/webcamCapture.test.ts create mode 100644 src/test/webcamPanel.test.ts diff --git a/BUILD_PLAN.md b/BUILD_PLAN.md index 5f97ba5..7052f36 100644 --- a/BUILD_PLAN.md +++ b/BUILD_PLAN.md @@ -120,6 +120,12 @@ rejected; selected cell concurrency passes a repeatable load test. ## M5 — profile and webcam faces +Status: **implemented and integrated**. Signed-in members can start an optional +camera face only from the Character dialog; a persistent live indicator and +one-click Stop remain visible while active. Capture is tab-ephemeral, audio-free, +never recorded or uploaded, follows the humanoid across scene transitions, and +is torn down on Stop, track end, revocation, or page exit. + - Generated profile face is the default. - Webcam face is opt-in and ephemeral: explicit contextual permission, active indicator, one-click stop, no recording/storage by default. @@ -131,9 +137,17 @@ cannot subscribe; the full product works without a camera. ## M6 — office media surfaces +Status: **implemented and integrated for peer-to-peer hosted sharing**. Authored +Three.js monitors remain dark until an authorized viewer explicitly opts in; +presenters choose a tab/window through the browser prompt and have an immediate +kill switch. Signaling grants, TURN grants, video textures, late join, reconnect, +and revoke lifecycle are bounded and tested. Presenter encoding defaults to +1280×720 at 15 fps and 1.5 Mbps with capability-safe browser fallback. + - `MediaSurface` records identify office/room/screen, ACL, source, and state. -- WebRTC SFU track after authorization and explicit viewer opt-in; Three.js gets - a video texture only while subscribed. +- WebRTC peer track after authorization and explicit viewer opt-in; Three.js + gets a video texture only while subscribed. An SFU is a later scale-out + option, not a core requirement for the bounded eight-participant room. - Share tab/window by default, muted viewer autoplay, visible broadcast state, presenter kill switch, late join and reconnect. diff --git a/deploy/STATIC.md b/deploy/STATIC.md index 3f4be9b..c429217 100644 --- a/deploy/STATIC.md +++ b/deploy/STATIC.md @@ -32,9 +32,10 @@ tera.example.com { file_server header { Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; object-src 'none'" - # Screen sharing remains a per-action browser prompt. Webcam capture stays - # disabled until that separate feature is deliberately deployed. - Permissions-Policy "display-capture=(self), camera=(), microphone=(), geolocation=(), payment=(), usb=()" + # Screen sharing and webcam faces remain contextual, per-action browser + # prompts. Same-origin permission enables those prompts; it does not grant + # capture. Microphone capture is deliberately unavailable. + Permissions-Policy "display-capture=(self), camera=(self), microphone=(), geolocation=(), payment=(), usb=()" } } ``` @@ -48,6 +49,30 @@ is drawn on a canvas at runtime rather than shipped as a file — see ARCHITECTURE.md §3.1 for why that is a licensing decision and not a technical one. +### Capture-policy acceptance check + +Webcam faces require a secure context and `camera=(self)` on both the Tera and +Office hostnames. That policy only permits the application to ask: capture must +still begin inside the explicit face or screen action, display a persistent +active indicator, and stop on the in-product control or `pagehide`. Keep +`microphone=()` because neither webcam faces nor office screens request audio. + +After changing the live Caddy site, reload Caddy through the normal reviewed +deployment procedure and verify both hostnames before enabling the UI: + +```bash +curl -fsSI https://tera.example.com/ | grep -i '^permissions-policy:' +curl -fsSI https://office.example.com/ | grep -i '^permissions-policy:' +``` + +Both responses must contain `display-capture=(self)`, `camera=(self)`, and +`microphone=()`. In browser developer tools, +`document.permissionsPolicy?.allowsFeature("camera")` should be true on the +top-level same-origin page; denying the browser prompt must leave the generated +face and every office screen placeholder intact. The CSP needs no camera/media +host exception: webcam and display tracks are caller-owned `MediaStream`s, not +network media URLs, and signaling remains under same-origin `connect-src`. + ## Serving the API too Only needed for live weather, real ADS-B, or markers from an external source. diff --git a/index.html b/index.html index 78d30b4..9521827 100644 --- a/index.html +++ b/index.html @@ -988,6 +988,7 @@ +