Merge remote-tracking branch 'origin/main' into codex/toolkit-release-20261012

This commit is contained in:
ruv 2026-10-12 11:49:52 -04:00
commit 2c1222789d
36 changed files with 1229 additions and 3 deletions

View file

@ -206,6 +206,9 @@ jobs:
- name: Run UI unit tests
run: node --test ui/sw.test.mjs ui/services/ws-ticket.test.mjs ui/services/websocket.service.test.mjs ui/services/sensing.service.test.mjs ui/components/DashboardTab.test.mjs v2/crates/wifi-densepose-desktop/ui/build-config.test.mjs
- name: Run virtual-view adapter boundary tests
run: node --test integrations/virtual-view/adapter.test.mjs
# Unit and Integration Tests
# Python pytest matrix — runs against the archived v1 Python tree.
# `continue-on-error: true` for the same reason as code-quality above:

44
.github/workflows/readme-visuals.yml vendored Normal file
View file

@ -0,0 +1,44 @@
name: README visual asset validation
on:
pull_request:
paths:
- 'README.md'
- 'assets/readme/**'
- 'docs/readme-visual-system.md'
- 'scripts/*readme*'
- 'scripts/tests/test_readme_visuals_xml.py'
- '.github/workflows/readme-visuals.yml'
push:
branches: [main]
paths:
- 'README.md'
- 'assets/readme/**'
- 'docs/readme-visual-system.md'
- 'scripts/*readme*'
- 'scripts/tests/test_readme_visuals_xml.py'
- '.github/workflows/readme-visuals.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
verify:
name: Safe and reproducible README visuals
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
with:
python-version: '3.11'
- name: Install visual tooling dependency
run: python -m pip install -r scripts/requirements-readme-visuals.txt
- name: Test XML input boundaries
run: python -m unittest discover -s scripts/tests -p test_readme_visuals_xml.py
- name: Verify reproducible and accessible assets
run: |
python scripts/render-readme-visuals.py
python scripts/verify-readme-visuals.py
git diff --exit-code -- assets/readme
test -z "$(git ls-files --others --exclude-standard -- assets/readme)"

View file

@ -1,11 +1,31 @@
# π RuView
[![RuView: spaces become signals, with an animated conceptual RF field](assets/readme/hero.svg)](docs/user-guide.md)
<table>
<tr>
<td align="center" width="33%"><a href="docs/user-guide.md"><img src="assets/readme/nav-start.svg" width="180" alt="Start here" /></a><br/><a href="docs/user-guide.md">Start here</a></td>
<td align="center" width="33%"><a href="firmware/esp32-csi-node/README.md"><img src="assets/readme/nav-hardware.svg" width="180" alt="Hardware" /></a><br/><a href="firmware/esp32-csi-node/README.md">Hardware</a></td>
<td align="center" width="33%"><a href="https://huggingface.co/ruvnet/wifi-densepose-pretrained"><img src="assets/readme/nav-models.svg" width="180" alt="Models" /></a><br/><a href="https://huggingface.co/ruvnet/wifi-densepose-pretrained">Models</a></td>
</tr>
<tr>
<td align="center" width="33%"><a href="harness/ruview/README.md"><img src="assets/readme/nav-agents.svg" width="180" alt="AI toolkit" /></a><br/><a href="harness/ruview/README.md">AI toolkit</a></td>
<td align="center" width="33%"><a href="docs/integrations/home-assistant.md"><img src="assets/readme/nav-home.svg" width="180" alt="Smart home" /></a><br/><a href="docs/integrations/home-assistant.md">Smart home</a></td>
<td align="center" width="33%"><a href="https://cognitum.one/seed"><img src="assets/readme/nav-seed.svg" width="180" alt="Cognitum Seed" /></a><br/><a href="https://cognitum.one/seed">Cognitum Seed</a></td>
</tr>
</table>
<details>
<summary>Watch the original RuView sensing demo</summary>
<p align="center">
<a href="https://cognitum.one/seed">
<img src="assets/ruview-hero-h3-v3.gif" alt="RuView - WiFi DensePose — animated visualization of real-time pose estimation, breathing, and heart-rate sensing through WiFi" width="100%">
</a>
</p>
</details>
<p align="center">
<a href="https://ruos.cognitum.one">
<img src="assets/ruos-animated.svg" alt="RuView — WiFi becomes spatial awareness, with Ruflo coordination and a ruOS sensing workspace" width="100%">
@ -27,6 +47,8 @@ Works natively with the four major smart-home ecosystems: **[Home Assistant](doc
Every WiFi router already fills your space with radio waves. When people move, breathe, or even sit still, they disturb those waves in measurable ways. RuView captures these disturbances using Channel State Information (CSI) from low-cost ESP32 sensors and turns them into actionable data: who's there, what they're doing, and whether they're okay.
[![Explore RuView sensing, models and automation](assets/readme/header-features.svg)](docs/user-guide.md)
**What it senses:**
- **Presence and occupancy** — detect people through walls, count them, track entries and exits
- **Vital signs** — breathing rate and heart rate, contactless, while sleeping or sitting
@ -43,6 +65,22 @@ Every WiFi router already fills your space with radio waves. When people move, b
- **Governed evidence** — attach privacy policy, uncertainty, provenance, and witness records to sensing events
- **RuView MetaHarness** — use an AI operator to onboard, calibrate, train, verify, and check sensing claims
<!-- ruview-capability-cards -->
<table>
<tr>
<td width="50%"><a href="docs/user-guide.md"><img src="assets/readme/feature-presence.svg" width="580" alt="Presence and motion: open documentation and current limitations" /></a><br/><a href="docs/user-guide.md">Presence and motion</a></td>
<td width="50%"><a href="v2/crates/wifi-densepose-signal/README.md"><img src="assets/readme/feature-vitals.svg" width="580" alt="Breathing and vital signals: open documentation and current limitations" /></a><br/><a href="v2/crates/wifi-densepose-signal/README.md">Breathing and vital signals</a></td>
</tr>
<tr>
<td width="50%"><a href="docs/benchmarks/pose-estimation-cog.md"><img src="assets/readme/feature-pose.svg" width="580" alt="Pose research and limitations: open documentation and current limitations" /></a><br/><a href="docs/benchmarks/pose-estimation-cog.md">Pose research and limitations</a></td>
<td width="50%"><a href="firmware/esp32-csi-node/README.md"><img src="assets/readme/feature-edge.svg" width="580" alt="Edge sensor mesh: open documentation and current limitations" /></a><br/><a href="firmware/esp32-csi-node/README.md">Edge sensor mesh</a></td>
</tr>
<tr>
<td width="50%"><a href="docs/integrations/home-assistant.md"><img src="assets/readme/feature-automation.svg" width="580" alt="Local automation: open documentation and current limitations" /></a><br/><a href="docs/integrations/home-assistant.md">Local automation</a></td>
<td width="50%"><a href="harness/ruview/README.md"><img src="assets/readme/feature-agents.svg" width="580" alt="Guided AI operator: open documentation and current limitations" /></a><br/><a href="harness/ruview/README.md">Guided AI operator</a></td>
</tr>
</table>
<details>
<summary><strong>RuView MetaHarness</strong> — guided operation for humans and AI agents</summary>
@ -263,6 +301,8 @@ The separate **17-keypoint pose-estimation model** is now published at [`ruvnet/
### Results & proof
[![RuView visual guide: header-evidence](assets/readme/header-evidence.svg)](docs/benchmarks/pose-estimation-cog.md)
See the measured benchmarks, witness records, and one-command reproducibility check.
<details>
@ -317,6 +357,8 @@ number. The path to a first *reproducible* on-device baseline (PCK@20 ≥ 35%) i
## 🧩 Edge Module Catalog
[![RuView visual guide: header-edge](assets/readme/header-edge.svg)](firmware/esp32-csi-node/README.md)
Add signed modules for health, security, buildings, industry, research, AI, and more.
<details>
@ -491,6 +533,8 @@ Browse and install modules at [seed.cognitum.one/store](https://seed.cognitum.on
## 🔬 How It Works
[![RuView visual guide: overview](assets/readme/overview.svg)](docs/user-guide.md)
WiFi routers flood every room with radio waves. When a person moves — or even breathes — those waves scatter differently. WiFi DensePose reads that scattering pattern and reconstructs what happened:
```
@ -615,6 +659,8 @@ These scenarios exploit WiFi's ability to penetrate solid materials — concrete
## 🧠 Self-Learning WiFi AI
[![RuView visual guide: header-learning](assets/readme/header-learning.svg)](docs/user-guide.md)
Learn compact room fingerprints from raw CSI and adapt the model to each environment.
<details>
@ -744,6 +790,8 @@ For the portable RuView MetaHarness, use `npx @ruvnet/ruview@0.9.1`. The quick c
## 📖 Documentation
[![RuView visual guide: header-docs](assets/readme/header-docs.svg)](docs/user-guide.md)
Start with the user, build, and calibration guides; expand for the full reference map.
<details>
@ -785,6 +833,8 @@ Start with the user, build, and calibration guides; expand for the full referenc
>
> Contributions and bug reports welcome at [Issues](https://github.com/ruvnet/RuView/issues).
[![Cognitum One: Ambient Intelligence at the edge of the physical world](assets/readme/cognitum.svg)](https://cognitum.one)
## 📄 License
MIT License — see [LICENSE](LICENSE) for details.

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="304" viewBox="0 0 1200 304" role="img" aria-labelledby="title desc"><title id="title">Explore Cognitum One</title><desc id="desc">Ambient Intelligence at the edge of the physical world. Open cognitum.one.</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="1199" height="303" rx="18" fill="url(#wash)" stroke="#245360"/><text x="44" y="42" font-size="12" fill="#72eee0" text-anchor="start" class="mono">COGNITUM ONE</text><text x="44" y="99" font-size="42" fill="#ecfafb" text-anchor="start" >Ambient Intelligence.</text><text x="44" y="147" font-size="25" fill="#72eee0" text-anchor="start" >At the edge of the physical world.</text><text x="44" y="190" font-size="16" fill="#9abac5" text-anchor="start" >Explore the platform behind the ecosystem.</text><rect x="44" y="224" width="256" height="46" rx="11" fill="url(#accent)"/><text x="65" y="254" font-size="18" fill="#041117" text-anchor="start" >Explore Cognitum</text><path d="M263 247H284M277 240L284 247L277 254" fill="none" stroke="#041117" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /><circle cx="947" cy="151" r="173" fill="url(#halo)"/><g transform="translate(945 150) scale(2.4)"><path d="M0 -38L31 -20V18L0 38L-31 18V-20ZM-31 -20L0 -1L31 -20M0 -1V38" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /><path d="M-15 1L-5 11L17 -12" fill="none" stroke="#70b6ff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="trace"/></g><ellipse cx="945" cy="150" rx="140" ry="30" transform="rotate(-25 945 150)" fill="none" stroke="#3c8897" stroke-dasharray="5 18" class="flow"/><ellipse cx="945" cy="150" rx="160" ry="43" transform="rotate(-25 945 150)" fill="none" stroke="#3c8897" stroke-dasharray="5 18" class="flow"/><ellipse cx="945" cy="150" rx="180" ry="56" transform="rotate(-25 945 150)" fill="none" stroke="#3c8897" stroke-dasharray="5 18" class="flow"/></svg>

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="580" height="276" viewBox="0 0 580 276" role="img" aria-labelledby="title desc"><title id="title">A guided AI operator</title><desc id="desc">Source-cited setup and verification. CLI, MCP and agent workflows. Conceptual animation; follow the linked source for capability limits.</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="579" height="275" rx="18" fill="url(#wash)" stroke="#245360"/><text x="30" y="39" font-size="11" fill="#72eee0" text-anchor="start" class="mono">RUVIEW / 06</text><text x="30" y="94" font-size="29" fill="#ecfafb" text-anchor="start" >A guided AI operator</text><text x="30" y="136" font-size="16" fill="#9abac5" text-anchor="start" >Source-cited setup and verification.</text><text x="30" y="164" font-size="16" fill="#9abac5" text-anchor="start" >CLI, MCP and agent workflows.</text><circle cx="465" cy="170" r="110" fill="url(#halo)"/><g transform="translate(474 168) scale(0.83)"><path d="M0 0L0 -33" fill="none" stroke="#70b6ff" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow"/><circle cx="0" cy="-33" r="7" stroke="#72eee0" fill="none" class="breathe"/><path d="M0 0L-35 23" fill="none" stroke="#70b6ff" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow"/><circle cx="-35" cy="23" r="7" stroke="#72eee0" fill="none" class="breathe"/><path d="M0 0L35 23" fill="none" stroke="#70b6ff" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow"/><circle cx="35" cy="23" r="7" stroke="#72eee0" fill="none" class="breathe"/><circle cx="0" cy="0" r="15" stroke="#72eee0" fill="none" /><path d="M-6 0L-1 5L8 -6" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></g><path d="M30 204H550" fill="none" stroke="#245360" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" /><path d="M30 204H550" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity=".5"/><text x="30" y="245" font-size="12" fill="#72eee0" text-anchor="start" class="mono">TOOLKIT GUIDE</text><path d="M510 239H540M532 231L540 239L532 247" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="580" height="276" viewBox="0 0 580 276" role="img" aria-labelledby="title desc"><title id="title">Local automation</title><desc id="desc">Connect rooms to your smart home. Home Assistant, MQTT and Matter. Conceptual animation; follow the linked source for capability limits.</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="579" height="275" rx="18" fill="url(#wash)" stroke="#245360"/><text x="30" y="39" font-size="11" fill="#72eee0" text-anchor="start" class="mono">RUVIEW / 05</text><text x="30" y="94" font-size="29" fill="#ecfafb" text-anchor="start" >Local automation</text><text x="30" y="136" font-size="16" fill="#9abac5" text-anchor="start" >Connect rooms to your smart home.</text><text x="30" y="164" font-size="16" fill="#9abac5" text-anchor="start" >Home Assistant, MQTT and Matter.</text><circle cx="465" cy="170" r="110" fill="url(#halo)"/><g transform="translate(474 168) scale(0.83)"><path d="M-37 -3L0 -34L37 -3M-28 -10V32H28V-10M-9 32V8H9V32" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /><circle cx="0" cy="-9" r="5" stroke="#70b6ff" fill="none" class="breathe"/></g><path d="M30 204H550" fill="none" stroke="#245360" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" /><path d="M30 204H550" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity=".5"/><text x="30" y="245" font-size="12" fill="#72eee0" text-anchor="start" class="mono">INTEGRATION GUIDE</text><path d="M510 239H540M532 231L540 239L532 247" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="580" height="276" viewBox="0 0 580 276" role="img" aria-labelledby="title desc"><title id="title">Edge sensor mesh</title><desc id="desc">Provision, capture and calibrate. Explore ESP32 firmware and runbooks. Conceptual animation; follow the linked source for capability limits.</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="579" height="275" rx="18" fill="url(#wash)" stroke="#245360"/><text x="30" y="39" font-size="11" fill="#72eee0" text-anchor="start" class="mono">RUVIEW / 04</text><text x="30" y="94" font-size="29" fill="#ecfafb" text-anchor="start" >Edge sensor mesh</text><text x="30" y="136" font-size="16" fill="#9abac5" text-anchor="start" >Provision, capture and calibrate.</text><text x="30" y="164" font-size="16" fill="#9abac5" text-anchor="start" >Explore ESP32 firmware and runbooks.</text><circle cx="465" cy="170" r="110" fill="url(#halo)"/><g transform="translate(474 168) scale(0.83)"><rect x="-25" y="-25" width="50" height="50" rx="7" fill="#0a2630" stroke="#72eee0"/><rect x="-13" y="-13" width="26" height="26" rx="3" fill="none" stroke="#70b6ff"/><path d="M-16 -36V-25M-16 25V36M-36 -16H-25M25 -16H36" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /><path d="M0 -36V-25M0 25V36M-36 0H-25M25 0H36" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /><path d="M16 -36V-25M16 25V36M-36 16H-25M25 16H36" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /><circle cx="0" cy="0" r="4" stroke="#72eee0" fill="#72eee0" class="breathe"/></g><path d="M30 204H550" fill="none" stroke="#245360" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" /><path d="M30 204H550" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity=".5"/><text x="30" y="245" font-size="12" fill="#72eee0" text-anchor="start" class="mono">HARDWARE GUIDE</text><path d="M510 239H540M532 231L540 239L532 247" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="580" height="276" viewBox="0 0 580 276" role="img" aria-labelledby="title desc"><title id="title">Pose research</title><desc id="desc">Train and compare body keypoints. Live accuracy differs from benchmarks. Conceptual animation; follow the linked source for capability limits.</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="579" height="275" rx="18" fill="url(#wash)" stroke="#245360"/><text x="30" y="39" font-size="11" fill="#72eee0" text-anchor="start" class="mono">RUVIEW / 03</text><text x="30" y="94" font-size="29" fill="#ecfafb" text-anchor="start" >Pose research</text><text x="30" y="136" font-size="16" fill="#9abac5" text-anchor="start" >Train and compare body keypoints.</text><text x="30" y="164" font-size="16" fill="#9abac5" text-anchor="start" >Live accuracy differs from benchmarks.</text><circle cx="465" cy="170" r="110" fill="url(#halo)"/><g transform="translate(474 168) scale(0.83)"><circle cx="0" cy="-28" r="8" stroke="#72eee0" fill="none" /><path d="M0 -20V12M-24 -6L0 -14L24 -6M0 12L-18 38M0 12L18 38" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /><circle cx="0" cy="-14" r="3" stroke="#70b6ff" fill="#041117" class="breathe"/><circle cx="0" cy="12" r="3" stroke="#70b6ff" fill="#041117" class="breathe"/><circle cx="-24" cy="-6" r="3" stroke="#70b6ff" fill="#041117" class="breathe"/><circle cx="24" cy="-6" r="3" stroke="#70b6ff" fill="#041117" class="breathe"/><circle cx="-18" cy="38" r="3" stroke="#70b6ff" fill="#041117" class="breathe"/><circle cx="18" cy="38" r="3" stroke="#70b6ff" fill="#041117" class="breathe"/></g><path d="M30 204H550" fill="none" stroke="#245360" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" /><path d="M30 204H550" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity=".5"/><text x="30" y="245" font-size="12" fill="#72eee0" text-anchor="start" class="mono">READ THE EVIDENCE</text><path d="M510 239H540M532 231L540 239L532 247" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="580" height="276" viewBox="0 0 580 276" role="img" aria-labelledby="title desc"><title id="title">Presence &amp; motion</title><desc id="desc">Explore changes in room occupancy. Inspect motion features from CSI. Conceptual animation; follow the linked source for capability limits.</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="579" height="275" rx="18" fill="url(#wash)" stroke="#245360"/><text x="30" y="39" font-size="11" fill="#72eee0" text-anchor="start" class="mono">RUVIEW / 01</text><text x="30" y="94" font-size="29" fill="#ecfafb" text-anchor="start" >Presence &amp; motion</text><text x="30" y="136" font-size="16" fill="#9abac5" text-anchor="start" >Explore changes in room occupancy.</text><text x="30" y="164" font-size="16" fill="#9abac5" text-anchor="start" >Inspect motion features from CSI.</text><circle cx="465" cy="170" r="110" fill="url(#halo)"/><g transform="translate(474 168) scale(0.83)"><circle cx="0" cy="-28" r="8" stroke="#72eee0" fill="none" /><path d="M0 -20V12M-24 -6L0 -14L24 -6M0 12L-18 38M0 12L18 38" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /><ellipse cx="0" cy="14" rx="40" ry="13" fill="none" stroke="#70b6ff" stroke-dasharray="3 8" class="flow"/></g><path d="M30 204H550" fill="none" stroke="#245360" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" /><path d="M30 204H550" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity=".5"/><text x="30" y="245" font-size="12" fill="#72eee0" text-anchor="start" class="mono">SENSING GUIDE</text><path d="M510 239H540M532 231L540 239L532 247" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="580" height="276" viewBox="0 0 580 276" role="img" aria-labelledby="title desc"><title id="title">Breathing &amp; vital signals</title><desc id="desc">Inspect phase-derived signal trends. Validate estimates on your hardware. Conceptual animation; follow the linked source for capability limits.</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="579" height="275" rx="18" fill="url(#wash)" stroke="#245360"/><text x="30" y="39" font-size="11" fill="#72eee0" text-anchor="start" class="mono">RUVIEW / 02</text><text x="30" y="94" font-size="29" fill="#ecfafb" text-anchor="start" >Breathing &amp; vital signals</text><text x="30" y="136" font-size="16" fill="#9abac5" text-anchor="start" >Inspect phase-derived signal trends.</text><text x="30" y="164" font-size="16" fill="#9abac5" text-anchor="start" >Validate estimates on your hardware.</text><circle cx="465" cy="170" r="110" fill="url(#halo)"/><g transform="translate(474 168) scale(0.83)"><path d="M-42 6H-27L-18 -10L-9 25L3 -25L14 10L24 -3L31 6H43" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="trace"/><path d="M-42 31H43" fill="none" stroke="#214451" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" /></g><path d="M30 204H550" fill="none" stroke="#245360" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" /><path d="M30 204H550" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity=".5"/><text x="30" y="245" font-size="12" fill="#72eee0" text-anchor="start" class="mono">SIGNAL PIPELINE</text><path d="M510 239H540M532 231L540 239L532 247" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="148" viewBox="0 0 1200 148" role="img" aria-labelledby="title desc"><title id="title">Choose your next step</title><desc id="desc">Animated section heading. GUIDES / HARDWARE / INTEGRATIONS</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="1199" height="147" rx="18" fill="url(#wash)" stroke="#245360"/><g transform="translate(76 75) scale(0.75)"><path d="M0 0L0 -33" fill="none" stroke="#70b6ff" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow"/><circle cx="0" cy="-33" r="7" stroke="#72eee0" fill="none" class="breathe"/><path d="M0 0L-35 23" fill="none" stroke="#70b6ff" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow"/><circle cx="-35" cy="23" r="7" stroke="#72eee0" fill="none" class="breathe"/><path d="M0 0L35 23" fill="none" stroke="#70b6ff" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow"/><circle cx="35" cy="23" r="7" stroke="#72eee0" fill="none" class="breathe"/><circle cx="0" cy="0" r="15" stroke="#72eee0" fill="none" /><path d="M-6 0L-1 5L8 -6" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></g><text x="140" y="63" font-size="34" fill="#ecfafb" text-anchor="start" >Choose your next step</text><text x="142" y="99" font-size="12" fill="#72eee0" text-anchor="start" class="mono">GUIDES / HARDWARE / INTEGRATIONS</text><path d="M865 44Q960 10 1124 52" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.65"/><path d="M865 61Q960 38 1124 69" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.55"/><path d="M865 78Q960 66 1124 86" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.45"/><path d="M865 95Q960 94 1124 103" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.35"/><path d="M1140 70H1160M1152 62L1160 70L1152 78" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="148" viewBox="0 0 1200 148" role="img" aria-labelledby="title desc"><title id="title">Intelligence at the edge</title><desc id="desc">Animated section heading. SENSORS / FIRMWARE / MODULES</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="1199" height="147" rx="18" fill="url(#wash)" stroke="#245360"/><g transform="translate(76 75) scale(0.75)"><rect x="-25" y="-25" width="50" height="50" rx="7" fill="#0a2630" stroke="#72eee0"/><rect x="-13" y="-13" width="26" height="26" rx="3" fill="none" stroke="#70b6ff"/><path d="M-16 -36V-25M-16 25V36M-36 -16H-25M25 -16H36" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /><path d="M0 -36V-25M0 25V36M-36 0H-25M25 0H36" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /><path d="M16 -36V-25M16 25V36M-36 16H-25M25 16H36" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /><circle cx="0" cy="0" r="4" stroke="#72eee0" fill="#72eee0" class="breathe"/></g><text x="140" y="63" font-size="34" fill="#ecfafb" text-anchor="start" >Intelligence at the edge</text><text x="142" y="99" font-size="12" fill="#72eee0" text-anchor="start" class="mono">SENSORS / FIRMWARE / MODULES</text><path d="M865 44Q960 10 1124 52" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.65"/><path d="M865 61Q960 38 1124 69" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.55"/><path d="M865 78Q960 66 1124 86" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.45"/><path d="M865 95Q960 94 1124 103" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.35"/><path d="M1140 70H1160M1152 62L1160 70L1152 78" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="148" viewBox="0 0 1200 148" role="img" aria-labelledby="title desc"><title id="title">Evidence before accuracy claims</title><desc id="desc">Animated section heading. BENCHMARKS / REPRODUCERS / LIMITS</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="1199" height="147" rx="18" fill="url(#wash)" stroke="#245360"/><g transform="translate(76 75) scale(0.75)"><path d="M0 -38L31 -20V18L0 38L-31 18V-20ZM-31 -20L0 -1L31 -20M0 -1V38" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /><path d="M-15 1L-5 11L17 -12" fill="none" stroke="#70b6ff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="trace"/></g><text x="140" y="63" font-size="34" fill="#ecfafb" text-anchor="start" >Evidence before accuracy claims</text><text x="142" y="99" font-size="12" fill="#72eee0" text-anchor="start" class="mono">BENCHMARKS / REPRODUCERS / LIMITS</text><path d="M865 44Q960 10 1124 52" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.65"/><path d="M865 61Q960 38 1124 69" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.55"/><path d="M865 78Q960 66 1124 86" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.45"/><path d="M865 95Q960 94 1124 103" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.35"/><path d="M1140 70H1160M1152 62L1160 70L1152 78" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="148" viewBox="0 0 1200 148" role="img" aria-labelledby="title desc"><title id="title">Explore the capabilities</title><desc id="desc">Animated section heading. SENSING / MODELS / AUTOMATION</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="1199" height="147" rx="18" fill="url(#wash)" stroke="#245360"/><g transform="translate(76 75) scale(0.75)"><circle cx="0" cy="-28" r="8" stroke="#72eee0" fill="none" /><path d="M0 -20V12M-24 -6L0 -14L24 -6M0 12L-18 38M0 12L18 38" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /><ellipse cx="0" cy="14" rx="40" ry="13" fill="none" stroke="#70b6ff" stroke-dasharray="3 8" class="flow"/></g><text x="140" y="63" font-size="34" fill="#ecfafb" text-anchor="start" >Explore the capabilities</text><text x="142" y="99" font-size="12" fill="#72eee0" text-anchor="start" class="mono">SENSING / MODELS / AUTOMATION</text><path d="M865 44Q960 10 1124 52" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.65"/><path d="M865 61Q960 38 1124 69" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.55"/><path d="M865 78Q960 66 1124 86" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.45"/><path d="M865 95Q960 94 1124 103" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.35"/><path d="M1140 70H1160M1152 62L1160 70L1152 78" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="148" viewBox="0 0 1200 148" role="img" aria-labelledby="title desc"><title id="title">From recordings to models</title><desc id="desc">Animated section heading. CAPTURE / TRAIN / EVALUATE</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="1199" height="147" rx="18" fill="url(#wash)" stroke="#245360"/><g transform="translate(76 75) scale(0.75)"><path d="M-42 6H-27L-18 -10L-9 25L3 -25L14 10L24 -3L31 6H43" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="trace"/><path d="M-42 31H43" fill="none" stroke="#214451" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" /></g><text x="140" y="63" font-size="34" fill="#ecfafb" text-anchor="start" >From recordings to models</text><text x="142" y="99" font-size="12" fill="#72eee0" text-anchor="start" class="mono">CAPTURE / TRAIN / EVALUATE</text><path d="M865 44Q960 10 1124 52" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.65"/><path d="M865 61Q960 38 1124 69" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.55"/><path d="M865 78Q960 66 1124 86" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.45"/><path d="M865 95Q960 94 1124 103" fill="none" stroke="#72eee0" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow" opacity="0.35"/><path d="M1140 70H1160M1152 62L1160 70L1152 78" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

1
assets/readme/hero.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.8 KiB

147
assets/readme/manifest.json Normal file
View file

@ -0,0 +1,147 @@
{
"schema_version": 1,
"style": "Cognitum cyan on midnight",
"motion": "Decorative CSS; prefers-reduced-motion supported; no script or remote dependencies",
"assets": [
{
"file": "assets/readme/hero.svg",
"title": "RuView: spaces become signals",
"target": "docs/user-guide.md",
"width": 1200,
"height": 450
},
{
"file": "assets/readme/nav-start.svg",
"title": "Start here",
"target": "docs/user-guide.md",
"width": 180,
"height": 116
},
{
"file": "assets/readme/nav-hardware.svg",
"title": "Hardware",
"target": "firmware/esp32-csi-node/README.md",
"width": 180,
"height": 116
},
{
"file": "assets/readme/nav-models.svg",
"title": "Models",
"target": "https://huggingface.co/ruvnet/wifi-densepose-pretrained",
"width": 180,
"height": 116
},
{
"file": "assets/readme/nav-agents.svg",
"title": "AI toolkit",
"target": "harness/ruview/README.md",
"width": 180,
"height": 116
},
{
"file": "assets/readme/nav-home.svg",
"title": "Smart home",
"target": "docs/integrations/home-assistant.md",
"width": 180,
"height": 116
},
{
"file": "assets/readme/nav-seed.svg",
"title": "Cognitum",
"target": "https://cognitum.one/seed",
"width": 180,
"height": 116
},
{
"file": "assets/readme/feature-presence.svg",
"title": "Presence & motion",
"target": "docs/user-guide.md",
"width": 580,
"height": 276
},
{
"file": "assets/readme/feature-vitals.svg",
"title": "Breathing & vital signals",
"target": "v2/crates/wifi-densepose-signal/README.md",
"width": 580,
"height": 276
},
{
"file": "assets/readme/feature-pose.svg",
"title": "Pose research",
"target": "docs/benchmarks/pose-estimation-cog.md",
"width": 580,
"height": 276
},
{
"file": "assets/readme/feature-edge.svg",
"title": "Edge sensor mesh",
"target": "firmware/esp32-csi-node/README.md",
"width": 580,
"height": 276
},
{
"file": "assets/readme/feature-automation.svg",
"title": "Local automation",
"target": "docs/integrations/home-assistant.md",
"width": 580,
"height": 276
},
{
"file": "assets/readme/feature-agents.svg",
"title": "A guided AI operator",
"target": "harness/ruview/README.md",
"width": 580,
"height": 276
},
{
"file": "assets/readme/overview.svg",
"title": "RuView sensing overview",
"target": "docs/user-guide.md",
"width": 1200,
"height": 396
},
{
"file": "assets/readme/header-features.svg",
"title": "Explore the capabilities",
"target": "docs/user-guide.md",
"width": 1200,
"height": 148
},
{
"file": "assets/readme/header-edge.svg",
"title": "Intelligence at the edge",
"target": "firmware/esp32-csi-node/README.md",
"width": 1200,
"height": 148
},
{
"file": "assets/readme/header-learning.svg",
"title": "From recordings to models",
"target": "docs/user-guide.md",
"width": 1200,
"height": 148
},
{
"file": "assets/readme/header-evidence.svg",
"title": "Evidence before accuracy claims",
"target": "docs/benchmarks/pose-estimation-cog.md",
"width": 1200,
"height": 148
},
{
"file": "assets/readme/header-docs.svg",
"title": "Choose your next step",
"target": "docs/user-guide.md",
"width": 1200,
"height": 148
},
{
"file": "assets/readme/cognitum.svg",
"title": "Explore Cognitum One",
"target": "https://cognitum.one",
"width": 1200,
"height": 304
}
]
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="180" height="116" viewBox="0 0 180 116" role="img" aria-labelledby="title desc"><title id="title">AI toolkit</title><desc id="desc">Open AI toolkit. Animated navigation icon.</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="179" height="115" rx="18" fill="url(#wash)" stroke="#245360"/><g transform="translate(90 45) scale(0.65)"><path d="M0 0L0 -33" fill="none" stroke="#70b6ff" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow"/><circle cx="0" cy="-33" r="7" stroke="#72eee0" fill="none" class="breathe"/><path d="M0 0L-35 23" fill="none" stroke="#70b6ff" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow"/><circle cx="-35" cy="23" r="7" stroke="#72eee0" fill="none" class="breathe"/><path d="M0 0L35 23" fill="none" stroke="#70b6ff" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="flow"/><circle cx="35" cy="23" r="7" stroke="#72eee0" fill="none" class="breathe"/><circle cx="0" cy="0" r="15" stroke="#72eee0" fill="none" /><path d="M-6 0L-1 5L8 -6" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></g><text x="90" y="98" font-size="16" fill="#ecfafb" text-anchor="middle" >AI toolkit</text></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="180" height="116" viewBox="0 0 180 116" role="img" aria-labelledby="title desc"><title id="title">Hardware</title><desc id="desc">Open Hardware. Animated navigation icon.</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="179" height="115" rx="18" fill="url(#wash)" stroke="#245360"/><g transform="translate(90 45) scale(0.65)"><rect x="-25" y="-25" width="50" height="50" rx="7" fill="#0a2630" stroke="#72eee0"/><rect x="-13" y="-13" width="26" height="26" rx="3" fill="none" stroke="#70b6ff"/><path d="M-16 -36V-25M-16 25V36M-36 -16H-25M25 -16H36" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /><path d="M0 -36V-25M0 25V36M-36 0H-25M25 0H36" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /><path d="M16 -36V-25M16 25V36M-36 16H-25M25 16H36" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /><circle cx="0" cy="0" r="4" stroke="#72eee0" fill="#72eee0" class="breathe"/></g><text x="90" y="98" font-size="16" fill="#ecfafb" text-anchor="middle" >Hardware</text></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="180" height="116" viewBox="0 0 180 116" role="img" aria-labelledby="title desc"><title id="title">Smart home</title><desc id="desc">Open Smart home. Animated navigation icon.</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="179" height="115" rx="18" fill="url(#wash)" stroke="#245360"/><g transform="translate(90 45) scale(0.65)"><path d="M-37 -3L0 -34L37 -3M-28 -10V32H28V-10M-9 32V8H9V32" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /><circle cx="0" cy="-9" r="5" stroke="#70b6ff" fill="none" class="breathe"/></g><text x="90" y="98" font-size="16" fill="#ecfafb" text-anchor="middle" >Smart home</text></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="180" height="116" viewBox="0 0 180 116" role="img" aria-labelledby="title desc"><title id="title">Models</title><desc id="desc">Open Models. Animated navigation icon.</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="179" height="115" rx="18" fill="url(#wash)" stroke="#245360"/><g transform="translate(90 45) scale(0.65)"><path d="M-42 6H-27L-18 -10L-9 25L3 -25L14 10L24 -3L31 6H43" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="trace"/><path d="M-42 31H43" fill="none" stroke="#214451" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" /></g><text x="90" y="98" font-size="16" fill="#ecfafb" text-anchor="middle" >Models</text></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="180" height="116" viewBox="0 0 180 116" role="img" aria-labelledby="title desc"><title id="title">Cognitum</title><desc id="desc">Open Cognitum. Animated navigation icon.</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="179" height="115" rx="18" fill="url(#wash)" stroke="#245360"/><g transform="translate(90 45) scale(0.65)"><path d="M0 -38L31 -20V18L0 38L-31 18V-20ZM-31 -20L0 -1L31 -20M0 -1V38" fill="none" stroke="#72eee0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /><path d="M-15 1L-5 11L17 -12" fill="none" stroke="#70b6ff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="trace"/></g><text x="90" y="98" font-size="16" fill="#ecfafb" text-anchor="middle" >Cognitum</text></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="180" height="116" viewBox="0 0 180 116" role="img" aria-labelledby="title desc"><title id="title">Start here</title><desc id="desc">Open Start here. Animated navigation icon.</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{font-family:Arial,Helvetica,sans-serif}.mono{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}.flow{stroke-dasharray:5 12;animation:flow 12s linear infinite}.breathe{animation:breathe 6s ease-in-out infinite}.trace{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}.float{animation:float 9s ease-in-out infinite}@keyframes flow{to{stroke-dashoffset:-136}}@keyframes breathe{50%{opacity:.4}}@keyframes trace{0%,100%{stroke-dashoffset:0}50%{stroke-dashoffset:90}}@keyframes float{50%{transform:translateY(-6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x=".5" y=".5" width="179" height="115" rx="18" fill="url(#wash)" stroke="#245360"/><g transform="translate(90 45) scale(0.65)"><path d="M-10.4 -5.2Q0 -18.2 10.4 -5.2" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="breathe" style="animation-delay:-0.65s"/><path d="M-20.0 -10.0Q0 -35.0 20.0 -10.0" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="breathe" style="animation-delay:-1.25s"/><path d="M-30.400000000000002 -15.200000000000001Q0 -53.199999999999996 30.400000000000002 -15.200000000000001" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="breathe" style="animation-delay:-1.9s"/><circle cx="0" cy="20" r="4" stroke="#72eee0" fill="#72eee0"/><path d="M0 12V-1" fill="none" stroke="#72eee0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></g><text x="90" y="98" font-size="16" fill="#ecfafb" text-anchor="middle" >Start here</text></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.9 KiB

View file

@ -0,0 +1,36 @@
# RuView README visual system
The README uses a Cognitum-inspired palette: midnight backgrounds, cyan gradients, wireframe geometry and restrained signal motion. It includes a hero, six feature cards, six navigation icons, an overview, five chapter headings and a Cognitum destination banner.
## Rebuild and check
```bash
python3 -m pip install -r scripts/requirements-readme-visuals.txt
python3 scripts/render-readme-visuals.py
python3 scripts/verify-readme-visuals.py
python3 -m unittest discover -s scripts/tests -p test_readme_visuals_xml.py
```
Install the tooling dependency in a virtual environment. The generator and verifier
use `defusedxml` to reject DTDs, entities and external XML references. SVG inputs
must be smaller than 20,000 bytes; the verifier reads at most that limit and
rejects oversized inputs before parsing. Both tools use UTF-8, and generated
files use LF line endings for reproducibility across platforms. They make no
network calls. This dependency is only for visual maintenance, not the runtime.
Manifest paths must be relative: SVG files stay inside `assets/readme`, and
local link targets stay inside the repository, including after symlink resolution.
The committed SVGs are the deployment artifacts. The manifest in
`assets/readme/manifest.json` lists every destination.
## Links and rendering
GitHub renders SVGs as images, so image-internal links are not used. Each SVG has an outer Markdown or HTML link in the README. Each card and navigation icon also has a plain text link for narrow screens and assistive technology. Source headings remain intact so existing README anchors still work.
Animation uses CSS only. All copy and essential geometry are visible without animation, JavaScript, remote fonts or remote images. `prefers-reduced-motion` disables decorative movement. The original GIF remains available in an expandable demo section; the existing ruOS promotion remains linked.
## Content boundaries
The overview and RF room are conceptual illustrations, not live measurements or benchmark evidence. Feature cards link to current source documentation. Pose research is identified as such, and the signal card directs readers to hardware validation. No new speed, accuracy, medical or device-support claims are introduced.
Sources: `docs/user-guide.md`, `docs/benchmarks/pose-estimation-cog.md`, `v2/crates/wifi-densepose-signal/README.md`, `firmware/esp32-csi-node/README.md`, `docs/integrations/home-assistant.md`, and `harness/ruview/README.md`.

View file

@ -0,0 +1,50 @@
# Virtual-view broker adapter (research prototype)
Opt-in integration with Dream Machine's `scripts/virtual-view-broker.mjs`.
Disabled by default. No cameras, model calls, network, hardware commands or
automatic promotion. It is NOT part of the Rust production perception pipeline.
The reviewed companion is Dream Machine commit
[`5707c825b2c065de44abed02594e1abf5eab95ea`](https://github.com/ruvnet/dream-machine/tree/5707c825b2c065de44abed02594e1abf5eab95ea),
merged in [Dream Machine PR #162](https://github.com/ruvnet/dream-machine/pull/162).
Use a checkout at that revision when reproducing the integration.
```js
import { generateKeyPairSync } from 'node:crypto';
import { broker } from '../../../dream-machine/scripts/virtual-view-broker.mjs';
import { createVirtualViewAdapter } from './adapter.mjs';
// Example in-memory research identity only; use a trusted host signer in deployment.
const { privateKey } = generateKeyPairSync('ed25519');
const adapter = createVirtualViewAdapter({ broker, privateKey, enabled: true });
// adapter.observe(calibratedSnapshot) returns RENDER, SKIP or BLOCK plus authority:none.
```
The example assumes sibling RuView and Dream Machine clones; adjust the import
from the executing module's location. Pass calibrated `virtual-view.v1` scene
snapshots in room-local ENU metres. See Dream Machine ADR-0108 for every field,
threshold, trust boundary, receipt and acceptance rule. The adapter does not
transform raw `spatial.evidence.v1` RF Gaussians or `ruview.lidar.depth.v1` packets:
neither supplies the independently tracked manipulation scene this method needs.
The injected broker is trusted executable code, not sandboxed by this adapter.
The no-network/no-actuation behavior applies to this adapter and the reviewed
companion broker. Before reading scene markers or calling the broker, the adapter
checks the entire snapshot as bounded plain JSON using property descriptors.
It rejects proxies, getters, inherited/custom prototypes, nonfinite values,
sparse arrays and non-JSON data, including nested values. Structural limits match
the reviewed companion: depth 12, 20,000 values, 1,024 array elements, 64 object
properties, 128-character keys and 4,096-character strings. Full geometry,
provenance-label and signing-key validation remain the companion broker's responsibility.
```sh
node --test integrations/virtual-view/adapter.test.mjs
# From the sibling Dream Machine checkout:
node scripts/bench-virtual-view.mjs ../ruview/integrations/virtual-view/adapter.mjs
```
The cross-repository benchmark exercises this adapter with the real broker,
renders 256-object synthetic scenes, signs and replays receipts. It measures local
CPU overhead only. Required robot evidence remains missing: +20 points on two of
three held-out tasks, no regression beyond five points, added p95 below 150 ms,
at least 95% pre-action drift detection, and replayable receipts for all trials.
No receipt grants actuator authority. Do not wire this to live robot control.

View file

@ -0,0 +1,58 @@
// Opt-in cross-repository prototype. Imports no hardware, network or model client.
import { types } from 'node:util';
const snapshotError = () => new TypeError(
'calibrated metric scene snapshot required; raw RF/LiDAR packets and non-JSON data are not supported',
);
// Structural preflight only, matching the reviewed companion's JSON budgets.
// Never invoke input methods, getters or proxy traps while inspecting data.
function validateSnapshotData(value, depth = 0, budget = { nodes: 0 }) {
if (depth > 12 || ++budget.nodes > 20000) throw snapshotError();
if (value === null || typeof value === 'boolean') return;
if (typeof value === 'string' && value.length <= 4096) return;
if (typeof value === 'number' && Number.isFinite(value)) return;
if (typeof value !== 'object' || types.isProxy(value)) throw snapshotError();
const array = Array.isArray(value);
if (Object.getPrototypeOf(value) !== (array ? Array.prototype : Object.prototype)) {
throw snapshotError();
}
if (array) {
const length = Object.getOwnPropertyDescriptor(value, 'length').value;
if (length > 1024) throw snapshotError();
if (Reflect.ownKeys(value).length !== length + 1) throw snapshotError();
for (let index = 0; index < length; index++) {
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
if (!descriptor?.enumerable || !Object.hasOwn(descriptor, 'value')) throw snapshotError();
validateSnapshotData(descriptor.value, depth + 1, budget);
}
return;
}
const keys = Reflect.ownKeys(value);
if (keys.length > 64) throw snapshotError();
for (const key of keys) {
if (typeof key !== 'string' || key.length > 128) throw snapshotError();
const descriptor = Object.getOwnPropertyDescriptor(value, key);
if (!descriptor.enumerable || !Object.hasOwn(descriptor, 'value')) throw snapshotError();
validateSnapshotData(descriptor.value, depth + 1, budget);
}
}
export function createVirtualViewAdapter({ broker, privateKey, enabled = false }) {
if (typeof broker !== 'function' || typeof enabled !== 'boolean') throw new TypeError('adapter configuration');
return Object.freeze({
observe(snapshot) {
if (!enabled) return { decision: 'BLOCK', reason: 'disabled', authority: 'none', svg: null };
validateSnapshotData(snapshot);
// Explicit metric scene contract only. RF Gaussians are not manipulation geometry.
const schema = snapshot && Object.getOwnPropertyDescriptor(snapshot, 'schema');
const frame = snapshot && Object.getOwnPropertyDescriptor(snapshot, 'frame');
if (schema?.value !== 'virtual-view.v1' || frame?.value !== 'room_enu') {
throw snapshotError();
}
return broker(snapshot, { privateKey });
},
hardwareActuation: false,
});
}

View file

@ -0,0 +1,170 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createVirtualViewAdapter } from './adapter.mjs';
test('disabled by default and never actuates', () => {
const adapter = createVirtualViewAdapter({ broker: () => { throw Error('not called'); } });
let reads = 0;
const snapshot = { get schema() { reads++; throw Error('not read'); } };
assert.deepEqual(adapter.observe(snapshot), {
decision: 'BLOCK', reason: 'disabled', authority: 'none', svg: null,
});
assert.equal(reads, 0);
assert.equal(adapter.hardwareActuation, false);
assert.equal(Object.isFrozen(adapter), true);
});
test('raw RF, LiDAR and malformed scene markers never reach the broker', () => {
let calls = 0;
const adapter = createVirtualViewAdapter({ enabled: true, broker: () => { calls++; } });
for (const snapshot of [
null, undefined, 'virtual-view.v1', [], {},
{ schema: 'spatial.evidence.v1', frame: 'room_enu' },
{ schema: 'ruview.lidar.depth.v1', frame: 'room_enu' },
{ schema: 'virtual-view.v1' },
{ schema: 'virtual-view.v1', frame: 'camera' },
Object.create({ schema: 'virtual-view.v1', frame: 'room_enu' }),
Object.defineProperty({ frame: 'room_enu' }, 'schema', { value: 'virtual-view.v1' }),
]) {
assert.throws(() => adapter.observe(snapshot), /calibrated metric scene snapshot required/);
}
assert.equal(calls, 0);
});
test('scene marker getters are rejected without executing input code', () => {
let reads = 0, calls = 0;
const adapter = createVirtualViewAdapter({ enabled: true, broker: () => { calls++; } });
for (const key of ['schema', 'frame']) {
const snapshot = { schema: 'virtual-view.v1', frame: 'room_enu' };
Object.defineProperty(snapshot, key, {
enumerable: true,
get() { reads++; return key === 'schema' ? 'virtual-view.v1' : 'room_enu'; },
});
assert.throws(() => adapter.observe(snapshot), /calibrated metric scene snapshot required/);
}
assert.equal(reads, 0);
assert.equal(calls, 0);
});
test('proxy snapshots are rejected without running inspection traps', () => {
let traps = 0, calls = 0;
const snapshot = new Proxy({ schema: 'virtual-view.v1', frame: 'room_enu' }, {
get() { traps++; throw Error('get trap'); },
getPrototypeOf() { traps++; throw Error('prototype trap'); },
getOwnPropertyDescriptor() { traps++; throw Error('descriptor trap'); },
});
const adapter = createVirtualViewAdapter({ enabled: true, broker: () => { calls++; } });
assert.throws(() => adapter.observe(snapshot), /calibrated metric scene snapshot required/);
assert.equal(traps, 0);
assert.equal(calls, 0);
});
test('nested object and array getters are rejected without reads or broker calls', () => {
let reads = 0, calls = 0;
const adapter = createVirtualViewAdapter({ enabled: true, broker: () => { calls++; } });
const getter = { enumerable: true, get() { reads++; return 0; } };
const object = Object.defineProperty({}, 'uncertaintyM', getter);
const array = Object.defineProperty([0], '0', getter);
for (const extra of [object, [object], { nested: array }]) {
assert.throws(() => adapter.observe({ schema: 'virtual-view.v1', frame: 'room_enu', extra }),
/calibrated metric scene snapshot required/);
}
assert.equal(reads, 0);
assert.equal(calls, 0);
});
test('nested proxies and custom array prototypes never execute input code', () => {
let traps = 0, calls = 0;
const adapter = createVirtualViewAdapter({ enabled: true, broker: () => { calls++; } });
const handler = {
get() { traps++; throw Error('get trap'); },
getPrototypeOf() { traps++; throw Error('prototype trap'); },
ownKeys() { traps++; throw Error('keys trap'); },
getOwnPropertyDescriptor() { traps++; throw Error('descriptor trap'); },
};
const custom = Object.setPrototypeOf([], {
get [Symbol.iterator]() { traps++; throw Error('iterator getter'); },
});
class SceneArray extends Array {}
for (const extra of [
{ nested: new Proxy({}, handler) },
[new Proxy([], handler)],
{ nested: custom },
new SceneArray(),
]) {
assert.throws(() => adapter.observe({ schema: 'virtual-view.v1', frame: 'room_enu', extra }),
/calibrated metric scene snapshot required/);
}
assert.equal(traps, 0);
assert.equal(calls, 0);
});
test('cyclic, non-JSON and oversized structures are rejected before the broker', () => {
let calls = 0;
const adapter = createVirtualViewAdapter({ enabled: true, broker: () => { calls++; } });
const cyclic = {}; cyclic.self = cyclic;
let deep = 0;
for (let index = 0; index < 13; index++) deep = { next: deep };
const sparse = Array(2); sparse[1] = 0;
const extraProperty = [0]; extraProperty.extra = 0;
for (const extra of [
cyclic, deep, sparse, extraProperty, NaN, Infinity, undefined, () => {}, new Date(),
'x'.repeat(4097), Array(1025).fill(0),
{ ['x'.repeat(129)]: 0 },
Object.fromEntries(Array.from({ length: 65 }, (_, index) => [`k${index}`, 0])),
Array.from({ length: 20 }, () => Array(1024).fill(0)),
]) {
assert.throws(() => adapter.observe({ schema: 'virtual-view.v1', frame: 'room_enu', extra }),
/calibrated metric scene snapshot required/);
}
assert.equal(calls, 0);
});
test('a complete 256-object synthetic scene stays within the structural budget', () => {
const scene = {
schema: 'virtual-view.v1', sceneId: 'fixture', requestId: 'request:1', policyId: 'fixed',
revision: 1, frame: 'room_enu', proof: 'SYNTHETIC', nowMs: 1000, observedMs: 990,
contact: 'static', target: 'o0', reference: 'o1', physicalAxis: 'x',
objects: Array.from({ length: 256 }, (_, index) => ({
id: `o${index}`, observed: [index / 100, index % 3, 0.1],
predicted: [index / 100, index % 3, 0.1], size: [0.01, 0.01, 0.01],
uncertaintyM: 0.015, sourceReceipt: `fixture:${index}`,
})),
};
let calls = 0;
const result = { authority: 'none' };
const adapter = createVirtualViewAdapter({ enabled: true, broker: received => {
calls++;
assert.equal(received, scene);
return result;
} });
assert.equal(adapter.observe(scene), result);
assert.equal(calls, 1);
});
test('explicit immutable scene and signer are forwarded unchanged to trusted broker', () => {
const snapshot = Object.freeze({ schema: 'virtual-view.v1', frame: 'room_enu' });
const privateKey = Object.freeze({ researchSigner: true });
const result = Object.freeze({ decision: 'SKIP', authority: 'none', svg: null });
let calls = 0;
const adapter = createVirtualViewAdapter({ enabled: true, privateKey, broker: (received, options) => {
calls++;
assert.equal(received, snapshot);
assert.equal(options.privateKey, privateKey);
return result;
} });
assert.equal(adapter.observe(snapshot), result);
assert.equal(calls, 1);
});
test('full scene validation remains delegated and broker rejection propagates', () => {
const rejection = new TypeError('missing tracked scene geometry');
const adapter = createVirtualViewAdapter({ enabled: true, broker: () => { throw rejection; } });
assert.throws(() => adapter.observe({ schema: 'virtual-view.v1', frame: 'room_enu' }),
error => error === rejection);
});
test('enablement must be explicit and configuration types are checked', () => {
for (const configuration of [
{ broker: null }, { broker: () => {}, enabled: 'true' }, { broker: () => {}, enabled: 1 },
]) assert.throws(() => createVirtualViewAdapter(configuration), /adapter configuration/);
});

View file

@ -0,0 +1,50 @@
"""Bounded, hardened XML parsing for the README visual maintenance tools."""
from pathlib import Path, PurePosixPath, PureWindowsPath
from defusedxml import ElementTree
# Keep the visual verifier's existing exclusive per-asset size limit.
MAX_SVG_BYTES = 20_000
def resolve_repo_file(root: Path, relative: str, *, asset: bool = False) -> Path:
"""Resolve a portable manifest path inside its trusted repository boundary."""
if (
not isinstance(relative, str)
or not relative
or "\\" in relative
or ":" in relative
or "\x00" in relative
or PurePosixPath(relative).is_absolute()
or PureWindowsPath(relative).drive
or ".." in relative.split("/")
):
raise ValueError("Expected a repository-relative path without traversal")
repository = root.resolve()
boundary = repository / "assets" / "readme" if asset else repository
resolved = (repository / relative).resolve()
if not resolved.is_relative_to(boundary):
raise ValueError("Manifest path escapes its allowed repository directory")
if not resolved.is_file():
raise ValueError("Manifest path must name an existing file")
return resolved
def parse_svg(raw: bytes):
"""Reject oversized input and DTD/entity declarations before building a tree."""
if not isinstance(raw, bytes):
raise TypeError("SVG input must be bytes")
if len(raw) >= MAX_SVG_BYTES:
raise ValueError(f"SVG must be smaller than {MAX_SVG_BYTES} bytes")
return ElementTree.fromstring(
raw, forbid_dtd=True, forbid_entities=True, forbid_external=True
)
def read_svg(path: Path):
"""Read at most the rejection boundary, including when a file grows mid-read."""
with path.open("rb") as stream:
raw = stream.read(MAX_SVG_BYTES)
return raw, parse_svg(raw)

View file

@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Generate the RuView README visual system. Deterministic, no network."""
from pathlib import Path
from html import escape
import math,json
from readme_visuals_xml import parse_svg
ROOT=Path(__file__).resolve().parents[1];OUT=ROOT/'assets/readme';OUT.mkdir(parents=True,exist_ok=True)
C='#72eee0';BLUE='#70b6ff';INK='#041117';WHITE='#ecfafb';MUTED='#9abac5'
def text(x,y,value,size=16,color=MUTED,anchor='start',extra=''):
return f'<text x="{x}" y="{y}" font-size="{size}" fill="{color}" text-anchor="{anchor}" {extra}>{escape(value)}</text>'
def path(d,color=C,width=1.5,extra=''):
return f'<path d="{d}" fill="none" stroke="{color}" stroke-width="{width}" stroke-linecap="round" stroke-linejoin="round" {extra}/>'
def circle(x,y,r,color=C,extra=''):
fill = '' if 'fill=' in extra else 'fill="none"'
return f'<circle cx="{x}" cy="{y}" r="{r}" stroke="{color}" {fill} {extra}/>'
def icon(kind,x,y,scale=1):
s=''
if kind in ['signal','start']:
for r in [13,25,38]:s+=path(f'M{-r*.8} {-r*.4}Q0 {-r*1.4} {r*.8} {-r*.4}',extra=f'class="breathe" style="animation-delay:-{r/20}s"')
s+=circle(0,20,4,extra='fill="#72eee0"')+path('M0 12V-1')
elif kind in ['presence','pose']:
s+=circle(0,-28,8)+path('M0 -20V12M-24 -6L0 -14L24 -6M0 12L-18 38M0 12L18 38',width=2)
if kind=='pose':
for px,py in [(0,-14),(0,12),(-24,-6),(24,-6),(-18,38),(18,38)]:s+=circle(px,py,3,BLUE,extra='fill="#041117" class="breathe"')
else:s+=f'<ellipse cx="0" cy="14" rx="40" ry="13" fill="none" stroke="{BLUE}" stroke-dasharray="3 8" class="flow"/>'
elif kind in ['vitals','models']:
s+=path('M-42 6H-27L-18 -10L-9 25L3 -25L14 10L24 -3L31 6H43',width=2,extra='class="trace"')+path('M-42 31H43','#214451',1)
elif kind in ['edge','hardware']:
s+='<rect x="-25" y="-25" width="50" height="50" rx="7" fill="#0a2630" stroke="#72eee0"/>'
s+='<rect x="-13" y="-13" width="26" height="26" rx="3" fill="none" stroke="#70b6ff"/>'
for v in [-16,0,16]:s+=path(f'M{v} -36V-25M{v} 25V36M-36 {v}H-25M25 {v}H36')
s+=circle(0,0,4,extra='fill="#72eee0" class="breathe"')
elif kind in ['home','automation']:
s+=path('M-37 -3L0 -34L37 -3M-28 -10V32H28V-10M-9 32V8H9V32',width=2)
s+=circle(0,-9,5,BLUE,extra='class="breathe"')
elif kind in ['agent','agents']:
for px,py in [(0,-33),(-35,23),(35,23)]:s+=path(f'M0 0L{px} {py}',BLUE,1,extra='class="flow"')+circle(px,py,7,extra='class="breathe"')
s+=circle(0,0,15)+path('M-6 0L-1 5L8 -6')
elif kind in ['seed','evidence']:
s+=path('M0 -38L31 -20V18L0 38L-31 18V-20ZM-31 -20L0 -1L31 -20M0 -1V38',width=2)
s+=path('M-15 1L-5 11L17 -12',BLUE,2,extra='class="trace"')
else:
for j in range(3):s+=path(f'M-38 {j*14-20}L0 {j*14-38}L38 {j*14-20}L0 {j*14-2}Z',extra='class="breathe"')
return f'<g transform="translate({x} {y}) scale({scale})">{s}</g>'
def frame(w,h,title,desc,body):
return f'''<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" viewBox="0 0 {w} {h}" role="img" aria-labelledby="title desc"><title id="title">{escape(title)}</title><desc id="desc">{escape(desc)}</desc><defs><linearGradient id="wash" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0b303a"/><stop offset=".55" stop-color="#06171e"/><stop offset="1" stop-color="#030c13"/></linearGradient><linearGradient id="accent"><stop stop-color="#91f6df"/><stop offset="1" stop-color="#26bad7"/></linearGradient><radialGradient id="halo"><stop stop-color="#35d4db" stop-opacity=".17"/><stop offset="1" stop-color="#35d4db" stop-opacity="0"/></radialGradient></defs><style>text{{font-family:Arial,Helvetica,sans-serif}}.mono{{font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}}.flow{{stroke-dasharray:5 12;animation:flow 12s linear infinite}}.breathe{{animation:breathe 6s ease-in-out infinite}}.trace{{stroke-dasharray:400;stroke-dashoffset:0;animation:trace 10s ease-in-out infinite}}.float{{animation:float 9s ease-in-out infinite}}@keyframes flow{{to{{stroke-dashoffset:-136}}}}@keyframes breathe{{50%{{opacity:.4}}}}@keyframes trace{{0%,100%{{stroke-dashoffset:0}}50%{{stroke-dashoffset:90}}}}@keyframes float{{50%{{transform:translateY(-6px)}}}}@media(prefers-reduced-motion:reduce){{*{{animation:none!important}}}}</style><rect x=".5" y=".5" width="{w-1}" height="{h-1}" rx="18" fill="url(#wash)" stroke="#245360"/>{body}</svg>'''
manifest=[]
def save(name,w,h,title,desc,body,target):
s=frame(w,h,title,desc,body);raw=(s+'\n').encode('utf-8');parse_svg(raw);(OUT/(name+'.svg')).write_bytes(raw);manifest.append({'file':'assets/readme/'+name+'.svg','title':title,'target':target,'width':w,'height':h})
# Main title panel: a radio field passes through an illustrated room.
s=text(48,44,'RUVIEW / COGNITUM ECOSYSTEM',13,C,extra='class="mono"')+text(48,121,'RuView',76,WHITE)+text(48,184,'Spaces become signals.',38,WHITE)+text(48,234,'Signals become understanding.',32,C)+text(50,279,'Explore RF sensing, edge inference and local automation.',18)
s+='<rect x="48" y="326" width="270" height="52" rx="12" fill="url(#accent)"/>'+text(72,359,'Explore the guide',20,INK)+path('M272 352H292M285 345L292 352L285 359',INK,2)
s+=text(49,419,'RF SENSING / EDGE MODELS / LOCAL ACTION',11,MUTED,extra='class="mono"')
s+='<circle cx="950" cy="235" r="235" fill="url(#halo)"/>'
for i in range(9):s+=path(f'M{750+i*40} 282L{610+i*80} 443','#12343d',1)
for y in [306,336,372,418]:s+=path(f'M680 {y}H1168','#12343d',1)
s+=path('M803 157L1008 106L1137 177L930 236ZM803 157V314L930 391L1137 324V177M930 236V391M1008 106V268L803 314M1008 268L1137 324','#4b9ba4',1.5)
s+=icon('presence',976,265,1.35)+icon('edge',780,349,.7)
for i in range(4):s+=path(f'M{794+i*12} {333-i*9}Q{830+i*25} {245-i*24} {896+i*29} {207-i*3}',C,1.4,extra=f'class="flow" style="animation-delay:-{i}s" opacity="{.8-i*.12}"')
for x,y in [(803,157),(1008,106),(1137,177),(930,236),(930,391),(803,314),(1137,324)]:s+=circle(x,y,3,extra='fill="#72eee0" class="breathe"')
s+=text(1148,419,'CONCEPTUAL RF FIELD',10,MUTED,'end',extra='class="mono"')
save('hero',1200,450,'RuView: spaces become signals','Animated conceptual RF field and room geometry. Explore the RuView user guide. This illustration is not a sensor measurement.',s,'docs/user-guide.md')
# Visual navigation icons, each receives a real outer link in Markdown.
nav=[('start','Start here','docs/user-guide.md'),('hardware','Hardware','firmware/esp32-csi-node/README.md'),('models','Models','https://huggingface.co/ruvnet/wifi-densepose-pretrained'),('agents','AI toolkit','harness/ruview/README.md'),('home','Smart home','docs/integrations/home-assistant.md'),('seed','Cognitum','https://cognitum.one/seed')]
for k,label,target in nav:save('nav-'+k,180,116,label,'Open '+label+'. Animated navigation icon.',icon(k,90,45,.65)+text(90,98,label,16,WHITE,'middle'),target)
# Capability cards. No unverified performance claims.
cards=[('presence','01','Presence & motion','Explore changes in room occupancy.','Inspect motion features from CSI.','SENSING GUIDE','docs/user-guide.md'),('vitals','02','Breathing & vital signals','Inspect phase-derived signal trends.','Validate estimates on your hardware.','SIGNAL PIPELINE','v2/crates/wifi-densepose-signal/README.md'),('pose','03','Pose research','Train and compare body keypoints.','Live accuracy differs from benchmarks.','READ THE EVIDENCE','docs/benchmarks/pose-estimation-cog.md'),('edge','04','Edge sensor mesh','Provision, capture and calibrate.','Explore ESP32 firmware and runbooks.','HARDWARE GUIDE','firmware/esp32-csi-node/README.md'),('automation','05','Local automation','Connect rooms to your smart home.','Home Assistant, MQTT and Matter.','INTEGRATION GUIDE','docs/integrations/home-assistant.md'),('agents','06','A guided AI operator','Source-cited setup and verification.','CLI, MCP and agent workflows.','TOOLKIT GUIDE','harness/ruview/README.md')]
for k,num,title,l1,l2,cta,target in cards:
s=text(30,39,'RUVIEW / '+num,11,C,extra='class="mono"')+text(30,94,title,29,WHITE)+text(30,136,l1,16)+text(30,164,l2,16)
s+='<circle cx="465" cy="170" r="110" fill="url(#halo)"/>'+icon(k,474,168,.83)+path('M30 204H550','#245360',1)+path('M30 204H550',C,1,extra='class="flow" opacity=".5"')
s+=text(30,245,cta,12,C,extra='class="mono"')+path('M510 239H540M532 231L540 239L532 247',C,1.5)
save('feature-'+k,580,276,title,l1+' '+l2+' Conceptual animation; follow the linked source for capability limits.',s,target)
# Overview uses three clearly labelled stages rather than fabricated telemetry.
s=text(36,44,'SIGNAL TO CONTEXT',12,C,extra='class="mono"')+text(36,89,'A sensing loop you can inspect.',34,WHITE)
for i,(label,sub,kind,tail) in enumerate([('Capture RF','CSI from supported hardware','signal','Provision and calibrate'),('Extract context','Signal processing and models','models','Compare against real data'),('Act locally','Events, APIs and integrations','automation','Apply rules and review outputs')]):
x=36+i*396;s+=f'<rect x="{x}" y="120" width="336" height="220" rx="14" fill="#071b23" stroke="#285360"/>'
s+=text(x+24,153,f'0{i+1}',12,C)+text(x+24,188,label,26,WHITE)+text(x+24,216,sub,15)+icon(kind,x+269,281,.62)+text(x+24,311,tail,13)
if i<2:s+=path(f'M{x+344} 229H{x+384}M{x+376} 221L{x+384} 229L{x+376} 237',C,2,extra='class="flow"')
s+=text(36,373,'CONCEPTUAL PIPELINE / HARDWARE AND ENVIRONMENT VALIDATION MATTER',11,MUTED,extra='class="mono"')
save('overview',1200,396,'RuView sensing overview','Conceptual sequence: capture RF, extract context, act locally. Links to architecture and user documentation.',s,'docs/user-guide.md')
# Chapter headings act as visual landmarks, all remain links.
headers=[('features','Explore the capabilities','SENSING / MODELS / AUTOMATION','presence','docs/user-guide.md'),('edge','Intelligence at the edge','SENSORS / FIRMWARE / MODULES','edge','firmware/esp32-csi-node/README.md'),('learning','From recordings to models','CAPTURE / TRAIN / EVALUATE','models','docs/user-guide.md'),('evidence','Evidence before accuracy claims','BENCHMARKS / REPRODUCERS / LIMITS','evidence','docs/benchmarks/pose-estimation-cog.md'),('docs','Choose your next step','GUIDES / HARDWARE / INTEGRATIONS','agents','docs/user-guide.md')]
for k,title,sub,kind,target in headers:
s=icon(kind,76,75,.75)+text(140,63,title,34,WHITE)+text(142,99,sub,12,C,extra='class="mono"')
for i in range(4):s+=path(f'M865 {44+i*17}Q960 {10+i*28} 1124 {52+i*17}',C,1,extra=f'class="flow" opacity="{.65-i*.1}"')
s+=path('M1140 70H1160M1152 62L1160 70L1152 78',C,2)
save('header-'+k,1200,148,title,'Animated section heading. '+sub,s,target)
# Brand destination; original ruOS promotion remains in the README.
s=text(44,42,'COGNITUM ONE',12,C,extra='class="mono"')+text(44,99,'Ambient Intelligence.',42,WHITE)+text(44,147,'At the edge of the physical world.',25,C)+text(44,190,'Explore the platform behind the ecosystem.',16)
s+='<rect x="44" y="224" width="256" height="46" rx="11" fill="url(#accent)"/>'+text(65,254,'Explore Cognitum',18,INK)+path('M263 247H284M277 240L284 247L277 254',INK,2)
s+='<circle cx="947" cy="151" r="173" fill="url(#halo)"/>'+icon('seed',945,150,2.4)
for j in range(3):s+=f'<ellipse cx="945" cy="150" rx="{140+j*20}" ry="{30+j*13}" transform="rotate(-25 945 150)" fill="none" stroke="#3c8897" stroke-dasharray="5 18" class="flow"/>'
save('cognitum',1200,304,'Explore Cognitum One','Ambient Intelligence at the edge of the physical world. Open cognitum.one.',s,'https://cognitum.one')
(OUT/'manifest.json').write_bytes((json.dumps({'schema_version':1,'style':'Cognitum cyan on midnight','motion':'Decorative CSS; prefers-reduced-motion supported; no script or remote dependencies','assets':manifest},indent=2)+'\n').encode('utf-8'))
print(f'Generated {len(manifest)} SVGs, {sum((ROOT/x["file"]).stat().st_size for x in manifest):,} bytes')

View file

@ -0,0 +1,2 @@
# Development-only dependency for README SVG generation and verification.
defusedxml==0.7.1

View file

@ -0,0 +1,152 @@
"""Security regressions for the README visual tools' shared XML boundary."""
import io
from pathlib import Path
import sys
import tempfile
import unittest
from unittest.mock import patch
from defusedxml.common import DTDForbidden
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import readme_visuals_xml as svg_xml
class ReadmeVisualXmlTests(unittest.TestCase):
def test_manifest_paths_reject_absolute_and_traversal_before_resolution(self):
for relative in [
'', '/tmp/outside.svg', '//server/share/outside.svg',
'C:/outside.svg', 'C:outside.svg', r'\\server\share\outside.svg',
r'\outside.svg', r'\\?\C:\outside.svg', 'file.svg:stream',
'../outside.svg', 'assets/readme/../../../outside.svg',
r'assets\readme\..\outside.svg', 'assets/readme/\x00.svg',
]:
with self.subTest(relative=relative):
with patch.object(Path, 'resolve') as resolve:
with self.assertRaises(ValueError):
svg_xml.resolve_repo_file(Path('repository'), relative)
resolve.assert_not_called()
def test_manifest_paths_keep_assets_and_targets_in_separate_boundaries(self):
with tempfile.TemporaryDirectory() as directory:
repository = Path(directory).resolve()
asset = repository / 'assets' / 'readme' / 'ordinary.svg'
asset.parent.mkdir(parents=True)
asset.write_bytes(b'<svg/>')
target = repository / 'README.md'
target.write_bytes(b'# Readme\n')
self.assertEqual(svg_xml.resolve_repo_file(repository, 'assets/readme/ordinary.svg', asset=True), asset)
self.assertEqual(svg_xml.resolve_repo_file(repository, 'README.md'), target)
with patch.object(Path, 'is_file') as is_file:
with self.assertRaises(ValueError):
svg_xml.resolve_repo_file(repository, 'README.md', asset=True)
is_file.assert_not_called()
def test_manifest_paths_reject_symlink_escape_before_file_inspection(self):
with tempfile.TemporaryDirectory() as directory:
base = Path(directory)
repository = base / 'repository'
assets = repository / 'assets' / 'readme'
assets.mkdir(parents=True)
outside = base / 'outside.svg'
outside.write_bytes(b'<svg/>')
try:
(assets / 'escape.svg').symlink_to(outside)
(repository / 'escaped-directory').symlink_to(base, target_is_directory=True)
except (OSError, NotImplementedError) as error:
if sys.platform == 'win32' and (isinstance(error, NotImplementedError) or getattr(error, 'winerror', None) == 1314):
self.skipTest('Windows symlink privilege unavailable')
raise
for relative, asset in [('assets/readme/escape.svg', True), ('escaped-directory/outside.svg', False)]:
with self.subTest(relative=relative):
with patch.object(Path, 'is_file') as is_file:
with self.assertRaises(ValueError):
svg_xml.resolve_repo_file(repository, relative, asset=asset)
is_file.assert_not_called()
def test_asset_directory_symlink_cannot_redefine_trusted_boundary(self):
with tempfile.TemporaryDirectory() as directory:
base = Path(directory)
repository = base / 'repository'
(repository / 'assets').mkdir(parents=True)
outside = base / 'outside'
outside.mkdir()
(outside / 'escape.svg').write_bytes(b'<svg/>')
try:
(repository / 'assets' / 'readme').symlink_to(outside, target_is_directory=True)
except (OSError, NotImplementedError) as error:
if sys.platform == 'win32' and (isinstance(error, NotImplementedError) or getattr(error, 'winerror', None) == 1314):
self.skipTest('Windows symlink privilege unavailable')
raise
with patch.object(Path, 'is_file') as is_file:
with self.assertRaises(ValueError):
svg_xml.resolve_repo_file(repository, 'assets/readme/escape.svg', asset=True)
is_file.assert_not_called()
def test_plain_utf8_svg_parses(self):
root = svg_xml.parse_svg('<svg><title>Présence</title></svg>'.encode('utf-8'))
self.assertEqual(root.tag, 'svg')
self.assertEqual(root.find('title').text, 'Présence')
def test_dtd_and_entity_payloads_are_rejected(self):
for raw in [
b'<!DOCTYPE svg><svg/>',
b'<!DOCTYPE svg [<!ENTITY payload "expanded">]><svg>&payload;</svg>',
b'<!DOCTYPE svg [<!ENTITY payload SYSTEM "file:///private.txt">]><svg>&payload;</svg>',
b'<!DOCTYPE svg SYSTEM "https://example.invalid/remote.dtd"><svg/>',
]:
with self.subTest(raw=raw):
with self.assertRaises(DTDForbidden):
svg_xml.parse_svg(raw)
def test_size_limit_is_enforced_before_parser_runs(self):
for size in [svg_xml.MAX_SVG_BYTES, svg_xml.MAX_SVG_BYTES + 1]:
with self.subTest(size=size):
with patch.object(svg_xml.ElementTree, 'fromstring') as parser:
with self.assertRaisesRegex(ValueError, 'smaller than 20000 bytes'):
svg_xml.parse_svg(b' ' * size)
parser.assert_not_called()
def test_limit_counts_utf8_bytes_and_allows_last_valid_size(self):
valid = b'<svg/>' + b' ' * (svg_xml.MAX_SVG_BYTES - 7)
self.assertEqual(len(valid), svg_xml.MAX_SVG_BYTES - 1)
self.assertEqual(svg_xml.parse_svg(valid).tag, 'svg')
text = '<svg>' + 'é' * 10000 + '</svg>'
self.assertLess(len(text), svg_xml.MAX_SVG_BYTES)
with self.assertRaises(ValueError):
svg_xml.parse_svg(text.encode('utf-8'))
def test_reader_never_requests_unbounded_input(self):
class BoundedReader(io.BytesIO):
requested = None
def read(self, size=-1):
self.requested = size
if size < 0 or size > svg_xml.MAX_SVG_BYTES:
raise AssertionError('unbounded read')
return super().read(size)
stream = BoundedReader(b' ' * (svg_xml.MAX_SVG_BYTES * 2))
with patch.object(Path, 'open', return_value=stream):
with patch.object(svg_xml.ElementTree, 'fromstring') as parser:
with self.assertRaises(ValueError):
svg_xml.read_svg(Path('oversized.svg'))
parser.assert_not_called()
self.assertEqual(stream.requested, svg_xml.MAX_SVG_BYTES)
def test_file_reader_uses_same_hardened_parser(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'fixture.svg'
raw = b'<svg><title>ordinary</title></svg>'
path.write_bytes(raw)
read, root = svg_xml.read_svg(path)
self.assertEqual(read, raw)
self.assertEqual(root.find('title').text, 'ordinary')
path.write_bytes(b'<!DOCTYPE svg><svg/>')
with self.assertRaises(DTDForbidden):
svg_xml.read_svg(path)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""Verify the generated SVGs and GitHub-compatible image link wrappers."""
from pathlib import Path
import json,re
from urllib.parse import urlparse
from readme_visuals_xml import read_svg,resolve_repo_file
ROOT=Path(__file__).resolve().parents[1];NS='{http://www.w3.org/2000/svg}'
manifest=json.loads((ROOT/'assets/readme/manifest.json').read_text(encoding='utf-8'));readme=(ROOT/'README.md').read_text(encoding='utf-8');total=0
for asset in manifest['assets']:
file=resolve_repo_file(ROOT,asset['file'],asset=True);data,root=read_svg(file);raw=data.decode('utf-8');total+=len(data)
assert root.tag==NS+'svg' and root.get('viewBox'),asset['file']
assert root.find(NS+'title') is not None and root.find(NS+'desc') is not None
ids=[e.get('id') for e in root.iter() if e.get('id')];assert len(ids)==len(set(ids))
assert 'prefers-reduced-motion:reduce' in raw and '@keyframes' in raw
for e in root.iter():
assert e.tag not in [NS+'script',NS+'foreignObject',NS+'image'],asset['file']
assert all(not k.lower().startswith('on') for k in e.attrib)
assert not any(k.endswith('href') for k in e.attrib),'Links belong to README wrappers'
assert not re.search(r'url\((?!#)',raw),'Remote dependency'
target=asset['target'];parsed=urlparse(target)
if parsed.scheme:
assert parsed.scheme=='https' and parsed.hostname in ['cognitum.one','huggingface.co']
else:resolve_repo_file(ROOT,target.split('#')[0])
escaped=re.escape(asset['file']);destination=re.escape(target)
linked=bool(re.search(r'\[!\[[^\]]*\]\('+escaped+r'\)\]\('+destination+r'\)',readme))
linked=linked or bool(re.search(r'<a href="'+destination+r'">\s*<img src="'+escaped+r'"',readme))
assert linked,('Missing clickable wrapper',asset['file'])
assert total<100000
print(f'PASS: {len(manifest["assets"])} accessible animated SVGs, link targets and README wrappers; {total:,} bytes. No scripts, remote resources or SVG-internal links.')

View file

@ -88,7 +88,29 @@ cargo run -p wifi-densepose-sensing-server -- \
Complete the normal empty-room calibration, then call
`POST /api/v1/calibration/bootstrap/promote`. The server measures a separate 12-sample holdout
before storing an aggregate field-model snapshot. On a later start with the same installation ID,
before storing an aggregate field-model snapshot. Promotion requires all of the following:
- Exactly 12 fresh samples, spaced at least one second apart, with no stale samples.
- At least 10 of the 12 samples classified as empty.
- Zero samples containing numeric vital-sign estimates.
The 10-of-12 tolerance applies only to the empty classification. A holdout with 12 empty samples
and five samples containing vital-sign estimates still fails; a vital-sign estimate does not
prove occupancy, but it prevents storing the startup baseline under this conservative check.
Completed holdout evaluations return the existing `validation` counters and additive
`validation_diagnostics.requirements` and `validation_diagnostics.failures` fields. A failed
check retains `success: false`, `error_code`, and `error`; the error describes the observed
counts and required limits. Each failure identifies its `check`, `observed` and `required`
counts, and `comparison`, so clients can explain every failed condition. For example, five
vital-sign samples produce a `vital_sign_sample_count` failure with `observed: 5`,
`required: 0`, and `comparison: "at_most"`.
The bounded refinement endpoint, `POST /api/v1/calibration/bootstrap/refine`, also requires
12 fresh samples with zero stale or vital-sign samples. Its diagnostics omit the empty-sample
minimum because refinement does not apply that promotion-only check.
On a later start with the same installation ID,
the snapshot is restored with `bootstrap_only` authority. It can reduce startup background false
positives, but cannot authorize calibrated evidence or numeric heart and breathing rates. Use
`POST /api/v1/calibration/reset` with administrator scope to remove it.

View file

@ -90,6 +90,131 @@ pub struct BootstrapValidationResult {
pub passed: bool,
}
/// Independent ADR-355 requirements. Refinement does not count empty votes.
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct BootstrapValidationRequirements {
pub sample_count: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_empty_sample_count: Option<usize>,
pub max_stale_sample_count: usize,
pub max_vital_sign_sample_count: usize,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct BootstrapValidationFailure {
pub check: &'static str,
pub observed: usize,
pub required: usize,
pub comparison: &'static str,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct BootstrapValidationDiagnostics {
pub requirements: BootstrapValidationRequirements,
pub failures: Vec<BootstrapValidationFailure>,
}
impl BootstrapValidationResult {
pub fn diagnostics(&self) -> BootstrapValidationDiagnostics {
validation_diagnostics(
self.sample_count,
Some(self.empty_sample_count),
self.stale_sample_count,
self.vital_sign_sample_count,
)
}
}
impl BootstrapValidationDiagnostics {
/// Describe every failed check, without interpreting estimates as people.
pub fn failure_summary(&self) -> String {
self.failures
.iter()
.map(|failure| match failure.check {
"sample_count" => format!(
"{} samples collected (exactly {} required); check the CSI stream",
failure.observed, failure.required,
),
"empty_sample_count" => format!(
"{} samples reported empty (at least {} required); vacate the room and check for moving objects",
failure.observed, failure.required,
),
"stale_sample_count" => format!(
"{} stale samples ({} allowed); check the CSI stream",
failure.observed, failure.required,
),
_ => format!(
"numeric vital-sign estimates in {}/{} samples ({} allowed); vacate the room and check for moving objects",
failure.observed, self.requirements.sample_count, failure.required,
),
})
.collect::<Vec<_>>()
.join("; ")
}
}
/// Explain existing gate counters without changing how samples are counted.
/// Promotion supplies all attempted samples; refinement supplies only fresh
/// residuals. Stale samples are a separate check, never added to sample_count.
pub fn validation_diagnostics(
sample_count: usize,
empty_sample_count: Option<usize>,
stale_sample_count: usize,
vital_sign_sample_count: usize,
) -> BootstrapValidationDiagnostics {
let requirements = BootstrapValidationRequirements {
sample_count: BOOTSTRAP_VALIDATION_SAMPLES,
min_empty_sample_count: empty_sample_count.map(|_| BOOTSTRAP_VALIDATION_MIN_EMPTY),
max_stale_sample_count: 0,
max_vital_sign_sample_count: 0,
};
let mut failures = Vec::new();
let checks = [
(
"sample_count",
sample_count,
requirements.sample_count,
"equal",
sample_count != requirements.sample_count,
),
(
"empty_sample_count",
empty_sample_count.unwrap_or(0),
BOOTSTRAP_VALIDATION_MIN_EMPTY,
"at_least",
empty_sample_count.is_some_and(|count| count < BOOTSTRAP_VALIDATION_MIN_EMPTY),
),
(
"stale_sample_count",
stale_sample_count,
requirements.max_stale_sample_count,
"at_most",
stale_sample_count != 0,
),
(
"vital_sign_sample_count",
vital_sign_sample_count,
requirements.max_vital_sign_sample_count,
"at_most",
vital_sign_sample_count != 0,
),
];
for (check, observed, required, comparison, failed) in checks {
if failed {
failures.push(BootstrapValidationFailure {
check,
observed,
required,
comparison,
});
}
}
BootstrapValidationDiagnostics {
requirements,
failures,
}
}
#[derive(Debug, Error)]
pub enum BootstrapBaselineError {
#[error("a stable installation_id is required for a local bootstrap baseline")]
@ -505,6 +630,167 @@ mod tests {
assert!(!evaluate_validation(&vital).passed);
}
fn empty_validation_samples(count: usize) -> Vec<BootstrapValidationSample> {
vec![
BootstrapValidationSample {
fresh_tick: true,
calibrated_empty: true,
vital_signs_absent: true,
};
count
]
}
#[test]
fn validation_diagnostics_keep_empty_tolerance_separate_from_vitals() {
let mut samples = empty_validation_samples(12);
samples[0].calibrated_empty = false;
samples[1].calibrated_empty = false;
let passing = evaluate_validation(&samples);
assert!(passing.passed);
assert_eq!(passing.empty_sample_count, 10);
assert!(passing.diagnostics().failures.is_empty());
assert!(passing.diagnostics().failure_summary().is_empty());
samples[2].calibrated_empty = false;
let rejected = evaluate_validation(&samples);
assert!(!rejected.passed);
assert_eq!(
rejected.diagnostics().failures,
vec![BootstrapValidationFailure {
check: "empty_sample_count",
observed: 9,
required: 10,
comparison: "at_least",
}]
);
}
#[test]
fn validation_diagnostics_explain_numeric_vital_veto_even_when_all_empty() {
for count in [1, 5] {
let mut samples = empty_validation_samples(12);
for sample in samples.iter_mut().take(count) {
sample.vital_signs_absent = false;
}
let result = evaluate_validation(&samples);
assert!(!result.passed);
assert_eq!(result.empty_sample_count, 12);
let diagnostics = result.diagnostics();
let json = serde_json::to_value(&diagnostics).unwrap();
assert_eq!(
json["requirements"],
serde_json::json!({
"sample_count": 12,
"min_empty_sample_count": 10,
"max_stale_sample_count": 0,
"max_vital_sign_sample_count": 0,
})
);
assert_eq!(
json["failures"],
serde_json::json!([{
"check": "vital_sign_sample_count",
"observed": count,
"required": 0,
"comparison": "at_most",
}])
);
assert!(diagnostics.failure_summary().contains(&format!(
"numeric vital-sign estimates in {count}/12 samples (0 allowed)"
)));
}
}
#[test]
fn validation_diagnostics_reject_stale_incomplete_and_oversized_windows() {
for count in [0, 11, 13] {
let result = evaluate_validation(&empty_validation_samples(count));
assert!(!result.passed);
let diagnostics = result.diagnostics();
assert_eq!(diagnostics.failures[0].check, "sample_count");
assert_eq!(diagnostics.failures[0].observed, count);
assert_eq!(diagnostics.failures[0].required, 12);
assert!(diagnostics
.failure_summary()
.contains("check the CSI stream"));
}
let mut samples = empty_validation_samples(12);
samples[0].fresh_tick = false;
let result = evaluate_validation(&samples);
assert!(!result.passed);
assert_eq!(result.sample_count, 12);
assert_eq!(result.empty_sample_count, 11);
assert_eq!(
result.diagnostics().failures,
vec![BootstrapValidationFailure {
check: "stale_sample_count",
observed: 1,
required: 0,
comparison: "at_most",
}]
);
}
#[test]
fn validation_diagnostics_report_all_simultaneous_failures() {
let mut samples = empty_validation_samples(11);
samples[0].fresh_tick = false;
samples[1].calibrated_empty = false;
samples[2].vital_signs_absent = false;
let result = evaluate_validation(&samples);
assert!(!result.passed);
let diagnostics = result.diagnostics();
assert_eq!(
diagnostics
.failures
.iter()
.map(|failure| failure.check)
.collect::<Vec<_>>(),
[
"sample_count",
"empty_sample_count",
"stale_sample_count",
"vital_sign_sample_count",
]
);
let summary = diagnostics.failure_summary();
for expected in [
"11 samples collected (exactly 12 required)",
"9 samples reported empty (at least 10 required)",
"1 stale samples (0 allowed)",
"numeric vital-sign estimates in 1/12 samples (0 allowed)",
] {
assert!(summary.contains(expected), "missing {expected}: {summary}");
}
}
#[test]
fn refinement_diagnostics_keep_fresh_count_and_omit_empty_vote_requirement() {
let diagnostics = validation_diagnostics(11, None, 1, 5);
let json = serde_json::to_value(&diagnostics).unwrap();
assert!(json["requirements"].get("min_empty_sample_count").is_none());
assert_eq!(
diagnostics
.failures
.iter()
.map(|failure| failure.check)
.collect::<Vec<_>>(),
[
"sample_count",
"stale_sample_count",
"vital_sign_sample_count",
]
);
assert_eq!(diagnostics.failures[0].observed, 11);
assert!(diagnostics
.failure_summary()
.contains("5/12 samples (0 allowed)"));
let passing = validation_diagnostics(12, None, 0, 0);
assert!(passing.failures.is_empty());
assert!(passing.failure_summary().is_empty());
}
#[test]
fn restart_verifies_the_exact_stored_payload_bytes() {
let dir = tempfile::tempdir().unwrap();

View file

@ -10036,12 +10036,14 @@ async fn calibration_promote_bootstrap(
}
let validation = bootstrap_baseline::evaluate_validation(&samples);
let validation_diagnostics = validation.diagnostics();
if !validation.passed {
return Json(serde_json::json!({
"success": false,
"error_code": "bootstrap_validation_failed",
"error": "The held out empty room check did not pass. The model was not stored.",
"error": format!("The held out empty room check did not pass: {}. The model was not stored.", validation_diagnostics.failure_summary()),
"validation": validation,
"validation_diagnostics": validation_diagnostics,
}));
}
@ -10107,6 +10109,7 @@ async fn calibration_promote_bootstrap(
"success": true,
"message": "Validated startup baseline stored locally.",
"validation": validation,
"validation_diagnostics": validation_diagnostics,
"holdout_window_size": holdout_window_size,
"bootstrap_baseline": stored,
"calibrated_evidence_authorized": false,
@ -10236,6 +10239,12 @@ async fn calibration_refine_bootstrap(
+ Duration::from_millis(BOOTSTRAP_VALIDATION_MIN_SPACING_MS);
}
let validation_diagnostics = bootstrap_baseline::validation_diagnostics(
residuals.len(),
None,
stale_sample_count,
vital_sign_sample_count,
);
if residuals.len() != BOOTSTRAP_VALIDATION_SAMPLES
|| stale_sample_count != 0
|| vital_sign_sample_count != 0
@ -10243,12 +10252,13 @@ async fn calibration_refine_bootstrap(
return Json(serde_json::json!({
"success": false,
"error_code": "bootstrap_refinement_validation_failed",
"error": "The fresh empty-room refinement gate did not pass. The stored baseline was not changed.",
"error": format!("The fresh empty-room refinement gate did not pass: {}. The stored baseline was not changed.", validation_diagnostics.failure_summary()),
"validation": {
"sample_count": residuals.len(),
"stale_sample_count": stale_sample_count,
"vital_sign_sample_count": vital_sign_sample_count,
},
"validation_diagnostics": validation_diagnostics,
}));
}
@ -10335,6 +10345,7 @@ async fn calibration_refine_bootstrap(
Json(serde_json::json!({
"success": true,
"message": "The local empty-room startup baseline was refined.",
"validation_diagnostics": validation_diagnostics,
"validation": {
"sample_count": residuals.len(),
"stale_sample_count": stale_sample_count,