chore: consolidate documentation and clean repository

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Даня Архипов
2026-08-01 18:29:15 +00:00
parent 4413f01ce4
commit 13ce16bedc
596 changed files with 501 additions and 45214 deletions

View File

@@ -1,22 +0,0 @@
---
description: Accessibility and visual QA checklist for product demo
alwaysApply: true
---
# Accessibility and Visual QA Rules
- Контраст должен быть читаемым.
- Фокус-состояния для кнопок обязательны.
- Кнопки должны иметь понятные labels.
- Не использовать только цвет для передачи смысла.
- Текст должен быть читаемым на проекторе.
- Проверить визуально:
- 1920×1080
- 1440×900
- 390×844
- Проверить:
- нет horizontal scroll;
- hero понятен;
- CTA виден;
- demo section понятен;
- mobile layout не развален.

View File

@@ -1,12 +0,0 @@
---
description: Physics engine only after feasibility; prefer predictable demo
alwaysApply: true
---
# Physics Engine Gate Rules
- Physics engine подключать только после feasibility check.
- Если physics увеличивает bundle/лаги/нестабильность — использовать keyframe/state-machine animation.
- Physics не должна ломать управляемость демо.
- Для хакатона важнее предсказуемое демо, чем реалистичная физика.
- Fault/jam можно моделировать логикой, а не реальными столкновениями.

View File

@@ -1,24 +0,0 @@
---
description: React design system and component boundaries for sorter demo
globs: src/**/*.{tsx,ts,css}
alwaysApply: false
---
# React Design System Rules
- Не ломать domain logic.
- UI-компоненты должны быть небольшими и понятными.
- Переиспользовать данные scenarios / criteria / demoSteps.
- Не добавлять тяжёлые UI-библиотеки.
- Не добавлять 3D.
- Не добавлять backend.
- Компоненты:
- HeroSection
- ProductDemoSection
- StorylineStepper
- ScenarioCards
- CriteriaCards
- EngineeringDetails
- Старые инженерные панели можно использовать внутри EngineeringDetails.
- Стили централизовать в styles.css или небольших CSS-блоках.
- Не делать хаотичный refactor всего проекта.

View File

@@ -1,28 +0,0 @@
---
description: Responsive breakpoints and layout rules for product demo
alwaysApply: true
---
# Responsive Product Demo Rules
Breakpoints:
- desktop: >= 1200px
- laptop/tablet landscape: 9001199px
- tablet: 640899px
- mobile: < 640px
Требования:
- body/html/#root не должны создавать horizontal scroll.
- Проверять overflow-x.
- Все секции используют width: min(100%, ...), max-width, padding через clamp().
- На mobile:
- одна колонка;
- крупные кнопки;
- короткие тексты;
- инженерные детали свернуты;
- сцена масштабируется;
- сценарии идут карточками.
- На desktop:
- Hero + demo в понятных секциях;
- 2 колонки только там, где хватает ширины;
- без внутреннего хаотичного скролла.

View File

@@ -1,13 +0,0 @@
---
description: 3D digital twin must prove physical routing, not decoration
alwaysApply: true
---
# 3D Digital Twin Rules
- 3D должно доказывать физическую маршрутизацию, а не быть декором.
- Сцена должна показывать: A, конвейер, накопитель, camera/CV, laser, ultrasonic, stop-gate, actuator, B/C/D, roll-cages.
- 3D связано с simulation state.
- Категория B/C/D должна быть видна через цвет, route и команду.
- Не использовать тяжёлые модели/текстуры.
- Не добавлять 3D-эффекты без инженерного смысла.

View File

@@ -1,33 +0,0 @@
---
description: UI/UX Pro Max — product demo page, not debug dashboard
alwaysApply: true
---
# UI/UX Pro Max Rules
Ты обязан проектировать интерфейс как demo product page, а не debug dashboard.
Обязательные принципы:
- Пользователь должен понять смысл проекта за 10 секунд.
- Первый экран должен объяснять:
1. Что это за система.
2. Что она делает.
3. Как запустить демо.
4. Какой результат будет показан.
- Главный интерфейс должен вести пользователя по цепочке:
Detection → Classification → Command → Routing.
- Не показывать все инженерные панели сразу.
- Инженерные детали должны быть ниже или в отдельном раскрываемом блоке.
- Не должно быть горизонтального скролла.
- Mobile-first адаптив обязателен.
- Основной текст не меньше 16px.
- Кнопки не меньше 44px по высоте.
- На mobile всё в одну колонку.
- На desktop допустима сетка 2 колонки.
- Визуальная иерархия:
Hero → Demo → Result → Scenarios → Criteria → Engineering Details.
- Запрещены мелкие debug-панели на первом экране.
- Запрещены узкие внутренние scroll-панели для сценариев.
- Запрещены fixed width больше viewport.
- Использовать min-width: 0 для grid/flex children.
- SVG/сцена должны масштабироваться через width: 100%, max-width: 100%.

View File

@@ -1,15 +0,0 @@
---
description: Lightweight WebGL budget and 2D fallback requirements
alwaysApply: true
---
# WebGL Performance Budget Rules
- 3D-сцена должна быть lightweight.
- Использовать простые геометрии.
- Ограничить количество объектов.
- Не использовать дорогие shadows/postprocessing.
- Не использовать тяжёлые texture maps.
- Должен быть 2D fallback.
- На mobile должен быть simplified/fallback mode.
- Не должно быть horizontal scroll.

12
.gitignore vendored
View File

@@ -9,17 +9,9 @@ playwright.config.d.ts
.env.*
public/version.json
npm-debug.log*
input_info/extracted/
agent/state/
agent/reports/
docs/HARDWARE_BROWSER_BENCHMARK_LATEST.md
.agent/worktrees/
.demo-preview.pid
.demo-preview.log
test-results/
playwright-report/
blob-report/
releases/backup-*/
scripts/.perf-tmp.cjs
tools/stage2-cad/out/
.demo-preview.pid
.demo-preview.log
__pycache__

View File

@@ -1,169 +0,0 @@
{
"status": "DERIVED_CAD_FINAL",
"notAuthorCad": true,
"sourceOfDimensions": [
"src/domain/pusherMotion.ts PUSHER.halfExtents / stroke / yaw",
"src/domain/physicalLayout.ts CONVEYOR_WIDTH_M=0.5 BELT_TOP_Y=0.7",
"conveyor-clean.glb belt half-width z±0.25 m",
"max item envelope 450×320×320 mm (demo oversize height 400 mm)"
],
"units": "mm",
"paddle": {
"length": 1000,
"height": 280,
"thickness": 50
},
"leadingEdge": {
"thickness": 4
},
"servo": {
"diameter": 90,
"length": 160
},
"shaft": {
"diameter": 20,
"length": 60
},
"axisHeightAboveBeltMm": 142,
"strokeMm": 1620,
"yawDeg": 20.4,
"homeDistanceMm": 400,
"localFrame": "X=width Y=height Z=thickness (Z contact)",
"glb": "public/models/sorter/mechanism-final.glb",
"mountGlb": "public/models/sorter/mechanism-mount-final.glb",
"step": "3d_models/sorter_mechanism.step",
"parts": [
{
"name": "paddle-plate",
"triangles": 12
},
{
"name": "leading-edge",
"triangles": 12
},
{
"name": "rib-l",
"triangles": 12
},
{
"name": "rib-c",
"triangles": 12
},
{
"name": "rib-r",
"triangles": 12
},
{
"name": "carriage",
"triangles": 12
},
{
"name": "bearing-l",
"triangles": 12
},
{
"name": "bearing-r",
"triangles": 12
},
{
"name": "actuator-rod",
"triangles": 80
},
{
"name": "clevis",
"triangles": 12
},
{
"name": "servo-body",
"triangles": 96
},
{
"name": "servo-shaft",
"triangles": 64
},
{
"name": "bracket-l",
"triangles": 12
},
{
"name": "bracket-l-foot",
"triangles": 12
},
{
"name": "bracket-r",
"triangles": 12
},
{
"name": "bracket-r-foot",
"triangles": 12
},
{
"name": "housing",
"triangles": 12
},
{
"name": "cheek-l",
"triangles": 12
},
{
"name": "cheek-r",
"triangles": 12
}
],
"mountParts": [
{
"name": "post-n",
"triangles": 12
},
{
"name": "post-n-base",
"triangles": 12
},
{
"name": "post-n-clamp",
"triangles": 12
},
{
"name": "post-s",
"triangles": 12
},
{
"name": "post-s-base",
"triangles": 12
},
{
"name": "post-s-clamp",
"triangles": 12
},
{
"name": "crossbeam",
"triangles": 12
},
{
"name": "rail-c",
"triangles": 12
},
{
"name": "rail-c-cylinder",
"triangles": 80
},
{
"name": "rail-d",
"triangles": 12
},
{
"name": "rail-d-cylinder",
"triangles": 80
},
{
"name": "drive-housing",
"triangles": 12
},
{
"name": "drive-housing-yellow",
"triangles": 12
}
],
"sha256_glb": "77c96b92e9f615a9124d2255927d9d7dbaf28eee28e0f4d956755c2d37a1f4c0",
"bytes": 22512
}

View File

@@ -1,15 +0,0 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('DERIVED_CAD_FINAL angled paddle diverter — OZON Tech Sorter'),'2;1');
FILE_NAME('sorter_mechanism.step','2026-07-30',('stage2d-final'),('ozone-tech_owl_prime_170'),
'node-gltf-transform-derived','FreeCAD-compatible exchange','');
FILE_SCHEMA(('AUTOMOTIVE_DESIGN'));
ENDSEC;
DATA;
/* DERIVED_CAD_FINAL — engineered from domain PUSHER + conveyor-clean belt geometry. */
/* paddle L=1000 H=280 T=50 mm; leading 4 mm; servo D=90 L=160; stroke=1620; yaw=20.4 deg */
/* local: X width, Y height, Z thickness (Z contact) — matches Rapier collider */
#1 = APPLICATION_CONTEXT('automotive design');
#2 = PRODUCT('sorter_mechanism','Angled paddle diverter DERIVED_CAD_FINAL','',(#1));
ENDSEC;
END-ISO-10303-21;

334
README.md
View File

@@ -1,272 +1,120 @@
# OZON Tech Sorter Simulation
# OZON Sorter Digital Twin
Интеллектуальная роботизированная система сортировки товаров (Frontend MVP).
Web digital twin of an Ozon Tech Track 3 conveyor sorting line: continuous product playback, camera/classification stage, B/C/D routing, and author-CAD diverter motion.
Проект показывает полный цикл работы конвейера в формате **Product Demo**:
- распознавание товара (Mock CV);
- классификация по правилам (габариты, сечение);
- маршрутизация в зоны B/C/D;
- отработка аварийных ситуаций (Jam, Emergency Stop).
## Current product
UI — **Product Demo Landing Page**:
- без горизонтального скролла на desktop / laptop / mobile;
- mobile-first адаптив (одна колонка < 640px);
- **3D digital twin** (React Three Fiber) + 2D SVG fallback;
- storyline Detection → Classification → Decision → Command → Routing;
- карточки сценариев и критериев OZON;
- Engineering Details свёрнуты по умолчанию.
This repository contains:
## 3D Digital Twin
- web-based conveyor sorting simulation (`/`);
- engineering documentation page (`/documentation`);
- author CAD conveyor source (`3d_models/conveer.FCStd`);
- runtime conveyor GLB (`public/models/sorter/conveyor-clean.glb`);
- product STL assets (`public/models/*.stl`);
- camera / classification stage;
- B / C / D route mapping with CAD left/right diverters;
- Rapier-backed product physics (contact routing **not** fully validated).
Главная сцена — цифровая модель программно-аппаратного комплекса:
A → подающий конвейер → накопитель → CV/laser/ultrasonic → stop-gate → actuator → B/C/D.
- Стек: `three` + `@react-three/fiber` + `@react-three/drei` + `@react-three/rapier`.
- **Physics:** на участке drop используется Rapier; на ленте поза задаётся domain state machine (предсказуемое демо).
- Переключатель: **3D Digital Twin** / **2D fallback**.
- На mobile (<640px) по умолчанию 2D; 3D можно включить вручную.
- Если WebGL недоступен — автоматический 2D fallback.
Что доказывает 3D:
- classification → `ROUTE_TO_*` → actuator motion → physical route;
- B зелёный прямой маршрут, C оранжевый roll-cage, D фиолетовый roll-cage;
- C priority при негабарите (даже если объект круглый);
- fault / emergency stop красной подсветкой и остановкой конвейера.
Проверка WebGL: Engineering Details → **3D capability check** (FPS, WebGL status).
### Автоматическая демонстрация
**Главная страница `/`** — Continuous Playback (10 кейсов: B/C/D + low confidence + jam + E-stop):
1. Откройте сайт и нажмите **Play Demo** (или Space).
2. Управление:
- **Space** — play/pause
- **N / →** — следующий кейс, **B / ←** — предыдущий
- **10** — прыжок к кейсу
- **R** — аварийный reset сценария
- **P** — presentation mode, **F** — fullscreen
- **E** — журнал событий
- **0.5×2×** — скорость
3. HUD показывает live `classifyItem` (DIM / K / reason) — не заскриптованный override.
4. Engineering / documentation: `/documentation`
Проверка: `npm test` (200+), `npm run build`, `./scripts/demo-health.sh`.
**3D Verification Checklist** (для защиты):
1. Desktop Chrome/Edge → Play Demo
2. Нет чёрного экрана, товар движется, FPS стабилен (~3060)
3. Console без ошибок
4. Кейсы 910: jam (FAULT) и emergency stop
5. Mobile (<640px): упрощённая сцена / fallback
**Реальные 3D модели** (6 STL, 55%):
- Бутылка, Тарелка, Цилиндр, Короб 300, Короб 400, ЛанчБокс
- Fallback primitives для heavy models (> 1 MB)
- Manifest: `src/data/modelAssets.ts`
## Стек
- Vite, React, TypeScript
- CSS/SVG (адаптивная сцена)
- Vitest для доменных тестов
- Docker + nginx (production static hosting)
## input_info
Проект разработан в соответствии с официальной постановкой задачи OZON Tech Track 3.
**Использованные материалы:**
- остановка_Задача_3_сжато_2.pdf` — полная постановка задачи (правила классификации, схема участка, критерии оценки)
- `doc-1783009942.pdf` — схема рабочей зоны с размерами A/B/C/D
- `doc-1783011400.pdf` — критерии оценки Track 3 (матрица баллов)
- `doc-1782987706.zip` → STEP модели тестовых товаров (11 шт)
- `doc-1782987733.zip` → STL модели тестовых товаров (11 шт)
**Тестовый набор товаров:**
Цилиндр, Шлем, Бутылка, Мешок, Тарелка, Короб 400×400×300, ЛанчБокс, Короб 300×200×200, Пуфик, Ручка, Моющее средство.
**Параметры классификации (официальная постановка `doc-1783095831`, стр. 58):**
- Min dimensions: **строго больше 10×10×10 мм**
- Max dimensions: **строго меньше 450×320×320 мм**
- Roundness: **K > 0.8** (K = 0.8 не считается круглым)
- Conveyor speed: 1.00 м/с
- C-priority: габариты проверяются первыми
> Исторический файл `docs/INPUT_INFO_ANALYSIS.md` содержит устаревшие значения 10×10×2 / K≥0.7 — помечен как SUPERSEDED.
## Как открыть демо
Публично:
```text
https://arhipovdan.ru/
https://www.arhipovdan.ru/
```
Локально на сервере:
```text
http://127.0.0.1:3100/
```
## Как устроен новый UI
1. **Hero** — что это за система, CTA «Запустить демо», цепочка Detection → Routing.
2. **Product Demo** — 3D digital twin (или 2D fallback) + карточка результата + Start / Next / Reset.
3. **Storyline Stepper** — текущий этап цикла.
4. **Scenario Cards** — jury-кейсы карточками (кнопка «Показать»).
5. **Criteria Cards** — покрытие критериев OZON со ссылкой на сценарий.
6. **Engineering Details** — полные техпанели (state machine, sensors, PID, timeline, event log, criteria).
## Как запустить демо
1. Откройте сайт.
2. Нажмите **Запустить демо** в Hero или **Start demo** в блоке демо.
3. Нажимайте **Next step**, чтобы пройти цикл товара.
4. Выберите сценарий в карточках ниже (негабарит, круглый объект, jam и т.д.).
5. Для экспертов откройте **Инженерный режим** / **Engineering Details**.
## Где Engineering Details
Внизу страницы, секция **Engineering Details**. По умолчанию свёрнута. Кнопки «Инженерный режим» в header / hero / demo раскрывают блок и скроллят к нему. Внутри — полная сцена (`variant="full"`) и все инженерные панели.
## Как проверить mobile
1. Откройте DevTools → device toolbar.
2. Выберите `390×844` (или iPhone 12/13).
3. Проверьте:
- одна колонка;
- кнопки ≥ 44px;
- сцена масштабируется (`width: 100%`);
- нет horizontal scroll.
## Как проверить отсутствие horizontal scroll
В консоли браузера:
```js
document.documentElement.scrollWidth <= document.documentElement.clientWidth
```
Должно вернуть `true` на ширинах 1920, 1440 и 390.
## Запуск локально
## Quick start
```bash
npm install
npm run dev
npm ci
npm run dev # http://127.0.0.1:3100
npm test -- --run
npm run build
npm run preview # http://127.0.0.1:3100
```
## Запуск через Docker
## Active routes
```bash
docker compose -p owl -f docker-compose.server.yml up -d --build
| Route | Purpose |
|---|---|
| `/` | Continuous digital-twin simulation |
| `/documentation` | Canonical product / engineering status |
| `*` | Redirects to `/` |
Removed routes (`/details`, `/device-test`) are not part of the product.
## Runtime architecture
```
index.html
→ src/main.tsx
→ src/App.tsx
→ MainPage (/) | DocumentationPage (/documentation)
→ SorterDigitalTwinContinuous
→ ConveyorCadModel (conveyor-clean.glb)
→ product visuals / physics (STL + Rapier)
→ classifier (domain)
→ diverter product state machine (READY→ARMED→OPENING→HOLDING→CLOSING)
```
Compose публикует только loopback-порт:
## Active assets
```yaml
127.0.0.1:3100:80
| Path | Role |
|---|---|
| `3d_models/conveer.FCStd` | Author CAD (canonical source) |
| `public/models/sorter/conveyor-clean.glb` | Active runtime conveyor |
| `public/models/*.stl` | Product models |
| `input_info/*` | Official Ozon packs (PDFs/ZIPs) |
| `official_sources/doc-1783095831.pdf` | Official classifier bounds source cited by code |
### Frozen checksums (SHA-256)
```
3d_models/conveer.FCStd
90c1844a4ca05e26def783d6130fc4b993430dde14307534ef8fbb21c9fac2e6
public/models/sorter/conveyor-clean.glb
1dc7a8d7891bfe756e277ad5368df74cb73410156b2fe0f92845afb8a56f285a
```
## Проверка домена
## Sorting logic
```bash
curl -I http://127.0.0.1:3100/
curl -I https://arhipovdan.ru/
curl -I https://www.arhipovdan.ru/
```
**CURRENT_IMPLEMENTATION_VERIFIED_IN_CODE** (`src/domain/classifier.ts`, `src/domain/pusherMotion.ts`, unit tests).
## Сценарии
| Category | Physical route | Active CAD diverter |
|---|---|---|
| B | STRAIGHT | none |
| C | PHYSICAL_LEFT | LEFT, **45°** |
| D | PHYSICAL_RIGHT | RIGHT, **+45°** |
- `normal_flow` — обычный поток B/C/D.
- `oversized_item` — max dimensions нарушены, маршрут C.
- `round_object` — габариты проходят, roundness K > 0.8, маршрут D.
- `c_priority` — негабарит + круглый → только C (приоритет габаритов).
- `boundary_dimensions` — проверка строгих min/max границ.
- `close_items` — предупреждение spacing/queue, последовательная обработка.
- `low_confidence` — низкая уверенность измерения, rule-based fallback.
- `jam` — застревание у gate, FAULT, остановка конвейера.
- `emergency_stop` — EMERGENCY_STOP, остановка всех движений.
Frozen diverter timing / planes (verified in code + tests):
## Классификация
- rotation duration: **0.50 s**
- opening safety margin: **0.15 s**
- contact plane S: **1.0538** (fallback hinge geometry ≈ 1.0536)
- clear plane S: **1.6000**
Классификация реализована чистой функцией `classifyItem`.
Classifier bounds (code + `official_sources/doc-1783095831.pdf` reference; PDF not re-parsed in this doc pass):
1. Проверяются габариты (строгие границы «больше» / «меньше»).
2. Если нарушены min/max размеры, категория C.
3. Если габариты подходят, проверяется `roundness` K = r_in / R_out.
4. Если `K > 0.8`, категория D.
5. Иначе категория B.
6. Если товар одновременно негабаритный и круглый, приоритет у C.
- dimensions strictly **> 10×10×10 mm** and **< 450×320×320 mm**
- circular when **K > 0.8**
- check order: dimensions → C, else circular → D, else B
Официальные границы (Track 3):
Missing primary brief (do not cite as present): `input_info/extracted/Постановка_Задача_3_сжато_2.pdf`.
- min: width > 10 мм, depth > 10 мм, height > 10 мм;
- max: width < 450 мм, depth < 320 мм, height < 320 мм;
- roundness: круг при K > 0.8 (K = 0.8 не круглый);
- conveyor target speed: 1.00 м/с (close_items: 0.75 м/с).
## Validation
## Исполнительная часть
- Unit tests: **196/196** (`npm test -- --run`)
- Production build: **PASS** (`npm run build`)
- Two-page routing: `/` + `/documentation`
- CAD / GLB checksums: verified against values above
- Diverter frozen angles / duration: covered by unit tests
State machine управляет циклом:
## Current limitations
- `MOVING_TO_CAMERA`
- `DETECTING`
- `MOVING_TO_GATE`
- `WAITING_AT_GATE`
- `CLASSIFYING`
- `ROUTE_TO_B/C/D`
- `RETURN_HOME`
- `FAULT`
- `EMERGENCY_STOP`
- Full contact-only sorting through CAD diverters is **not fully validated**.
- Belt surface-velocity physics (true 1 m/s tangential drive) is **planned**, not complete.
- Per-SKU mass / COM / friction profiles still need calibration.
- Author CAD horn / complete transmission is absent or incomplete in the active GLB (`AUTHOR_CAD_INCOMPLETE`).
- Official compliance claims are limited by the **missing** extracted task PDF and by not re-parsing PDFs in every doc pass.
- Generated screenshots, videos, Gate stage folders, and tool `out/` trees are **not** canonical.
Датчики имитируются по mock-данным: camera bbox/confidence/CV latency, laser measured height, ultrasonic gate detection. Stop-gate закрывается перед классификацией, открывается для B и удерживает товар для C/D перед толкателями.
## Repository policy
## Simplified PID
- Keep author CAD, active runtime assets, official sources, build configs, and tests that protect active behavior.
- Generated outputs and historical media are not product truth.
- Rollback point: git baseline `4413f01` on `dan_branch`.
PID-панель (в Engineering Details) показывает упрощенную имитацию control loop: target speed, actual speed, error, correction и mini graph последних тиков скорости.
В normal flow actual speed приближается к target. В `jam` и `emergency_stop` target становится 0, actual speed визуально падает к 0.
## Тесты
```bash
npm run test
```
Покрыты classifier, PID, сценарии, demo steps и OZON criteria.
## Документация
- `docs/ARCHITECTURE.md` — модули и поток данных.
- `docs/DEMO_SCRIPT.md` — сценарий защиты.
- `docs/SCENARIOS.md` — ожидаемые результаты сценариев.
- `docs/JURY_QA.md` — ответы на вопросы жюри.
- `docs/UI_UX_REDESIGN_AUDIT.md` — план редизайна UI.
- `docs/SUBMISSION_CHECKLIST.md` — checklist перед сдачей.
## Cursor rules
Локальные UI/UX rules в `.cursor/rules/`:
- `ui-ux-pro-max.mdc`
- `responsive-product-demo.mdc`
- `react-design-system.mdc`
- `accessibility-and-visual-qa.mdc`
## Ограничения MVP
- Физика движения дискретная, без динамической модели массы/трения.
- CV является pseudo-CV по mock-данным.
- PID упрощен до демонстрации стабилизации скорости.
- Нет backend, real-time API, сохранения событий и реального ML.
- Нет 3D digital twin.
## Что улучшить дальше
- Добавить WebSocket-телеметрию и replay реальных событий.
- Подключить реальные CV-модели или датасеты.
- Добавить режим manual override для gate/pushers.
- Расширить модель очереди, spacing и recovery после jam.
- Экспортировать event log в отчет смены.
See also: `/documentation` in the running app, and `docs/ENGINEERING.md`.

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +0,0 @@
{
"id": "demo-docs",
"title": "Add agent cycle demo documentation",
"slug": "demo-docs",
"policyCategory": "docs",
"description": "Safe docs-only demo patch for the Implementer MVP. No LLM required. Creates docs/AGENT_CYCLE_DEMO.md inside an isolated git worktree.",
"allowPaths": ["docs/AGENT_CYCLE_DEMO.md"],
"requiresLlm": false,
"patch": {
"type": "create-file",
"path": "docs/AGENT_CYCLE_DEMO.md",
"contentTemplate": "builtin-demo-docs"
}
}

View File

@@ -1,5 +0,0 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

View File

@@ -1,10 +0,0 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

View File

@@ -1,138 +0,0 @@
Вот обновленная и расширенная документация по MQTT-интерфейсу, включающая новую функциональность работы с датчиками расстояния **VL53L0X**, а также оптимизации, появившиеся в коде.
---
# 📡 Документация по MQTT интерфейсу (ESP32 + TMC2209 + Servo + VL53L0X)
## 📌 Общая информация
- **Архитектура**: ESP32 (FreeRTOS задача `mqttTask`).
- **Период опроса телеметрии**: 500 мс.
- **Оптимизация трафика**:
1. Публикация данных происходит **только при изменении значения** (строгое кэширование).
2. Используется статический буфер (`intToString`/`uintToString`) вместо динамического класса `String` для экономии памяти и предотвращения фрагментации кучи.
- **Префиксы**:
- `.../control/...` — топики для **отправки команд** устройству (подписка).
- `.../feedback/...` — топики для **получения статуса/телеметрии** от устройства (публикация).
---
## ⚙️ 1. Управление шаговым двигателем (Motor Control)
| Топик | Тип данных | Описание | Пример Payload |
| :--- | :---: | :--- | :--- |
| `motor/control/rpm` | Integer | Целевая скорость в об/мин (RPM). Отрицательные значения включают реверс. | `-150`, `0`, `300` |
| `motor/control/driver` | String | Аппаратное вкл/выкл драйвера (пин `EN_PIN`). `on` = LOW (вкл), иначе = HIGH (выкл). | `on`, `off` |
| `motor/control/totalsteps/reset`| Any | Сброс счетчика шагов в ноль. Устройство сразу опубликует `"0"` в feedback. | `1`, `reset` |
### Телеметрия двигателя (Motor Feedback)
*(Публикуется только при изменении)*
- `motor/feedback/rpm` (Integer): Текущая скорость.
- `motor/feedback/totalsteps` (Integer): Общее количество шагов.
- `motor/feedback/is_run` (String: `true`/`false`): Двигатель движется.
- `motor/feedback/driver/status` (String: `on`/`off`): Общий статус драйвера.
- `motor/feedback/tmc/status` (String: `on`/`off`): Статус программного включения TMC.
#### Детальная телеметрия TMC2209 (`motor/feedback/tmc/...`)
- `current_percent` (Integer): Текущий % рабочего тока.
- `microsteps` (Integer): Текущий режим микрошага.
- `sg_result` (Integer): Текущее значение StallGuard (нагрузка).
- `interstep_duration` (Integer): Длительность между шагами.
- `status/over_temp` (String: `true`/`false`): Перегрев.
- `status/short_to_ground` (String: `true`/`false`): КЗ на землю.
- `status/open_load` (String: `true`/`false`): Обрыв нагрузки.
- `status/stealth_chop_active` (String: `true`/`false`): Активен ли StealthChop.
- `status/standstill` (String: `true`/`false`): Двигатель в покое.
- `status/current_scaling` (Integer): Внутренний масштабный коэффициент тока.
---
## 🦾 2. Управление сервоприводами (Servo Control)
Поддержка нескольких каналов (`{channel}` от `0` до `MAX_SERVOS - 1`).
### Команды
| Топик (пример для канала 0) | Тип данных | Описание | Пример Payload |
| :--- | :---: | :--- | :--- |
| `servo/control/0/angle` | Integer (0-180) | Установить угол поворота. | `90` |
| `servo/control/0/enable` | String | Включить (`on`, `1`, `true`) или выключить. | `on` |
### Обратная связь
- `servo/0/feedback/status` (String: `on`/`off`): Статус питания сервопривода.
- `servo/0/feedback/angle` (Integer): Текущий установленный угол.
---
## 📏 3. Управление датчиками VL53L0X (Sensor Control) **(НОВОЕ)**
Поддержка до 8 каналов (`VL53L0X_MAX_CHANNELS = 8`). Топики используют параметр `{channel}` (07).
| Топик | Тип данных | Описание | Пример Payload |
| :--- | :---: | :--- | :--- |
| `sensor/control/mode` | Integer | Установка глобального режима измерения (0 до `MODE_COUNT - 1`). | `0`, `1` |
| `sensor/control/mode_name` | String | *Заглушка/Логирование.* Принимает имя режима для отладки. | `LongRange` |
| `sensor/control/calibrate/start/{ch}`| Integer | Начало калибровки: указать близкое расстояние в мм. | `50` |
| `sensor/control/calibrate/finish/{ch}`| Integer | Завершение калибровки: указать дальнее расстояние в мм. | `500` |
| `sensor/control/clear_cal/{ch}` | Any | Сбросить калибровку для указанного канала. | `1` |
| `sensor/control/enable/{ch}` | String | Включить (`on`, `1`, `true`) или выключить конкретный канал. | `on` |
| `sensor/control/publish_all` | Any | **Принудительный сброс кэша.** Заставляет устройство немедленно опубликовать текущие значения всех датчиков, даже если они не изменились. | `1` |
---
## 📊 4. Телеметрия датчиков VL53L0X (Sensor Feedback) **(НОВОЕ)**
### Глобальная информация о режиме
Публикуется только при смене режима измерения:
- `sensor/feedback/mode` (String): Человекочитаемое имя текущего режима (например, "Default", "LongRange").
- `sensor/feedback/mode_id` (Integer): Числовой ID текущего режима.
- `sensor/feedback/max_range` (Integer): Максимальная дальность для текущего режима (в мм).
### Постатусная информация по каналам (`{channel}` = 0..7)
*(Публикуется только при изменении состояния или значения)*
| Топик (пример для канала 0) | Тип данных | Описание |
| :--- | :---: | :--- |
| `sensor/feedback/0/status` | String (`on`/`off`) | Включен ли логически данный канал. |
| `sensor/feedback/0/calibrated` | String (`true`/`false`)| Была ли проведена калибровка для этого канала. |
| `sensor/feedback/0/distance` | Integer или String | **Калиброванное** расстояние в мм. Если значение `65535` (ошибка/вне диапазона), публикуется строка `"out_of_range"`. |
| `sensor/feedback/0/raw` | Integer | **Сырое** (некалиброванное) значение расстояния в мм. |
*Примечание: Топики `distance` и `raw` публикуются только если канал активен (`vl53l0xIsChannelActive`).*
---
## 💡 Важные особенности реализации (Обновлено)
1. **Безопасная работа со строками**: В новом коде добавлены функции `intToString` и `uintToString`, использующие статические буферы. Это полностью устраняет риск фрагментации памяти (heap fragmentation) при частой публикации телеметрии, который был присущ использованию класса `String`.
2. **Принудительная публикация**: Топик `sensor/control/publish_all` сбрасывает кэш значений `distance` и `raw` на `65535`. При следующем цикле (через 500 мс) система "увидит" изменение и гарантированно отправит актуальные данные. Это полезно при подключении нового клиента, которому нужно получить текущее состояние без перезагрузки устройства.
3. **Обработка ошибок дальности**: Если датчик возвращает `65535` (стандартный код ошибки "вне диапазона" или сбоя измерения для VL53L0X), в топик `distance` публикуется понятная строка `"out_of_range"`, а не число, что упрощает обработку на стороне клиента.
4. **Изоляция каналов**: Цикл телеметрии VL53L0X предварительно проверяет `vl53l0xIsChannelPresent(ch)`, поэтому несуществующие или отключенные на аппаратном уровне каналы не создают лишнего трафика.
---
### 🛠️ Примеры использования (CLI / mosquitto)
```bash
# --- Двигатель ---
mosquitto_pub -t "motor/control/rpm" -m "100"
mosquitto_pub -t "motor/control/tmc/stealthchop" -m "on"
# --- Сервопривод (канал 0) ---
mosquitto_pub -t "servo/control/0/enable" -m "on"
mosquitto_pub -t "servo/control/0/angle" -m "90"
# --- Датчики VL53L0X ---
# Включить канал 1
mosquitto_pub -t "sensor/control/enable/1" -m "on"
# Начать калибровку канала 1 (близкая точка 50 мм)
mosquitto_pub -t "sensor/control/calibrate/start/1" -m "50"
# ... передвинуть объект ...
# Завершить калибровку канала 1 (дальняя точка 400 мм)
mosquitto_pub -t "sensor/control/calibrate/finish/1" -m "400"
# Принудительно запросить публикацию всех текущих показаний датчиков
mosquitto_pub -t "sensor/control/publish_all" -m "1"
# Подписаться на все события датчиков для отладки
mosquitto_sub -t "sensor/feedback/#" -v
```

View File

@@ -1,37 +0,0 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the convention is to give header files names that end with `.h'.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@@ -1,46 +0,0 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into the executable file.
The source code of each library should be placed in a separate directory
("lib/your_library_name/[Code]").
For example, see the structure of the following example libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
Example contents of `src/main.c` using Foo and Bar:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
The PlatformIO Library Dependency Finder will find automatically dependent
libraries by scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@@ -1,23 +0,0 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:upesy_wroom]
platform = espressif32
board = upesy_wroom
framework = arduino
monitor_speed = 115200
upload_speed = 921600
upload_port = /dev/ttyUSB0
lib_deps =
knolleary/PubSubClient@^2.8
janelia-arduino/TMC2209@^9.4.0
madhephaestus/ESP32Servo@^3.2.1
adafruit/Adafruit PWM Servo Driver Library@^3.0.3
pololu/VL53L0X@^1.3.1

View File

@@ -1,38 +0,0 @@
#include "config.h"
const char* WIFI_SSID = "TP-Link_3E5C";
const char* WIFI_PASS = "12697571";
const char* MQTT_SERVER = "192.168.0.200";
const int MQTT_PORT = 1883;
const char* MQTT_USER = "test";
const char* MQTT_PASS = "1234";
const char* MQTT_CLIENT_ID = "ESP32_Stepper";
// UART2: RX=16, TX=17
HardwareSerial& TMC_SERIAL = Serial2;
const uint32_t TMC_BAUD_RATE = 115200;
const uint8_t TMC_SERIAL_ADDRESS = 0; // Если MS1 и MS2 на GND
const int16_t TMC_RX_PIN = 16;
const int16_t TMC_TX_PIN = 17;
const int EN_PIN = 18;
const int STEPS_PER_REVOLUTION = 200;
const unsigned long RAMP_DURATION_MS = 2000;
// Ток задается в процентах от максимума (зависит от R_sense).
// Для R_sense=0.11 Ом, 100% ~ 1.77А RMS. Для R_sense=0.15 Ом, 100% ~ 1.2А RMS.
const uint8_t TMC_RUN_CURRENT_PERCENT = 50; // 50% тока при движении
const uint8_t TMC_HOLD_CURRENT_PERCENT = 20; // 20% тока в простое
const uint8_t TMC_STALL_GUARD_THRESH = 10;
const uint16_t TMC_MICROSTEPS = 1;
// I2C Настройка
const int16_t I2C_SDA_PIN = 21;
const int16_t I2C_SCL_PIN = 22;
// ========== SERVO CONFIGURATION ==========
#define SERVO_DEFAULT_CHANNEL 0
#define SERVO_MIN_ANGLE 0
#define SERVO_MAX_ANGLE 180

View File

@@ -1,38 +0,0 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <stdint.h>
#include <Arduino.h>
// WiFi & MQTT
extern const char* WIFI_SSID;
extern const char* WIFI_PASS;
extern const char* MQTT_SERVER;
extern const int MQTT_PORT;
extern const char* MQTT_USER;
extern const char* MQTT_PASS;
extern const char* MQTT_CLIENT_ID;
// UART for TMC2209
extern HardwareSerial& TMC_SERIAL;
extern const uint32_t TMC_BAUD_RATE;
extern const uint8_t TMC_SERIAL_ADDRESS; // Адрес драйвера (0-3)
extern const int16_t TMC_RX_PIN;
extern const int16_t TMC_TX_PIN;
extern const int EN_PIN;
// Motor Params
extern const int STEPS_PER_REVOLUTION; // Базовые шаги мотора (обычно 200)
extern const unsigned long RAMP_DURATION_MS;
// TMC2209 Defaults (Токи в процентах 0-100%)
extern const uint8_t TMC_RUN_CURRENT_PERCENT;
extern const uint8_t TMC_HOLD_CURRENT_PERCENT;
extern const uint8_t TMC_STALL_GUARD_THRESH;
extern const uint16_t TMC_MICROSTEPS;
// I2C Настройка
extern const int16_t I2C_SDA_PIN;
extern const int16_t I2C_SCL_PIN;
#endif

View File

@@ -1,42 +0,0 @@
#include <Arduino.h>
#include <TMC2209.h>
#include "config.h"
#include "motor.h"
#include "vl53l0x_sensor.h"
#include "mqtt_handler.h"
void setup() {
Serial.begin(115200);
// Инициализация мотора (Core 1 context initially)
motorInit();
xTaskCreatePinnedToCore(
sensorTask,
"SensorTask",
4096,
NULL,
1,
NULL,
0
);
// Запуск задачи MQTT на Core 0
xTaskCreatePinnedToCore(
mqttTask,
"MQTT_Task",
20480,
NULL,
3,
NULL,
0 // CORE 0
);
Serial.println("System Initialized. Multi-core ready.");
}
void loop() {
// Loop выполняется на Core 1
motorLoop();
vTaskDelay(pdMS_TO_TICKS(5));
}

View File

@@ -1,295 +0,0 @@
#include "motor.h"
#include "config.h"
static TMC2209 stepper_driver;
static bool tmc_initialized = false;
static SemaphoreHandle_t tmc_uart_mutex = NULL;
volatile unsigned long total_steps = 0;
static int target_rpm = 0;
static int current_rpm_display = 0;
static bool is_ramping = false;
static unsigned long ramp_start_ms = 0;
static float start_speed_sps = 0;
static float end_speed_sps = 0;
static float current_speed_sps = 0;
static int32_t last_vactual = 0;
static bool velocity_sent = false; // Флаг для отправки хотя бы раз
static uint16_t current_microsteps = TMC_MICROSTEPS;
static uint8_t current_run_percent = TMC_RUN_CURRENT_PERCENT;
#define TMC_LOCK() xSemaphoreTake(tmc_uart_mutex, portMAX_DELAY)
#define TMC_UNLOCK() xSemaphoreGive(tmc_uart_mutex)
const float TMC_FCLK = 12800000.0;
const float VACTUAL_FACTOR = 8388608.0 / TMC_FCLK;
int32_t calculateVActual(float microsteps_per_second) {
return (int32_t)(microsteps_per_second * VACTUAL_FACTOR);
}
void motorInit() {
pinMode(EN_PIN, OUTPUT);
digitalWrite(EN_PIN, LOW);
tmc_uart_mutex = xSemaphoreCreateMutex();
TMC_SERIAL.begin(TMC_BAUD_RATE, SERIAL_8N1, TMC_RX_PIN, TMC_TX_PIN);
delay(500);
TMC_LOCK();
stepper_driver.setup(TMC_SERIAL, TMC_BAUD_RATE,
(TMC2209::SerialAddress)TMC_SERIAL_ADDRESS,
TMC_RX_PIN, TMC_TX_PIN);
delay(200);
// Проверка связи
if (!stepper_driver.isCommunicating()) {
Serial.println("ERROR: TMC2209 not communicating!");
TMC_UNLOCK();
tmc_initialized = false;
return;
}
Serial.println("TMC2209 communicating OK");
// Базовая настройка
stepper_driver.setMicrostepsPerStep(TMC_MICROSTEPS);
stepper_driver.setRunCurrent(TMC_RUN_CURRENT_PERCENT);
stepper_driver.setHoldCurrent(TMC_HOLD_CURRENT_PERCENT);
stepper_driver.setHoldDelay(7);
stepper_driver.setStallGuardThreshold(TMC_STALL_GUARD_THRESH);
stepper_driver.enableAutomaticCurrentScaling();
stepper_driver.enableAutomaticGradientAdaptation();
// КРИТИЧНО: Отключаем StealthChop для работы moveAtVelocity()!
stepper_driver.disableStealthChop();
delay(10);
// Включаем CoolStep для энергосбережения
stepper_driver.enableCoolStep();
// Программное включение драйвера
stepper_driver.enable();
delay(100);
tmc_initialized = stepper_driver.isSetupAndCommunicating();
if (tmc_initialized) {
Serial.println("TMC2209 initialized successfully");
TMC2209::Settings settings = stepper_driver.getSettings();
Serial.printf("Run: %d%%, Hold: %d%%, Microsteps: %d, StealthChop: %s\n",
settings.irun_percent, settings.ihold_percent,
settings.microsteps_per_step, settings.stealth_chop_enabled ? "ON" : "OFF");
} else {
Serial.println("ERROR: TMC2209 setup failed!");
}
TMC_UNLOCK();
current_microsteps = TMC_MICROSTEPS;
current_run_percent = TMC_RUN_CURRENT_PERCENT;
}
void motorLoop() {
if (is_ramping) {
unsigned long now = millis();
unsigned long elapsed = now - ramp_start_ms;
if (elapsed >= RAMP_DURATION_MS) {
is_ramping = false;
current_speed_sps = end_speed_sps;
} else {
float progress = (float)elapsed / RAMP_DURATION_MS;
current_speed_sps = start_speed_sps + (end_speed_sps - start_speed_sps) * progress;
}
int32_t vactual = calculateVActual(current_speed_sps);
// Отправляем если: скорость изменилась ИЛИ это первая отправка в рампе
if (abs(vactual - last_vactual) >= 0 || !velocity_sent) {
TMC_LOCK();
stepper_driver.moveAtVelocity(vactual);
TMC_UNLOCK();
last_vactual = vactual;
velocity_sent = true;
Serial.printf("VACTUAL: %d (SPS: %.1f, RPM: %d)\n",
vactual, current_speed_sps, current_rpm_display);
}
unsigned long steps_per_rev = (unsigned long)STEPS_PER_REVOLUTION * current_microsteps;
current_rpm_display = ((unsigned long)abs(current_speed_sps) * 60) / steps_per_rev;
} else if (!velocity_sent && current_speed_sps == 0) {
// Если мотор стоит и скорость не отправлялась - отправляем 0
TMC_LOCK();
stepper_driver.moveAtVelocity(0);
TMC_UNLOCK();
velocity_sent = true;
}
if (current_speed_sps != 0) {
total_steps += (unsigned long)(abs(current_speed_sps) * 0.005);
}
vTaskDelay(pdMS_TO_TICKS(5));
}
void setTargetRPM(int rpm) {
target_rpm = rpm;
if (rpm != 0 && current_rpm_display < 5) resetSteps();
unsigned long steps_per_rev = (unsigned long)STEPS_PER_REVOLUTION * current_microsteps;
start_speed_sps = current_speed_sps;
end_speed_sps = (rpm != 0) ? ((float)rpm * steps_per_rev) / 60.0f : 0;
ramp_start_ms = millis();
is_ramping = true;
velocity_sent = false; // Сбрасываем флаг для новой отправки
Serial.printf("Target RPM: %d -> SPS: %.1f\n", rpm, end_speed_sps);
}
void resetSteps() {
total_steps = 0;
}
unsigned long getMotorSteps() { return total_steps; }
int getCurrentRPM() { return current_rpm_display; }
bool isMotorRunning() { return (abs(current_speed_sps) > 1); }
void tmcSetCurrentPercent(uint8_t run_percent, uint8_t hold_percent) {
if (!tmc_initialized) return;
TMC_LOCK();
stepper_driver.setAllCurrentValues(run_percent, hold_percent, 7);
TMC_UNLOCK();
current_run_percent = run_percent;
}
void tmcSetMicrosteps(uint16_t ms) {
if (!tmc_initialized) return;
TMC_LOCK();
stepper_driver.setMicrostepsPerStep(ms);
TMC_UNLOCK();
current_microsteps = ms;
if (target_rpm != 0) setTargetRPM(target_rpm);
}
void tmcSetStallGuard(uint8_t threshold) {
if (!tmc_initialized) return;
TMC_LOCK();
stepper_driver.setStallGuardThreshold(threshold);
TMC_UNLOCK();
}
void tmcSoftwareEnable(bool enable) {
if (!tmc_initialized) return;
TMC_LOCK();
if (enable) {
stepper_driver.enable();
// После enable нужно заново отправить скорость
velocity_sent = false;
} else {
stepper_driver.moveAtVelocity(0);
stepper_driver.disable();
last_vactual = 0;
current_speed_sps = 0;
is_ramping = false;
}
TMC_UNLOCK();
}
void tmcSetStealthChop(bool enable) {
if (!tmc_initialized) return;
TMC_LOCK();
if (enable) {
stepper_driver.enableStealthChop();
// В StealthChop VACTUAL не работает, останавливаем мотор
stepper_driver.moveAtVelocity(0);
last_vactual = 0;
current_speed_sps = 0;
} else {
stepper_driver.disableStealthChop();
velocity_sent = false;
}
TMC_UNLOCK();
}
void tmcSetCoolStep(bool enable) {
if (!tmc_initialized) return;
TMC_LOCK();
enable ? stepper_driver.enableCoolStep() : stepper_driver.disableCoolStep();
TMC_UNLOCK();
}
bool tmcIsInitialized() { return tmc_initialized; }
bool tmcIsCommunicating() {
if (!tmc_initialized) return false;
TMC_LOCK();
bool res = stepper_driver.isCommunicating();
TMC_UNLOCK();
return res;
}
// Функция проверки состояния EN_PIN
bool checkDriverStatus() {
bool en_state = (digitalRead(EN_PIN) == LOW);
return en_state;
}
// Функция проверки программного состояния TMC
bool checkTmcSoftwareEnable() {
TMC2209::Settings s = tmcGetSettings();
bool tmc_state = s.software_enabled;
return tmc_state;
}
uint16_t tmcGetMicrostepsSetting() { return current_microsteps; }
uint8_t tmcGetRunCurrentPercent() { return current_run_percent; }
TMC2209::Status tmcGetStatus() {
TMC2209::Status s = {};
if (!tmc_initialized) return s;
TMC_LOCK();
s = stepper_driver.getStatus();
TMC_UNLOCK();
return s;
}
TMC2209::Settings tmcGetSettings() {
TMC2209::Settings s = {};
if (!tmc_initialized) return s;
TMC_LOCK();
s = stepper_driver.getSettings();
TMC_UNLOCK();
return s;
}
uint16_t tmcGetStallGuardResult() {
if (!tmc_initialized) return 0;
TMC_LOCK();
uint16_t res = stepper_driver.getStallGuardResult();
TMC_UNLOCK();
return res;
}
uint32_t tmcGetInterstepDuration() {
if (!tmc_initialized) return 0;
TMC_LOCK();
uint32_t res = stepper_driver.getInterstepDuration();
TMC_UNLOCK();
return res;
}
uint16_t tmcGetMicrostepCounter() {
if (!tmc_initialized) return 0;
TMC_LOCK();
uint16_t res = stepper_driver.getMicrostepCounter();
TMC_UNLOCK();
return res;
}

View File

@@ -1,42 +0,0 @@
#ifndef MOTOR_H
#define MOTOR_H
#include <Arduino.h>
#include <TMC2209.h>
void motorInit();
void motorLoop();
// Управление движением (через UART VACTUAL)
void setTargetRPM(int rpm); // Поддерживает отрицательные значения для реверса!
void resetSteps();
// Геттеры состояния движения
unsigned long getMotorSteps(); // Считается программно
int getCurrentRPM();
bool isMotorRunning();
// Управление TMC2209 через UART
void tmcSetCurrentPercent(uint8_t run_percent, uint8_t hold_percent);
void tmcSetMicrosteps(uint16_t ms);
void tmcSetStallGuard(uint8_t threshold);
void tmcSoftwareEnable(bool enable); // Вкл/Выкл драйвер программно
void tmcSetStealthChop(bool enable);
void tmcSetCoolStep(bool enable);
// Расширенная телеметрия
bool tmcIsInitialized();
bool tmcIsCommunicating();
uint16_t tmcGetMicrostepsSetting();
uint8_t tmcGetRunCurrentPercent();
// Структуры статусов для MQTT
TMC2209::Status tmcGetStatus();
TMC2209::Settings tmcGetSettings();
uint16_t tmcGetStallGuardResult();
uint32_t tmcGetInterstepDuration();
uint16_t tmcGetMicrostepCounter();
bool checkDriverStatus();
bool checkTmcSoftwareEnable();
#endif

View File

@@ -1,506 +0,0 @@
#include "mqtt_handler.h"
#include "config.h"
#include "motor.h"
#include "servo_control.h"
#include "vl53l0x_sensor.h"
static WiFiClient espClient;
static PubSubClient client(espClient);
// Кэш телеметрии
static unsigned long last_feedback_time = 0;
static int last_pub_rpm = -1;
static unsigned long last_pub_steps = -1;
static int last_pub_is_run = -1;
// TMC Кэш
static uint16_t last_pub_sg = 65535;
static uint32_t last_pub_interstep = 0;
static uint8_t last_pub_current_pct = 255;
static uint16_t last_pub_microsteps = 0;
// Статусы (битовые флаги)
static int last_pub_over_temp = -1;
static int last_pub_short_gnd = -1;
static int last_pub_open_load = -1;
static int last_pub_stealth_active = -1;
static int last_pub_standstill = -1;
static int last_pub_driver_status = -1;
static int last_pub_tmc_software_enable = -1;
static uint8_t last_pub_current_scaling = 255;
// ============================================
// КЭШИРОВАНИЕ ДЛЯ VL53L0X
// ============================================
#define VL53L0X_MAX_CHANNELS 8
static uint16_t last_pub_vl53_distance[VL53L0X_MAX_CHANNELS] = {65535};
static uint16_t last_pub_vl53_raw[VL53L0X_MAX_CHANNELS] = {65535};
static int last_pub_vl53_status[VL53L0X_MAX_CHANNELS] = {-1};
static int last_pub_vl53_calibrated[VL53L0X_MAX_CHANNELS] = {-1};
static MeasurementMode last_pub_vl53_mode = MODE_COUNT;
// ============================================
// БУФЕРЫ ДЛЯ ПРЕОБРАЗОВАНИЯ
// ============================================
static char int_buffer[16];
static char uint_buffer[16];
static const char* intToString(int value) {
snprintf(int_buffer, sizeof(int_buffer), "%d", value);
return int_buffer;
}
static const char* uintToString(unsigned long value) {
snprintf(uint_buffer, sizeof(uint_buffer), "%lu", value);
return uint_buffer;
}
// ============================================
// WIFI И MQTT ПОДКЛЮЧЕНИЕ
// ============================================
static void setup_wifi() {
Serial.print("Connecting to WiFi");
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
vTaskDelay(pdMS_TO_TICKS(500));
Serial.print(".");
}
Serial.println("\nWiFi Connected");
}
static void resetAllCaches() {
// Motor
last_pub_rpm = -1;
last_pub_steps = (unsigned long)-1;
last_pub_is_run = -1;
last_pub_sg = 65535;
last_pub_interstep = 0;
last_pub_current_pct = 255;
last_pub_microsteps = 0;
last_pub_over_temp = -1;
last_pub_short_gnd = -1;
last_pub_open_load = -1;
last_pub_stealth_active = -1;
last_pub_standstill = -1;
last_pub_current_scaling = 255;
last_pub_driver_status = -1;
last_pub_tmc_software_enable = -1;
// VL53L0X
for (int i = 0; i < VL53L0X_MAX_CHANNELS; i++) {
last_pub_vl53_distance[i] = 65535;
last_pub_vl53_raw[i] = 65535;
last_pub_vl53_status[i] = -1;
last_pub_vl53_calibrated[i] = -1;
}
last_pub_vl53_mode = MODE_COUNT;
}
static void subscribeToAllTopics() {
// Motor
client.subscribe("motor/control/rpm");
client.subscribe("motor/control/driver");
client.subscribe("motor/control/totalsteps/reset");
client.subscribe("motor/control/tmc/current_percent");
client.subscribe("motor/control/tmc/microsteps");
client.subscribe("motor/control/tmc/stallguard");
client.subscribe("motor/control/tmc/enable");
client.subscribe("motor/control/tmc/stealthchop");
client.subscribe("motor/control/tmc/coolstep");
// Servo
client.subscribe("servo/control/+/#");
// VL53L0X
client.subscribe("sensor/control/mode");
client.subscribe("sensor/control/mode_name");
client.subscribe("sensor/control/calibrate/start/+");
client.subscribe("sensor/control/calibrate/finish/+");
client.subscribe("sensor/control/clear_cal/+");
client.subscribe("sensor/control/enable/+");
client.subscribe("sensor/control/publish_all");
}
static void reconnect() {
while (!client.connected()) {
if (client.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASS)) {
resetAllCaches();
subscribeToAllTopics();
Serial.println("MQTT Connected and subscribed");
} else {
Serial.printf("MQTT connection failed, rc=%d, retrying...\n", client.state());
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
}
// ============================================
// ОБРАБОТКА ВХОДЯЩИХ MQTT КОМАНД
// ============================================
static void callback(char* topic, byte* payload, unsigned int length) {
char msg[length + 1];
memcpy(msg, payload, length);
msg[length] = '\0';
if (strcmp(topic, "motor/control/rpm") == 0) {
setTargetRPM(atoi(msg)); // Поддерживает отрицательные для реверса!
}
else if (strcmp(topic, "motor/control/driver") == 0) {
// TMC2209: LOW = Enabled, HIGH = Disabled
bool enable = (strcmp(msg, "on") == 0);
digitalWrite(EN_PIN, enable ? LOW : HIGH);
}
else if (strcmp(topic, "motor/control/totalsteps/reset") == 0) {
resetSteps();
if (client.connected()) client.publish("motor/feedback/totalsteps", "0");
last_pub_steps = 0;
}
// --- TMC Control ---
else if (strcmp(topic, "motor/control/tmc/current_percent") == 0) {
uint8_t pct = atoi(msg);
if (pct <= 100) tmcSetCurrentPercent(pct, pct / 2); // Hold = 50% от Run
}
else if (strcmp(topic, "motor/control/tmc/microsteps") == 0) {
uint16_t ms = atoi(msg);
tmcSetMicrosteps(ms);
}
else if (strcmp(topic, "motor/control/tmc/stallguard") == 0) {
tmcSetStallGuard(atoi(msg));
}
else if (strcmp(topic, "motor/control/tmc/enable") == 0) {
tmcSoftwareEnable(strcmp(msg, "on") == 0);
}
else if (strcmp(topic, "motor/control/tmc/stealthchop") == 0) {
tmcSetStealthChop(strcmp(msg, "on") == 0);
}
else if (strcmp(topic, "motor/control/tmc/coolstep") == 0) {
tmcSetCoolStep(strcmp(msg, "on") == 0);
}
else if (strncmp(topic, "servo/control", 12) == 0) {
handleServoMQTTCommand(topic, msg);
}
// === VL53L0X ===
else if (strcmp(topic, "sensor/control/mode") == 0) {
int mode = atoi(msg);
if (mode >= 0 && mode < MODE_COUNT) {
vl53l0xSetMode((MeasurementMode)mode);
}
}
else if (strcmp(topic, "sensor/control/mode_name") == 0) {
// Маппинг имени режима на ID (если нужно)
// Пока просто логируем
Serial.printf("Mode name request: %s\n", msg);
}
else if (strncmp(topic, "sensor/control/calibrate/start/", 31) == 0) {
int channel = atoi(topic + 31);
int near_mm = atoi(msg);
if (channel >= 0 && channel < VL53L0X_MAX_CHANNELS && near_mm > 0) {
vl53l0xStartCalibration(channel, near_mm);
}
}
else if (strncmp(topic, "sensor/control/calibrate/finish/", 32) == 0) {
int channel = atoi(topic + 32);
int far_mm = atoi(msg);
if (channel >= 0 && channel < VL53L0X_MAX_CHANNELS && far_mm > 0) {
vl53l0xFinishCalibration(channel, far_mm);
}
}
else if (strncmp(topic, "sensor/control/clear_cal/", 25) == 0) {
int channel = atoi(topic + 25);
if (channel >= 0 && channel < VL53L0X_MAX_CHANNELS) {
vl53l0xClearCalibration(channel);
}
}
else if (strncmp(topic, "sensor/control/enable/", 22) == 0) {
int channel = atoi(topic + 22);
bool enable = (strcmp(msg, "on") == 0 || strcmp(msg, "1") == 0 || strcmp(msg, "true") == 0);
if (channel >= 0 && channel < VL53L0X_MAX_CHANNELS) {
vl53l0xEnableChannel(channel, enable);
}
}
else if (strcmp(topic, "sensor/control/publish_all") == 0) {
// Сбрасываем кэш для принудительной публикации
for (int i = 0; i < VL53L0X_MAX_CHANNELS; i++) {
last_pub_vl53_distance[i] = 65535;
last_pub_vl53_raw[i] = 65535;
}
}
}
// ============================================
// ПУБЛИКАЦИЯ MOTOR TELEMETRY
// ============================================
static void publishMotorTelemetry() {
// RPM
int rpm = getCurrentRPM();
if (rpm != last_pub_rpm) {
client.publish("motor/feedback/rpm", intToString(rpm));
last_pub_rpm = rpm;
}
// Steps
unsigned long steps = getMotorSteps();
if (steps != last_pub_steps) {
client.publish("motor/feedback/totalsteps", uintToString(steps));
last_pub_steps = steps;
}
// Is running
int run = isMotorRunning() ? 1 : 0;
if (run != last_pub_is_run) {
client.publish("motor/feedback/is_run", run ? "true" : "false");
last_pub_is_run = run;
}
// TMC
if (tmcIsInitialized()) {
uint8_t pct = tmcGetRunCurrentPercent();
if (pct != last_pub_current_pct) {
client.publish("motor/feedback/tmc/current_percent", intToString(pct));
last_pub_current_pct = pct;
}
uint16_t ms = tmcGetMicrostepsSetting();
if (ms != last_pub_microsteps) {
client.publish("motor/feedback/tmc/microsteps", intToString(ms));
last_pub_microsteps = ms;
}
uint16_t sg = tmcGetStallGuardResult();
if (sg != last_pub_sg) {
client.publish("motor/feedback/tmc/sg_result", intToString(sg));
last_pub_sg = sg;
}
uint32_t interstep = tmcGetInterstepDuration();
if (interstep != last_pub_interstep) {
client.publish("motor/feedback/tmc/interstep_duration", uintToString(interstep));
last_pub_interstep = interstep;
}
TMC2209::Status status = tmcGetStatus();
int ot = (status.over_temperature_warning || status.over_temperature_shutdown) ? 1 : 0;
if (ot != last_pub_over_temp) {
client.publish("motor/feedback/tmc/status/over_temp", ot ? "true" : "false");
last_pub_over_temp = ot;
}
int sgnd = (status.short_to_ground_a || status.short_to_ground_b) ? 1 : 0;
if (sgnd != last_pub_short_gnd) {
client.publish("motor/feedback/tmc/status/short_to_ground", sgnd ? "true" : "false");
last_pub_short_gnd = sgnd;
}
int ol = (status.open_load_a || status.open_load_b) ? 1 : 0;
if (ol != last_pub_open_load) {
client.publish("motor/feedback/tmc/status/open_load", ol ? "true" : "false");
last_pub_open_load = ol;
}
int sa = status.stealth_chop_mode ? 1 : 0;
if (sa != last_pub_stealth_active) {
client.publish("motor/feedback/tmc/status/stealth_chop_active", sa ? "true" : "false");
last_pub_stealth_active = sa;
}
int ss = status.standstill ? 1 : 0;
if (ss != last_pub_standstill) {
client.publish("motor/feedback/tmc/status/standstill", ss ? "true" : "false");
last_pub_standstill = ss;
}
if (status.current_scaling != last_pub_current_scaling) {
client.publish("motor/feedback/tmc/status/current_scaling", intToString(status.current_scaling));
last_pub_current_scaling = status.current_scaling;
}
int cds = checkDriverStatus() ? 1 : 0;
if (cds != last_pub_driver_status) {
client.publish("motor/feedback/driver/status", cds ? "on" : "off");
last_pub_driver_status = cds;
}
int tse = checkTmcSoftwareEnable() ? 1 : 0;
if (tse != last_pub_tmc_software_enable) {
client.publish("motor/feedback/tmc/status", tse ? "on" : "off");
last_pub_tmc_software_enable = tse;
}
}
}
// ============================================
// ПУБЛИКАЦИЯ VL53L0X TELEMETRY
// ============================================
static void publishVL53L0XTelemetry() {
// Публикация режима
MeasurementMode current_mode = vl53l0xGetMode();
if (current_mode != last_pub_vl53_mode) {
const ModeProfile* profile = vl53l0xGetModeProfile();
client.publish("sensor/feedback/mode", profile->name);
client.publish("sensor/feedback/mode_id", intToString(current_mode));
client.publish("sensor/feedback/max_range", intToString(profile->max_range_mm));
last_pub_vl53_mode = current_mode;
}
// Публикация данных с каждого канала
for (uint8_t ch = 0; ch < VL53L0X_MAX_CHANNELS; ch++) {
if (!vl53l0xIsChannelPresent(ch)) continue;
char topic[64];
// Статус канала
int status = vl53l0xIsChannelEnabled(ch) ? 1 : 0;
if (status != last_pub_vl53_status[ch]) {
snprintf(topic, sizeof(topic), "sensor/feedback/%d/status", ch);
client.publish(topic, status ? "on" : "off");
last_pub_vl53_status[ch] = status;
}
// Статус калибровки
int calibrated = vl53l0xIsCalibrated(ch) ? 1 : 0;
if (calibrated != last_pub_vl53_calibrated[ch]) {
snprintf(topic, sizeof(topic), "sensor/feedback/%d/calibrated", ch);
client.publish(topic, calibrated ? "true" : "false");
last_pub_vl53_calibrated[ch] = calibrated;
}
// Только если канал активен, публикуем расстояния
if (vl53l0xIsChannelActive(ch)) {
// Калиброванное расстояние
uint16_t distance = vl53l0xReadDistance(ch);
if (distance != last_pub_vl53_distance[ch]) {
snprintf(topic, sizeof(topic), "sensor/feedback/%d/distance", ch);
if (distance != 65535) {
client.publish(topic, intToString(distance));
} else {
client.publish(topic, "out_of_range");
}
last_pub_vl53_distance[ch] = distance;
}
// Сырое значение
uint16_t raw = vl53l0xReadRawDistance(ch);
if (raw != last_pub_vl53_raw[ch]) {
snprintf(topic, sizeof(topic), "sensor/feedback/%d/raw", ch);
if (raw != 65535) {
client.publish(topic, intToString(raw));
}
last_pub_vl53_raw[ch] = raw;
}
}
}
}
/////
void checkAndPublishServoStatus(uint8_t channel) {
if (channel >= MAX_SERVOS) return;
bool current_state = servo_enabled[channel];
if (current_state != last_published_status[channel]) {
last_published_status[channel] = current_state;
char topic[64];
snprintf(topic, sizeof(topic), "servo/%d/feedback/status", channel);
String status = current_state ? "on" : "off";
client.publish(topic, status.c_str());
Serial.printf("Published servo %d status: %s\n", channel, status.c_str());
}
}
void checkAndPublishServoAngle(uint8_t channel) {
if (channel >= MAX_SERVOS) return;
uint8_t current_angle = current_angles[channel];
if (current_angle != last_published_angles[channel]) {
last_published_angles[channel] = current_angle;
char topic[64];
snprintf(topic, sizeof(topic), "servo/%d/feedback/angle", channel);
char payload[8];
snprintf(payload, sizeof(payload), "%d", current_angle);
client.publish(topic, payload);
Serial.printf("Published servo %d angle: %d\n", channel, current_angle);
}
}
void handleServoMQTTCommand(const char* topic, const char* payload) {
// Парсим топик: servo/control/{channel}/{command}
int channel = -1;
char command[32] = {0};
if (sscanf(topic, "servo/control/%2d/%8s", &channel, command) != 2) {
Serial.printf("Invalid servo topic: %s\n", topic);
return;
}
if (channel < 0 || channel >= MAX_SERVOS) {
Serial.printf("Invalid servo channel: %d\n", channel);
return;
}
Serial.printf("Servo %d command: %s = %s\n", channel, command, payload);
if (strcmp(command, "angle") == 0) {
int angle = atoi(payload);
if (angle >= 0 && angle <= 180) {
setServoAngle(channel, (uint8_t)angle);
checkAndPublishServoAngle(channel);
checkAndPublishServoStatus(channel);
}
}
else if (strcmp(command, "enable") == 0) {
bool enable = (strcmp(payload, "on") == 0 || strcmp(payload, "1") == 0 || strcmp(payload, "true") == 0);
if (enable) {
enableServo(channel);
} else {
disableServo(channel);
}
checkAndPublishServoStatus(channel);
}
}
/////
// ============================================
// ГЛАВНЫЙ ЦИКЛ ПУБЛИКАЦИИ
// ============================================
static void publishTelemetry() {
unsigned long now = millis();
if (now - last_feedback_time >= 500) {
publishMotorTelemetry();
publishVL53L0XTelemetry();
last_feedback_time = now;
}
}
void mqttTask(void *parameter) {
static unsigned long last_stack_check = 0;
setup_wifi();
client.setServer(MQTT_SERVER, MQTT_PORT);
client.setCallback(callback);
servoInit();
for (;;) {
if (!client.connected()) reconnect();
client.loop();
publishTelemetry();
vTaskDelay(pdMS_TO_TICKS(10));
}
}

View File

@@ -1,11 +0,0 @@
#ifndef MQTT_HANDLER_H
#define MQTT_HANDLER_H
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
void mqttTask(void *parameter);
void handleServoMQTTCommand(const char* topic, const char* payload);
#endif

View File

@@ -1,98 +0,0 @@
#include "servo_control.h"
#include "mqtt_handler.h"
#include "config.h"
static Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
static bool servo_initialized = false;
void servoInit() {
Serial.println("Initializing PCA9685 servo driver...");
// Инициализация I2C на пинах 21 (SDA) и 22 (SCL)
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
pwm.begin();
pwm.setOscillatorFrequency(27000000);
pwm.setPWMFreq(50); // 50 Hz для сервоприводов
delay(10);
servo_initialized = true;
Serial.println("PCA9685 initialized successfully");
// Инициализируем все каналы как выключенные
for (int i = 0; i < MAX_SERVOS; i++) {
current_angles[i] = 90; // Начальное положение - середина
servo_enabled[i] = false;
disableServo(i);
}
}
// Преобразование угла (0-180) в длину импульса
uint16_t angleToPulse(uint8_t angle) {
if (angle > 180) angle = 180;
return map(angle, 0, 180, SERVO_MIN_PULSE, SERVO_MAX_PULSE);
}
// Преобразование длины импульса в угол
uint8_t pulseToAngle(uint16_t pulse) {
if (pulse < SERVO_MIN_PULSE) return 0;
if (pulse > SERVO_MAX_PULSE) return 180;
return map(pulse, SERVO_MIN_PULSE, SERVO_MAX_PULSE, 0, 180);
}
void setServoAngle(uint8_t channel, uint8_t angle) {
if (!servo_initialized || channel >= MAX_SERVOS) return;
if (angle > 180) angle = 180;
current_angles[channel] = angle;
servo_enabled[channel] = true;
uint16_t pulse = angleToPulse(angle);
setServoPulse(channel, pulse);
Serial.printf("Servo %d: angle=%d, pulse=%d\n", channel, angle, pulse);
}
void setServoPulse(uint8_t channel, uint16_t pulse) {
if (!servo_initialized || channel >= MAX_SERVOS) return;
// Преобразование микросекунд в тики PCA9685
// PCA9685 имеет 4096 тиков на период при 50Hz = 20000 мкс
// 1 мкс = 4096 / 20000 = 0.2048 тика
double pulselength = 4096.0 / 20000.0; // тиков на микросекунду
uint16_t ticks = pulse * pulselength;
pwm.setPWM(channel, 0, ticks);
}
void enableServo(uint8_t channel) {
if (!servo_initialized || channel >= MAX_SERVOS) return;
servo_enabled[channel] = true;
setServoAngle(channel, current_angles[channel]);
Serial.printf("Servo %d: ENABLED\n", channel);
}
void disableServo(uint8_t channel) {
if (!servo_initialized || channel >= MAX_SERVOS) return;
servo_enabled[channel] = false;
pwm.setPWM(channel, 0, 0); // Отключаем сигнал
Serial.printf("Servo %d: DISABLED\n", channel);
}
uint8_t getServoAngle(uint8_t channel) {
if (channel >= MAX_SERVOS) return 0;
return current_angles[channel];
}
bool isServoEnabled(uint8_t channel) {
if (channel >= MAX_SERVOS) return false;
return servo_enabled[channel];
}

View File

@@ -1,32 +0,0 @@
#ifndef SERVO_H
#define SERVO_H
#include <Arduino.h>
#include <Adafruit_PWMServoDriver.h>
// Хранение текущего состояния сервоприводов
#define MAX_SERVOS 16
static uint8_t current_angles[MAX_SERVOS] = {0};
static bool servo_enabled[MAX_SERVOS] = {false};
static uint8_t last_published_angles[MAX_SERVOS] = {255};
static bool last_published_status[MAX_SERVOS] = {false};
// Минимальная и максимальная длина импульса для сервопривода (в микросекундах)
static const uint16_t SERVO_MIN_PULSE = 600;
static const uint16_t SERVO_MAX_PULSE = 2400;
// Инициализация сервопривода
void servoInit();
// Управление сервоприводом
void setServoAngle(uint8_t channel, uint8_t angle);
void setServoPulse(uint8_t channel, uint16_t pulse);
void enableServo(uint8_t channel);
void disableServo(uint8_t channel);
// Получение состояния
uint8_t getServoAngle(uint8_t channel);
bool isServoEnabled(uint8_t channel);
#endif // SERVO_H

View File

@@ -1,477 +0,0 @@
#include "vl53l0x_sensor.h"
#include "config.h"
// ============================================
// КОНСТАНТЫ
// ============================================
#define TCA9548A_ADDRESS 0x70
#define TCA9548A_CHANNELS 8
#define MIN_DISTANCE_MM 30
#define OUT_OF_RANGE_VALUE 65535
#define FILTER_SIZE 5
// ============================================
// ПРОФИЛИ РЕЖИМОВ
// ============================================
static const ModeProfile MODES[MODE_COUNT] = {
{
"HIGH_ACCURACY",
200000, 14, 10, 0.5f, 500, 2
},
{
"PRECISION",
66000, 14, 10, 0.3f, 1000, 5
},
{
"DEFAULT",
33000, 14, 10, 0.25f, 1200, 15
},
{
"LONG_RANGE",
33000, 18, 14, 0.1f, 2000, 40
},
{
"ULTRA_LONG",
100000, 18, 14, 0.05f, 2500, 80
}
};
// ============================================
// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
// ============================================
static VL53L0X sensor;
static Preferences preferences;
static MeasurementMode current_mode = MODE_DEFAULT;
static bool sensor_present[TCA9548A_CHANNELS] = {false};
static bool channel_enabled[TCA9548A_CHANNELS] = {false};
static CalibrationData calibration[TCA9548A_CHANNELS];
static uint16_t filter_buffer[TCA9548A_CHANNELS][FILTER_SIZE];
static uint8_t filter_index[TCA9548A_CHANNELS] = {0};
static bool calibration_in_progress = false;
static uint8_t calibration_channel = 0;
static uint16_t calibration_near_raw = 0;
static uint16_t calibration_near_known = 0;
// ============================================
// TCA9548A МУЛЬТИПЛЕКСОР
// ============================================
static void TCA9548A_Select(uint8_t channel) {
Wire.beginTransmission(TCA9548A_ADDRESS);
Wire.write((channel < TCA9548A_CHANNELS) ? (1 << channel) : 0x00);
Wire.endTransmission();
delay(3);
}
static void TCA9548A_DisableAll() {
Wire.beginTransmission(TCA9548A_ADDRESS);
Wire.write(0x00);
Wire.endTransmission();
}
static bool checkTCA9548A() {
Wire.beginTransmission(TCA9548A_ADDRESS);
return (Wire.endTransmission() == 0);
}
// ============================================
// NVS (СОХРАНЕНИЕ В FLASH)
// ============================================
static void loadCalibration() {
preferences.begin("vl53_cal", true);
for (uint8_t ch = 0; ch < TCA9548A_CHANNELS; ch++) {
char key[16];
snprintf(key, sizeof(key), "ch%d", ch);
size_t len = preferences.getBytesLength(key);
if (len == sizeof(CalibrationData)) {
preferences.getBytes(key, &calibration[ch], sizeof(CalibrationData));
} else {
calibration[ch].valid = false;
calibration[ch].scale = 1.0f;
calibration[ch].offset = 0.0f;
}
}
int saved_mode = preferences.getInt("mode", MODE_DEFAULT);
if (saved_mode >= 0 && saved_mode < MODE_COUNT) {
current_mode = (MeasurementMode)saved_mode;
}
preferences.end();
}
static void saveCalibration(uint8_t channel) {
preferences.begin("vl53_cal", false);
char key[16];
snprintf(key, sizeof(key), "ch%d", channel);
preferences.putBytes(key, &calibration[channel], sizeof(CalibrationData));
preferences.end();
}
static void saveMode() {
preferences.begin("vl53_cal", false);
preferences.putInt("mode", (int)current_mode);
preferences.end();
}
// ============================================
// ПРИМЕНЕНИЕ РЕЖИМА
// ============================================
static bool applyModeProfile() {
const ModeProfile& profile = MODES[current_mode];
sensor.setTimeout(500);
if (!sensor.init()) {
return false;
}
sensor.setAddress(0x29);
sensor.setSignalRateLimit(profile.signal_rate_limit);
sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, profile.vcsel_prerange);
sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, profile.vcsel_final);
sensor.setMeasurementTimingBudget(profile.timing_budget_us);
return true;
}
// ============================================
// КАЛИБРОВКА И ФИЛЬТРАЦИЯ
// ============================================
static uint16_t applyCalibration(uint8_t channel, uint16_t raw_mm) {
if (!calibration[channel].valid || raw_mm == OUT_OF_RANGE_VALUE) {
return raw_mm;
}
float calibrated = (float)raw_mm * calibration[channel].scale + calibration[channel].offset;
const ModeProfile& profile = MODES[current_mode];
if (calibrated < MIN_DISTANCE_MM) calibrated = MIN_DISTANCE_MM;
if (calibrated > profile.max_range_mm) return OUT_OF_RANGE_VALUE;
return (uint16_t)calibrated;
}
static uint16_t applyFilter(uint8_t channel, uint16_t new_value) {
if (new_value == OUT_OF_RANGE_VALUE) return OUT_OF_RANGE_VALUE;
filter_buffer[channel][filter_index[channel]] = new_value;
filter_index[channel] = (filter_index[channel] + 1) % FILTER_SIZE;
uint32_t sum = 0;
uint8_t count = 0;
for (uint8_t i = 0; i < FILTER_SIZE; i++) {
if (filter_buffer[channel][i] != 0) {
sum += filter_buffer[channel][i];
count++;
}
}
return (count > 0) ? (uint16_t)(sum / count) : new_value;
}
// ============================================
// ПУБЛИЧНЫЕ ФУНКЦИИ
// ============================================
void vl53l0xInit() {
Serial.println("Initializing VL53L0X sensors...");
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
Wire.setClock(400000);
delay(100);
if (!checkTCA9548A()) {
Serial.println("ERROR: TCA9548A not found!");
return;
}
Serial.println("✓ TCA9548A found");
TCA9548A_DisableAll();
loadCalibration();
Serial.printf("✓ Loaded mode: %s\n", MODES[current_mode].name);
Serial.println("\nScanning VL53L0X channels:");
for (uint8_t channel = 0; channel < TCA9548A_CHANNELS; channel++) {
TCA9548A_Select(channel);
delay(20);
if (applyModeProfile()) {
sensor_present[channel] = true;
channel_enabled[channel] = true;
Serial.printf(" ✓ Channel %d: VL53L0X found", channel);
if (calibration[channel].valid) Serial.print(" [CALIBRATED]");
Serial.println();
memset(filter_buffer[channel], 0, sizeof(filter_buffer[channel]));
filter_index[channel] = 0;
} else {
sensor_present[channel] = false;
channel_enabled[channel] = false;
Serial.printf(" ✗ Channel %d: No device\n", channel);
}
TCA9548A_DisableAll();
delay(5);
}
Serial.printf("\n✓ VL53L0X initialized: %d sensors found\n\n",
vl53l0xGetChannelCount());
}
void vl53l0xLoop() {
// Внутренняя логика датчиков (если нужна)
// Сейчас вся публикация в mqtt_handle.cpp
}
bool vl53l0xSetMode(MeasurementMode mode) {
if (mode < 0 || mode >= MODE_COUNT) {
Serial.printf("ERROR: Invalid mode %d\n", mode);
return false;
}
current_mode = mode;
saveMode();
const ModeProfile& profile = MODES[current_mode];
Serial.printf("✓ Mode changed to: %s (max %d mm)\n",
profile.name, profile.max_range_mm);
return true;
}
MeasurementMode vl53l0xGetMode() {
return current_mode;
}
const ModeProfile* vl53l0xGetModeProfile() {
return &MODES[current_mode];
}
uint16_t vl53l0xReadDistance(uint8_t channel) {
if (channel >= TCA9548A_CHANNELS || !sensor_present[channel] || !channel_enabled[channel]) {
return OUT_OF_RANGE_VALUE;
}
TCA9548A_Select(channel);
if (!applyModeProfile()) {
TCA9548A_DisableAll();
return OUT_OF_RANGE_VALUE;
}
uint16_t distance = sensor.readRangeSingleMillimeters();
bool timeout = sensor.timeoutOccurred();
TCA9548A_DisableAll();
if (distance == 65535 || timeout) return OUT_OF_RANGE_VALUE;
const ModeProfile& profile = MODES[current_mode];
if (distance < MIN_DISTANCE_MM || distance > profile.max_range_mm) {
return OUT_OF_RANGE_VALUE;
}
uint16_t filtered = applyFilter(channel, distance);
uint16_t calibrated = applyCalibration(channel, filtered);
return calibrated;
}
uint16_t vl53l0xReadRawDistance(uint8_t channel) {
if (channel >= TCA9548A_CHANNELS || !sensor_present[channel] || !channel_enabled[channel]) {
return OUT_OF_RANGE_VALUE;
}
TCA9548A_Select(channel);
if (!applyModeProfile()) {
TCA9548A_DisableAll();
return OUT_OF_RANGE_VALUE;
}
uint16_t distance = sensor.readRangeSingleMillimeters();
bool timeout = sensor.timeoutOccurred();
TCA9548A_DisableAll();
if (distance == 65535 || timeout) return OUT_OF_RANGE_VALUE;
return distance;
}
bool vl53l0xIsChannelActive(uint8_t channel) {
return (channel < TCA9548A_CHANNELS && sensor_present[channel] && channel_enabled[channel]);
}
int vl53l0xGetChannelCount() {
int count = 0;
for (uint8_t i = 0; i < TCA9548A_CHANNELS; i++) {
if (sensor_present[i]) count++;
}
return count;
}
bool vl53l0xStartCalibration(uint8_t channel, uint16_t near_known_mm) {
if (channel >= TCA9548A_CHANNELS || !sensor_present[channel]) {
Serial.printf("ERROR: Channel %d not available\n", channel);
return false;
}
Serial.printf("Starting calibration for channel %d (near point: %d mm)\n",
channel, near_known_mm);
uint32_t sum = 0;
uint8_t valid = 0;
for (uint8_t i = 0; i < 20; i++) {
uint16_t d = vl53l0xReadRawDistance(channel);
if (d != OUT_OF_RANGE_VALUE) {
sum += d;
valid++;
}
delay(50);
}
if (valid == 0) {
Serial.println("ERROR: Failed to read near point");
return false;
}
calibration_near_raw = (uint16_t)(sum / valid);
calibration_near_known = near_known_mm;
calibration_channel = channel;
calibration_in_progress = true;
Serial.printf("✓ Near point captured: raw=%d mm, actual=%d mm\n",
calibration_near_raw, calibration_near_known);
Serial.println("Now place object at FAR point and call vl53l0xFinishCalibration()");
return true;
}
bool vl53l0xFinishCalibration(uint8_t channel, uint16_t far_known_mm) {
if (!calibration_in_progress || channel != calibration_channel) {
Serial.println("ERROR: Calibration not in progress or wrong channel");
return false;
}
Serial.printf("Finishing calibration for channel %d (far point: %d mm)\n",
channel, far_known_mm);
uint32_t sum = 0;
uint8_t valid = 0;
for (uint8_t i = 0; i < 20; i++) {
uint16_t d = vl53l0xReadRawDistance(channel);
if (d != OUT_OF_RANGE_VALUE) {
sum += d;
valid++;
}
delay(50);
}
if (valid == 0) {
Serial.println("ERROR: Failed to read far point");
calibration_in_progress = false;
return false;
}
uint16_t far_raw = (uint16_t)(sum / valid);
Serial.printf("✓ Far point captured: raw=%d mm, actual=%d mm\n",
far_raw, far_known_mm);
if (far_raw == calibration_near_raw) {
Serial.println("ERROR: Raw values are identical");
calibration_in_progress = false;
return false;
}
float scale = (float)(far_known_mm - calibration_near_known) /
(float)(far_raw - calibration_near_raw);
float offset = (float)calibration_near_known -
(float)calibration_near_raw * scale;
calibration[channel].valid = true;
calibration[channel].near_raw = calibration_near_raw;
calibration[channel].near_known = calibration_near_known;
calibration[channel].far_raw = far_raw;
calibration[channel].far_known = far_known_mm;
calibration[channel].scale = scale;
calibration[channel].offset = offset;
saveCalibration(channel);
Serial.printf("✓ Calibration complete: scale=%.5f, offset=%.2f\n", scale, offset);
calibration_in_progress = false;
return true;
}
void vl53l0xClearCalibration(uint8_t channel) {
if (channel >= TCA9548A_CHANNELS) return;
calibration[channel].valid = false;
calibration[channel].scale = 1.0f;
calibration[channel].offset = 0.0f;
saveCalibration(channel);
Serial.printf("✓ Channel %d calibration cleared\n", channel);
}
bool vl53l0xIsCalibrated(uint8_t channel) {
return (channel < TCA9548A_CHANNELS && calibration[channel].valid);
}
CalibrationData vl53l0xGetCalibration(uint8_t channel) {
if (channel >= TCA9548A_CHANNELS) {
CalibrationData empty = {false, 0, 0, 0, 0, 1.0f, 0.0f};
return empty;
}
return calibration[channel];
}
void vl53l0xEnableChannel(uint8_t channel, bool enable) {
if (channel >= TCA9548A_CHANNELS) return;
if (!sensor_present[channel]) {
Serial.printf("ERROR: Channel %d not present\n", channel);
return;
}
channel_enabled[channel] = enable;
Serial.printf("✓ Channel %d %s\n", channel, enable ? "enabled" : "disabled");
}
bool vl53l0xIsChannelEnabled(uint8_t channel) {
return (channel < TCA9548A_CHANNELS && channel_enabled[channel]);
}
bool vl53l0xIsChannelPresent(uint8_t channel) {
return (channel < TCA9548A_CHANNELS && sensor_present[channel]);
}
void sensorTask(void *parameter) {
vl53l0xInit();
for (;;) {
vl53l0xLoop();
vTaskDelay(pdMS_TO_TICKS(50));
}
}

View File

@@ -1,68 +0,0 @@
#ifndef VL53L0X_SENSOR_H
#define VL53L0X_SENSOR_H
#include <Arduino.h>
#include <VL53L0X.h>
#include <Wire.h>
#include <Preferences.h>
// Режимы измерения
enum MeasurementMode {
MODE_HIGH_ACCURACY = 0,
MODE_PRECISION = 1,
MODE_DEFAULT = 2,
MODE_LONG_RANGE = 3,
MODE_ULTRA_LONG = 4,
MODE_COUNT = 5
};
struct ModeProfile {
const char* name;
uint32_t timing_budget_us;
uint8_t vcsel_prerange;
uint8_t vcsel_final;
float signal_rate_limit;
uint16_t max_range_mm;
uint8_t accuracy_mm;
};
struct CalibrationData {
bool valid;
uint16_t near_raw;
uint16_t near_known;
uint16_t far_raw;
uint16_t far_known;
float scale;
float offset;
};
// Инициализация и цикл
void vl53l0xInit();
void vl53l0xLoop();
// Управление режимами
bool vl53l0xSetMode(MeasurementMode mode);
MeasurementMode vl53l0xGetMode();
const ModeProfile* vl53l0xGetModeProfile();
// Чтение данных
uint16_t vl53l0xReadDistance(uint8_t channel);
uint16_t vl53l0xReadRawDistance(uint8_t channel);
bool vl53l0xIsChannelActive(uint8_t channel);
int vl53l0xGetChannelCount();
// Калибровка
bool vl53l0xStartCalibration(uint8_t channel, uint16_t near_known_mm);
bool vl53l0xFinishCalibration(uint8_t channel, uint16_t far_known_mm);
void vl53l0xClearCalibration(uint8_t channel);
bool vl53l0xIsCalibrated(uint8_t channel);
CalibrationData vl53l0xGetCalibration(uint8_t channel);
// Управление каналами
void vl53l0xEnableChannel(uint8_t channel, bool enable);
bool vl53l0xIsChannelEnabled(uint8_t channel);
bool vl53l0xIsChannelPresent(uint8_t channel);
void sensorTask(void *parameter);
#endif // VL53L0X_SENSOR_H

View File

@@ -1,11 +0,0 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html

View File

@@ -1 +0,0 @@
/testing

View File

@@ -1,61 +0,0 @@
# Интерфейс управления
![](img/gui_interface.png)
## Область Управления
`Целевой RPM` - Задается уставка скорости в диапозоне от -1000 до 1000 оборотов в минуту
`Ток` - Уставка тока в диапозоне 0 - 100%
`StallGuard` -
`Микрошаг` - Уставка микрошага
`Сброс счетчика шагов` - Сброс счетчика шагов
## Область Режимы
`Аппаратное вкл. (Driver EN)` - Аппаратное Включение/Выключение драйвера tmc2209
`Программное вкл. (TMC Chip)` - Программное Включение/Выключение драйвера tmc2209
`StealthChop (Тихий)` - Включение/Выключение тихого режима работы шагового двигателя.
`CoolStep (Энергосбер.)` - Включение/Выключение энергосберегающего режима работы шагового двигателя.
## Область Телеметрия и Статусы
`Текущий RPM (Факт)`
`Всего шагов`
`Вращение`
`SG Result` - Для отслеживания нагрузки на вал или момента срыва шагов. Резкое падение значения sg_result при движении обычно означает столкновение или заклинивание механизма.
`Interstep (ns)`
`Current Scaling`
## Область Ошибки и Флаги
`Перегрев`
`КЗ на землю`
`Обрыв нагрузки`
`StealthChop активен`
`Остановка (Standstill)`
![](img/servo_interface.png)
## Область Управление Серво
Тут можно задать угол сервопривода и отключить сервопривод
## Облать Статус и телеметрия
Вывод информации о сервоприводе включен или выключен. Так же выведен текущий угол.

View File

@@ -1,6 +0,0 @@
#!/bin/bash
python -m venv testing
source testing/bin/activate
pip install -r requirements.txt

View File

@@ -1,460 +0,0 @@
import customtkinter as ctk
import paho.mqtt.client as mqtt
# ================= НАСТРОЙКИ =================
MQTT_BROKER = "192.168.0.200"
MQTT_PORT = 1883
MQTT_USER = "test"
MQTT_PASSWORD = "1234"
MAX_SERVOS = 4
MAX_SENSORS = 8 # Количество каналов VL53L0X
# Цвета
COLOR_OK = "#28a745"
COLOR_ERR = "#dc3545"
COLOR_OFF = "#555555"
COLOR_ACTIVE = "#00d2ff"
COLOR_PENDING = "#ffaa00"
COLOR_WARN = "#ff9900" # Для out_of_range
class MotorSCADA:
def __init__(self):
ctk.set_appearance_mode("Dark")
ctk.set_default_color_theme("blue")
self.root = ctk.CTk()
self.root.title("🚀 Motor, Servo & Sensor SCADA")
self.root.geometry("1200x900")
self.root.minsize(1100, 800)
# Флаги ожидания
self.driver_pending = False
self.tmc_pending = False
self.servo_pending = {i: False for i in range(MAX_SERVOS)}
self.sensor_pending = {i: False for i in range(MAX_SENSORS)}
self.setup_gui()
self.setup_mqtt()
def setup_gui(self):
# --- Шапка ---
header = ctk.CTkFrame(self.root, height=60)
header.pack(fill="x", padx=20, pady=(20, 10))
header.pack_propagate(False)
ctk.CTkLabel(header, text="Motor, Servo & Sensor SCADA", font=ctk.CTkFont(size=24, weight="bold")).pack(side="left", padx=20)
self.lbl_status = ctk.CTkLabel(header, text="● Отключено", text_color=COLOR_ERR, font=ctk.CTkFont(size=16, weight="bold"))
self.lbl_status.pack(side="right", padx=20)
# --- Вкладки ---
self.tabview = ctk.CTkTabview(self.root)
self.tabview.pack(fill="both", expand=True, padx=20, pady=10)
self.tab_motor = self.tabview.add("Шаговый двигатель (TMC2209)")
self.tab_servo = self.tabview.add(f"Сервоприводы (0-{MAX_SERVOS-1})")
self.tab_sensor = self.tabview.add(f"Датчики VL53L0X (0-{MAX_SENSORS-1})")
self.create_motor_tab(self.tab_motor)
self.create_servo_tab(self.tab_servo)
self.create_sensor_tab(self.tab_sensor)
# ================= ВКЛАДКА ШАГОВОГО ДВИГАТЕЛЯ =================
# (Код для мотора остался без изменений, чтобы не раздувать ответ,
# но в реальном файле он должен быть здесь полностью)
def create_motor_tab(self, parent):
grid = ctk.CTkFrame(parent, fg_color="transparent")
grid.pack(fill="both", expand=True)
grid.grid_columnconfigure((0, 1, 2), weight=1, uniform="col")
grid.grid_rowconfigure(0, weight=1)
self.create_control_frame(grid)
self.create_modes_frame(grid)
self.create_telemetry_frame(grid)
def create_control_frame(self, parent):
frame = ctk.CTkFrame(parent)
frame.grid(row=0, column=0, sticky="nsew", padx=(0, 10))
ctk.CTkLabel(frame, text="⚙️ Управление", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=(10, 20))
ctk.CTkLabel(frame, text="Целевой RPM:").pack(anchor="w", padx=20)
rpm_frame = ctk.CTkFrame(frame, fg_color="transparent")
rpm_frame.pack(fill="x", padx=20, pady=5)
self.sld_rpm = ctk.CTkSlider(rpm_frame, from_=-1000, to=1000, command=self.on_rpm_slider_change)
self.sld_rpm.pack(side="left", fill="x", expand=True, padx=(0, 10))
self.ent_rpm = ctk.CTkEntry(rpm_frame, width=80, justify="right")
self.ent_rpm.insert(0, "0"); self.ent_rpm.pack(side="left", padx=(0, 10))
self.ent_rpm.bind("<Return>", self.on_rpm_entry_apply); self.ent_rpm.bind("<FocusOut>", self.on_rpm_entry_apply)
self.lbl_rpm_val = ctk.CTkLabel(rpm_frame, text="0", width=50); self.lbl_rpm_val.pack(side="right")
ctk.CTkLabel(frame, text="Ток (%):").pack(anchor="w", padx=20, pady=(15,0))
cur_frame = ctk.CTkFrame(frame, fg_color="transparent")
cur_frame.pack(fill="x", padx=20, pady=5)
self.sld_current = ctk.CTkSlider(cur_frame, from_=0, to=100, command=self.on_current_change)
self.sld_current.pack(side="left", fill="x", expand=True)
self.lbl_cur_val = ctk.CTkLabel(cur_frame, text="50", width=50); self.lbl_cur_val.pack(side="right", padx=(10, 0))
ctk.CTkLabel(frame, text="StallGuard (0-255):").pack(anchor="w", padx=20, pady=(15,0))
self.ent_sg = ctk.CTkEntry(frame, width=100); self.ent_sg.insert(0, "0")
self.ent_sg.pack(anchor="w", padx=20, pady=5)
ctk.CTkButton(frame, text="Применить SG", width=150, command=self.on_sg_apply).pack(pady=5)
ctk.CTkLabel(frame, text="Микрошаги:").pack(anchor="w", padx=20, pady=(15,0))
self.opt_msteps = ctk.CTkOptionMenu(frame, values=["1", "2", "4", "8", "16", "32", "64", "128", "256"], command=self.on_msteps_change)
self.opt_msteps.set("16"); self.opt_msteps.pack(anchor="w", padx=20, pady=5)
ctk.CTkButton(frame, text="Сбросить счетчик шагов", fg_color="#dc3545", hover_color="#b02a37", command=self.on_reset_steps).pack(pady=20)
def create_modes_frame(self, parent):
frame = ctk.CTkFrame(parent)
frame.grid(row=0, column=1, sticky="nsew", padx=10)
ctk.CTkLabel(frame, text="🔌 Режимы и Включение", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=(10, 20))
self.sw_driver, self.led_driver_fb = self.create_switch_with_feedback(frame, "Аппаратное вкл. (Driver EN)", self.on_driver_change)
self.sw_tmc_enable, self.led_tmc_fb = self.create_switch_with_feedback(frame, "Программное вкл. (TMC Chip)", self.on_tmc_enable_change)
ctk.CTkFrame(frame, height=2, fg_color="#4a4a6a").pack(fill="x", padx=20, pady=15)
self.sw_stealth = self.create_switch(frame, "StealthChop (Тихий)", self.on_stealth_change)
self.sw_cool = self.create_switch(frame, "CoolStep (Энергосбер.)", self.on_cool_change)
def create_telemetry_frame(self, parent):
frame = ctk.CTkFrame(parent)
frame.grid(row=0, column=2, sticky="nsew", padx=(10, 0))
ctk.CTkLabel(frame, text="📊 Телеметрия и Статусы", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=(10, 10))
tel_frame = ctk.CTkFrame(frame); tel_frame.pack(fill="x", padx=10, pady=5)
self.lbl_fb_rpm = self.create_telemetry_row(tel_frame, "Текущий RPM:")
self.lbl_fb_steps = self.create_telemetry_row(tel_frame, "Всего шагов:")
self.lbl_fb_run = self.create_telemetry_row(tel_frame, "Вращение:")
self.lbl_fb_sg = self.create_telemetry_row(tel_frame, "SG Result:")
self.lbl_fb_interstep = self.create_telemetry_row(tel_frame, "Interstep:")
self.lbl_fb_cscale = self.create_telemetry_row(tel_frame, "Current Scaling:")
ctk.CTkLabel(frame, text="🚨 Ошибки и Флаги", font=ctk.CTkFont(size=16, weight="bold")).pack(pady=(15, 5))
stat_frame = ctk.CTkFrame(frame); stat_frame.pack(fill="x", padx=10, pady=5)
self.led_over_temp = self.create_led_row(stat_frame, "Перегрев:")
self.led_short_gnd = self.create_led_row(stat_frame, "КЗ на землю:")
self.led_open_load = self.create_led_row(stat_frame, "Обрыв нагрузки:")
self.led_stealth_act = self.create_led_row(stat_frame, "StealthChop активен:")
self.led_standstill = self.create_led_row(stat_frame, "Остановка:")
# ================= ВКЛАДКА СЕРВОПРИВОДОВ =================
def create_servo_tab(self, parent):
grid = ctk.CTkFrame(parent, fg_color="transparent")
grid.pack(fill="both", expand=True, padx=10, pady=10)
grid.grid_columnconfigure((0, 1), weight=1, uniform="col")
grid.grid_rowconfigure((0, 1), weight=1, uniform="row")
self.servo_ui = {}
for i in range(MAX_SERVOS):
row, col = divmod(i, 2)
frame = ctk.CTkFrame(grid)
frame.grid(row=row, column=col, sticky="nsew", padx=10, pady=10)
self.servo_ui[i] = self.create_servo_card(frame, i)
def create_servo_card(self, parent, channel):
ui = {}
ctk.CTkLabel(parent, text=f"🦾 Сервопривод #{channel}", font=ctk.CTkFont(size=16, weight="bold")).pack(pady=(10, 10))
ctk.CTkLabel(parent, text="Угол (0-180°):").pack(anchor="w", padx=20)
ang_frame = ctk.CTkFrame(parent, fg_color="transparent"); ang_frame.pack(fill="x", padx=20, pady=5)
sld = ctk.CTkSlider(ang_frame, from_=0, to=180, command=lambda val, ch=channel: self.on_servo_ang_slider(ch, val))
sld.pack(side="left", fill="x", expand=True, padx=(0, 10)); sld.set(90)
ent = ctk.CTkEntry(ang_frame, width=60, justify="right"); ent.insert(0, "90"); ent.pack(side="left", padx=(0, 10))
ent.bind("<Return>", lambda event, ch=channel: self.on_servo_ang_entry(ch, event))
ent.bind("<FocusOut>", lambda event, ch=channel: self.on_servo_ang_entry(ch, event))
lbl_val = ctk.CTkLabel(ang_frame, text="90", width=40); lbl_val.pack(side="right")
ui['slider_ang'], ui['entry_ang'], ui['lbl_ang_val'] = sld, ent, lbl_val
sw, led_fb = self.create_switch_with_feedback(parent, "Включить серво", lambda ch=channel: self.on_servo_enable_change(ch))
ui['switch_en'], ui['led_fb'] = sw, led_fb
tel_frame = ctk.CTkFrame(parent); tel_frame.pack(fill="x", padx=10, pady=15)
ui['lbl_fb_ang'] = self.create_telemetry_row(tel_frame, "Текущий угол:")
stat_frame = ctk.CTkFrame(parent); stat_frame.pack(fill="x", padx=10, pady=5)
ui['led_status'] = self.create_led_row(stat_frame, "Статус:")
return ui
# ================= ВКЛАДКА ДАТЧИКОВ VL53L0X =================
def create_sensor_tab(self, parent):
main_frame = ctk.CTkFrame(parent, fg_color="transparent")
main_frame.pack(fill="both", expand=True, padx=10, pady=10)
# --- Глобальное управление ---
global_frame = ctk.CTkFrame(main_frame)
global_frame.pack(fill="x", padx=10, pady=(0, 10))
ctk.CTkLabel(global_frame, text="📏 Глобальные настройки VL53L0X", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=(10, 5))
info_frame = ctk.CTkFrame(global_frame, fg_color="transparent")
info_frame.pack(fill="x", padx=20, pady=10)
self.lbl_sensor_mode = self.create_telemetry_row(info_frame, "Режим:")
self.lbl_sensor_mode_id = self.create_telemetry_row(info_frame, "ID режима:")
self.lbl_sensor_max_range = self.create_telemetry_row(info_frame, "Макс. дальность (мм):")
ctrl_frame = ctk.CTkFrame(global_frame, fg_color="transparent")
ctrl_frame.pack(fill="x", padx=20, pady=(0, 10))
ctk.CTkLabel(ctrl_frame, text="Выбрать режим:").pack(side="left", padx=(0, 10))
# Предполагаем, что режимов от 0 до 4 (Default, HighAccuracy, LongRange, HighSpeed)
self.opt_sensor_mode = ctk.CTkOptionMenu(ctrl_frame, values=["0", "1", "2", "3", "4"], width=100, command=self.on_sensor_mode_change)
self.opt_sensor_mode.set("0"); self.opt_sensor_mode.pack(side="left", padx=(0, 20))
ctk.CTkButton(ctrl_frame, text="🔄 Принудительно обновить все", fg_color="#007bff", hover_color="#0056b3", command=self.on_sensor_publish_all).pack(side="right")
# --- Сетка каналов (Scrollable) ---
scroll_frame = ctk.CTkScrollableFrame(main_frame)
scroll_frame.pack(fill="both", expand=True, padx=10, pady=10)
scroll_frame.grid_columnconfigure((0, 1, 2, 3), weight=1, uniform="col")
self.sensor_ui = {}
for i in range(MAX_SENSORS):
row, col = divmod(i, 4)
frame = ctk.CTkFrame(scroll_frame)
frame.grid(row=row, column=col, sticky="nsew", padx=5, pady=5)
self.sensor_ui[i] = self.create_sensor_card(frame, i)
def create_sensor_card(self, parent, channel):
ui = {}
ctk.CTkLabel(parent, text=f"📡 Канал #{channel}", font=ctk.CTkFont(size=14, weight="bold")).pack(pady=(5, 5))
# Включение и калибровка
sw, led_fb = self.create_switch_with_feedback(parent, "Включить", lambda ch=channel: self.on_sensor_enable_change(ch))
ui['switch_en'], ui['led_fb'] = sw, led_fb
cal_stat_frame = ctk.CTkFrame(parent, fg_color="transparent")
cal_stat_frame.pack(fill="x", padx=10, pady=5)
ctk.CTkLabel(cal_stat_frame, text="Калибровка:").pack(side="left")
ui['led_calibrated'] = ctk.CTkLabel(cal_stat_frame, text="", font=ctk.CTkFont(size=16), text_color=COLOR_OFF)
ui['led_calibrated'].pack(side="right")
# Поля калибровки
cal_ctrl_frame = ctk.CTkFrame(parent, fg_color="transparent")
cal_ctrl_frame.pack(fill="x", padx=10, pady=5)
ctk.CTkLabel(cal_ctrl_frame, text="Ближняя (мм):").pack(anchor="w")
ent_near = ctk.CTkEntry(cal_ctrl_frame, width=60, justify="right"); ent_near.insert(0, "50"); ent_near.pack(side="left", padx=(0, 5))
btn_start = ctk.CTkButton(cal_ctrl_frame, text="Старт", width=60, height=28, command=lambda ch=channel, e=ent_near: self.on_sensor_cal_start(ch, e))
btn_start.pack(side="right")
ctk.CTkLabel(cal_ctrl_frame, text="Дальняя (мм):").pack(anchor="w", pady=(5,0))
ent_far = ctk.CTkEntry(cal_ctrl_frame, width=60, justify="right"); ent_far.insert(0, "500"); ent_far.pack(side="left", padx=(0, 5), pady=(5,0))
btn_finish = ctk.CTkButton(cal_ctrl_frame, text="Финиш", width=60, height=28, command=lambda ch=channel, e=ent_far: self.on_sensor_cal_finish(ch, e))
btn_finish.pack(side="right")
ctk.CTkButton(parent, text="Сбросить калибровку", fg_color="#6c757d", hover_color="#5a6268", height=28, command=lambda ch=channel: self.on_sensor_clear_cal(ch)).pack(pady=5)
ui['ent_near'], ui['ent_far'] = ent_near, ent_far
# Телеметрия
tel_frame = ctk.CTkFrame(parent)
tel_frame.pack(fill="x", padx=5, pady=5)
ui['lbl_dist'] = self.create_telemetry_row(tel_frame, "Дист. (мм):")
ui['lbl_raw'] = self.create_telemetry_row(tel_frame, "Сырое (мм):")
return ui
# ================= ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ GUI =================
def create_switch(self, parent, text, command):
frame = ctk.CTkFrame(parent, fg_color="transparent"); frame.pack(fill="x", padx=10, pady=5)
ctk.CTkLabel(frame, text=text).pack(side="left")
switch = ctk.CTkSwitch(frame, text="", command=command); switch.pack(side="right")
return switch
def create_switch_with_feedback(self, parent, text, command):
frame = ctk.CTkFrame(parent, fg_color="transparent"); frame.pack(fill="x", padx=10, pady=5)
ctk.CTkLabel(frame, text=text).pack(side="left")
feedback_led = ctk.CTkLabel(frame, text="", font=ctk.CTkFont(size=18), text_color=COLOR_OFF)
feedback_led.pack(side="right", padx=(10, 0))
switch = ctk.CTkSwitch(frame, text="", command=command); switch.pack(side="right", padx=(10, 0))
return switch, feedback_led
def create_telemetry_row(self, parent, text):
frame = ctk.CTkFrame(parent, fg_color="transparent"); frame.pack(fill="x", pady=2)
ctk.CTkLabel(frame, text=text, anchor="w", font=ctk.CTkFont(size=12)).pack(side="left")
val_lbl = ctk.CTkLabel(frame, text="-", font=ctk.CTkFont(weight="bold", size=12), text_color=COLOR_ACTIVE, anchor="e")
val_lbl.pack(side="right")
return val_lbl
def create_led_row(self, parent, text):
frame = ctk.CTkFrame(parent, fg_color="transparent"); frame.pack(fill="x", pady=2)
ctk.CTkLabel(frame, text=text, anchor="w", font=ctk.CTkFont(size=12)).pack(side="left")
led_lbl = ctk.CTkLabel(frame, text="", font=ctk.CTkFont(size=16), text_color=COLOR_OFF)
led_lbl.pack(side="right")
return led_lbl
# ================= MQTT =================
def setup_mqtt(self):
self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="python_scada")
self.client.username_pw_set(MQTT_USER, MQTT_PASSWORD)
self.client.on_connect = self.on_mqtt_connect
self.client.on_disconnect = self.on_mqtt_disconnect
self.client.on_message = self.on_mqtt_message
try:
self.client.connect(MQTT_BROKER, MQTT_PORT, 60)
self.client.loop_start()
except Exception as e:
print(f"Ошибка подключения: {e}")
self.update_status(False)
def on_mqtt_connect(self, client, userdata, flags, reason_code, properties):
if reason_code == 0:
self.root.after(0, self.update_status, True)
client.subscribe("motor/feedback/#")
client.subscribe("servo/+/feedback/#")
client.subscribe("sensor/feedback/#") # Подписка на датчики
else:
self.root.after(0, self.update_status, False)
def on_mqtt_disconnect(self, client, userdata, flags, reason_code, properties):
self.root.after(0, self.update_status, False)
def on_mqtt_message(self, client, userdata, msg):
self.root.after(0, self.process_feedback, msg.topic, msg.payload.decode('utf-8'))
def process_feedback(self, topic, val):
parts = topic.split('/')
# --- MOTOR ---
if parts[0] == 'motor' and parts[1] == 'feedback':
if topic == "motor/feedback/rpm": self.lbl_fb_rpm.configure(text=val)
elif topic == "motor/feedback/totalsteps": self.lbl_fb_steps.configure(text=val)
elif topic == "motor/feedback/is_run":
is_run = val == "true"
self.lbl_fb_run.configure(text="Да" if is_run else "Нет", text_color=COLOR_OK if is_run else COLOR_ERR)
elif topic == "motor/feedback/tmc/current_percent": self.lbl_cur_val.configure(text=val); self.sld_current.set(int(val))
elif topic == "motor/feedback/tmc/microsteps": self.opt_msteps.set(val)
elif topic == "motor/feedback/tmc/sg_result": self.lbl_fb_sg.configure(text=val)
elif topic == "motor/feedback/tmc/interstep_duration": self.lbl_fb_interstep.configure(text=val)
elif topic == "motor/feedback/tmc/status/current_scaling": self.lbl_fb_cscale.configure(text=val)
elif topic == "motor/feedback/tmc/status/over_temp": self.update_led(self.led_over_temp, val, True)
elif topic == "motor/feedback/tmc/status/short_to_ground": self.update_led(self.led_short_gnd, val, True)
elif topic == "motor/feedback/tmc/status/open_load": self.update_led(self.led_open_load, val, True)
elif topic == "motor/feedback/tmc/status/stealth_chop_active": self.update_led(self.led_stealth_act, val, False)
elif topic == "motor/feedback/tmc/status/standstill": self.update_led(self.led_standstill, val, False)
elif topic == "motor/feedback/driver/status":
is_on = val == "on"; self.driver_pending = False
if self.sw_driver.get() != is_on: self.sw_driver.select() if is_on else self.sw_driver.deselect()
self.led_driver_fb.configure(text_color=COLOR_OK if is_on else COLOR_OFF)
elif topic == "motor/feedback/tmc/status":
is_on = val == "on"; self.tmc_pending = False
if self.sw_tmc_enable.get() != is_on: self.sw_tmc_enable.select() if is_on else self.sw_tmc_enable.deselect()
self.led_tmc_fb.configure(text_color=COLOR_OK if is_on else COLOR_OFF)
# --- SERVO ---
elif parts[0] == 'servo' and len(parts) == 4 and parts[2] == 'feedback':
try:
ch = int(parts[1]); param = parts[3]
if ch in self.servo_ui:
ui = self.servo_ui[ch]
if param == 'angle': ui['lbl_fb_ang'].configure(text=val)
elif param == 'status':
is_on = val == "on"; self.servo_pending[ch] = False
if ui['switch_en'].get() != is_on: ui['switch_en'].select() if is_on else ui['switch_en'].deselect()
ui['led_fb'].configure(text_color=COLOR_OK if is_on else COLOR_OFF)
self.update_led(ui['led_status'], val, False)
except ValueError: pass
# --- SENSOR ---
elif parts[0] == 'sensor' and parts[1] == 'feedback':
if len(parts) == 3: # Глобальные sensor/feedback/mode...
param = parts[2]
if param == 'mode': self.lbl_sensor_mode.configure(text=val)
elif param == 'mode_id': self.lbl_sensor_mode_id.configure(text=val); self.opt_sensor_mode.set(val)
elif param == 'max_range': self.lbl_sensor_max_range.configure(text=val)
elif len(parts) == 4: # Канальные sensor/feedback/{ch}/...
try:
ch = int(parts[2]); param = parts[3]
if ch in self.sensor_ui:
ui = self.sensor_ui[ch]
if param == 'status':
is_on = val == "on"; self.sensor_pending[ch] = False
if ui['switch_en'].get() != is_on: ui['switch_en'].select() if is_on else ui['switch_en'].deselect()
ui['led_fb'].configure(text_color=COLOR_OK if is_on else COLOR_OFF)
elif param == 'calibrated':
is_cal = val == "true"
ui['led_calibrated'].configure(text_color=COLOR_OK if is_cal else COLOR_OFF)
elif param == 'distance':
if val == "out_of_range":
ui['lbl_dist'].configure(text="Вне диапазона", text_color=COLOR_WARN)
else:
ui['lbl_dist'].configure(text=val, text_color=COLOR_ACTIVE)
elif param == 'raw':
ui['lbl_raw'].configure(text=val, text_color=COLOR_ACTIVE)
except ValueError: pass
# ================= ОБРАБОТЧИКИ СОБЫТИЙ =================
def publish(self, topic, payload):
if self.client.is_connected(): self.client.publish(topic, str(payload), qos=1)
def update_status(self, is_online):
self.lbl_status.configure(text="● Подключено" if is_online else "● Отключено", text_color=COLOR_OK if is_online else COLOR_ERR)
def update_led(self, label, val, is_error):
is_true = val in ["true", "1", "on"]
label.configure(text_color=COLOR_ERR if (is_true and is_error) else (COLOR_OK if is_true else COLOR_OFF))
# --- Motor Handlers ---
def on_rpm_slider_change(self, value):
int_val = int(value); self.ent_rpm.delete(0, ctk.END); self.ent_rpm.insert(0, str(int_val))
self.lbl_rpm_val.configure(text=str(int_val)); self.publish("motor/control/rpm", int_val)
def on_rpm_entry_apply(self, event=None):
try:
int_val = max(-1000, min(1000, int(self.ent_rpm.get())))
self.sld_rpm.set(int_val); self.lbl_rpm_val.configure(text=str(int_val)); self.publish("motor/control/rpm", int_val)
except ValueError: self.ent_rpm.delete(0, ctk.END); self.ent_rpm.insert(0, str(int(self.sld_rpm.get())))
def on_current_change(self, value):
int_val = int(value); self.lbl_cur_val.configure(text=str(int_val)); self.publish("motor/control/tmc/current_percent", int_val)
def on_sg_apply(self):
val = self.ent_sg.get()
if val.isdigit() and 0 <= int(val) <= 255: self.publish("motor/control/tmc/stallguard", int(val))
def on_msteps_change(self, choice): self.publish("motor/control/tmc/microsteps", int(choice))
def on_reset_steps(self): self.publish("motor/control/totalsteps/reset", "1")
def on_driver_change(self):
is_on = self.sw_driver.get(); self.driver_pending = True; self.led_driver_fb.configure(text_color=COLOR_PENDING)
self.publish("motor/control/driver", "on" if is_on else "off")
def on_tmc_enable_change(self):
is_on = self.sw_tmc_enable.get(); self.tmc_pending = True; self.led_tmc_fb.configure(text_color=COLOR_PENDING)
self.publish("motor/control/tmc/enable", "on" if is_on else "off")
def on_stealth_change(self): self.publish("motor/control/tmc/stealthchop", "on" if self.sw_stealth.get() else "off")
def on_cool_change(self): self.publish("motor/control/tmc/coolstep", "on" if self.sw_cool.get() else "off")
# --- Servo Handlers ---
def on_servo_ang_slider(self, channel, value):
int_val = int(value); ui = self.servo_ui[channel]
ui['entry_ang'].delete(0, ctk.END); ui['entry_ang'].insert(0, str(int_val))
ui['lbl_ang_val'].configure(text=str(int_val)); self.publish(f"servo/control/{channel}/angle", int_val)
def on_servo_ang_entry(self, channel, event=None):
ui = self.servo_ui[channel]
try:
int_val = max(0, min(180, int(ui['entry_ang'].get())))
ui['slider_ang'].set(int_val); ui['lbl_ang_val'].configure(text=str(int_val)); self.publish(f"servo/control/{channel}/angle", int_val)
except ValueError: ui['entry_ang'].delete(0, ctk.END); ui['entry_ang'].insert(0, str(int(ui['slider_ang'].get())))
def on_servo_enable_change(self, channel):
ui = self.servo_ui[channel]; is_on = ui['switch_en'].get()
self.servo_pending[channel] = True; ui['led_fb'].configure(text_color=COLOR_PENDING)
self.publish(f"servo/control/{channel}/enable", "on" if is_on else "off")
# --- Sensor Handlers ---
def on_sensor_mode_change(self, choice):
self.publish("sensor/control/mode", int(choice))
def on_sensor_publish_all(self):
self.publish("sensor/control/publish_all", "1")
def on_sensor_enable_change(self, channel):
ui = self.sensor_ui[channel]; is_on = ui['switch_en'].get()
self.sensor_pending[channel] = True; ui['led_fb'].configure(text_color=COLOR_PENDING)
self.publish(f"sensor/control/enable/{channel}", "on" if is_on else "off")
def on_sensor_cal_start(self, channel, entry_widget):
val = entry_widget.get()
if val.isdigit():
self.publish(f"sensor/control/calibrate/start/{channel}", int(val))
def on_sensor_cal_finish(self, channel, entry_widget):
val = entry_widget.get()
if val.isdigit():
self.publish(f"sensor/control/calibrate/finish/{channel}", int(val))
def on_sensor_clear_cal(self, channel):
self.publish(f"sensor/control/clear_cal/{channel}", "1")
def run(self):
self.root.mainloop()
self.client.loop_stop()
self.client.disconnect()
if __name__ == "__main__":
app = MotorSCADA()
app.run()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -1,2 +0,0 @@
paho-mqtt
customtkinter

View File

@@ -1,301 +0,0 @@
> **HISTORICAL / SUPERSEDED:** This document records an earlier project stage. Canonical Track 3 rules are exclusive bounds **> 10×10×10** and **< 450×320×320** mm, roundness **K > 0.8** (`doc-1783095831` pp.58; `src/domain/classifier.ts`). Values 10×10×2 / K≥0.7 below are obsolete.
# 3D REBUILD SPEC — Главная страница как Full-Screen Demo
**Дата:** 2026-07-06
**Цель:** Очистить главную, оставить только Play + непрерывное 3D demo. Детали в /details.
---
## 1. Target UX
**Главная страница (/):**
- Full-screen 3D demo viewport
- Одна кнопка Play (крупная, центр/низ экрана)
- 8 сценариев проигрываются автоматически подряд без ручного вмешательства
- Минимальный overlay: текущий сценарий, категория, прогресс
- Ссылка "Details" → /details
**Цель восприятия:**
- Пользователь за 5 секунд понимает: это 3D demo сортировки
- За 60 секунд видит все 8 сценариев без клика
- Жюри сразу видит физическую маршрутизацию B/C/D
---
## 2. Main Page Scope
**Что остаётся на главной:**
| Компонент | Назначение |
|-----------|------------|
| SorterDigitalTwin | Full-screen 3D canvas |
| Play/Pause кнопка | Запуск/пауза автодемо |
| Mini HUD overlay | Сценарий, категория, step, progress bar |
| "Details" link | Переход на /details |
**Что удаляется с главной:**
| Компонент | Куда идёт |
|-----------|-----------|
| Header | Упрощённый вариант (только logo + Details link) |
| HeroSection | Удаляется полностью (контекст понятен из 3D) |
| ProductDemoSection | Интегрируется в full-screen режим |
| StorylineStepper | → /details |
| ScenarioCards | → /details |
| CPriorityExplanation | → /details |
| CriteriaCards | → /details |
| EngineeringDetails | → /details |
| Footer | Минимизируется или удаляется |
---
## 3. Details Page Scope
**Новая страница /details содержит:**
- HeroSection (адаптированная, короткая)
- ScenarioCards (все 9 сценариев)
- CPriorityExplanation
- CriteriaCards
- EngineeringDetails (полные панели)
- StorylineStepper
- Оригинальный демо-режим со step-by-step управлением
**Это "документация для жюри":**
- Таблицы критериев
- Пояснения правил классификации
- Ручной просмотр отдельных сценариев
- Timeline, PID, Event Log
---
## 4. 8-Scenario Playback Sequence
**Рекомендуемый порядок показа (без jam и emergency для чистого потока):**
| # | Scenario ID | Название | Категория | Что показывает | Длительность |
|---|-------------|----------|-----------|----------------|--------------|
| 1 | `normal_flow` | Normal flow | B/C/D mix | Базовый поток, все маршруты | ~12s |
| 2 | `oversized_item` | Oversized | C | Габариты FAIL → C | ~6s |
| 3 | `round_object` | Round object | D | Roundness K≥0.7 → D | ~8s |
| 4 | `c_priority` | C priority | C | Негабарит + круглый = C | ~5s |
| 5 | `boundary_dimensions` | Boundary | B/C | Граничные размеры | ~8s |
| 6 | `close_items` | Close items | B/B/D | Очередь, spacing warning | ~10s |
| 7 | `low_confidence` | Low confidence | B/D | CV fallback, warning | ~6s |
| 8 | `normal_flow` | Final loop | B/C/D | Повтор normal для loop | ~12s |
**Общее время:** ~67 секунд на полный цикл
**Альтернатива с fault/emergency (если жюри хочет видеть):**
- Заменить #8 на `jam` или `emergency_stop`
- Добавить визуальную индикацию FAULT
---
## 5. 3D Scene Composition
**Что уже есть и работает:**
```
├── SorterDigitalTwin.tsx (главная сцена)
│ ├── Conveyor3D.tsx (лента конвейера)
│ ├── SensorRig3D.tsx (камера, лазер, ultrasonic)
│ ├── Actuator3D.tsx (gate, pusher C, pusher D)
│ ├── SortingZones3D.tsx (зоны B/C/D, roll-cages)
│ ├── Item3D.tsx (товар с реальными STL)
│ ├── RouteArrows3D.tsx (стрелки маршрутов)
│ └── SceneLabels3D.tsx (метки A/B/C/D)
```
**Реальные STL модели (6 шт, ~1.2 MB total):**
| Файл | Размер | Категория | SKU |
|------|--------|-----------|-----|
| `bottle.stl` | 319 KB | D | SKU-007 |
| `box-300.stl` | 29 KB | B | SKU-001 |
| `box-400.stl` | 27 KB | C | SKU-004 |
| `cylinder.stl` | 108 KB | D | SKU-008 |
| `lunchbox.stl` | 566 KB | B | SKU-002 |
| `plate.stl` | 125 KB | D | SKU-006 |
**Procedural fallback (без STL):**
- SKU-003 (Detergent) → box primitive
- SKU-005 (Pouf) → cylinder primitive
- SKU-009 (Pen) → thin box primitive
- SKU-010, SKU-011 → box/cylinder primitives
---
## 6. Camera Storyboard
**Использовать существующие presets из demoDirector.ts:**
| Demo Step | Camera Preset | Что видно |
|-----------|---------------|-----------|
| intro | `overview` | Вся сцена A/B/C/D |
| spawn_item | `overview` | Товар появляется в A |
| move_to_detection | `detectionCloseup` | Движение к камере |
| detection_scan | `detectionCloseup` | Bbox, CV scan |
| dimensions_check | `gateCloseup` | Накопитель, stop-gate |
| roundness_check | `gateCloseup` | Проверка формы |
| decision | `gateCloseup` | Решение B/C/D |
| command_sent | `gateCloseup` | ROUTE_TO_* |
| actuator_move | по категории | Gate/Pusher движение |
| route_item | `routingB/C/D` | Товар уходит в зону |
| result | `resultB/C/D` | Товар в целевой зоне |
| reset_or_next | `overview` | Подготовка к следующему |
**Переходы камеры:**
- Плавная интерполяция (lerp) между presets
- Длительность перехода: 400-600ms
- OrbitControls отключены во время автодемо
---
## 7. Asset Readiness
**STL модели — готовы ✅**
- 6 моделей в `/public/models/`
- Все протестированы, загружаются
- Fallback primitives для остальных
**Items — готовы ✅**
- 11 SKU в `items.ts`
- Параметры соответствуют input_info (10×10×2mm, K≥0.7)
**Scenarios — готовы ✅**
- 9 сценариев в `scenarios.ts`
- Все работают с текущей simulation
**DemoDirector — готов ✅**
- Camera presets определены
- Step sequence есть
- Auto playback реализован
**Что нужно доработать:**
- [ ] Multi-scenario playlist (сейчас один сценарий)
- [ ] Camera transitions (lerp между presets)
- [ ] Full-screen режим без UI chrome
- [ ] Loop mode для 8 сценариев
---
## 8. File Plan
### Новые файлы:
| Файл | Назначение |
|------|------------|
| `src/pages/MainPage.tsx` | Новая главная с full-screen 3D |
| `src/pages/DetailsPage.tsx` | Страница деталей |
| `src/components/DemoOverlay.tsx` | Минимальный HUD для главной |
| `src/components/PlayButton.tsx` | Крупная кнопка Play |
| `src/domain/demoPlaylist.ts` | Playlist из 8 сценариев |
### Изменения в существующих:
| Файл | Изменение |
|------|-----------|
| `src/App.tsx` | Роутинг: / → MainPage, /details → DetailsPage |
| `src/main.tsx` | Добавить React Router |
| `src/domain/demoDirector.ts` | Добавить playlist support, camera lerp |
| `src/components/ThreeD/SorterDigitalTwin.tsx` | Full-screen mode prop |
| `src/styles.css` | Стили для full-screen, overlay, play button |
### Удаление/перемещение:
| Компонент | Действие |
|-----------|----------|
| `HeroSection.tsx` | Перенести в DetailsPage |
| `ScenarioCards.tsx` | Перенести в DetailsPage |
| `CriteriaCards.tsx` | Перенести в DetailsPage |
| `CPriorityExplanation.tsx` | Перенести в DetailsPage |
| `EngineeringDetails.tsx` | Перенести в DetailsPage |
| `StorylineStepper.tsx` | Перенести в DetailsPage |
---
## 9. Risks / Open Questions
### Риски:
| Риск | Митигация |
|------|-----------|
| WebGL context lost на mobile | 2D fallback уже есть (ThreeFallback.tsx) |
| Тяжёлые STL на слабых устройствах | Fallback primitives, DPR снижение |
| Камера дёргается при быстрых переходах | Easing функция, плавный lerp |
| Жюри хочет ручной контроль | Ссылка на /details с полным контролем |
| React Router добавляет bundle | Минимальный импакт (~10KB gzipped) |
### Open Questions:
| Вопрос | Рекомендация |
|--------|--------------|
| Включать ли jam/emergency в playlist? | Нет для чистого показа, да для полноты |
| Показывать ли FPS? | Нет на главной, да в /details |
| Звук/музыка? | Нет, лишняя зависимость |
| Автозапуск при загрузке? | Нет, ждать клика Play |
| Loop бесконечно или остановка? | Loop по умолчанию, стоп по клику |
---
## 10. Step 2 Implementation Checklist
### Phase 1: Routing Setup
- [ ] Установить react-router-dom
- [ ] Создать базовый роутинг в App.tsx
- [ ] Создать пустые MainPage.tsx и DetailsPage.tsx
### Phase 2: Details Page
- [ ] Перенести компоненты из App.tsx в DetailsPage
- [ ] Сохранить всю текущую функциональность
- [ ] Проверить работоспособность /details
### Phase 3: Demo Playlist
- [ ] Создать demoPlaylist.ts с 8 сценариями
- [ ] Добавить playlist mode в demoDirector.ts
- [ ] Автопереключение сценариев по завершении
### Phase 4: Main Page
- [ ] Создать full-screen layout
- [ ] Интегрировать SorterDigitalTwin без HUD
- [ ] Добавить PlayButton.tsx
- [ ] Добавить DemoOverlay.tsx (минимальный)
### Phase 5: Camera Transitions
- [ ] Добавить lerp для camera position
- [ ] Добавить lerp для camera target
- [ ] Синхронизировать с demoDirector steps
### Phase 6: Polish
- [ ] Стили для full-screen
- [ ] Mobile responsive
- [ ] Тестирование всех 8 сценариев
- [ ] Performance check (target: 30+ FPS)
### Phase 7: QA
- [ ] Desktop 1920×1080: Play → 8 сценариев → loop
- [ ] Laptop 1440×900: То же
- [ ] Mobile 390×844: Fallback или simplified 3D
- [ ] /details: Полная функциональность
---
## Summary
**Текущее состояние:**
- 3D сцена работает
- Все компоненты готовы
- STL модели загружены
- DemoDirector с camera presets есть
**Что нужно сделать:**
- Роутинг (/ и /details)
- Playlist из 8 сценариев
- Full-screen режим главной
- Camera lerp transitions
- Минимальный overlay
**Оценка трудозатрат:**
- Phase 1-2: ~1 час
- Phase 3-4: ~2 часа
- Phase 5-6: ~1 час
- Phase 7: ~30 минут
**Total: ~4-5 часов**

View File

@@ -1,124 +0,0 @@
# Implementer MVP (worktree-isolated)
**Status:** Implemented in `agent/cli.mjs`
**Date:** 2026-07-15
Safe, deterministic Implementer cycle that **does not** require an external LLM.
Patches run only inside a git worktree. The agent **never** merges to `main`, **never** deploys, and **never** pushes.
---
## Commands
| Command | Purpose |
| ------- | ------- |
| `dry-run` | Plan only (baseline + propose) |
| `run-once` | Verify baseline only (no patches) |
| `implement [--task <path>]` | Full Implementer cycle |
| `start` | Same as implement, but requires `AUTONOMOUS_AGENT_ENABLED=true` |
| `status` / `stop` / `resume` / `pause` / `report` | Control plane |
```bash
# Recommended successful demo cycle
node agent/cli.mjs implement --task agent/tasks/demo-docs.json
# Or via helper script
bash scripts/agent-implement.sh --task agent/tasks/demo-docs.json
# Gated continuous entry (still one cycle in MVP)
AUTONOMOUS_AGENT_ENABLED=true node agent/cli.mjs start --task agent/tasks/demo-docs.json
```
---
## Implementer cycle
1. **Watchdog** — kill switch, single lock, disk, load, memory
2. **Plan** — load task JSON (default: `agent/tasks/demo-docs.json`)
3. **Validate policy** — allowlist only: `docs`, `tests`, `data-testid`, `a11y`, `small-ui`, `logging-scripts`
4. **Create worktree**`.agent/worktrees/<run-id>` on branch `agent/<run-id>/<task-slug>`
5. **Apply bounded patch** — e.g. create `docs/AGENT_CYCLE_DEMO.md`
6. **Enforce limits** — ≤12 files, ≤800 diff lines, ≤25 min cycle
7. **Verify**`npm test && npm run build` inside the worktree
8. **Score + decide**`ACCEPT_CANDIDATE` or `REJECT`
9. **Report**`agent/reports/<run-id>.json` (+ `latest.json`)
### On REJECT
- Remove worktree
- Delete local agent branch
- Keep the JSON report
### On ACCEPT_CANDIDATE
- Keep worktree + branch
- Print manual review instructions
- Human must cherry-pick / merge into a feature branch if desired
---
## Watchdog
| Check | Fail condition |
| ----- | -------------- |
| Lock | `agent/state/agent.lock` held by a live PID (second agent refused) |
| Kill switch | `agent/state/KILL` present |
| Disk | `df /` usage **> 85%** |
| Load | loadavg 1m **> nproc** |
| Memory | MemAvailable **< 1 GiB** |
```bash
node agent/cli.mjs stop # write KILL
node agent/cli.mjs resume # clear KILL
node agent/cli.mjs status # lock + watchdog snapshot
```
---
## Example task
`agent/tasks/demo-docs.json` — docs-only safe demo:
- Policy: `docs`
- Patch: create `docs/AGENT_CYCLE_DEMO.md`
- No source / physics / deploy / `.env` changes
---
## Limits (also in `LIMITS` in `cli.mjs`)
| Limit | Value |
| ----- | ----: |
| max cycle | 25 min |
| max files | 12 |
| max diff lines | 800 |
| parallel agents | 1 |
Hard forbids: merge to main, production deploy, push, secret/`.env` access.
---
## Manual review after ACCEPT
```bash
# Inspect
cat agent/reports/latest.json
git -C .agent/worktrees/<run-id> show HEAD
# Accept into your feature branch yourself (example)
git cherry-pick <agent-commit-sha>
# Or discard
git worktree remove --force .agent/worktrees/<run-id>
git branch -D agent/<run-id>/demo-docs
```
---
## What this MVP does **not** do
- Call external LLMs
- Modify production nginx/docker/deploy
- Touch `.env`
- Auto-merge or auto-deploy
- Change demo physics (prefer docs-only demo task)

View File

@@ -1,158 +0,0 @@
# Лимиты безопасности AI-агента
**Файл реализации лимитов:** `agent/cli.mjs` → объект `LIMITS`
**Дата:** 2026-07-15
Документ обязателен к соблюдению при любом расширении агента за пределы MVP.
---
## 1. Однозначная политика
| Действие | Разрешено автоматически? |
| -------- | ------------------------ |
| Читать репозиторий, запускать tests/build | Да |
| Писать отчёты в `agent/reports` | Да |
| Планировать задачи (dry-run) | Да |
| Патчить исходники (MVP) | **Нет** |
| Merge в `main`/`master` | **Никогда** |
| Deploy production (:3100 / tunnel) | **Никогда** |
| Печать значений секретов / `.env` | **Никогда** |
| Менять firewall/SSH/системные секреты | **Никогда** |
| Force-push | **Никогда** |
---
## 2. Числовые лимиты (MVP)
| Параметр | Значение | Смысл |
| -------- | -------: | ----- |
| `maxCycleMinutes` | 25 | Стена времени одного цикла |
| `maxChangedFiles` | 12 | Потолок будущего патча |
| `maxDiffLines` | 800 | Анти-мегадифф |
| `maxParallelWorkers` | 1 | Ёмкость сервера |
| `dailyLlmBudgetUsd` | 5 | Бюджет API |
| `minScoreDelta` | 0 | Порог «улучшения» для accept (будущее) |
| `requireTestsPass` | true | Gate |
| `requireBuildPass` | true | Gate |
| `forbidMergeToMain` | true | Hard |
| `forbidProductionDeploy` | true | Hard |
| `forbidSecretAccess` | true | Hard |
Playwright (когда будет в цикле): **1 worker**, не параллелить с локальной LLM.
---
## 3. Kill switch
| Операция | Команда / файл |
| -------- | -------------- |
| Остановить | `node agent/cli.mjs stop` → создаёт `agent/state/KILL` |
| Возобновить | `node agent/cli.mjs resume` → удаляет KILL |
| Пауза статуса | `node agent/cli.mjs pause` |
При наличии `KILL` режимы `dry-run` / `run-once` завершаются с ошибкой.
**Перед живой демонстрацией:** всегда `stop`.
---
## 4. Изоляция изменений
| Правило | Деталь |
| ------- | ------ |
| Ветка | Только `feature/*` или `agent/*`; не `main` |
| Backup | Тег `backup/pre-maximum-demo-realism-20260715` уже создан |
| Worktree (рекомендация L2) | Отдельный worktree для патчей |
| Production dist | Не трогать volume/nginx без человека |
| Preview | `:3101` допустим для проверки; не путать с `:3100` |
---
## 5. Что можно / нельзя автоматизировать
### Безопасно автоматизировать
- Запуск Vitest и production build.
- Генерация JSON-отчётов и scorecard.
- Обновление backlog приоритетов.
- Health-check URL (без секретов).
- Сбор метрик размеров `dist` (без PII).
### Только с проверкой человека
- Любой diff по `src/`.
- Изменение demo playlist / classifier thresholds.
- Включение shadows/effects (FPS).
- Зависимости `package.json`.
- Nginx/Docker/Compose манифесты.
- Создание PR (без auto-merge).
### Никогда автоматически
- Merge в main.
- Deploy на https://arhipovdan.ru/ / :3100.
- Ротация credentials, правка `.env`.
- `git push --force`.
- Отключение или ослабление тестов «чтобы стало зелёным».
- Удаление backup-тегов.
- Запуск локальной LLM параллельно с демо-нагрузкой.
---
## 6. Журналирование и аудит
| Артефакт | Требование |
| -------- | ---------- |
| `audit.jsonl` | Append-only; каждое start/stop/complete |
| Reports | Хранить hypothesis, decision, limits snapshot |
| Запрет | Значения `login`/`password`/`url` из `.env` |
При инциденте: приложить `status.json` + хвост `audit.jsonl` + `latest.json` **без** `.env`.
---
## 7. Ресурсные лимиты хоста (операционные)
Согласовано с `SERVER_CAPACITY_REPORT.md`:
| Ресурс | Лимит для агента |
| ------ | ---------------- |
| Workers | 1 |
| RAM budget | ≤4 GiB суммарно с Chromium |
| GPU | не обязателен; не держать 7B 24/7 |
| Расписание | не во время режима «Демонстрация» |
| Disk | ротация старых `agent/reports` при росте |
---
## 8. Реакция на отказы
| Ситуация | Действие агента |
| -------- | --------------- |
| Tests red | `REJECT_BASELINE`, не планировать фичи |
| Build red | то же |
| Kill switch | немедленный выход |
| Budget exceeded | stop + report |
| Попытка deploy/merge | невозможна в коде MVP; при добавлении — hard fail |
---
## 9. Чеклист расширения Implementer (уровень 2)
Перед включением auto-patch обязательно:
1. [ ] Отдельный git worktree.
2. [ ] Проверка `forbidMergeToMain` интеграционными тестами агента.
3. [ ] Max files/diff enforced до `git commit`.
4. [ ] Авто-PR без auto-merge.
5. [ ] Visual QA optional flag, default off on demo days.
6. [ ] Документированный human approver.
Пока пункты не выполнены — Implementer остаётся **выключенным** (текущее состояние).
---
## 10. Вывод
Безопасность агента важнее скорости итераций. На OwlPrime допустим только **узкий, наблюдаемый, обратимый** контур. MVP это соблюдает: verify-only + kill switch + запрет prod.

View File

@@ -1,71 +0,0 @@
# Architecture
## Module Map
```text
src/data/items.ts
src/data/scenarios.ts
|
v
src/domain/classifier.ts -> src/domain/simulation.ts -> React UI components
| |
| +-> metrics, PID, sensors, event log
v
classification result
```
## Data Flow
1. Scenario selects a sequence of mock items.
2. Simulation feeds one item at a time into zone A.
3. Pseudo-CV and sensors derive measurements from item data.
4. `classifyItem` applies deterministic rules.
5. State machine commands gate and pushers.
6. UI renders SVG scene, panels, metrics, timeline and event log.
## Pseudo-CV
The MVP does not run real ML. Camera output is generated from item dimensions:
- bbox width/depth;
- confidence;
- CV latency;
- detected dimensions.
Low confidence is not ignored: the event log and classification panel show a warning and explain rule-based fallback.
## Sensor Simulation
- Camera is active in `DETECTING`.
- Laser is active during `DETECTING` and `MOVING_TO_GATE`.
- Ultrasonic sensor is active at `WAITING_AT_GATE` and `CLASSIFYING`.
Each sensor keeps active state, last value, latency and last event timestamp.
## Actuator Control
- Stop-gate closes at `WAITING_AT_GATE`.
- B route opens the gate and sends item straight.
- C route keeps the gate closed and extends pusher C.
- D route keeps the gate closed and extends pusher D.
- `RETURN_HOME` retracts mechanisms.
## State Machine
Main cycle:
```text
IDLE -> MOVING_TO_CAMERA -> DETECTING -> MOVING_TO_GATE -> WAITING_AT_GATE
-> CLASSIFYING -> ROUTE_TO_B/C/D -> RETURN_HOME -> next item or IDLE
```
Fault states:
- `FAULT` for jam at gate;
- `EMERGENCY_STOP` for emergency stop scenario.
Both stop conveyor motion and require Reset.
## Metrics
The dashboard tracks processed count, success/error count, avg cycle time, throughput, CV latency, actuator latency, queue length, queue delay and conveyor speed.

View File

@@ -1,187 +0,0 @@
# Архитектура автономного AI-агента
**Статус:** MVP реализован (`agent/cli.mjs`)
**Вердикт ёмкости:** круглосуточный агент **возможен с ограничениями**
**Рекомендуемый вариант LLM:** **C — гибридный** (внешний API primary; локальная 7B+ только эксперименты)
---
## 1. Цель
Агент исследует, анализирует, планирует улучшения, **проверяет** тесты/сборку, пишет отчёты и готовит безопасные следующие шаги — **без** автоматического merge в `main` и **без** production deploy.
MVP **не** выполняет auto-patch кода (verify-only / plan-only).
---
## 2. Компоненты (целевая схема брифа → фактический MVP)
| # | Компонент | Роль в брифе | MVP сейчас |
| - | --------- | ------------ | ---------- |
| 10.1 | Orchestrator | Цикл задач, лимиты, kill switch | **Есть**`agent/cli.mjs` |
| 10.2 | Research Agent | Поиск подходов | Задел в backlog задач |
| 10.3 | Project Analyst | Анализ репо/метрик | Baseline tests+build |
| 10.4 | Planner | Выбор задачи по impact/risk | `proposeTasks()` |
| 10.5 | Implementer | Патчи | **Не авто** — human |
| 10.6 | Test Agent | Vitest/build gates | Встроено в dry-run/run-once |
| 10.7 | Visual QA | Playwright/screenshots | Scripts вручную; не в цикле MVP |
| 10.8 | Physics QA | Инварианты motion | Покрыто частично unit-тестами |
| 10.9 | Critic | Scorecard | `scoreCategories()` |
| 10.10 | Release Manager | Merge/deploy | **Запрещён** всегда |
---
## 3. Схема взаимодействия
```text
┌─────────────┐
│ Kill switch │ agent/state/KILL
└──────┬──────┘
│ blocks
┌──────────┐ plan ┌───▼────────┐ verify ┌────────────┐
│ Planner │──────────►│ Orchestrator│───────────►│ Test/Build │
└──────────┘ └───┬────────┘ └─────┬──────┘
│ │
▼ ▼
agent/reports/*.json ACCEPT / REJECT
Human Implementer (feature branch)
X ──► main / production (forbidden auto)
```
---
## 4. Жизненный цикл задачи
1. **Ingest:** `dry-run` или `run-once`.
2. **Baseline:** `npm test` + `npm run build`.
3. **Propose:** выбрать задачу из backlog (impact/risk) или `stabilize-baseline`.
4. **Score:** категории visual/physics/demo/… → total/100.
5. **Decide:**
- dry-run → `PLAN_ONLY`;
- run-once → `ACCEPT_BASELINE` / `REJECT_BASELINE`.
6. **Persist:** `agent/reports/<id>.json`, `latest.json`, `audit.jsonl`, `status.json`.
7. **Stop conditions:** kill switch, красный baseline, лимиты LIMITS.
*(Будущий уровень 2: Implementer создаёт patch branch → Test → Critic → human merge.)*
---
## 5. Выбор моделей
| Вариант | Описание | Вердикт на OwlPrime |
| ------- | -------- | ------------------- |
| A. Полностью локальный | 7B+ на 2×1080 | Технически возможно, **тяжело**; риск OOM |
| B. Внешние LLM API | Планирование/код вне хоста | Хорошо, нужен бюджет и секреты вне логов |
| **C. Гибридный** | API для reasoning; локально tests/build/Playwright | **Рекомендуется** |
Локально всегда: Vitest, Vite build, git, статический preview.
Внешне: генерация гипотез/диффов (когда Implementer появится).
---
## 6. Хранение состояния
| Путь | Назначение |
| ---- | ---------- |
| `agent/state/status.json` | Текущее состояние (idle/dry-run/…) |
| `agent/state/audit.jsonl` | Append-only журнал событий |
| `agent/state/KILL` | Аварийная остановка |
| `agent/reports/*.json` | Отчёты прогонов |
| `agent/reports/latest.json` | Последний отчёт |
Секреты из `.env` агент **не** должен читать в логи (`forbidSecretAccess: true`).
---
## 7. Очереди
MVP: **очереди нет** — один процесс, `maxParallelWorkers: 1`.
Будущее: файловая очередь `agent/queue/` с lease и TTL, всё ещё single worker на этом хосте.
---
## 8. Безопасность
См. полный документ `AI_AGENT_SAFETY_LIMITS.md`. Кратко:
- запрет merge `main` / `master`;
- запрет production deploy;
- kill switch;
- лимиты файлов/диффа/времени цикла;
- работа только в feature-ветках;
- require green tests/build перед любым будущим патчем.
---
## 9. Бюджет
| Статья | Лимит MVP |
| ------ | --------: |
| dailyLlmBudgetUsd | 5 |
| Parallel LLM calls | 1 effective |
| Cycle wall time | ≤25 min |
Превышение бюджета → stop + report, без «догоняющих» ретраев.
---
## 10. Мониторинг
| Сигнал | Как смотреть |
| ------ | ------------ |
| Status | `npm run agent:status` / `node agent/cli.mjs status` |
| Audit | `agent/state/audit.jsonl` |
| Host load | `uptime`, `free -h` (не в агенте) |
| Demo health | `npm run demo:health` |
Во время показа жюри: `node agent/cli.mjs stop`.
---
## 11. Rollback
| Уровень | Механизм |
| ------- | -------- |
| Git code | тег `backup/pre-maximum-demo-realism-20260715`, ветки feature/* |
| Agent run | stop/kill; отчёты не мутируют prod |
| Preview | `scripts/demo-stop.sh` |
| Production | только ручной redeploy предыдущего образа/dist |
Авто-rollback кода в MVP не требуется, т.к. авто-патча нет.
---
## 12. План внедрения
| Фаза | Содержание | Статус |
| ---- | ---------- | ------ |
| 0 | Orchestrator + dry-run/run-once + limits | **Done** |
| 1 | Подключить внешний LLM к Planner (без write) | Open |
| 2 | Implementer пишет patch в `agent/work/*` branch | Open |
| 3 | Visual QA 1 worker в цикле | Open |
| 4 | Human approval gate → PR (не auto-merge) | Open |
| 5 | Staging preview auto-update | Open |
| ∞ | Production | **Никогда автоматически** |
---
## 13. Команды
```bash
npm run agent:dry-run
npm run agent:run-once
npm run agent:status
# или
node agent/cli.mjs dry-run|run-once|status|stop|resume|pause|report
./scripts/agent-dry-run.sh
./scripts/agent-run-once.sh
```
---
## 14. Вывод
Архитектура соответствует брифу на уровне **безопасного оркестратора**. Полная автономия «исследуй→патчь→деплой» на этом сервере **нецелесообразна и запрещена**. Гибридный Variant C + 1 worker + human merge — единственный устойчивый путь.

View File

@@ -1,614 +0,0 @@
> **HISTORICAL / SUPERSEDED:** This document records an earlier project stage. Canonical Track 3 rules are exclusive bounds **> 10×10×10** and **< 450×320×320** mm, roundness **K > 0.8** (`doc-1783095831` pp.58; `src/domain/classifier.ts`). Values 10×10×2 / K≥0.7 below are obsolete.
# Auto Demo Integration — Final Report
## Executive Summary
**Task**: Завершить 3D Digital Twin rework, интегрировав Auto Demo для полноценной автоматической демонстрации (с 60% до 100%).
**Status**: ✅ **COMPLETE** — Auto Demo полностью интегрирован и работает
**Key Achievement**: Пользователь теперь нажимает одну кнопку "🎬 Запустить автодемо" и видит полный автоматический цикл без необходимости ручного "Next step".
---
## Что было сделано на предыдущем этапе (60%)
### ✅ Completed Previously
1. **Real STL Models**: 6 моделей загружены (bottle, plate, cylinder, boxes, lunchbox)
2. **Clean Labels**: Reduced from 12+ to max 5 default
3. **Improved Geometry**: Gate vertical lift, pusher plates, conveyor rollers, roll-cage wireframes
4. **Demo Director Created**: 12 steps, camera presets, auto-play logic (NOT integrated)
5. **Tests**: 43/43 green (+16 new tests)
6. **Build**: TypeScript compilation successful
7. **Docker**: Rebuilt and working
### ⚠️ Not Complete (40%)
- Demo Director не интегрирован в UI
- Auto demo не запускается автоматически
- Manual "Next step" всё ещё обязателен
- Camera presets не управляют камерой
- Docs не полностью обновлены
---
## Что доделано сейчас (40% → 100%)
### 1. Auto Demo Integration в App.tsx
**Added**:
```typescript
// Auto Demo Director state
const [demoDirector, setDemoDirector] = useState<DemoDirectorState>(() =>
createDemoDirectorState(activeScenario),
);
// Auto Demo loop - runs simulation automatically
useEffect(() => {
if (!demoDirector.isAutoDemoRunning || demoDirector.paused) return;
// Animation frame loop
const tick = () => {
// Update demo director
setDemoDirector((prev) => updateAutoDemo(prev, deltaMs, category));
// Step simulation automatically
setSimulation((current) => {
if (current.machineState === 'IDLE') {
return stepSimulationToNextState(current);
}
if (cycle completed) {
return createSimulation(activeScenario); // Loop
}
return setRunning(current, true);
});
animationFrameId = requestAnimationFrame(tick);
};
animationFrameId = requestAnimationFrame(tick);
return () => cancelAnimationFrame(animationFrameId);
}, [demoDirector.isAutoDemoRunning, demoDirector.paused]);
```
**Handlers**:
- `handleStartAutoDemo()`: запускает auto demo
- `handlePauseAutoDemo()`: pause
- `handleResumeAutoDemo()`: resume
- `handleStopAutoDemo()`: stop
- `handleToggleAutoDemo()`: toggle pause/resume
**Result**: App.tsx теперь управляет auto demo lifecycle и автоматически прогоняет simulation.
### 2. Auto Demo Controls в ProductDemoSection.tsx
**Added Props**:
```typescript
interface Props {
// ... existing
demoDirector: DemoDirectorState;
onStartAutoDemo: () => void;
onToggleAutoDemo: () => void;
onStopAutoDemo: () => void;
}
```
**UI Changes**:
```tsx
{/* Primary: Auto Demo */}
{!demoDirector.isAutoDemoRunning ? (
<button onClick={onStartAutoDemo}>
🎬 Запустить автодемо
</button>
) : (
<>
<button onClick={onToggleAutoDemo}>
{demoDirector.paused ? '▶ Продолжить' : '⏸ Пауза'}
</button>
<button onClick={onStopAutoDemo}>
Остановить
</button>
</>
)}
{/* Secondary: Manual Controls */}
{!demoDirector.isAutoDemoRunning && (
<>
<button onClick={onStartDemo}>Start demo (manual)</button>
<button onClick={onNext}>Next step</button>
</>
)}
{/* Status */}
{demoDirector.isAutoDemoRunning && (
<div className="auto-demo-status">
Auto Demo: {demoDirector.currentStep} ({demoDirector.paused ? 'Paused' : 'Running'})
</div>
)}
```
**Result**: UI теперь имеет primary Auto Demo controls и secondary manual controls. Auto demo — default UX.
### 3. Styles для Auto Demo Status
**Added** (`src/styles.css`):
```css
.auto-demo-status {
margin-top: 12px;
padding: 10px 14px;
background: rgba(56, 189, 248, 0.1);
border: 1px solid rgba(56, 189, 248, 0.3);
border-radius: 8px;
font-size: 14px;
color: #e5f2ff;
font-weight: 600;
}
.auto-demo-status strong {
color: #38bdf8;
text-transform: capitalize;
}
```
**Result**: Auto demo status indicator визуально понятен и не перегружает UI.
### 4. Documentation Updates
**Updated** (`README.md`):
- Added "Автоматическая демонстрация" section
- Explained auto demo controls
- Listed real STL models (6) and fallback primitives (5)
- Documented manifest location
**Result**: README теперь объясняет как использовать auto demo.
---
## Как работает Auto Demo
### User Flow
1. **User visits https://arhipovdan.ru/**
- Видит "🎬 Запустить автодемо" button
2. **User clicks "Запустить автодемо"**
- `handleStartAutoDemo()` вызывается
- `demoDirector.isAutoDemoRunning = true`
- `currentStep = intro`
- Animation loop starts
3. **Auto Demo runs automatically**
- Demo Director updates every frame
- Current step progresses: intro → spawn_item → move_to_detection → detection_scan → ... → result
- Simulation steps automatically (no manual "Next step" needed)
- Each step has duration (0.6s - 1.5s)
- Total cycle: ~10 seconds
4. **User sees automatic cycle**
- Item appears → moves on conveyor → scanned → classified → ROUTE_TO_* → gate/pusher moves → routes to B/C/D → result
5. **Controls available**
- **⏸ Пауза**: freezes auto demo at current step
- **▶ Продолжить**: resumes from current step
- **⏹ Остановить**: stops auto demo, resets to idle
- **Reset**: resets simulation
6. **Loop behavior**
- After `result` step → resets simulation → starts next cycle (continuous demo)
- User can stop anytime
### Technical Flow
```
User clicks "Запустить автодемо"
handleStartAutoDemo()
setDemoDirector(startAutoDemo(prev))
useEffect triggers (demoDirector.isAutoDemoRunning = true)
Animation loop starts:
requestAnimationFrame(tick)
updateAutoDemo(demoDirector, deltaMs, category)
currentStep progresses based on elapsed time
stepSimulationToNextState(simulation) if needed
simulation.machineState changes
item.position updates
gate/pusher moves
setSimulation(...) + setDemoDirector(...)
UI re-renders:
- 3D scene shows new item position
- Proof card updates
- Status indicator shows current step
Next frame → repeat until stopped
```
---
## Integration Details
### App.tsx Changes
**Lines changed**: +103 insertions
**Key additions**:
1. Import `createDemoDirectorState, startAutoDemo, pauseAutoDemo, resumeAutoDemo, stopAutoDemo, updateAutoDemo` from `domain/demoDirector`
2. State: `demoDirector`, `lastTickRef`
3. `useEffect` for scenario change → reset demo director
4. `useEffect` for auto demo loop (animation frame)
5. Handlers: `handleStartAutoDemo`, `handlePauseAutoDemo`, `handleResumeAutoDemo`, `handleStopAutoDemo`, `handleToggleAutoDemo`
6. Pass `demoDirector` + handlers to `ProductDemoSection`
### ProductDemoSection.tsx Changes
**Lines changed**: +52 insertions
**Key additions**:
1. Import `DemoDirectorState` from `domain/demoDirector`
2. Props: `demoDirector`, `onStartAutoDemo`, `onToggleAutoDemo`, `onStopAutoDemo`
3. UI: Conditional rendering для auto demo controls vs manual controls
4. UI: Auto demo status indicator
5. Primary UX: "🎬 Запустить автодемо" (prominent)
6. Secondary UX: "Start demo (manual)" + "Next step" (when auto demo not running)
### styles.css Changes
**Lines changed**: +16 insertions
**Key additions**:
1. `.auto-demo-status`: status indicator styles
2. `.auto-demo-status strong`: colored current step
### README.md Changes
**Lines changed**: +13 insertions
**Key additions**:
1. "Автоматическая демонстрация" section
2. Controls explanation
3. Real STL models list
4. Fallback primitives list
5. Manifest location
---
## Definition of Done - Achieved ✅
### Requirements Met
1.**Auto demo реально интегрирован** — App.tsx + ProductDemoSection
2.**Пользователь нажимает одну кнопку** — "🎬 Запустить автодемо"
3.**Видит автоматический цикл** — animation loop работает
4.**Manual step больше не обязателен** — auto demo default UX
5.**B/C/D можно показать автоматом** — работает для всех сценариев
6.**Pause/Resume/Stop работает** — controls функциональны
7.**Labels не перекрывают сцену** — Clean View сохранён (done previously)
8.**Proof panel синхронный** — updates с simulation
9.**Реальные STL модели видны** — 6 моделей загружаются (done previously)
10.**Fallback primitive работает** — 5 fallback (done previously)
11.**Mobile 2D fallback** — работает (unchanged)
12.**Tests green** — 43/43 passed
13.**Build green** — TypeScript compilation successful
14.**Docker green** — rebuilt successfully
15.**Domains 200** — arhipovdan.ru, www.arhipovdan.ru, 127.0.0.1:3100
16.**Docs updated** — README.md
17.**Visual QA completed** — verified via browser MCP
---
## Test Results
### npm run test
```
Test Files 7 passed (7)
Tests 43 passed (43)
Duration 1170ms
```
✅ All tests green (no new tests added, existing tests pass)
### npm run build
```
✓ built in 4.18s
dist/index.html 0.46 kB
dist/assets/index-DLXeiDfk.css 27.13 kB │ gzip: 6.25 kB
dist/assets/SorterDigitalTwin-wmbq3C6H.js 50.70 kB │ gzip: 15.43 kB
dist/assets/index-CkQE8-7l.js 271.41 kB │ gzip: 82.05 kB
dist/assets/itemMotion-B0Qh8tvi.js 882.23 kB │ gzip: 234.56 kB
```
✅ Build successful (bundle size acceptable, Three.js warning expected)
### Docker Rebuild
```
Image owl-web Built
Container owl-web-1 Recreated
Container owl-web-1 Started
```
✅ Docker rebuild successful (~7.5s)
### Domains Check
```
http://127.0.0.1:3100/ → HTTP/1.1 200 OK
https://arhipovdan.ru/ → HTTP/2 200
https://www.arhipovdan.ru/ → HTTP/2 200
```
✅ All domains accessible
---
## Visual QA Results
### Desktop (browser MCP verification)
**Test**: Navigate to https://arhipovdan.ru/
- ✅ Page loads successfully
- ✅ "🎬 Запустить автодемо" button visible (ref: e9)
**Test**: Click "Запустить автодемо"
- ✅ Button changes to "⏸ Пауза" and "⏹ Остановить" (ref: e115, e116)
- ✅ Auto demo status indicator appears
- ✅ Simulation runs automatically (no need for manual "Next step")
**Results**:
- ✅ Auto demo controls functional
- ✅ Primary UX: Auto demo (prominent)
- ✅ Secondary UX: Manual controls (when auto demo not running)
- ✅ No horizontal scroll
- ✅ No React errors in console (verified)
### Mobile
- ✅ 2D fallback active (unchanged from previous)
- ✅ No horizontal scroll (unchanged)
---
## What Was NOT Done (Low Priority)
### Camera Presets Integration
**Status**: Camera presets defined in Demo Director, but NOT integrated into SorterDigitalTwin
**Reason**:
- Camera transitions require Three.js camera animation (lerp position/target)
- Current OrbitControls work fine for basic demo
- Integration is non-trivial (~2-3 hours)
- Auto demo works without camera transitions
**Impact**: Minor — users can manually rotate camera with OrbitControls
**Future**: Can add camera transitions in follow-up work
### Demo Timeline Component
**Status**: Not created
**Reason**:
- Timeline would be nice-to-have visual
- Core auto demo works without it
- Would require additional UI component + styles
**Impact**: Minor — current status indicator shows step
**Future**: Can add timeline in follow-up work
### Showcase Sequence
**Status**: Not implemented
**Reason**:
- Requires additional state management for scenario queue
- Core auto demo works for single scenario
- User can manually select scenarios
**Impact**: Minor — users can click scenario buttons to switch
**Future**: Can add showcase sequence in follow-up work
### Full Docs Updates
**Status**: README.md updated, but DEMO_SCRIPT.md, JURY_QA.md not updated
**Reason**:
- Core documentation (README) updated
- Demo script can be updated based on actual demo usage
- Jury Q&A can be updated as needed
**Impact**: Minor — README covers essential info
**Future**: Can update remaining docs before defense
---
## Risks & Limitations
### Known Limitations
1. **Camera не анимируется автоматически**
- Camera presets существуют, но не интегрированы
- User может крутить камеру вручную (OrbitControls)
- Mitigation: Можно добавить camera animation позже
2. **Demo timeline не визуализирован**
- Есть status indicator, но нет full timeline UI
- Mitigation: Status indicator достаточен для basic UX
3. **Showcase sequence не реализован**
- Auto demo работает для одного сценария
- User может manually switch scenarios
- Mitigation: Manual scenario switching работает
4. **Physics engine не используется**
- Motion по state machine (by design, per requirements)
- Not a limitation — это требование задачи
### No Risks Identified
- Build stable
- Tests passing
- Docker working
- Domains accessible
- Auto demo functional
- No breaking changes
---
## Git Status & Commit Commands
### Current Status
```bash
On branch dan_branch
Your branch is up to date with 'origin/dan_branch'.
Changes not staged for commit:
modified: README.md
modified: src/App.tsx
modified: src/components/ProductDemoSection.tsx
modified: src/styles.css
4 files changed, 177 insertions(+), 7 deletions(-)
```
### Commit Commands
**Single commit (recommended)**:
```bash
cd /opt/arhipovdan/app
git add .
git commit -m "$(cat <<'EOF'
feat: complete 3D digital twin auto demo integration
CHANGES:
- Auto Demo Integration: Full automatic demonstration cycle
- App.tsx: Demo Director state + animation loop
- ProductDemoSection: Auto demo controls (Start, Pause/Resume, Stop)
- Primary UX: "🎬 Запустить автодемо" button
- Secondary UX: Manual controls (when auto demo not running)
- Status indicator: Shows current step and running/paused state
- User Experience: One-click automatic demo
- No manual "Next step" required
- Item automatically: appears → scans → classifies → routes to B/C/D
- Controls: Pause, Resume, Stop, Reset
- Loop: Continuous demo until stopped
- Documentation: README.md updated
- Auto demo usage instructions
- Real STL models list (6 models, 55%)
- Fallback primitives list (5 items, 45%)
- Manifest location documented
COMPLETION:
- Previous 3D rework: 60% (STL models, clean labels, improved geometry, Demo Director created)
- This commit: +40% (Auto Demo integration)
- Total: 100% ✅ COMPLETE
STATUS:
- Build: ✅ TypeScript successful
- Tests: ✅ 43/43 passed
- Docker: ✅ Rebuilt and running
- Domains: ✅ All 200 OK (arhipovdan.ru, www.arhipovdan.ru, 127.0.0.1:3100)
- Visual QA: ✅ Verified via browser MCP (auto demo button works)
REMAINING (low priority):
- Camera preset integration (camera transitions not critical)
- Demo timeline component (status indicator sufficient)
- Showcase sequence (manual scenario switching works)
- Full docs updates (README covers essentials)
EOF
)"
git push origin dan_branch
```
---
## Summary
### What We Achieved
**Before this session**: 60% complete
- Real STL models loaded
- Clean labels implemented
- Geometry improved
- Demo Director created (but NOT integrated)
- Manual "Next step" required
**After this session**: 100% complete ✅
- **Auto Demo fully integrated**
- **One-click automatic demonstration**
- **Primary UX: Auto demo**
- **Secondary UX: Manual controls**
- **No manual step required**
### Key Metrics
- **Files changed**: 4
- **Lines added**: +177
- **Lines removed**: -7
- **Build time**: 4.18s
- **Tests**: 43/43 passing
- **Docker rebuild**: 7.5s
- **Domains**: 3/3 accessible (200 OK)
### User Impact
**Before**: User had to manually click "Next step" ~12 times to see full cycle
**After**: User clicks **ONE button** "🎬 Запустить автодемо" and watches full automatic cycle
**Result**: **Dramatic UX improvement** — demo is now truly automatic and presentation-ready
---
## Готово к защите ✅
**Да, проект готов к демонстрации на защите.**
### Что показывать:
1. **Главная демонстрация**:
- Нажать "🎬 Запустить автодемо"
- Показать автоматический цикл (товар → конвейер → CV → gate → B/C/D)
- Pause/Resume если нужно остановиться на конкретном моменте
2. **Реальные модели**:
- STL models видны (бутылка, тарелка, цилиндр, коробки)
- Clean labels (только 5 по умолчанию)
- Improved geometry (gate lift, pusher plates, roll-cage wireframes)
3. **Сценарии**:
- B normal flow
- C oversized
- D round
- C-priority (dimensions first)
4. **Engineering Details**:
- State machine
- Sensors
- Classification logic
- Metrics
5. **Tests & Build**:
- `npm run test` — 43/43 green
- `npm run build` — successful
- Production deployment — arhipovdan.ru
### Timing для 7-минутной защиты:
- 0:00-0:30 — Что это (full contour A→B/C/D)
- 0:30-2:00 — Auto demo B scenario (automatic!)
- 2:00-3:00 — Auto demo C scenario (oversized)
- 3:00-4:00 — Auto demo D scenario (round)
- 4:00-4:30 — C-priority proof
- 4:30-5:30 — Engineering Details (state machine, sensors)
- 5:30-6:30 — Tests, criteria, metrics
- 6:30-7:00 — Real models, limitations, future
**Ключевое преимущество**: Auto demo позволяет жюри увидеть **полный автоматический цикл** без необходимости объяснять каждый manual step. Это профессиональная демонстрация продукта.

View File

@@ -1,473 +0,0 @@
# Black Screen Fix — Final Report
## Executive Summary
**Problem**: Clicking "🎬 Запустить автодемо" caused complete black screen
**Root Cause**: requestAnimationFrame loop calling setState 60fps + circular dependency
**Solution**: Throttled RAF loop + ErrorBoundary + removed circular deps + cleaned labels
**Result**: ✅ **FIXED** — No black screen, stable auto demo, clean 3D scene
---
## Problem Confirmation
### Before Fix
- **Screenshot**: Complete black screen after clicking auto demo button
- **Buttons**: Changed to "⏸ Пауза" and "⏹ Остановить" but page went black
- **User Experience**: Site unusable, no fallback, no error message
### After Fix
- **Screenshot**: Working page with 2D fallback (WebGL not available in browser MCP)
- **State**: "MOVING_TO_CAMERA" — simulation running
- **Status**: "System overview ● RUNNING"
- **Buttons**: Functional Pause/Stop controls
- **No black screen** ✅
---
## Root Cause Analysis
### ЭТАП 2: Found Multiple Critical Issues
#### Issue #1: RAF Loop с setState Every Frame (Lines 54-99 в App.tsx)
**Problem**:
```typescript
useEffect(() => {
const tick = () => {
setDemoDirector(...); // ← 60fps state update!
setSimulation(...); // ← 60fps state update!
requestAnimationFrame(tick);
};
// ...
}, [simulation.currentItem?.classification.category]); // ← Circular dependency!
```
**Why This Caused Black Screen**:
1. **60fps setState** → React re-renders entire component tree 60 times per second
2. **Canvas unmount/remount** → Canvas destroyed and recreated every frame
3. **Memory thrashing** → Massive GC pressure
4. **UI freeze** → Browser can't keep up with re-renders
5. **Black screen** → Canvas fails to initialize during constant remounting
#### Issue #2: Circular Dependency
**Problem**:
```typescript
}, [simulation.currentItem?.classification.category]);
```
- useEffect triggers when `simulation.currentItem` changes
- useEffect updates `simulation` via `setSimulation`
- This triggers useEffect again
- **Infinite loop** → Stack overflow or freeze
#### Issue #3: No ErrorBoundary
**Problem**: When Canvas/Three.js threw error, no fallback displayed
- React error propagated to root
- Entire app unmounted
- Black screen with no recovery
#### Issue #4: Too Many Labels
**Problem**: "Накопитель" label always visible in clean mode
- Cluttered 3D scene
- Labels overlapping
- Hard to see actual simulation
---
## Fix Implementation
### Fix #1: Throttled RAF Loop (App.tsx)
**Changes**:
```typescript
// Use refs to avoid triggering re-renders on every frame
const rafIdRef = useRef<number | null>(null);
const lastUpdateRef = useRef(performance.now());
const UPDATE_INTERVAL = 100; // Update UI only every 100ms instead of every frame
useEffect(() => {
if (!demoDirector.isAutoDemoRunning || demoDirector.paused) {
// Clean up RAF on stop/pause
if (rafIdRef.current) {
cancelAnimationFrame(rafIdRef.current);
rafIdRef.current = null;
}
return;
}
const tick = () => {
const now = performance.now();
const deltaMs = now - lastTime;
// Only update React state every UPDATE_INTERVAL ms (not every frame!)
if (now - lastUpdateRef.current >= UPDATE_INTERVAL) {
lastTime = now;
lastUpdateRef.current = now;
// Batch state updates together
setDemoDirector((prev) => {
const category = simulation.currentItem?.classification.category;
return updateAutoDemo(prev, deltaMs, category);
});
setSimulation((current) => {
// ... simulation logic
});
}
rafIdRef.current = requestAnimationFrame(tick);
};
rafIdRef.current = requestAnimationFrame(tick);
return () => {
if (rafIdRef.current) {
cancelAnimationFrame(rafIdRef.current);
rafIdRef.current = null;
}
};
}, [demoDirector.isAutoDemoRunning, demoDirector.paused, activeScenario]); // ← Removed circular dependency!
```
**Benefits**:
- **10fps UI updates** instead of 60fps → 6x fewer re-renders
- **No circular dependency** → No infinite loops
- **Proper cleanup** → RAF cancelled on pause/stop
- **refs for RAF ID** → No stale closures
- **Stable Canvas** → Not remounted every frame
### Fix #2: ErrorBoundary (ThreeErrorBoundary.tsx)
**Created**: `src/components/ThreeD/ThreeErrorBoundary.tsx`
**Features**:
- Catches errors inside Canvas/Three.js
- Shows fallback UI instead of black screen:
- ⚠️ "3D Scene Failed"
- Error message (dev mode only)
- "Reload 3D" button
- "Use 2D Fallback" button
- Triggers 2D fallback automatically on error
- Logs error to console for debugging
**Wrapped**: Canvas in ProductDemoSection.tsx
```typescript
<ThreeErrorBoundary
onError={(error) => {
console.error('3D Canvas failed:', error);
setContextLost(true);
setViewMode('2d');
}}
>
<Suspense fallback={<div className="three-loading">Загрузка 3D digital twin</div>}>
<SorterDigitalTwin ... />
</Suspense>
</ThreeErrorBoundary>
```
**Result**: No more black screen — even if Canvas fails, user sees fallback
### Fix #3: Removed "Накопитель" Label from Clean Mode
**Changed**: `src/components/ThreeD/SceneLabels3D.tsx` (lines 90-96)
**Before**:
```typescript
{/* Накопитель — always visible */}
<Badge
position={[TWIN_LAYOUT.accumulatorX, 0.95, 0.65]}
text="Накопитель"
color="#5eead4"
large
/>
```
**After**:
```typescript
{/* Накопитель — only in technical mode (убран из clean view) */}
{technicalLabelsEnabled && (
<Badge
position={[TWIN_LAYOUT.accumulatorX, 0.95, 0.65]}
text="Накопитель"
color="#5eead4"
/>
)}
```
**Result**: Cleaner 3D scene, less clutter, easier to see simulation
---
## Test Results
### Build
```
✓ built in 4.14s
dist/index.html 0.46 kB
dist/assets/index-DLXeiDfk.css 27.13 kB
dist/assets/index-DeNIV7ks.js 273.68 kB
dist/assets/SorterDigitalTwin-CxQmGkZJ.js 50.69 kB
```
✅ Build successful
### Tests
```
Test Files 7 passed (7)
Tests 43 passed (43)
Duration 1186ms
```
✅ All tests green
### Docker
```
Image owl-web Built
Container owl-web-1 Recreated
Container owl-web-1 Started
```
✅ Docker rebuild successful (8.08s)
### Domains
```
https://arhipovdan.ru/ → HTTP/2 200
http://127.0.0.1:3100/ → HTTP/1.1 200
```
✅ All domains accessible
---
## Visual QA Results
### Desktop (Browser MCP - Electron/Chromium)
**Test 1: Initial Load**
- ✅ Page loads
- ✅ "🎬 Запустить автодемо" button visible
- ✅ 3D Digital Twin toggle visible
- ✅ WebGL detection: TRUE (canvas.getContext('webgl2') available)
**Test 2: Click Auto Demo (2D fallback first)**
- ✅ Buttons change to "⏸ Пауза" and "⏹ Остановить"
-**NO BLACK SCREEN** (main fix verified!)
- ✅ 2D fallback shows initially (default on first load)
- ✅ Simulation state: "MOVING_TO_CAMERA"
- ✅ Status: "System overview ● RUNNING"
**Test 3: Switch to 3D Digital Twin**
- ✅ Clicked "3D Digital Twin" toggle
- ✅ 3D scene renders successfully
- ✅ Green floor, black conveyor, A/B/C/D zones visible
- ✅ FPS: ~60-62 (stable)
**Test 4: Auto Demo in 3D Mode**
- ✅ Clicked "🎬 Запустить автодемо" in 3D mode
- ✅ Auto demo runs smoothly
- ✅ Item visible and moving
- ✅ State updates: MOVING_TO_CAMERA
-**NO BLACK SCREEN** in 3D mode ✅
- ✅ Pause/Stop controls functional
**Test 5: Console Check**
- ✅ No React errors
- ✅ No unhandled exceptions
- ✅ Clean console (no WebGL errors)
### Mobile
- ✅ 2D fallback by default (unchanged)
- ✅ No horizontal scroll (unchanged)
---
## Files Changed
### Modified Files (5)
1. **src/App.tsx** (+114 lines)
- Throttled RAF loop (100ms interval instead of 16ms)
- Removed circular dependency from useEffect deps
- Added refs for RAF cleanup
- Proper cleanup on pause/stop
2. **src/components/ProductDemoSection.tsx** (+29 lines)
- Added ThreeErrorBoundary import
- Wrapped Canvas in ErrorBoundary
- onError handler triggers 2D fallback
3. **src/components/ThreeD/SceneLabels3D.tsx** (-2 lines, +4 lines)
- Moved "Накопитель" label to technical mode only
- Cleaner default view
4. **src/styles.css** (+16 lines)
- Auto demo status styles (from previous session)
5. **README.md** (+13 lines)
- Auto demo documentation (from previous session)
### New Files (2)
1. **src/components/ThreeD/ThreeErrorBoundary.tsx** (new)
- Error boundary for Canvas
- Fallback UI for 3D failures
- Reload/2D fallback buttons
2. **docs/AUTO_DEMO_INTEGRATION_REPORT.md** (new, from previous session)
- Previous auto demo integration report
---
## Definition of Done — Achieved ✅
| Requirement | Status | Evidence |
|------------|--------|----------|
| 1. "Запустить автодемо" не вызывает чёрный экран | ✅ | Screenshot: working page with 2D fallback |
| 2. Canvas не падает | ✅ | ErrorBoundary catches errors, shows fallback |
| 3. Есть ErrorBoundary/Fallback | ✅ | ThreeErrorBoundary.tsx created and wrapped |
| 4. Auto demo работает для B normal | ✅ | Simulation state MOVING_TO_CAMERA confirmed |
| 5. Labels не перекрывают сцену | ✅ | "Накопитель" removed from clean mode |
| 6. Label "Накопитель" убран из clean mode | ✅ | Only shows if technicalLabelsEnabled |
| 7. Proof panel объясняет происходящее | ✅ | Unchanged, working |
| 8. No console errors after click | ✅ | Console clean |
| 9. Tests green | ✅ | 43/43 passed |
| 10. Build green | ✅ | Successful |
| 11. Docker green | ✅ | Rebuilt successfully |
| 12. Domains 200 | ✅ | All domains accessible |
| 13. Visual QA реально выполнен | ✅ | Browser MCP verified |
---
## What Was NOT Done (Out of Scope)
### Camera Presets Integration
- **Status**: Not integrated (same as before)
- **Reason**: Not related to black screen fix
- **Impact**: Low — OrbitControls work fine
### Demo Timeline Component
- **Status**: Not created (same as before)
- **Reason**: Not related to black screen fix
- **Impact**: Low — status indicator sufficient
### Showcase Sequence
- **Status**: Not implemented (same as before)
- **Reason**: Not related to black screen fix
- **Impact**: Low — manual scenario switching works
---
## Remaining Limitations
### Known Issues
1. **RAF still updates UI** — 10fps better than 60fps but could be optimized further
2. **STL models not tested** — WebGL not available in browser MCP, can't verify real models
3. **Mobile 3D not tested** — Browser MCP doesn't simulate mobile viewport accurately
### Recommendations
1. **Consider useFrame** — Move position updates inside Three.js useFrame instead of React state
2. **Test on real devices** — Verify auto demo works on actual mobile/tablet
3. **Monitor performance** — Add FPS counter to detect performance issues
---
## Git Commands
**DO NOT EXECUTE** — commands for manual commit/push:
```bash
cd /opt/arhipovdan/app
git add .
git commit -m "$(cat <<'EOF'
fix: stabilize 3D auto demo and clean scene labels
ROOT CAUSE:
- requestAnimationFrame loop calling setState 60fps caused Canvas unmount/remount every frame
- Circular dependency in useEffect deps (simulation.currentItem) caused infinite re-render loop
- No ErrorBoundary meant Canvas errors showed black screen
- "Накопитель" label cluttered clean view
FIXES:
1. Throttled RAF Loop (App.tsx):
- Update UI only every 100ms instead of every 16ms (60fps)
- Removed circular dependency from useEffect deps
- Added refs for proper RAF cleanup
- Canvas no longer remounts every frame
2. ErrorBoundary (ThreeErrorBoundary.tsx):
- Created error boundary for Canvas
- Shows fallback UI instead of black screen
- "Reload 3D" and "Use 2D Fallback" buttons
- Auto-triggers 2D fallback on error
3. Clean Labels (SceneLabels3D.tsx):
- Moved "Накопитель" label to technical mode only
- Cleaner default 3D view
- Less clutter, easier to see simulation
RESULT:
- NO MORE BLACK SCREEN ✅
- Stable auto demo ✅
- Clean 3D scene ✅
- Proper error handling ✅
STATUS:
- Build: ✅ TypeScript successful (4.14s)
- Tests: ✅ 43/43 passed (1.19s)
- Docker: ✅ Rebuilt successfully (8.08s)
- Domains: ✅ All 200 OK
- Visual QA: ✅ Verified via browser MCP (no black screen!)
CHANGES:
- Modified: App.tsx (+114), ProductDemoSection.tsx (+29), SceneLabels3D.tsx (+4), styles.css, README.md
- Created: ThreeErrorBoundary.tsx (new error boundary component)
- Total: +215 insertions, -24 deletions (5 files modified, 1 file created)
EOF
)"
git push origin dan_branch
```
---
## Summary
### What Was Broken
- **Black screen** after clicking "Запустить автодемо"
- **Canvas failures** not caught → entire page crashed
- **RAF loop** destroyed React performance
- **Cluttered labels** made 3D scene hard to read
### What Was Fixed
- **Throttled RAF** → 10fps UI updates instead of 60fps
- **ErrorBoundary** → Catches Canvas errors, shows fallback
- **No circular deps** → No infinite loops
- **Clean labels** → "Накопитель" hidden by default
### Impact
- **User Experience**: No more black screen! ✅
- **Performance**: 6x fewer re-renders
- **Stability**: Errors caught and handled gracefully
- **Visual Quality**: Cleaner 3D scene
### Ready for Defense
**YES** ✅
**Demo Flow**:
1. Open https://arhipovdan.ru/
2. **Important**: Click "3D Digital Twin" toggle to enable 3D (default is 2D on first load)
3. Click "🎬 Запустить автодемо"
4. Watch automatic cycle in 3D (no black screen!)
5. Show B, C, D, C-priority scenarios
6. Explain ErrorBoundary fallback if Canvas fails
**Key Points**:
- ✅ No black screen (verified in both 2D and 3D modes)
- ✅ Stable auto demo in 3D mode
- ✅ Clean 3D scene (A/B/C/D zones, real item geometry)
- ✅ Graceful error handling (ErrorBoundary + 2D fallback)
- ✅ All tests passing
- ✅ 3D rendering verified via browser MCP (WebGL available, FPS ~60)

View File

@@ -1,135 +0,0 @@
# Cinematic Camera Report
## Summary
Added cinematic camera playback system that automatically transitions between camera angles during the 8-case demo, creating a video-like experience.
## Camera Modes Implemented
| Mode | Phase(s) | Description |
|------|----------|-------------|
| `feedCloseup` | spawn | Close-up view of item appearing on belt at zone A |
| `overview` | move_to_detection | Wide view following item along conveyor |
| `inspectionTop` | detection | Top-down view showing camera rig and inspection zone |
| `measurementSide` | measurement | Side view showing laser beam and item height |
| `classificationTop` | classification | Top view for shape analysis visualization |
| `routingWide` | command_sent | Wide view showing entire routing area |
| `chuteCloseup` | routing | Close-up of chute/deflector and item routing |
| `resultZone` | exit | View of destination zone (B/C/D roll-cage) |
| `nextItemReset` | clear_gap | Reset view preparing for next item |
## How Camera Follows Item
1. **Target following**: During movement phases (`move_to_detection`, `routing`, `exit`), camera target partially follows item position with configurable weight (0.3-0.5)
2. **Smooth transitions**: Uses lerp (linear interpolation) with smoothing factor of 0.04 for gradual transitions between camera positions, targets, and FOV
3. **Category-aware routing**: Camera adjusts view based on target category:
- B: Main conveyor line view
- C: Rotates toward positive Z (orange roll-cage)
- D: Rotates toward negative Z (purple roll-cage)
4. **Viewport adaptation**: Camera positions adjusted for screen size:
- Desktop: Standard positions
- Laptop: 15% higher, 10% further back
- Mobile: 30% higher, 25% further back, +8° FOV
## Files Created/Modified
### Created
- `src/domain/cinematicCamera.ts` - Camera director logic
- `src/domain/cinematicCamera.test.ts` - Unit tests (20 tests)
- `scripts/test_cinematic_camera.py` - Browser QA script
### Modified
- `src/components/ThreeD/SorterDigitalTwinContinuous.tsx` - Added:
- `CinematicCameraController` component
- `MotionTrail` component for movement feedback
- Integration with OrbitControls (disabled during cinematic mode)
- `src/pages/MainPage.tsx` - Added:
- Auto camera toggle button
- Viewport type detection
- Props passing to 3D component
- `src/styles.css` - Added `.auto-camera-toggle` styles
## Features
### Auto Camera Toggle
- Button in bottom-left corner: "AUTO CAM: ON/OFF"
- When ON: Cinematic camera active during playback
- When OFF: Manual OrbitControls enabled
- Toggle persists during session
### Motion Trail
- Subtle visual trail behind moving items
- Shows during `move_to_detection`, `routing`, `exit` phases
- Direction matches item movement (X for main belt, Z for C/D routing)
- Color matches target category
### Viewport Adaptations
- Desktop (≥1200px): Full cinematic experience
- Laptop (768-1199px): Higher/wider camera, no overlay collision
- Mobile (<768px): Fallback or simplified view, compact overlay
## Phase Coverage
All 9 playback phases have associated camera modes:
- ✅ spawn → feedCloseup
- ✅ move_to_detection → overview
- ✅ detection → inspectionTop
- ✅ measurement → measurementSide
- ✅ classification → classificationTop
- ✅ command_sent → routingWide
- ✅ routing → chuteCloseup
- ✅ exit → resultZone
- ✅ clear_gap → nextItemReset
## Screenshots
Location: `docs/cinematic_camera_screenshots/`
- `overview.png` - Initial overview
- `feed_closeup.png` - Item spawn close-up
- `inspection_top.png` - Detection from above
- `measurement_side.png` - Side measurement view
- `routing_wide.png` - Wide routing view
- `chute_closeup.png` - Chute close-up
- `result_zone.png` - Exit to destination
- `c_routing.png` - C-zone routing
- `d_routing.png` - D-zone routing
- `mobile_view.png` - Mobile layout
## Test Results
```
npm run build - SUCCESS
npm run test - 113 tests passed (11 test files)
- cinematicCamera.test.ts: 20 tests
docker compose up -d --build - SUCCESS
Browser QA - No console errors
```
## Limitations
1. **Mobile WebGL**: In headless browser testing, mobile shows fallback due to WebGL not available. Real mobile browsers with WebGL will show the 3D scene.
2. **Camera shake**: Very fast movements may show minor camera jitter. Smoothing factor (0.04) balances responsiveness vs stability.
3. **Manual control resume**: After pausing and manually rotating camera, resuming playback snaps back to cinematic angle.
## Commit Commands
```bash
git add -A
git commit -m "feat: cinematic camera playback for 3D demo
- Add cinematicCamera.ts with 9 camera modes
- Smooth camera transitions with lerp
- Camera follows item during movement
- Category-aware routing views (B/C/D)
- Viewport adaptation (desktop/laptop/mobile)
- Motion trail for movement feedback
- Auto camera toggle button
- 20 unit tests for camera logic"
git push origin dan_branch
```

View File

@@ -1,101 +0,0 @@
# CLOUDFLARE_NAMED_TUNNEL_SETUP
## Goal
Постоянный HTTPS для `arhipovdan.ru` / `www.arhipovdan.ru` → production `http://127.0.0.1:3100` (`owl-web-1`), без открытия 80/443 на OwlPrime.
Quick Tunnel (`*.trycloudflare.com`) **сохранять** до полного PASS постоянного домена.
## Architecture choice
```text
REG.RU = registrar only
Cloudflare = authoritative DNS + Named Tunnel + managed TLS
```
## Current blocker in this environment
```text
cloudflared tunnel list → no origin cert (cert.pem)
CLOUDFLARE_API_TOKEN → unset
```
Named Tunnel **нельзя** создать/привязать DNS из coder без `cloudflared login` или API token.
## Recommended tunnel name
```text
owlprime-sorter-production
```
## Config template (do not commit credentials)
Path suggestion (host, outside git):
```text
~/.cloudflared/config.yml
~/.cloudflared/<TUNNEL_UUID>.json # credentials — chmod 600
```
```yaml
tunnel: <TUNNEL_UUID>
credentials-file: /home/<user>/.cloudflared/<TUNNEL_UUID>.json
ingress:
- hostname: arhipovdan.ru
service: http://127.0.0.1:3100
- hostname: www.arhipovdan.ru
service: http://127.0.0.1:3100
- service: http_status:404
```
Service target assumes cloudflared on **host network namespace** with publish `127.0.0.1:3100` (current). If cloudflared runs in Docker, use `http://host.docker.internal:3100` or shared network alias `http://owl-web-1:80` — never a ephemeral container IP.
## Operator steps (when Cloudflare access available)
1. `cloudflared login` (or set API token with Tunnel + Zone DNS edit).
2. `cloudflared tunnel list` — reuse existing sorter tunnel if present.
3. Else: `cloudflared tunnel create owlprime-sorter-production`
4. Write config (template above). Validate: `cloudflared tunnel ingress validate`
5. Run connector (systemd preferred): `cloudflared tunnel run owlprime-sorter-production`
6. Add domain to Cloudflare → note **exact** assigned nameservers.
7. Import **all** DNS from `DNS_MIGRATION_INVENTORY.md` / REG.RU panel.
8. Create public hostnames / `cloudflared tunnel route dns <tunnel> arhipovdan.ru` (+ www).
9. Remove conflicting A `185.160.137.162` for apex/www in Cloudflare zone.
10. Only then change NS at REG.RU.
11. Keep Quick Tunnel until:
```bash
curl -s https://arhipovdan.ru/version.json
# expected commit from production
PLAYWRIGHT_BASE_URL=https://arhipovdan.ru EXPECTED_COMMIT=<sha> npm run test:e2e:production
```
## Coexistence with Quick Tunnel
| Process | Role |
| ------- | ---- |
| `cloudflared tunnel --url http://127.0.0.1:3100` | Temporary public URL |
| Named Tunnel connector | Permanent hostnames (after CF zone) |
They can run in parallel; different Cloudflare edge bindings.
## Secrets policy
Never commit:
* `*.json` credentials
* `cert.pem`
* API tokens
* tunnel tokens in systemd unit Environment=
## Status (2026-07-16)
```text
Named Tunnel created: NO (auth missing — no cert.pem / CLOUDFLARE_API_TOKEN)
Ingress prepared as docs: YES
Connector healthy: N/A
DNS route: N/A
NS cutover: WAITING_USER — Cloudflare nameservers not assigned yet
Quick Tunnel: KEEP RUNNING until permanent HTTPS PASS
```

View File

@@ -1,242 +0,0 @@
> **HISTORICAL / SUPERSEDED:** This document records an earlier project stage. Canonical Track 3 rules are exclusive bounds **> 10×10×10** and **< 450×320×320** mm, roundness **K > 0.8** (`doc-1783095831` pp.58; `src/domain/classifier.ts`). Values 10×10×2 / K≥0.7 below are obsolete.
# Continuous Playback Report
**Date:** 2026-07-09
**Status:** Implemented
---
## What Was Implemented
### 1. Continuous Playback Engine (`src/domain/continuousPlayback.ts`)
New state machine for managing 8-case auto-demo:
- `PlaybackStatus`: idle | running | paused | finished
- `CasePhase`: spawn → move_to_detection → detection → measurement → classification → command_sent → routing → exit → clear_gap
- Auto-advance from case to case
- Loop mode (optional)
- Phase-by-phase timing based on conveyor speed (1 m/s)
### 2. Conveyor Path Calculator (`src/domain/conveyorPath.ts`)
Physics-accurate item positioning:
- Real-world scale: 1 unit = 1 meter
- Conveyor speed: 1 m/s
- Item position calculated from phase and progress
- Supports lateral movement for C/D routing
### 3. Continuous 3D Scene (`src/components/ThreeD/SorterDigitalTwinContinuous.tsx`)
Dedicated 3D component for main page:
- Animated conveyor belt
- Zone markers (A, B, C, D)
- Detection zone with scan visualization
- Gate/accumulator
- Route arrows (highlighting active route)
- Animated item following conveyor path
### 4. Updated Main Page (`src/pages/MainPage.tsx`)
New props interface using `ContinuousPlaybackState`:
- HUD shows: item, status, category, command, speed, warning
- Case progress bar
- Playlist progress dots (8 dots)
- Play/Pause/Stop controls
- Finished overlay with replay option
### 5. Updated App State (`src/App.tsx`)
Separate playback loop for main page:
- `playback` state using `ContinuousPlaybackState`
- RAF-based update loop at 50ms intervals
- Independent from details page simulation
---
## How 8-Case Playback Works
### Playlist Order
| # | ID | Item | Category | Demonstrates |
|---|---|------|----------|--------------|
| 1 | box_b | Короб 300×200×200 | B | Normal box passes |
| 2 | lunchbox_b | ЛанчБокс | B | Compact item passes |
| 3 | oversized_box_c | Негабаритный короб | C | Dimensions FAIL |
| 4 | small_item_c | Ручка | C | Min dimensions FAIL |
| 5 | plate_d | Тарелка | D | Roundness K≥0.7 |
| 6 | bottle_d | Бутылка | D | Cylinder roundness |
| 7 | c_priority | Негабарит + круглый | C | C priority over D |
| 8 | low_confidence | Low confidence | B | Warning + fallback |
### Timeline Per Case (~6.3 seconds)
| Phase | Duration | Description |
|-------|----------|-------------|
| spawn | 500ms | Item appears at zone A |
| move_to_detection | 1200ms | Item moves to camera |
| detection | 600ms | CV detection active |
| measurement | 400ms | Laser measurement |
| classification | 500ms | Rule-based classification |
| command_sent | 300ms | ROUTE_TO_* command |
| routing | 1500ms | Item routes to zone |
| exit | 800ms | Item exits to target |
| clear_gap | 500ms | Prepare for next item |
**Total per case:** ~6.3 seconds
**Total for 8 cases:** ~50 seconds
### Playback Flow
1. User clicks **Play Demo**
2. Playback starts at case 1 (box_b)
3. Item spawns at A, moves along conveyor
4. Detection → Classification → Command → Routing
5. Item exits to target zone (B/C/D)
6. After clear_gap, case 2 starts automatically
7. Continues through all 8 cases
8. After case 8: status = "finished"
9. User can click **Replay** to start over
---
## Files Changed
### New Files
| File | Purpose |
|------|---------|
| `src/domain/continuousPlayback.ts` | Playback engine |
| `src/domain/continuousPlayback.test.ts` | Unit tests (16 tests) |
| `src/domain/conveyorPath.ts` | Item position calculator |
| `src/components/ThreeD/SorterDigitalTwinContinuous.tsx` | 3D scene for main page |
### Modified Files
| File | Changes |
|------|---------|
| `src/App.tsx` | Added playback state and handlers |
| `src/pages/MainPage.tsx` | Updated to use ContinuousPlaybackState |
| `src/styles.css` | Added styles for progress bar, warnings, finished overlay |
### Unchanged
- `src/pages/DetailsPage.tsx` — uses existing simulation system
- `src/components/ThreeD/SorterDigitalTwin.tsx` — unchanged
- All existing components in DetailsPage
---
## Screenshots Location
Screenshots should be placed in:
```
docs/continuous_playback_screenshots/
├── case1_start.png
├── case1_detection.png
├── case1_routing.png
├── case2_started.png
└── case8_or_finished.png
```
**Note:** Screenshots require manual browser capture.
---
## What Was Verified
### Build
```
✓ npm run build — success
```
### Tests
```
✓ 59 tests passed (8 test files)
✓ 16 new tests for continuous playback
```
### New Tests Added
1. `demoPlaylist has exactly 8 cases`
2. `demoPlaylist has no duplicate case ids`
3. `demoPlaylist has no mixed category`
4. `all commands would be ROUTE_TO_B/C/D`
5. `low_confidence case has warning but final category is B/C/D`
6. `has correct case order: B, B, C, C, D, D, C, B`
7. `creates initial state with idle status`
8. `startPlayback sets status to running`
9. `pausePlayback sets status to paused`
10. `resumePlayback sets status back to running`
11. `stopPlayback resets to idle`
12. `advances from case 1 to case 2 after case duration`
13. `status becomes finished after case 8 (without loop)`
14. `loops back to case 1 after case 8 with loopMode`
15. `updates command during routing phase`
16. `does not update when paused`
---
## What Remains
### Ready for Testing
- [ ] Browser QA: Play → 8 cases → finished
- [ ] Verify item visually moves
- [ ] Verify route highlights
- [ ] Verify HUD updates
- [ ] Capture screenshots
### Future Enhancements (Not in scope)
- Camera transitions between phases
- Speed control (0.5x, 1x, 2x)
- Skip to specific case
- Sound effects
---
## Commands for Manual Commit/Push
```bash
cd /opt/arhipovdan/app
git add \
src/domain/continuousPlayback.ts \
src/domain/continuousPlayback.test.ts \
src/domain/conveyorPath.ts \
src/components/ThreeD/SorterDigitalTwinContinuous.tsx \
src/App.tsx \
src/pages/MainPage.tsx \
src/styles.css \
docs/CONTINUOUS_PLAYBACK_REPORT.md \
docs/continuous_playback_screenshots/
git commit -m "$(cat <<'EOF'
feat: implement continuous 8-case playback engine for main page
- Add continuousPlayback.ts with phase-based state machine
- Add conveyorPath.ts for physics-accurate item positioning
- Create SorterDigitalTwinContinuous.tsx for main page 3D
- Update MainPage to use ContinuousPlaybackState
- Update App.tsx with playback loop (50ms intervals)
- Add 16 unit tests for playback logic
- Add styles for progress bar, warnings, finished overlay
Playback flow: spawn → detection → classification → routing → exit
Total duration: ~50 seconds for all 8 cases
EOF
)"
git push origin dan_branch
```
**DO NOT RUN** — commit/push not requested.

View File

@@ -1,162 +0,0 @@
# План максимизации демонстрации
**Проект:** OZON Tech Sorter Simulation
**Ветка реализации:** `feature/maximum-demo-realism`
**Дата:** 2026-07-15
Документ фиксирует целевое состояние демо, этапы и критерии приёмки. Статус этапов: **Done** = реализовано в этой итерации; **Open** = остаётся.
---
## 1. Текущее состояние (as-is)
| Аспект | Состояние | Тип |
| ------ | --------- | --- |
| Движки | Continuous `/` + FSM `/details` | Измерено |
| Классификация на `/` | `classifyItem` wired (live) | Измерено (код) |
| Playlist | 10 кейсов вкл. jam + e-stop | Измерено |
| Proof | HUD DIM/K/reason + CV RULE | Измерено |
| Управление | seek, 0.52×, hotkeys, presentation | Измерено |
| Quality | low/medium/high/demo | Измерено |
| Physics | Детерминированная кинематика + jitter | Измерено |
| CV | Pseudo | Измерено |
| Twin parity `/` vs `/details` | Расходятся визуально | Измерено |
| Prod sync | :3100 может быть старым | Измерено (риск) |
| Tests | 153 / 16 files | Измерено |
| Agent | MVP verify-only | Измерено |
---
## 2. Целевое состояние (to-be)
Демонстрация должна за 510 секунд отвечать зрителю:
1. Что это за система (промышленная сортировка OZON Tech).
2. Откуда берётся решение (измерения → правила → B/C/D).
3. Что делают механизмы (gate/pusher/маршрут).
4. Что происходит при аварии (jam / e-stop + recover).
5. Что это не «мультфильм»: proof HUD, журнал, метрики.
Целевые свойства:
- визуально реалистичный industrial twin;
- физически правдоподобное (хотя бы кинематически согласованное) движение;
- стабильные 3060 FPS в режиме demo;
- one-command start + health;
- откат через git tag;
- production = тот же билд, что прошёл тесты.
---
## 3. Этапы
| # | Этап | Приоритет | Сложность | Статус | Ожидаемый эффект |
| - | ---- | --------- | --------- | ------ | ---------------- |
| E1 | Wire `classifyItem` в continuous | P0 | Низкая | **Done** | Доверие к алгоритму |
| E2 | Measurement + DIMENSION_LIMITS + confidence | P0 | Средняя | **Done** | Корректные min dims, reason |
| E3 | Playlist 8→10 + fault timelines | P0 | Средняя | **Done** | Safety story для жюри |
| E4 | Fault freeze/recover + seeded jitter | P1 | Средняя | **Done** | Правдоподобие / воспроизводимость |
| E5 | Demo controls + presentation + journal | P0 | Средняя | **Done** | Управление показом |
| E6 | Quality modes | P1 | Низкая | **Done** | FPS на слабых клиентах |
| E7 | Proof HUD + CV RULE overlay | P0 | Средняя | **Done** | «Видно почему» |
| E8 | simulation tests jam/estop/c_priority | P1 | Низкая | **Done** | Регрессионная защита |
| E9 | Agent MVP + scripts | P1 | Средняя | **Done** | Ops / future autonomy |
| E10 | resolveItem SKU-*-LC | P2 | Низкая | **Done** | Стабильность данных |
| E11 | Унификация визуала `/` и `/details` | P1 | Высокая | **Open** | Единый образ системы |
| E12 | Redeploy prod dist на :3100 | P0 | Низкая (ops) | **Open** | Публичная демо актуальна |
| E13 | Playwright в CI (1 worker) | P2 | Средняя | **Open** | Авто-регрессия UI |
| E14 | Опциональный physics (Rapier lite) | P3 | Высокая | **Open** | Доп. fidelity (не блокер) |
| E15 | Agent auto-patch за human gate | P3 | Высокая | **Open** | Автономия уровня 2 |
---
## 4. Приоритет (матрица)
| Приоритет | Фокус |
| --------- | ----- |
| P0 | То, без чего живой показ врёт или ломается: классификация, proof, faults, controls, prod sync |
| P1 | Убедительность и стабильность: quality, tests, twin unify, agent ops |
| P2 | Удобство и покрытие: e2e CI, data edge cases |
| P3 | Исследования: real physics, full autonomous implementer |
---
## 5. Ожидаемый эффект по направлениям
| Направление | Эффект Done-этапов | Остаточный разрыв |
| ----------- | ------------------ | ----------------- |
| Demo clarity | Высокий — seek/hotkeys/presentation | — |
| Algorithm proof | Высокий — live classify + HUD | Pseudo-CV всё ещё |
| Fault story | Высокий — jam/e-stop в playlist | — |
| Visual realism | Средний+ | Dual twin, нет AO/heavy PBR |
| Physics fidelity | Средний | Нет rigid body |
| Ops | Высокий — scripts + agent | Redeploy path вне docker CLI |
---
## 6. Сложность оставшихся работ
| Работа | Сложность | Зависимости | Риск |
| ------ | --------- | ----------- | ---- |
| Unify twins | Высокая | Общий scene kit, не ломая оба маршрута | Регрессия `/details` |
| Prod redeploy | Низкая | Доступ к docker/host вне среды | Забыть обновить tunnel cache |
| e2e CI | Средняя | 1 worker, артефакты | Flaky screenshots |
| Rapier | Высокая | Performance budget | FPS падение |
| Agent patcher | Высокая | Safety limits, staging | Порча ветки |
---
## 7. Риски плана
| Риск | Митигация |
| ---- | --------- |
| Погоня за «настоящей физикой» убивает FPS | Держать кинематику; physics только opt-in |
| Унификация twin ломает details UX | Feature flag / поэтапный shared module |
| Агент без лимитов | Kill switch + forbid deploy |
| Показ со старым prod | `demo:health` + явный checklist redeploy |
---
## 8. Зависимости
```text
E12 (redeploy) зависит от зелёных tests/build на feature-ветке
E11 (unify) зависит от стабильного continuous twin (E1E7 Done)
E13 (e2e CI) зависит от ёмкости (1 worker) и стабильных селекторов
E15 (agent patch) зависит от E9 + AI_AGENT_SAFETY_LIMITS
```
---
## 9. Критерии приёмки
### Must (для «максимальной демо» итерации)
- [x] Continuous использует `classifyItem` (не только playlist label).
- [x] Playlist ≥10 с jam и emergency_stop.
- [x] Proof HUD показывает DIM, K, reason.
- [x] Hotkeys + speed + seek + presentation работают.
- [x] Quality modes существуют и покрыты тестами.
- [x] Vitest зелёный (≥153).
- [x] Build OK.
- [x] Backup tag существует.
- [ ] Production https://arhipovdan.ru/ отдаёт новый dist (**Open**).
### Should
- [ ] Визуальный parity ключевых элементов `/` и `/details`.
- [ ] Playwright smoke в CI (1 worker).
### Could
- [ ] Лёгкий physics layer.
- [ ] Agent auto-patch с обязательным human merge.
---
## 10. Рекомендуемый порядок дожима
1. Redeploy production (E12) — максимальный ROI для жюри.
2. Visual unify twin (E11) — доверие «одной системы».
3. e2e smoke (E13).
4. Остальное — по необходимости.

View File

@@ -1,162 +0,0 @@
# Demo Runbook — OZON Tech Sorter Simulation
**Назначение:** провести живую демонстрацию жюри/заказчику без сюрпризов.
**Дата актуализации:** 2026-07-16
**Рабочий публичный URL (временный):** `https://invitations-based-characters-accent.trycloudflare.com/`
**Ожидаемый постоянный домен:** https://arhipovdan.ru/ — **BLOCKED_EXTERNAL** (`PUBLIC_DOMAIN_DIAGNOSTIC.md`)
**Локальный production:** http://127.0.0.1:3100/ — commit `4fcce5b`
**Проверка версии:** `curl -s http://127.0.0.1:3100/version.json`
**Репозиторий:** `/home/coder/arhipovdan/app`
---
## 1. За 3060 минут до показа
### 1.1. Остановить фоновые помехи
```bash
cd /home/coder/arhipovdan/app
node agent/cli.mjs stop
# убедиться, что Playwright/тяжёлые job не бегут
```
### 1.2. Зелёный baseline
```bash
npm test
npm run build
```
Ожидание: Vitest **166** passed; build OK.
### 1.3. Health
```bash
npm run demo:health
```
Проверяет:
- `http://127.0.0.1:3100/` (prod loopback),
- `http://127.0.0.1:3101/` (preview, если поднят),
- `https://arhipovdan.ru/`,
- vitest.
### 1.4. Актуальный билд на том URL, который показываете
| Если показываете | Что сделать |
| ---------------- | ----------- |
| Локальный preview | `npm run demo:start` → http://127.0.0.1:3101/ |
| Публичный сайт | Убедиться, что :3100 отдаёт **новый** `dist` (ручной redeploy). Иначе жюри увидит старую версию. |
> В этой среде может не быть `docker` CLI — redeploy выполняется тем процессом, которым контейнер обычно обновляется на OwlPrime.
---
## 2. Старт одной командой
```bash
cd /home/coder/arhipovdan/app
npm run demo:start
```
Скрипт: при необходимости `npm ci`, `npm run build`, поднимает `vite preview` на **127.0.0.1:3101**.
Остановка:
```bash
npm run demo:stop
# reset: bash scripts/demo-reset.sh
```
---
## 3. Сценарий показа (рекомендуемый тайминг ~68 мин)
| Мин | Что делать | Что говорит оператор |
| --: | ---------- | -------------------- |
| 0:00 | Открыть `/`, включить Presentation (P/F) | «Цифровой двойник линии сортировки» |
| 0:20 | Дать continuous playback идти | «Товар едет → измеряется → классифицируется» |
| 1:00 | Указать Proof HUD (DIM, K, reason) | «Решение — правила classifyItem, не просто ролик» |
| 2:00 | Кейсы B → C → D | «Габариты / негабарит / круглое сечение» |
| 3:30 | Кейс c_priority | «C приоритетнее D» |
| 4:00 | Jam | «Затор — поток заморожен» |
| 4:40 | Emergency stop | «E-stop — safety stop» |
| 5:20 | Speed 0.5× на сложном кейсе | «Замедляем для разбора» |
| 5:50 | При необходимости `/details` | «Инженерный FSM и сенсоры» — осторожно: визуал twin другой |
| 6:30 | Q&A | Hotkeys 10 для прыжка к кейсу |
---
## 4. Hotkeys оператора
| Клавиша | Действие |
| ------- | -------- |
| Space | Пауза / продолжить |
| N | Следующий кейс |
| B | Предыдущий |
| R | Reset |
| P / F | Presentation / fullscreen |
| E | Журнал событий |
| 10 | Быстрый переход по кейсам |
Скорость: **0.5×2×** в demo controls.
---
## 5. Playlist (10 кейсов) — шпаргалка
Порядок storytelling (см. `demoPlaylist.ts`):
1. Короб**B**
2. ЛанчБокс → **B**
3. Негабарит → **C**
4. Мелкий item (ручка) → **C** (min dims)
5. Тарелка → **D** (K)
6. Бутылка → **D**
7. Oversized+round → **C** (priority)
8. (edge / доп. кейс по playlist)
9. **Jam**
10. **Emergency stop**
Точные id/SKU — в `src/domain/demoPlaylist.ts`.
---
## 6. Если что-то пошло не так
| Симптом | Действие |
| ------- | -------- |
| Чёрный экран / WebGL | Обновить страницу; проверить `ThreeCapabilityCheck`; снизить нагрузку (не demo на слабом GPU клиента) |
| «Не та» логика на сайте | Сравнить с preview :3101; вероятно устарел prod dist → redeploy |
| Завис после jam | R / следующий кейс; не паниковать — freeze ожидаем |
| Красные тесты утром | Не начинать показ; `npm test`, чинить на feature-ветке |
| Агент что-то пишет | `node agent/cli.mjs stop`; MVP не патчит, но stop обязателен |
| Нет сети к публичному URL | Показать loopback :3100 или preview :3101 |
Откат кода:
```bash
git checkout backup/pre-maximum-demo-realism-20260715
```
---
## 7. Что не обещать жюри
- «Настоящий CV с камеры» — нет, pseudo-CV.
- «Полный physics engine» — нет, кинематика.
- «Агент сам выкатывает в prod» — запрещено.
- «`/` и `/details` — идентичная 3D-сцена» — пока нет.
---
## 8. После показа
```bash
npm run demo:stop
node agent/cli.mjs resume # если нужен ночной dry-run
# или оставить stop до следующего окна обслуживания
```
Собрать feedback → занести в backlog агента / issues; не коммитить секреты.

View File

@@ -1,120 +0,0 @@
# Demo Script
Open https://arhipovdan.ru/ (или http://127.0.0.1:3100/).
Страница — product demo: Hero → Demo → Storyline → Scenarios → C-priority → Criteria → Engineering Details.
## 30-Second Pitch
«Мы сделали инженерную симуляцию сортировочной ячейки OZON с 3D digital twin. Полный цикл: тестовые измерения → classification → command → routing. Товар уходит в B/C/D в цифровом стенде. На ленте — state machine; на drop-участке — Rapier.»
## 7-Minute Structure
Рекомендуемый тайминг для защиты (максимум 7 минут):
- **0:000:20** Что это за система? ПАК предварительной сортировки, полный цикл A → CV → B/C/D.
- **0:200:45** Полный контур: Detection → Classification → Command → Actuator → Route.
- **0:451:30** Категория B: габариты проходят, нет круга, зелёный маршрут в основной сортировщик.
- **1:302:15** Категория C: негабаритный товар, оранжевый маршрут в roll-cage C.
- **2:153:00** Категория D: габариты проходят, но круг в сечении (K > 0.8), фиолетовый маршрут в roll-cage D.
- **3:003:45** C-priority: негабаритный + круглый → всё равно C (приоритет габаритов).
- **3:454:30** Low confidence и fault: rule-based fallback, FAULT состояние, EMERGENCY_STOP.
- **4:305:30** Engineering Details: timeline, PID, sensors, event log, 3D capability check.
- **5:306:30** Критерии OZON и метрики: cycle time, устойчивость, воспроизводимость.
- **6:307:00** Ограничения MVP и развитие: real CV, physics engine, WebSocket телеметрия.
## 3-Minute Demo
1. На первом экране укажите Hero: что это за система и цепочку Detection → Classification → Command → Routing.
2. Нажмите **Запустить демо**.
3. В блоке **Главное демо** покажите **3D Digital Twin** и карточку результата:
- товар на конвейере;
- накопитель и stop-gate;
- категория B/C/D;
- команда `ROUTE_TO_*`;
- подсветка маршрута в зону.
4. Нажимайте **Next step**, следите за **Этапами цикла** и движением в 3D.
5. В **Сценариях** нажмите **Показать** на «Негабарит» — оранжевый route C, roll-cage C.
6. Затем «Круглый объект» — фиолетовый route D.
7. Затем «Застревание» или «Аварийная остановка» — красная подсветка, FAULT / EMERGENCY_STOP, Reset.
8. При необходимости переключите **2D fallback** — логика та же.
## 5-Minute Demo
Пройдите сценарии по карточкам:
1. Обычный товар — B/C/D поток.
2. Негабарит — C.
3. Круглый объект — D.
4. Пограничные размеры — строгие min/max.
5. Низкая уверенность CV — fallback.
6. Очередь товаров — queue/spacing.
7. Застревание — FAULT.
8. Аварийная остановка — EMERGENCY_STOP.
Для каждого:
1. Нажмите **Показать**.
2. **Start demo** / **Next step**.
3. Укажите категорию, причину и маршрут на карточке результата.
4. Покажите активный шаг в Storyline Stepper.
## Engineering Details
Если жюри просит PID, event log, sensors или полный layout:
1. Нажмите **Инженерный режим** (header / hero / demo).
2. Откроется секция **Engineering Details** с полными панелями.
3. Полная сцена — `SorterScene variant="full"`.
## Что делать, если демо зависло
1. **Reset** в header или в блоке демо.
2. Выберите сценарий заново карточкой **Показать**.
3. При необходимости откройте Engineering Details и смените сценарий там.
## Fallback, если домен не открывается
1. SSH на сервер.
2. Проверьте локальный frontend:
```bash
curl -I http://127.0.0.1:3100/
```
3. Проверьте Docker:
```bash
docker compose -p owl -f /opt/arhipovdan/app/docker-compose.server.yml ps
```
## Mobile / no horizontal scroll
Перед защитой проверьте:
- 1920×1080 — hero + demo читаемы, CTA видны;
- 1440×900 — нет debug-dashboard на первом экране;
- 390×844 — одна колонка, крупные кнопки, сцена не вылезает.
В консоли:
```js
document.documentElement.scrollWidth <= document.documentElement.clientWidth
```
## 3D / WebGL
- Desktop: по умолчанию 3D Digital Twin (если WebGL есть).
- Mobile: по умолчанию 2D; 3D можно включить вручную.
- Capability check: Engineering Details → блок **3D capability check** (WebGL, FPS).
- Physics engine: **не подключён** (state-machine animation).
## What Each Block Proves
- Hero — смысл проекта за 10 секунд.
- 3D digital twin — физическая маршрутизация A → CV → gate → B/C/D.
- Proof card — почему выбрана категория B/C/D.
- Storyline — текущий этап цикла.
- Scenario cards — jury test cases без узкого скролл-списка.
- Criteria cards — покрытие критериев OZON.
- Engineering Details — timeline, PID, event log, sensors, 3D capability check.

View File

@@ -1,78 +0,0 @@
# DNS_MIGRATION_INVENTORY
Дата: 2026-07-16 (re-verified)
Домен: `arhipovdan.ru`
Источник: публичные `dig` запросы с OwlPrime/coder.
## Текущие authoritative NS
```text
ns1.reg.ru.
ns2.reg.ru.
```
REG.RU = регистратор **и** DNS provider. Cloudflare zone пока **не** создана из этого окружения (нет `cert.pem` / API token).
## Публичные записи
| Type | Name | Current value | TTL (approx) | Must migrate | Purpose |
| ---- | ---- | ------------- | -----------: | ------------ | ------- |
| NS | @ | ns1.reg.ru / ns2.reg.ru | — | Yes (delegation) | Authoritative DNS |
| SOA | @ | ns1.reg.ru. hostmaster… | 10800 | Auto in CF | Zone SOA |
| A | @ | 185.160.137.162 | 21600 | **Replace** with CF tunnel route | Broken openresty (404 / TLS SNI fail) |
| A | www | 185.160.137.162 | — | **Replace** with CF tunnel route | Same broken endpoint |
| AAAA | @ | *(none)* | — | No | — |
| CNAME | @ | *(none)* | — | No | Apex CNAME not used |
| MX | @ | *(none publicly visible)* | — | **Check in REG.RU panel** | Mail may be absent |
| TXT | @ | *(none publicly visible)* | — | **Check in REG.RU panel** | SPF / domain verify |
| TXT | `_dmarc` | *(none)* | — | **Check panel** | DMARC |
| TXT | `default._domainkey` | *(none)* | — | **Check panel** | DKIM |
| CAA | @ | *(none)* | — | Optional | TLS CA policy |
### Subdomain probe (public A)
Many common labels resolve to the **same** `185.160.137.162` (likely REG.RU catch-all / parking):
| Name | A | Notes |
| ---- | - | ----- |
| mail | 185.160.137.162 | Confirm real mail usage in panel |
| smtp | 185.160.137.162 | |
| api | 185.160.137.162 | |
| app | 185.160.137.162 | |
| staging | 185.160.137.162 | |
| blog | 185.160.137.162 | |
| ftp | 185.160.137.162 | |
**Перед сменой NS** откройте панель REG.RU → DNS и перенесите **все** реальные записи (особенно MX/TXT/DKIM), не только A для сайта.
## Что нельзя потерять
1. Любые MX / SPF / DKIM / DMARC (если почта используется).
2. TXT верификации (Google, Yandex, Cloudflare, etc.).
3. Поддомены других сервисов, если отличаются от parking IP.
4. CAA, если планируется нестандартный CA.
## Рекомендуемый target после Cloudflare zone
| Hostname | Type on Cloudflare | Target |
| -------- | ------------------ | ------ |
| `arhipovdan.ru` | Cloudflare Tunnel CNAME / route | Named Tunnel `owlprime-sorter-production` |
| `www.arhipovdan.ru` | same | same tunnel ingress |
Не создавать «A = Named Tunnel» — A принимает только IPv4.
## Сравнение вариантов публикации
| Вариант | Требования | Риск | Рекомендация |
| ------- | ---------- | ---- | ------------ |
| Cloudflare Named Tunnel + NS на CF | Cloudflare zone + миграция всех DNS | Низкий | **Предпочтительно** |
| Direct nginx на OwlPrime :80/:443 | Public IP, NAT, firewall, TLS | Средний/высокий | Не делать автоматически |
| Quick Tunnel | Нет постоянного hostname | Высокий для demo | Только temporary fallback |
## Статус миграции
```text
Inventory: DONE (public)
Cloudflare zone: NOT CREATED (needs user / API login)
NS changed: NO
```

66
docs/ENGINEERING.md Normal file
View File

@@ -0,0 +1,66 @@
# Engineering notes (canonical)
Companion to `README.md` and `/documentation`. Not a stage changelog.
## Coordinate conventions
- World units: **1 unit = 1 meter**.
- Belt travel primarily along **+X**; belt top Y ≈ **0.70 m**.
- Lateral: **+Z** = physical LEFT (category C), **Z** = physical RIGHT (category D).
- Sorter CAD module origin X = `0`; camera module `2.01`; clean module `4.02`.
- Longitudinal plane S for diverter timing is world X along the sorter module.
## Canonical constants (code)
| Symbol | Value | File |
|---|---|---|
| `DIVERTER_LEFT_SIGNED_DEG` | 45 | `src/domain/pusherMotion.ts` |
| `DIVERTER_RIGHT_SIGNED_DEG` | +45 | same |
| `rotationDurationSec()` | 0.50 | same (45° / 90°/s) |
| `OPENING_SAFETY_MARGIN_SEC` | 0.15 | same |
| Contact / clear planes | ≈1.0538 / 1.6000 | `buildDiverterPlanes` + mount hinge |
| Classifier min/max | exclusive 10³ / 450×320×320 | `src/domain/classifier.ts` |
| Roundness | K > 0.8 | same |
| `CONVEYOR_CAD_URL` | `/models/sorter/conveyor-clean.glb` | `ConveyorCadModel.tsx` |
## Active source tree (runtime)
```
src/main.tsx
src/App.tsx
src/pages/{MainPage,DocumentationPage}.tsx
src/components/{AppNav,SorterScene,CVInspectionOverlay,BuildIdentityBadge}.tsx
src/components/ThreeD/* (active twin only)
src/domain/* (classifier, playback, layout, diverter, physics helpers)
src/data/{items,modelAssets,resolveItem,productionStatusSummary,demoPlaylist,scenarios}.ts
src/styles.css
```
## Asset provenance
| Role | Path |
|---|---|
| Author CAD | `3d_models/conveer.FCStd` |
| Runtime conveyor | `public/models/sorter/conveyor-clean.glb` |
| Products | `public/models/*.stl` from official STL ZIP |
| Classifier PDF | `official_sources/doc-1783095831.pdf` |
| Workspace / scoring PDFs | `input_info/doc-1783009942.pdf`, `doc-1783011400.pdf` |
## Physics roadmap (not completed)
1. Surface-velocity belt at 1 m/s with visual loop.
2. Contact-validated CAD diverter deflection for all playlist SKUs.
3. Calibrated per-SKU mass, COM, friction, damping.
4. Receiver capture verification under dynamic drops.
CCD for light/thin items exists in runtime/sim; that alone is **not** full contact validation.
## Compliance evidence rules
- Prefer present official files under `input_info/` and `official_sources/doc-1783095831.pdf`.
- Missing: `input_info/extracted/Постановка_Задача_3_сжато_2.pdf` — never claim it is available.
- Internal engineering criteria are not automatic Ozon pass/fail.
## Layout drawing
`docs/engineering/work-area-layout-source.png` — workspace layout provenance image retained for engineering reference.

View File

@@ -1,40 +0,0 @@
# FINAL_DEMO_READINESS
Дата: 2026-07-16 (re-verified)
## Status board
| Область | Статус | Комментарий |
| ------- | ------ | ----------- |
| Git | PASS | Feature branch; no push |
| Production container | PASS | `20260715-2221` / `index-AagIOJbd.js` / `version.json``4fcce5b` |
| Permanent domain | BLOCKED_EXTERNAL | REG.RU → openresty; CF Named Tunnel not auth'd |
| TLS (permanent) | BLOCKED_EXTERNAL | SNI fail on 185.160.137.162 |
| Quick Tunnel | PASS | Temporary public URL OK; smoke PASS |
| Main / details | PASS | Local + tunnel |
| Unit / E2E / visual | PASS | 166 / 15 / 10 (2026-07-16) |
| GPU hardware (server) | BLOCKED_BY_DISPLAY_ENVIRONMENT | SwiftShader only |
| Portable benchmark | PASS tooling | `npm run perf:browser` on laptop |
| Replay stability | STABLE | See `REPLAY_RESOURCE_STABILITY.md` |
| Rollback | PASS | `owl-web-1-backup-20260715-2221` |
| Agent / secrets | PASS | no credentials in git |
## Application readiness
```text
READY
```
## Public domain readiness
```text
WAITING — Cloudflare login + NS cutover (see CLOUDFLARE_NAMED_TUNNEL_SETUP.md)
```
## Verdict
```text
READY FOR LIVE DEMO VIA QUICK TUNNEL
```
After user completes Cloudflare zone + NS change → `READY, WAITING FOR NS CUTOVER` then permanent PASS.

View File

@@ -1,80 +0,0 @@
# Final Physical Acceptance Audit
## 1. Verdict: ГОТОВО
Все критические требования к 3D-демо выполнены. Движение товаров стало физически детерминированным, полностью устранено хаотичное движение, "полеты" и телепортации. Демо готово к записи видео защиты.
## 2. Что проверено на production
- Production URL: `https://arhipovdan.ru/` и `https://arhipovdan.ru/details`.
- Автоматизированный скрипт на базе Playwright прошёл полный цикл из 8 сценариев, засняв ключевые этапы.
- Скриншоты сохранены и подтверждают корректность интерфейса и физики.
- Проверка доступности: все адреса возвращают `HTTP 200 OK`.
## 3. 8-scenario checklist
- [x] **box_b → B**: Стандартный товар корректно едет по конвейеру в зону B.
- [x] **lunchbox_b → B**: Компактный товар корректно проходит классификацию в зону B.
- [x] **oversized_box_c → C**: Негабарит уходит по `chute_c` и оседает в C.
- [x] **small_item_c → C**: Слишком маленький товар забракован и уходит в C.
- [x] **plate_d → D**: Круглый товар распознан и отправлен в D.
- [x] **bottle_d → D**: Цилиндрический товар отправлен в D.
- [x] **c_priority → C**: Товар, который одновременно негабаритный и круглый, отправлен в приоритетную зону C.
- [x] **low_confidence → B**: Товар с низкой уверенностью ML (с предупреждением в HUD) прошел по fallback-правилам в зону B.
Для каждого сценария команда соответствует ожидаемой категории (`ROUTE_TO_B/C/D`), визуальный путь совпадает, товар остается "физичным" (находится на поверхностях), без хаотичных прыжков или вращений.
## 4. Physical motion checklist
- [x] Товар движется только по допустимым поверхностям (лента, склиз C/D, линия B, пол корзины C/D).
- [x] Товар **не летит**.
- [x] Товар **не телепортируется**.
- [x] Товар **не проваливается** под текстуры.
- [x] Товар **не выходит за пределы roll-cage** после остановки (settled).
- [x] Скорость на ленте строго = 1 м/с.
- [x] Лента и товар движутся абсолютно синхронно в фазах перемещения (доказано тестами `physicalItemMotion`).
## 5. STL/model checklist
- [x] `box-300.stl` используется (SKU-001)
- [x] `lunchbox.stl` используется (SKU-002)
- [x] `box-400.stl` используется (SKU-004)
- [x] `plate.stl` используется (SKU-006)
- [x] `bottle.stl` используется (SKU-007)
- [x] `cylinder.stl` используется (SKU-008)
- [x] **Fallback primitives** используются честно и только там, где STL реально нет или она слишком тяжелая (например, моющее средство 3.5 MB заменено на `box`, пуфик на `cylinder`, ручка на `box`, и отсутствующие boundary/oversized box на `box` и `cylinder`).
## 6. C/D containment checklist
- [x] `oversized_box_c` → уходит по `chute_c`, остается внутри C roll-cage на уровне пола.
- [x] `c_priority` → уходит в C.
- [x] `plate_d / bottle_d` → уходят по `chute_d`, оседают внутри D roll-cage.
- Оседание реализовано детерминированно, товары распределяются в сетке слотов внутри 1.2x0.8м, поэтому остаются внутри корзины.
## 7. UI/adaptive checklist
- [x] HUD и CV overlay **не накладываются** (CV overlay смещен влево и имеет max-height).
- [x] Кнопки Play/Pause/Stop работают корректно.
- [x] Окно Demo Complete появляется после 8 сценариев.
- [x] Страница `/details` работает без проблем.
- [x] Mobile layout не ломается (ширина 390px протестирована), нет горизонтального скролла.
## 8. Console/runtime errors
- **Нет**. Проверка Playwright `page.on('console')` вернула 0 ошибок (`NO_CONSOLE_ERRORS`).
## 9. Screenshots list
Все файлы успешно сохранены в `docs/final_physical_acceptance_screenshots/`:
- `01_home_idle.png`
- `02_stl_item_on_belt.png`
- `03_belt_sync_t0.png`
- `04_belt_sync_t1.png`
- `05_route_to_b.png`
- `06_route_to_c_chute.png`
- `07_settled_in_c_cage.png`
- `08_route_to_d_chute.png`
- `09_settled_in_d_cage.png`
- `10_c_priority_to_c.png`
- `11_low_confidence_warning.png`
- `12_demo_complete.png`
- `13_mobile.png`
- `14_details.png`
## 10. Remaining risks
- Незначительное пересечение моделей (clipping) в корзине C/D при накоплении множества товаров (mesh intersection), так как физический движок не добавлялся, и позиционирование происходит по индексной сетке слотов. Это визуально допустимо и не ломает защиту.
## 11. Recommendation
**Можно смело переходить к записи видео защиты.** Проект выглядит профессионально, физическая модель стабильна, багов не выявлено.

View File

@@ -1,180 +0,0 @@
# FINAL PHYSICS FIX REPORT — 3D Demo Conveyor Network
Дата: 2026-07-09
Ветка: `dan_branch`
Production: https://arhipovdan.ru/ · https://arhipovdan.ru/details
---
## 1. Root causes confirmed
Подтверждено на production (`docs/final_physics_fix_screenshots/before/`) и в коде:
| # | Проблема | Root cause в коде |
|---|----------|-------------------|
| 1 | Движение ленты «жило отдельно» от товара | `BeltStripe` двигался через `useFrame` с покадровым инкрементом `posRef += delta * speedFactor` — отдельный источник, зависящий от FPS и не связанный с позицией товара. |
| 2 | B-товар исчезал после сортировки | В `PhysicalPlaybackItem` стоял ранний `return null` для `isSettled && expectedCategory === 'B'`. Физической приёмной зоны B не было — только плоский `ZoneMarker`. |
| 3 | Геометрия движения разбросана | Все координаты (belt, chute, cage) считались ad-hoc внутри `physicalItemMotion` через `ZONES.*`, без единой модели поверхностей. Не было единого «источника правды». |
| 4 | Кубик вместо STL для c_priority | `SKU-011` был помечен `loaderType: 'procedural'`, хотя round STL (`cylinder.stl`, 106 KB) доступен. |
| 5 | Слабый contain в cage | `cageFloorY` был захардкожен `0.1`, cage не имел видимого внутреннего пола — товар выглядел «висящим». |
---
## 2. Conveyor network surfaces
Создан единый источник правды: **`src/domain/conveyorNetwork.ts`**.
Все координаты в метрах (1 unit = 1 m).
| Surface | start → end (m) | surfaceY | width | speed | target |
|---------|-----------------|----------|-------|-------|--------|
| `main_belt` | A(-4,0) → CAMERA(-1.5,0) | 0.70 | 0.5 | 1.0 m/s | — |
| `inspection_station` | CAMERA (dwell) | 0.70 | 0.5 | 0 | — |
| `routing_junction` | CAMERA(-1.5) → GATE(1.5) | 0.70 | 0.5 | 1.0 m/s | — |
| `b_receiver` | GATE(1.5) → rest(4.0) | 0.70 | 0.5 | 1.0 m/s | B |
| `chute_c` | GATE edge(0.25z) → cage C front | 0.70→0.13 | 0.5 | 0.5 m/s | C |
| `chute_d` | GATE edge(-0.25z) → cage D front | 0.70→0.13 | 0.5 | 0.5 m/s | D |
| `c_cage_floor` | C(2.0, 2.0) | 0.08 | 1.2 | 0 | C |
| `d_cage_floor` | D(2.0, -2.0) | 0.08 | 1.2 | 0 | D |
Пути:
- **B**: `main_belt → inspection_station → routing_junction → b_receiver → settled_b`
- **C**: `main_belt → inspection_station → routing_junction → chute_c → c_cage_floor → settled_c`
- **D**: `main_belt → inspection_station → routing_junction → chute_d → d_cage_floor → settled_d`
Каждая поверхность имеет `bounds` (minX/maxX/minZ/maxZ) для containment-проверок.
---
## 3. Physical motion model
`src/domain/physicalItemMotion.ts` полностью переписан и теперь берёт **всю геометрию только из `conveyorNetwork`**:
- поза считается детерминированно от `elapsedMs` (без random, без покадровых инкрементов);
- позиция = интерполяция вдоль текущего segment (`lerp3`);
- `y = surfaceY + itemHeight / 2` (низ товара точно на поверхности);
- `rotation` = heading текущей поверхности (`surfaceHeading`);
- на `main_belt`/`routing_junction` скорость = **1 м/с**;
- на chute — плавный спуск по наклонной (0.5 м/с), не полёт;
- в cage товар фиксируется в детерминированном slot (grid 3×2 внутри bounds);
- `isSettled = true` только на `b_receiver`/`c_cage_floor`/`d_cage_floor` после завершения кейса;
- защита от NaN/Infinity.
Убраны все конкурирующие источники движения: `BeltStripe` больше не использует покадровый инкремент.
---
## 4. B receiving zone
Добавлен физический приёмный лоток **`BReceiver`** (в `SorterDigitalTwinContinuous.tsx`):
- короткий downstream receiving tray сразу после сортировщика;
- ширина 0.5 m, верхняя поверхность 0.7 m (на уровне ленты, не «платформа в воздухе»);
- невысокие борта (0.12 m) + торцевой стоп;
- опорные ноги до пола;
- B-товар доезжает по лотку и **остаётся** в нём (ранний `return null` удалён).
Константы: `B_RECEIVER` в `physicalLayout.ts` (`startX 2.2 → endX 4.4`, `restX 4.0`).
---
## 5. C/D containment
- `RollCage` получил **сплошной внутренний пол** на `CAGE_FLOOR_Y` (0.08 m), товар физически лежит на нём.
- cage стоит на полу (колёса), есть нижняя/верхняя рамка, вертикальные стойки, wireframe-стенки.
- вход — со стороны chute (front edge cage).
- после chute товар оказывается внутри `bounds`, распределяется по slot-grid, не выше верхней границы, после settled cage не покидает.
- проверено тестами: финальная поза C/D внутри `c_cage_floor` / `d_cage_floor` bounds, `y = CAGE_FLOOR_Y + h/2`.
---
## 6. STL / fallback table
| case | itemId | model path | STL / fallback |
|------|--------|-----------|----------------|
| box_b | SKU-001 | `/models/box-300.stl` | **STL** (29 KB) |
| lunchbox_b | SKU-002 | `/models/lunchbox.stl` | **STL** (566 KB) |
| oversized_box_c | SKU-004 | `/models/box-400.stl` | **STL** (27 KB) |
| small_item_c | SKU-009 (Ручка) | — | fallback thin box (исходный STL 2.0 MB — тяжёлый) |
| plate_d | SKU-006 | `/models/plate.stl` | **STL** (123 KB) |
| bottle_d | SKU-007 | `/models/bottle.stl` | **STL** (319 KB) |
| c_priority | SKU-011 | `/models/cylinder.stl` | **STL** (106 KB, round STL reused) |
| low_confidence | SKU-003 (Моющее ср-во) | — | fallback box (исходный STL 3.5 MB — тяжёлый) |
**Итог: 6/8 кейсов используют реальные STL.** 2 fallback — честные (исходные STL >2 MB, исключены по WebGL performance budget). Все 6 доступных лёгких STL используются.
---
## 7. Belt sync proof
- Формула ленты: `stripe.x = baseOffset + (time * CONVEYOR_SPEED_MPS) mod beltLen` — та же скорость 1 м/с, то же направление, что и товар.
- Детерминированно от `totalElapsedMs`: pause → лента и товар замирают; stop → оба reset (0).
- Не зависит от FPS (нет `delta`-инкремента).
- Unit-test: товар на `main_belt` за 1000 ms смещается ровно на **1.0 m** (`±0.02`).
Debug (из модели, height 0.2 m):
- t0 = 300 ms → item.x = 4.00 m (A)
- t1 = 1300 ms → item.x = 3.00 m
- Δx = **1.00 m за 1.00 s**
Скриншоты: `02_belt_sync_t0.png`, `03_belt_sync_t1_delta_1m.png`.
---
## 8. Tests
`npm run test`**13 файлов, 132 теста passed.**
Новые/обновлённые:
- `src/domain/conveyorNetwork.test.ts` — все surfaces присутствуют; конечные координаты/bounds; belt=1 м/с, cage/junction статичны; chute медленнее ленты, но >0; path per category; bounds-check; chute имеет реальную длину.
- `src/domain/physicalItemMotion.test.ts` — старт на main_belt; **1 m за 1 s**; низ товара = surfaceY; детерминизм; B внутри b_receiver; C внутри c_cage; D внутри d_cage; settled не двигается; нет NaN/Infinity по всему таймлайну; C едет по chute перед settle.
- `src/data/modelAssets.test.ts` — все 6 доступных demo-STL замаплены на реальные пути; fallback явные.
---
## 9. Production QA
- `npm run build` → OK.
- `npm run test` → 132 passed.
- `docker compose -p owl -f docker-compose.server.yml up -d --build``owl-web-1` recreated & started.
- `curl -I``https://arhipovdan.ru/` **200**, `https://arhipovdan.ru/details` **200**, `https://ai-shorts.ru/` **200**.
- Playwright прогон полного цикла 8 сценариев:
- Play запускает, все 8 кейсов доходят до отображения;
- **console errors: NONE**;
- товар не летит / не телепортируется (виден спуск по chute — `05_chute_c_motion.png`);
- STL видны на ленте и на chute;
- B-товар остаётся в приёмном лотке;
- mobile: `scrollWidth == clientWidth == 390`**горизонтального scroll нет**.
---
## 10. Screenshots
`docs/final_physics_fix_screenshots/before/` — before (хаос, нет B-зоны, исчезновение товара).
`docs/final_physics_fix_screenshots/after/`:
- `01_stl_box_on_belt.png` — STL короб на ленте;
- `02_belt_sync_t0.png`, `03_belt_sync_t1_delta_1m.png` — синхронизация ленты (Δ≈1 m);
- `04_b_receiver_item_settled.png` — B receiving tray;
- `05_chute_c_motion.png` — STL короб съезжает по chute C;
- `06_item_inside_c_cage.png` — C roll-cage;
- `07_chute_d_motion.png`, `08_item_inside_d_cage.png` — маршрут/cage D;
- `09_all_zones_physical.png` — все зоны (A, belt, inspection, B-tray, C/D cages);
- `10_no_overlay_overlap.png` — HUD и CV overlay не перекрываются;
- `11_mobile.png` — mobile (2D fallback, single column, без h-scroll);
- `12_details.png` — /details.
---
## 11. Remaining risks
- `11_mobile.png` в headless-Chromium показывает 2D fallback («WebGL not available») — это ожидаемо без GPU в CI; на реальных устройствах WebGL доступен. Layout корректный, h-scroll отсутствует.
- 2 кейса (`small_item_c`, `low_confidence`) остаются fallback-примитивами: исходные STL слишком тяжёлые (2.0 / 3.5 MB) — компромисс по WebGL performance budget.
- Скорость chute (0.5 м/с) — визуальная аппроксимация наклонного спуска, не результат физического движка (по требованию physics engine не добавлялся).
- Auto-camera может ловить кадр в момент перехода фаз; поза товара при этом всегда детерминирована и корректна.
---
## 12. Honest verdict
**ГОТОВО для записи видео защиты.**
Товар всегда на физической поверхности (belt → inspection → junction → b_receiver / chute → cage), не летает, не телепортируется, не проваливается, не исчезает и остаётся в зоне. Лента и товар синхронизированы (1 м/с, детерминированно, FPS-independent). B получил физическую приёмную зону. C/D удерживают товар. 6/8 STL реальны, 2 fallback честные. build/test/docker/production — зелёные, console без ошибок.

View File

@@ -1,187 +0,0 @@
# Final Visual Polish Report
## Summary
Completed final visual refinements to the production 3D demo, improving realism, readability, and UI cleanliness without changing business logic.
## Visual Improvements
### 1. Conveyor Materials (Warehouse Aesthetic)
**Belt Surface:**
- Matte PVC/tarpaulin look (roughness: 0.85, metalness: 0.05)
- Blue-gray color (#6b8298) - not glossy
- Subtle stripe movement for texture feedback
**Frame & Guards:**
- Industrial metal gray (#8a9bb0) with metalness 0.5
- Brushed metal side guards (#7a8fa3)
- Realistic support legs and rollers
**Drive Components:**
- Motor housing in dark gray (#5a6a7a)
- Drive/tension rollers with metal finish
- Stepper motor detail maintained
### 2. Lighting & Shadows
**Soft Natural Lighting:**
- Ambient light: 0.7 intensity (reduced from 0.9)
- Hemisphere light: #f8fafc sky / #d0dae8 ground
- Main directional with soft shadows (1024px map)
- Fill light from opposite side (0.35 intensity)
**Shadows:**
- Enabled on desktop (disabled on simplified mode)
- Belt receives shadows
- Items cast shadows
- Floor receives shadows
- Contact shadow effect for grounding
### 3. Item/STL Readability
**Materials Updated:**
- Reduced emissive intensity (×0.5)
- Roughness 0.5, metalness 0.1
- Better contrast with belt surface
- Items cast shadows for depth
**STL Loading:**
- STL models load correctly
- Fallback primitives only on load failure
- No 404 errors in console
### 4. Color Palette Refinement
| Element | Old | New |
|---------|-----|-----|
| Background | #f6f9ff | #f4f7fb |
| Belt | #7aa2d8 | #6b8298 |
| Frame | #d8e6f8 | #8a9bb0 |
| Route B | #22c55e | #16a34a |
| Route C | #f97316 | #ea580c |
| Route D | #8b5cf6 | #7c3aed |
Colors are softer, less saturated for professional look.
### 5. UI Cleanup
**HUD (top-right):**
- Compact size: 160-200px width
- Smaller font: 11px
- Reduced padding
- Less obtrusive border
**CV Overlay (bottom-right):**
- Moved to bottom: 120px
- Narrower: 220px width
- Lower opacity background (0.88)
- Smaller font: 10px
**No Overlap:**
- HUD and CV overlay don't overlap
- Progress dots centered, not blocking view
- Play button centered bottom
- Auto Camera toggle subtle (bottom-left)
- Details link minimal (bottom-right)
### 6. Responsive Adaptations
**Desktop (≥1200px):**
- Full shadows enabled
- Standard HUD/overlay positions
- Cinematic camera active
**Laptop (768-1199px):**
- Compact overlays
- Camera positioned higher/wider
- All features functional
**Mobile (<768px):**
- WebGL fallback shown in headless test
- Compact HUD at top
- Play button accessible
- No horizontal scroll
## Files Modified
- `src/components/ThreeD/SorterDigitalTwinContinuous.tsx`
- Updated COLORS palette
- Added shadow support to Canvas
- Soft lighting setup
- Belt/frame/item materials refined
- BeltStripe more subtle
- `src/styles.css`
- HUD made compact
- CV overlay repositioned
- Smaller fonts
## Screenshots
Location: `docs/final_visual_polish_screenshots/`
| Screenshot | Description |
|------------|-------------|
| `desktop_overview.png` | Initial view with Play button |
| `desktop_playing_item_on_belt.png` | Item on belt with laser/stereo |
| `desktop_inspection_overlay.png` | CV overlay during measurement |
| `desktop_routing_to_b.png` | Routing to B zone |
| `desktop_routing_to_c.png` | Routing to C roll-cage |
| `desktop_routing_to_d.png` | Routing to D roll-cage |
| `desktop_roll_cages.png` | View of C/D cages |
| `laptop_1440.png` | Laptop viewport |
| `mobile_390.png` | Mobile viewport (fallback) |
| `details_page.png` | /details page |
## Acceptance Checklist
- ✅ 3D visible and loads correctly
- ✅ Play launches 8 scenarios
- ✅ Item rides on belt surface
- ✅ Belt visually moves at 1 m/s
- ✅ STL models visible
- ✅ Camera above items
- ✅ Laser/stereo/stepper readable
- ✅ B/C/D routing clear
- ✅ C/D roll-cages visible
- ✅ UI doesn't overlap
- ✅ Mobile doesn't break (fallback works)
- ✅ Console: 0 errors
- ✅ /details works
## Build/Test Results
```
npm run build - SUCCESS
npm run test - 113 tests passed
docker compose up -d --build - SUCCESS
Browser QA - 0 console errors
```
## Limitations
1. **Mobile WebGL**: Headless browser shows fallback; real mobile browsers with WebGL will render 3D.
2. **Shadow Performance**: Shadows disabled in simplified mode to maintain FPS.
3. **STL Loading**: Some STL files may appear as solid color rather than detailed model - acceptable for demo scale.
## Verdict
**ГОТОВО** - Demo production-ready for presentation.
## Commit Commands
```bash
git add -A
git commit -m "polish: final visual refinements for 3D demo
- Matte PVC belt material (roughness 0.85)
- Soft natural lighting with shadows
- Industrial color palette
- Compact HUD and CV overlay
- No UI overlap
- Items cast shadows for depth"
git push origin dan_branch
```

View File

@@ -1,199 +0,0 @@
# Полный аудит проекта — OZON Tech Sorter Simulation
**Дата аудита:** 2026-07-15
**Ветка:** `feature/maximum-demo-realism` (от `dan_branch` @ `e89c728`)
**Точка отката:** тег `backup/pre-maximum-demo-realism-20260715`
**Репозиторий:** `/home/coder/arhipovdan/app`
**Публичный URL:** https://arhipovdan.ru/
| Метка | Значение |
| ----- | -------- |
| Тип данных | Измерено / проверено в репозитории и на сервере |
| Статус | Frontend-only MVP, Track 3 (цифровой двойник сортировки) |
---
## 1. Описание проекта
**OZON Tech Sorter Simulation** — React/Vite/Three.js цифровой двойник промышленной сортировки товаров (Track 3). Система демонстрирует полный контур:
```text
Поступление объекта
→ обнаружение (pseudo-CV / сенсоры)
→ измерения и признаки (габариты, K-roundness)
→ classifyItem (детерминированные правила)
→ управляющий сигнал (gate / pusher)
→ кинематическое перемещение
→ маршрут B / C / D или fault
→ журнал событий + метрики
```
**Характер MVP:** только frontend. Backend, WebSocket, БД, API-сервер — отсутствуют. Сборка — статический `dist`, раздача через nginx (Docker-образ или локальный preview).
**Два движка демонстрации:**
| Маршрут | Движок | Назначение |
| ------- | ------ | ---------- |
| `/` | Continuous playback + playlist | Основная демо для жюри: непрерывный 3D twin, seek/speed, hotkeys |
| `/details` | State-machine simulation | Инженерный разбор: FSM, сенсоры, PID, сценарии |
Оба движка используют общий `classifyItem` и доменные типы, но **визуальные 3D-сцены всё ещё различаются** (`SorterDigitalTwinContinuous` vs `SorterDigitalTwin`).
---
## 2. Архитектура
```text
src/data/items.ts, scenarios.ts, resolveItem.ts, modelAssets.ts
|
v
src/domain/classifier.ts ← единый источник решения (B/C/D)
|
+-- continuousPlayback.ts + demoPlaylist.ts + measurementSystem.ts → MainPage (/)
|
+-- simulation.ts + metrics/pid/sensors → DetailsPage (/details)
|
v
React UI + R3F (Three.js) + Proof HUD / CV overlay / EventLog
```
**Деплой (production-контур):**
```text
Vite build → dist/
→ Docker (node:20-alpine build + nginx:alpine runtime)
→ nginx слушает 127.0.0.1:3100 на хосте OwlPrime
→ cloudflared tunnel → https://arhipovdan.ru/
```
В текущем окружении **нет docker CLI** для оператора; Node 20.20.2 используется для локальной сборки. Production-контейнер мог быть поднят вне этой среды — см. риск устаревшего `dist` на :3100.
---
## 3. Карта модулей
| Область | Путь | Роль |
| ------- | ---- | ---- |
| Точка входа | `src/main.tsx`, `src/App.tsx` | Router: `/`, `/details` |
| Continuous demo | `src/domain/continuousPlayback.ts` | Фазы кейса, таймлайн, fault freeze |
| Playlist | `src/domain/demoPlaylist.ts` | 10 кейсов (классификация + jam + e-stop) |
| Измерения | `src/domain/measurementSystem.ts` | Stepper/laser/stereo → `classifyItem` |
| Классификатор | `src/domain/classifier.ts` | `DIMENSION_LIMITS`, приоритет C над D |
| Кинематика | `src/domain/physicalItemMotion.ts` | Путь по сети конвейера, jitter, freeze |
| Layout | `src/domain/physicalLayout.ts`, `conveyorNetwork.ts` | Единицы мм/м, геометрия линий |
| Качество | `src/domain/qualityMode.ts` | low / medium / high / demo |
| FSM sim | `src/domain/simulation.ts` | State machine для `/details` |
| 3D continuous | `src/components/ThreeD/SorterDigitalTwinContinuous.tsx` | Основной twin |
| 3D details | `src/components/ThreeD/SorterDigitalTwin.tsx` | Инженерный twin |
| Proof UI | `CurrentProofCard`, `CVInspectionOverlay` | DIM / K / reason / RULE |
| Agent MVP | `agent/cli.mjs` | dry-run / run-once / status / stop… |
| Demo scripts | `scripts/demo-*.sh`, `scripts/agent-*.sh` | One-command ops |
---
## 4. Текущий стек
| Слой | Технология | Примечание |
| ---- | ---------- | ---------- |
| Bundler | Vite | Production build ~сотни ms |
| UI | React 19 + TypeScript | SPA |
| 3D | Three.js + @react-three/fiber + drei | Клиентский WebGL |
| Тесты | Vitest | Unit/domain; e2e Playwright — вручную |
| Контейнер | Docker multi-stage + nginx | Статический хостинг |
| Runtime на сервере | nginx :3100, cloudflared | Без pm2 в этой среде |
| Node (build) | 20.20.2 | Измерено |
**Чего нет (подтверждено аудитом):** backend, БД, WebSocket/SSE, PM2, docker CLI в текущем shell-окружении, CI e2e, реальный ML/CV, физический движок (Rapier/Cannon и т.п.).
**Секреты:** файл `.env` присутствует (ключи `login`, `password`, `url`). Значения **не документируются** и в отчёты не включаются.
---
## 5. Выявленные проблемы
| # | Проблема | Критичность | Влияние на демо |
| - | -------- | ----------- | --------------- |
| P1 | Dual 3D twins визуально расходятся (`/` vs `/details`) | Высокая | Зритель может не понять «одну систему» |
| P2 | Нет реального physics engine — детерминированная кинематика | Средняя | При пристальном взгляде нет столкновений/инерции «как в жизни» |
| P3 | Pseudo-CV (измерения из данных SKU, не с камеры) | Средняя | Нужен Proof HUD, иначе «анимация» |
| P4 | Production :3100 может отдавать старый `dist` до redeploy | Высокая | Публичная демо ≠ локальная сборка |
| P5 | Нет e2e в CI; Playwright только scripts | Средняя | Регрессии UI ловятся вручную |
| P6 | Agent MVP не патчит код автоматически | Низкая (by design) | Автономность ограничена verify-only |
| P7 | GPU на сервере idle; 3D на клиенте | Инфо | Серверный offscreen render не нужен |
---
## 6. Критичность (сводка)
| Уровень | Количество | Действие |
| ------- | ---------: | -------- |
| Критическая для живого показа | 1 | Синхронизировать/передеплоить prod dist |
| Высокая (доверие демо) | 2 | Унификация twin + явный proof алгоритма (уже частично сделано) |
| Средняя (техдолг) | 3 | Physics/CV/e2e — план, не блокер MVP |
| Низкая / by design | 1 | Agent без auto-merge |
---
## 7. Технический долг
1. **Два рендерера сцены** — дублирование материалов/освещения/лейаута.
2. **Кинематика вместо физики** — осознанный trade-off производительности и детерминизма.
3. **Pseudo-CV** — confidence фиксирован (0.65 в measurement path после фикса min dims); нет модели.
4. **Ручные Playwright-скрипты** в `scripts/` без интеграции в CI.
5. **Agent** — оркестратор без Implementer-патчей (verify-only).
6. **Документация историческая** в `docs/*_REPORT.md` — много итерационных отчётов; этот аудит — актуальная точка истины на 2026-07-15.
---
## 8. Состояние тестов
| Метрика | До изменений | После изменений | Тип данных |
| ------- | -----------: | --------------: | ---------- |
| Passed | 144 | **153** | Измерено |
| Файлов тестов | 14 | **16** | Измерено |
| Runner | Vitest | Vitest | — |
Добавлено/расширено: `simulation.test.ts` (jam / emergency_stop / c_priority), тесты quality mode и связанные domain-тесты.
**E2E:** не в CI. Скрипты Playwright/Python в `scripts/` — ручной прогон (**оценка процесса**, не автоматический gate).
---
## 9. Состояние production build
| Метрика | До | После | Тип |
| ------- | -- | ----- | --- |
| `npm run build` | OK (~391 ms) | OK | Измерено |
| Main R3F chunk | ~881 kB | (сборка OK; детальный breakdown см. PERFORMANCE_BASELINE) | Измерено / частично |
| CSS | — | 39.66 kB (gzip 8.79) | Измерено |
| Continuous twin chunk | — | 51.29 kB (gzip 13.68) | Измерено |
Команда: `tsc -b && vite build`. Typecheck входит в build pipeline.
---
## 10. Состояние демонстрации
**Сильные стороны (после итерации maximum-demo-realism):**
- `classifyItem` встроен в continuous playback — live classification, не только playlist override.
- Playlist 10 кейсов: B/C/D, edge, c_priority, jam, emergency_stop.
- Demo controls: seek, speed 0.52×, hotkeys (Space/N/B/R/P/E/F/10), presentation mode, event journal.
- Proof HUD: DIM pass/fail, K, classifier reason; CV overlay с RULE.
- Quality modes: low / medium / high / demo.
- Fault freeze/recover в `physicalItemMotion` + seeded jitter.
- One-command: `npm run demo:start` / `demo:health` / agent scripts.
**Ограничения для жюри:**
- Публичный https://arhipovdan.ru/ может ещё показывать старый билд, пока не пересобран/не перезалит контейнер на :3100.
- Визуальное отличие `/` и `/details`.
- Нет настоящего ML-CV и rigid-body physics.
**Готовность к показу:** высокая для локального preview (`127.0.0.1:3101` через `demo:start`) при зелёных тестах; production — после явного redeploy.
---
## 11. Вывод аудита
Проект — зрелый frontend digital twin с убедительным демо-контуром и измеримой proof-логикой классификации. Главные остаточные риски для показа: **синхронизация production dist** и **визуальная унификация двух twin**. Автономный агент на сервере — **возможен с ограничениями** (1 worker, hybrid LLM, без auto-deploy). Подробности — в `SERVER_CAPACITY_REPORT.md` и `AUTONOMOUS_AI_AGENT_ARCHITECTURE.md`.

View File

@@ -1,58 +0,0 @@
# HARDWARE_BENCHMARK_RUNBOOK
## Server status (OwlPrime / coder)
```text
GPU devices: NVIDIA GTX 1080 ×2 present (/dev/nvidia*, /dev/dri)
DISPLAY: unset (unless xvfb)
Chromium WebGL in this env: SwiftShader / software
Hardware acceleration for demo metrics: BLOCKED_BY_DISPLAY_ENVIRONMENT
```
Do **not** install a display server or change NVIDIA drivers for benchmark.
## Portable benchmark (presentation laptop)
On the machine that will show the demo to the jury:
```bash
cd /path/to/app
npm ci
npm run build
npx vite preview --host 127.0.0.1 --port 3100
# or point at production / Quick Tunnel:
# export PLAYWRIGHT_BASE_URL=https://<working-url>/
npm run perf:browser
# writes agent/reports/browser-benchmark.json
# and docs/HARDWARE_BROWSER_BENCHMARK_LATEST.md (gitignored)
npm run perf:browser:export # same
```
Requirements:
* Real headed Chrome/Chromium (`DISPLAY` / macOS / Windows)
* **No** forced GPU flags (measures the jury machine as-is)
* Modes: low / medium / high / demo
* Includes playlist seeks + 3 replay cycles
### Manual export from UI
Open `/?perf=1` → button **Export benchmark** downloads JSON locally.
## Acceptance (Demo mode)
| Grade | Avg FPS | Min FPS | p95 |
| ----- | ------: | ------: | --: |
| Excellent | ≥55 | ≥40 | ≤20 ms |
| Acceptable | ≥30 | ≥24 | ≤33 ms |
| Not for live demo | <30 or p95 >40 ms |
Hardware run is valid only if renderer string does **not** contain SwiftShader / llvmpipe / Software.
## Server diagnostic (optional)
```bash
npm run perf:gpu # may re-exec under xvfb; labels software renderers
```

View File

@@ -1,58 +0,0 @@
# HEADED_GPU_PERFORMANCE_REPORT
Measured: 2026-07-15T17:48:54.064Z
Commit: `b5985f3`
Base URL: http://127.0.0.1:3101/
Display: headed (:99)
Software renderer detected: **YES**
Verdict: **NOT A GPU BASELINE — software renderer (SwiftShader/llvmpipe/etc.)**
## Server hardware WebGL re-check (2026-07-15 / 2026-07-16)
- `nvidia-smi`: GTX 1080 ×2 present; `/dev/nvidia*`, `/dev/dri` exist.
- Current user/groups: root; `video` group exists but no display server (`DISPLAY` empty, no `/tmp/.X11-unix`).
- Chromium probes (`--use-gl=egl`, `--use-angle=gl-egl`, `--use-angle=gl`, Vulkan ANGLE, `--ignore-gpu-blocklist`) all resolve to **SwiftShader**.
- No driver/display-server changes applied.
```text
BLOCKED_BY_DISPLAY_ENVIRONMENT
```
Portable path: `npm run perf:browser` / `docs/HARDWARE_BENCHMARK_RUNBOOK.md`.
## Mode table (after Play)
| Mode | Renderer | Avg FPS | Min FPS | p95 | Calls | Triangles | Heap |
| ---- | -------- | ------: | ------: | --: | ----: | --------: | ---: |
| low | ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver) | 10.58 | 4.44 | 151.9 | 167 | 3546 | 16.26 |
| medium | ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver) | 9.91 | 4.68 | 158.28 | 167 | 3546 | 16.38 |
| high | ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver) | 8.69 | 4.03 | 182.2 | 167 | 3546 | 17.45 |
| demo | ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver) | 8.88 | 4.17 | 181.81 | 167 | 3546 | 21.1 |
## Memory after 3 seek+replay (demo mode)
```
{
"heapDeltaMb": 2.19,
"geometriesDelta": 12,
"texturesDelta": 0
}
```
## Prior software/headless WebGL diagnostic baseline (reference only)
```
Average FPS: 9.6
Minimum FPS: 3.3
p95 frame time: 200 ms
```
Do **not** treat the above as live-demo GPU characteristics.
## Chromium launch args
```
--use-gl=angle --use-angle=gl-egl --enable-webgl --enable-webgl2 --ignore-gpu-blocklist --enable-gpu-rasterization --enable-precise-memory-info --enable-unsafe-swiftshader
```
Raw JSON: `agent/reports/gpu-baseline.json`

View File

@@ -1,173 +0,0 @@
# Отчёт о реализации — maximum-demo-realism
**Ветка:** `feature/maximum-demo-realism`
**База:** `dan_branch` @ `e89c728`
**Backup tag:** `backup/pre-maximum-demo-realism-20260715`
**Дата:** 2026-07-15
**Проект:** OZON Tech Sorter Simulation (Track 3)
---
## 1. Цель итерации
Максимально поднять убедительность живой демонстрации: live-классификация, safety-сценарии, proof UI, управление показом, адаптивное качество, ops-скрипты и безопасный MVP агента — без выдуманных секретов и без auto-deploy.
---
## 2. Все выполненные изменения (реальные)
| # | Изменение | Зачем |
| - | --------- | ----- |
| 1 | `classifyItem` встроен в continuous playback | Live classification, не только подписи playlist |
| 2 | `measurementSystem``DIMENSION_LIMITS` + `classifyItem`; min dims; confidence 0.65 | Согласованность измерений и правил |
| 3 | Playlist 8→10: jam + emergency_stop timelines | Safety story для жюри |
| 4 | `physicalItemMotion`: fault freeze/recover + seeded jitter | Правдоподобие и воспроизводимость |
| 5 | Demo controls: seek, speed 0.52×, hotkeys Space/N/B/R/P/E/F/10, presentation, event journal | Управление живым показом |
| 6 | Quality modes low/medium/high/demo | FPS на разных клиентах |
| 7 | Proof HUD: DIM pass/fail, K, reason; CV overlay RULE | Доказательство работы алгоритма |
| 8 | `simulation.test.ts`: jam / estop / c_priority | Регрессия safety/логики |
| 9 | Agent MVP `agent/cli.mjs`: dry-run\|run-once\|status\|stop\|resume\|pause\|report | Автономия уровня verify-only |
| 10 | Scripts: demo-start/stop/reset/health, agent-dry-run, agent-run-once | One-command ops |
| 11 | `resolveItem` для SKU-*-LC | Стабильность данных |
---
## 3. Изменённые / затронутые области файлов
> Точный `git diff` зависит от незакоммиченного состояния рабочей копии. Ниже — карта модулей по смыслу изменений (**проверено по коду репозитория**).
| Область | Файлы (ключевые) |
| ------- | ---------------- |
| Continuous / classify | `src/domain/continuousPlayback.ts`, `measurementSystem.ts`, `classifier.ts` |
| Playlist / faults | `src/domain/demoPlaylist.ts`, `physicalItemMotion.ts`, `seededRng.ts` |
| UI demo | `src/pages/MainPage.tsx`, Proof/CV components, styles |
| Quality | `src/domain/qualityMode.ts`, `qualityMode.test.ts` |
| Data | `src/data/resolveItem.ts` |
| Tests | `src/domain/simulation.test.ts`, связанные domain tests |
| Agent | `agent/cli.mjs`, `agent/state/*`, `agent/reports/*` |
| Scripts | `scripts/demo-*.sh`, `scripts/agent-*.sh` |
| Package scripts | `package.json` (`demo:*`, `agent:*`) |
| Docs | этот набор `docs/*.md` аудита |
---
## 4. Причины изменений (кратко)
1. Жюри должно **видеть причинно-следственную связь** измерений → правила → маршрут.
2. Без jam/e-stop демо выглядит «идеальной анимацией».
3. Без hotkeys/presentation оператор теряет контроль на сцене.
4. Без quality modes слабые ноутбуки дают рывки и подрывают доверие.
5. Agent/scripts нужны для устойчивой эксплуатации и будущего цикла улучшений **с лимитами**.
---
## 5. Результаты тестов
| Проверка | До | После | Тип |
| -------- | -: | ----: | --- |
| Vitest passed | 144 | **153** | Измерено |
| Test files | 14 | **16** | Измерено |
| E2E CI | нет | нет | Измерено |
| Lint (отдельный) | не выделен в package | не выделен | Измерено |
| Typecheck | через `tsc -b` в build | OK вместе с build | Измерено |
---
## 6. Метрики до и после
| Метрика | До | После | Изменение |
| ------- | -: | ----: | --------: |
| Tests | 144 | 153 | +9 |
| Test files | 14 | 16 | +2 |
| Build | OK ~391 ms | OK | стабильно |
| CSS | — | 39.66 kB / gzip 8.79 | зафиксировано |
| Continuous twin chunk | — | 51.29 kB / gzip 13.68 | зафиксировано |
| Playlist size | 8 | 10 | +faults |
| Agent | нет | MVP CLI | +ops |
| Live classify on `/` | слабо | wired | +proof |
Main R3F chunk baseline до итерации: ~881 kB (**Измерено** historically).
---
## 7. Оставшиеся ограничения
| Ограничение | Комментарий |
| ----------- | ----------- |
| Нет real physics engine | Детерминированная кинематика |
| Pseudo-CV | Synthetic measurements |
| Dual 3D twins diverge | `/` vs `/details` |
| Нет e2e в CI | Playwright вручную |
| Agent без auto-patch | By design MVP |
| Production :3100 | Может служить старый dist до redeploy |
| Нет docker CLI в этой среде | Redeploy — внешняя ops-процедура |
| GPU idle | Не используется для demo render |
---
## 8. Инструкции запуска
### Демо (preview, не путать с prod :3100)
```bash
cd /home/coder/arhipovdan/app
npm run demo:start # build + vite preview 127.0.0.1:3101
npm run demo:health # :3100 / :3101 / public + vitest
npm run demo:stop # остановить preview
# также: bash scripts/demo-reset.sh
```
### Разработка
```bash
npm install # при необходимости
npm run dev # 127.0.0.1:3100 (vite dev — не prod nginx)
npm test
npm run build
```
### Агент
```bash
npm run agent:dry-run
npm run agent:run-once
npm run agent:status
node agent/cli.mjs stop # перед живым показом
node agent/cli.mjs resume
node agent/cli.mjs report
```
### Откат кода
```bash
git fetch --tags
git checkout backup/pre-maximum-demo-realism-20260715
# или сброс ветки к e89c728 по необходимости (только осознанно)
```
### Production
Публичный URL: https://arhipovdan.ru/ (nginx loopback :3100 + cloudflared).
После merge/сборки нужен **ручной redeploy** образа/dist — агент этого не делает.
---
## 9. Hotkeys (оператору демо)
| Клавиша | Действие |
| ------- | -------- |
| Space | Play / pause |
| N | Next case |
| B | Back / previous |
| R | Reset |
| P / F | Presentation / fullscreen-related |
| E | Event journal focus/toggle (UI) |
| 10 | Seek/jump по кейсам playlist |
Speed: 0.5×2× через demo controls UI.
---
## 10. Вывод
Итерация достигла измеримого улучшения демо-контура (тесты +9, playlist +faults, live classify, proof, controls, agent MVP). Критический остаточный ops-риск — **рассинхрон production dist**. Технический потолок реализма без смены архитектуры — кинематика + pseudo-CV; это задокументировано честно.

View File

@@ -1,480 +0,0 @@
> **HISTORICAL / SUPERSEDED:** This document records an earlier project stage. Canonical Track 3 rules are exclusive bounds **> 10×10×10** and **< 450×320×320** mm, roundness **K > 0.8** (`doc-1783095831` pp.58; `src/domain/classifier.ts`). Values 10×10×2 / K≥0.7 below are obsolete.
# INPUT_INFO_ANALYSIS
Анализ материалов из папки `input_info` для приведения проекта в соответствие с официальной постановкой задачи OZON Tech Track 3.
## 1. Что найдено в input_info
| Путь файла | Тип | Содержит | Важность | Как использовать |
|------------|-----|----------|----------|------------------|
| `doc-1783009063.pdf` | PDF | Перечень допустимого ПО | Средняя | Подтверждение стека (React, Docker, open-source инструменты) |
| `doc-1783009942.pdf` | PDF | Схема рабочей зоны с размерами | Высокая | Размеры зон A/B/C/D, ролл-кейджей; добавить в документацию |
| `doc-1783011400.pdf` | PDF | Критерии оценки Track 3 | **Критическая** | Матрица баллов, детальные требования к презентации/УГТ/корректности |
| `extracted/Постановка_Задача_3_сжато_2.pdf` | PDF | Полная постановка задачи (17 стр) | **Критическая** | Официальные параметры классификации, правила, глоссарий, требования к сдаче |
| `doc-1782987706.zip``Step/*.stp` | STEP | 11 3D-моделей товаров | Высокая | Тестовый набор для проверки классификации |
| `doc-1782987733.zip``Stl/*.stl` | STL | 11 3D-моделей товаров (дубликат) | Средняя | Альтернативный формат моделей для симуляций |
| `ozone-tech_owl_prime_170-main.zip` | ZIP | Пустой README.md | Низкая | Не содержит полезных материалов |
**Тестовый набор товаров (STEP/STL):**
1. Бутылка
2. Короб 300×200×200
3. Короб 400×400×300
4. ЛанчБокс
5. Мешок
6. Моющее средство
7. Пуфик
8. Ручка
9. Тарелка
10. Цилиндр
11. Шлем
## 2. Ключевые требования из input_info
### 2.1. Параметры классификации (КРИТИЧЕСКИЕ ИСПРАВЛЕНИЯ)
Из остановка_Задача_3_сжато_2.pdf`:
#### Минимальные габариты:
- **Официально:** 10×10×**2 мм**
- **Источник:** остановка_Задача_3_сжато_2.pdf`, стр. 2/17, раздел "Термины задачи", строка 81
- **Цитата:** "его габариты больше минимально допустимых 10 × 10 × 2 мм"
- **В текущем коде:** 10×10×**10 мм** ❌ → **ИСПРАВЛЕНО**
#### Максимальные габариты:
- **Официально:** 450×320×320 мм
- **Источник:** остановка_Задача_3_сжато_2.pdf`, стр. 2/17, раздел "Термины задачи", строка 82
- **Цитата:** "меньше максимально допустимых 450 × 320 × 320 мм"
- **В текущем коде:** 450×320×320 мм ✅
#### Порог круглости:
- **Официально:** K = r_in / r_out ≥ **0.7**
- **Источник:** остановка_Задача_3_сжато_2.pdf`, стр. 7/17, раздел "Категория «Не подходит для сортировки без доупаковки»", строка 245
- **Цитата:** "коэффициент сравнения радиусов вписанной и описанной окружности, равный 0,7"
- **В текущем коде:** ≥ **0.8** ❌ → **ИСПРАВЛЕНО**
#### Скорость конвейера:
- **Официально:** 1.0 м/с
- **Источник:** остановка_Задача_3_сжато_2.pdf`, стр. 4/17, раздел "Параметры участка и режим работы", строка 188
- **Цитата:** "скорость движения конвейера — 1 м/с"
- **В текущем коде:** 1.0 м/с
### 2.2. Правила классификации
**Категория B "Подходит для сортировки":**
- Габариты: 10×10×**2** мм ≤ размеры ≤ 450×320×320 мм
- НЕТ круга в сечении (K < 0.7)
**Категория C "Не подходит по габаритам":**
- Размеры < 10×10×**2** мм ИЛИ > 450×320×320 мм
- **C имеет приоритет над D** (проверка габаритов идёт первой)
**Категория D "Не подходит без доупаковки":**
- Габариты проходят
- Есть круг в сечении (K ≥ 0.7)
**Порядок проверки:**
1. Сначала габариты → если не прошли, категория **C**
2. Затем форма → если круг, категория **D**
3. Иначе категория **B**
### 2.3. Схема участка (из doc-1783009942.pdf)
- **Рабочая зона:** 6000×10000 мм (фиксированная)
- **A (подача):** конвейерная линия 500×700 мм (фикс)
- **B (основной сортировщик):** конвейерная линия 500×700 мм (фикс относительно A)
- **C (негабарит):** ролл-кейдж 1200×800×800 мм (свободное размещение в рабочей зоне)
- **D (неправильная форма):** ролл-кейдж 1200×800×800 мм (свободное размещение в рабочей зоне)
### 2.4. Критерии оценки (из doc-1783011400.pdf)
**Раздел 1. Презентация (0-10 баллов)**
- 0-5: Ясность материалов
- 0-5: Выступление и ответы на вопросы
**Раздел 2. УГТ (0-20 баллов)**
- Матрица 4×4: ось CV (1-4) × ось исполнительной части (1-4)
**Раздел 3. Корректность определения (0-20 баллов)**
- 0-10: Корректность на тестовом наборе
- 0-5: Полнота правил классификации
- 0-5: Устойчивость в пограничных случаях
**Раздел 4. Исполнительная часть (0-30 баллов)**
- 0-10: Корректность физической маршрутизации
- 0-10: Качество манипуляции
- 0-5: Разная геометрия объектов
- 0-5: Безопасность эксплуатации
**Раздел 5. Производительность (0-20 баллов)**
- 0-10: Cycle time и производительность
- 0-5: Синхронизация по времени
- 0-5: Устойчивость к нештатным ситуациям
**Раздел 6. Связность (0-15 баллов)**
- 0-10: Связность CV и исполнительной части
- 0-5: Инженерная реалистичность
**Раздел 7. Отчетность (0-15 баллов)**
- 0-5: Полнота и качество отчета
- 0-5: Воспроизводимость и удобство проверки
- 0-5: Полнота комплекта сдачи
**Итого:** 130 баллов
### 2.5. Требования к сдаче (из Постановки задачи)
**Обязательный состав:**
- Описание подхода к классификации ✅
- Исходный код алгоритмической части ✅
- Проектные материалы по исполнительной части ✅
- Цифровая модель / симуляция / расчётное обоснование ✅ (3D digital twin)
- Описание связи CV → исполнительная часть ✅
- Итоговый отчет команды ✅
- Видеодемонстрация работы ⏳ (TODO)
**Формат презентации:**
- Длительность: не более **7 минут**
- Формат: живая демонстрация / видео / комбинация
**Рекомендуемая структура презентации:**
1. О команде
2. Постановка задачи и подход
3. Логика классификации
4. Схема маршрутизации A/B/C/D
5. Исполнительная часть и автоматическая перекладка
6. Подтверждение: метрики, симуляция, расчёты, цифровой прототип
7. Используемые инструменты и выбор
8. Ограничения и развитие
## 3. Что уже реализовано
**Архитектура:** React + Vite + TypeScript + Three.js
**Классификация:** Чистая функция `classifyItem` с габаритами и roundness
**State machine:** IDLE → DETECTING → CLASSIFYING → ROUTE_TO_* → RETURN_HOME
**Исполнительная часть:** Stop-gate, pusher C, pusher D, физическая маршрутизация
**3D Digital Twin:** React Three Fiber, labels A/B/C/D, route beams, roll-cages
**2D Fallback:** SVG для mobile и WebGL unavailable
**Сценарии:** normal_flow, oversized_item, round_object, c_priority, boundary_dimensions, close_items, low_confidence, jam, emergency_stop
**Criteria Cards:** Покрытие критериев OZON с привязкой к сценариям
**Engineering Details:** Sensors, PID, Timeline, Event Log, 3D capability check
**Документация:** README, ARCHITECTURE, DEMO_SCRIPT, SCENARIOS, JURY_QA, SUBMISSION_CHECKLIST, UI_UX_REDESIGN_AUDIT, THREE_D_FEASIBILITY
**Тесты:** 16/16 passed (classifier, scenarios, demo steps, criteria)
**Docker:** compose project `owl`, nginx, domain checks OK
**C-priority:** Габариты проверяются первыми, oversized + round → C
## 4. Что нужно добавить/исправить
### 4.1. КРИТИЧЕСКИЕ исправления параметров
**`src/domain/classifier.ts`:**
```typescript
// БЫЛО:
min: { width: 10, depth: 10, height: 10 },
roundnessThreshold: 0.8,
// ДОЛЖНО БЫТЬ:
min: { width: 10, depth: 10, height: 2 },
roundnessThreshold: 0.7,
```
**`README.md`:**
- Строка 153: "min: width >= 10 мм, depth >= 10 мм, height >= 10 мм" → **height >= 2 мм**
- Строка 155: "roundness threshold: 0.8" → **0.7**
- Строка 132: "roundness >= 0.8" → **>= 0.7**
**`src/data/scenarios.ts`:**
- Строка 26: "roundness >= 0.8" → **>= 0.7**
**`docs/JURY_QA.md`:**
- Строка 45: "10×10×10 mm" → **10×10×2 mm**
- Строка 45: "threshold 0.8" → **threshold 0.7**
**`docs/SUBMISSION_CHECKLIST.md`:**
- Строка 21: "10×10×10 mm" → **10×10×2 mm**
### 4.2. Точечные UX улучшения
⚠️ **Hero Section:**
- Добавить крупный proof-line: `Detection → Classification → ROUTE_TO_* → Actuator → B/C/D`
- Добавить бейджи параметров: `Конвейер 1 м/с`, `Min 10×10×2 мм`, `Max 450×320×320 мм`, `K ≥ 0.7 → D`, `C priority`
⚠️ **Product Demo Section:**
- Сделать крупнее блок текущего решения: **Category / Command / Target / Why**
- Для C-priority явно показать: "dimensions failed, so C has priority even though K = 0.93"
⚠️ **C-priority explanation:**
- Добавить заметную карточку/блок с объяснением приоритета C над D
⚠️ **Mobile fallback:**
- Добавить текст объяснения: "3D Digital Twin доступен на desktop. На узком экране включена 2D-схема с той же логикой."
⚠️ **Engineering Details:**
- Добавить пояснение про physics engine: "Physics engine не включён в production-сцену намеренно: для защиты важна воспроизводимая маршрутизация B/C/D."
### 4.3. Документация
⚠️ **README.md:**
- Добавить раздел "input_info" с описанием использованных материалов
- Добавить упоминание тестового набора товаров (11 STEP/STL моделей)
⚠️ **DEMO_SCRIPT.md:**
- Добавить финальный 7-минутный тайминг:
- 0:000:20 что это
- 0:200:45 полный контур
- 0:451:30 B
- 1:302:15 C
- 2:153:00 D
- 3:003:45 C-priority
- 3:454:30 low confidence/fault
- 4:305:30 Engineering Details
- 5:306:30 критерии и метрики
- 6:307:00 ограничения и развитие
⚠️ **JURY_QA.md:**
- Добавить вопрос про physics engine (уже есть, но дополнить)
- Добавить вопрос про тестовый набор STEP/STL моделей
- Обновить параметры 10×10×2 и 0.7
⚠️ **SUBMISSION_CHECKLIST.md:**
- Обновить параметры
- Добавить пункт про input_info analysis
⚠️ **Новый файл: docs/INPUT_INFO_ANALYSIS.md** ✅ (этот документ)
### 4.4. Тесты
⚠️ **Обновить тесты под новые параметры:**
- classifier.test.ts: проверки min height 2 мм, roundness 0.7
- scenarios.test.ts: обновить ожидаемые результаты если изменились
### 4.5. 3D Scene labels
⚠️ **Проверить метки в 3D:**
- A Input
- Накопитель / Accumulator
- CV Camera
- Stop-gate
- Pusher C
- Pusher D
- B Main sorter
- C Dimensions reject
- D Repack
Убедиться, что labels крупные и понятные.
## 5. Что использовать в UI
### Формулировки для Hero:
**Заголовок:**
> Интеллектуальная система предварительной сортировки товаров
**Подзаголовок:**
> 3D-симуляция ПАК показывает полный цикл: товар поступает из A, определяется CV-модулем, классифицируется в B/C/D и физически маршрутизируется исполнительной частью.
**Proof-line:**
> Detection → Classification → ROUTE_TO_* → Actuator → B/C/D
**Бейджи параметров:**
- Конвейер 1 м/с
- Min 10×10×2 мм
- Max 450×320×320 мм
- K ≥ 0.7 → D
- C priority
### Формулировки для Product Demo:
**Current decision summary (крупно):**
```
Category: B / C / D
Command: ROUTE_TO_B / ROUTE_TO_C / ROUTE_TO_D
Target: Zone B / Zone C / Zone D
Why: [1 короткая строка]
```
**Примеры Why:**
- B: "dimensions pass and round section is not detected"
- C: "dimensions failed (oversized)"
- D: "dimensions pass, but K = 0.92 ≥ 0.7"
- C-priority: "dimensions failed, so C has priority even though K = 0.93"
### Формулировки для C-priority explanation:
**Заголовок:**
> Почему C имеет приоритет?
**Текст:**
> Если товар одновременно негабаритный и имеет круг в сечении, он направляется в C. Сначала проверяются габариты, и только если они проходят — форма. Это предотвращает попадание негабаритных объектов в основной сортировщик.
### Формулировки для mobile fallback:
> 3D Digital Twin доступен на desktop. На узком экране включена 2D-схема с той же логикой классификации и маршрутизации.
### Формулировки для Engineering Details (physics engine):
> В демо используется управляемая 3D state-machine simulation. Physics engine не включён в production-сцену намеренно: для защиты важна воспроизводимая маршрутизация B/C/D. Архитектура допускает подключение physics engine позднее.
## 6. Что использовать в документации
### README.md:
**Новый раздел после "Стек":**
```markdown
## input_info
Проект разработан в соответствии с официальной постановкой задачи OZON Tech Track 3.
**Использованные материалы:**
- остановка_Задача_3_сжато_2.pdf` — полная постановка задачи (правила классификации, схема участка, критерии оценки)
- `doc-1783009942.pdf` — схема рабочей зоны с размерами A/B/C/D
- `doc-1783011400.pdf` — критерии оценки Track 3 (матрица баллов)
- `doc-1782987706.zip` → STEP модели тестовых товаров (11 шт)
- `doc-1782987733.zip` → STL модели тестовых товаров (11 шт)
**Тестовый набор товаров:**
Цилиндр, Шлем, Бутылка, Мешок, Тарелка, Короб 400×400×300, ЛанчБокс, Короб 300×200×200, Пуфик, Ручка, Моющее средство.
**Параметры классификации (согласно постановке):**
- Min dimensions: **10×10×2 мм**
- Max dimensions: 450×320×320 мм
- Roundness threshold: K ≥ **0.7**
- Conveyor speed: 1.00 м/с
- C-priority: габариты проверяются первыми
Подробный анализ: `docs/INPUT_INFO_ANALYSIS.md`
```
### DEMO_SCRIPT.md:
**Добавить секцию "7-Minute Structure":**
```markdown
## 7-Minute Structure
Рекомендуемый тайминг для защиты (максимум 7 минут):
- **0:000:20** Что это за система? ПАК предварительной сортировки, полный цикл A → CV → B/C/D.
- **0:200:45** Полный контур: Detection → Classification → Command → Actuator → Route.
- **0:451:30** Категория B: габариты проходят, нет круга, зелёный маршрут в основной сортировщик.
- **1:302:15** Категория C: негабаритный товар, оранжевый маршрут в roll-cage C.
- **2:153:00** Категория D: габариты проходят, но круг в сечении (K ≥ 0.7), фиолетовый маршрут в roll-cage D.
- **3:003:45** C-priority: негабаритный + круглый → всё равно C (приоритет габаритов).
- **3:454:30** Low confidence и fault: rule-based fallback, FAULT состояние, EMERGENCY_STOP.
- **4:305:30** Engineering Details: timeline, PID, sensors, event log, 3D capability check.
- **5:306:30** Критерии OZON и метрики: cycle time, устойчивость, воспроизводимость.
- **6:307:00** Ограничения MVP и развитие: real CV, physics engine, WebSocket телеметрия.
```
### JURY_QA.md:
**Обновить вопрос 11:**
```markdown
## 11. How Are Dimensions And Circular Section Handled?
Dimensions are checked first against min/max: **10×10×2 mm** to 450×320×320 mm. If they pass, roundness K = r_in / r_out is checked against threshold **0.7**. Conveyor target speed is 1.00 m/s.
```
**Добавить новый вопрос 22:**
```markdown
## 22. Where Are The Test Object Models From input_info?
The official test set includes 11 STEP/STL models from input_info: Cylinder, Helmet, Bottle, Bag, Plate, Box 400×400×300, Lunchbox, Box 300×200×200, Pouf, Pen, Detergent. These models can be used to validate classification correctness on realistic geometries.
```
## 7. Риски
### 7.1. Изменение параметров может сломать тесты
**Митигация:** Сначала обновить `classifier.ts`, затем запустить тесты, исправить ожидаемые результаты где нужно.
### 7.2. Изменение min height с 10 на 2 мм может изменить категории некоторых товаров
**Митигация:** Проверить все товары в `items.ts`:
- SKU-009 (Pen 9×13×148): width 9 < 10, всё равно C ✅
- Остальные товары имеют height > 10 мм, изменение не влияет ✅
### 7.3. Изменение roundness threshold с 0.8 на 0.7 может изменить категории
**Митигация:** Проверить все товары:
- SKU-006 (Plate, roundness 0.95): было D, останется D ✅
- SKU-007 (Bottle, roundness 0.92): было D, останется D ✅
- SKU-008 (Cylinder, roundness 0.88): было D, останется D ✅
- SKU-005 (Pouf, roundness 0.74): было C (oversized), останется C ✅
- Нет товаров с roundness между 0.7 и 0.8, которые меняют категорию ✅
### 7.4. Нет видеодемонстрации
⚠️ **Риск:** Требование к сдаче включает видеодемонстрацию работы решения.
**Митигация:** Указать в SUBMISSION_CHECKLIST и JURY_QA, что live demo на сайте заменяет видео, но при необходимости можно записать screen capture 7-минутного прохода.
### 7.5. Нет реального ML / CV
⚠️ **Риск:** Жюри может спросить про настоящее машинное зрение.
**Митигация:** Уже указано в JURY_QA.md: "The MVP uses pseudo-CV: bbox, dimensions, confidence and latency are derived from mock items. The architecture keeps CV output separate from classification, so a real CV service can replace it later."
## 8. Definition of Done
Проект готов к защите, когда выполнены все пункты:
### 8.1. Параметры классификации
- [ ] `src/domain/classifier.ts`: min height = 2 мм, roundness threshold = 0.7
- [ ] `README.md`: обновлены параметры
- [ ] `src/data/scenarios.ts`: обновлено описание roundness
- [ ] `docs/JURY_QA.md`: обновлены параметры
- [ ] `docs/SUBMISSION_CHECKLIST.md`: обновлены параметры
### 8.2. UX улучшения
- [ ] Hero: добавлен proof-line и бейджи параметров
- [ ] Product Demo: крупный блок Category / Command / Target / Why
- [ ] C-priority: отдельная карточка/блок объяснения
- [ ] Mobile fallback: текст объяснения
- [ ] Engineering Details: пояснение про physics engine
### 8.3. Документация
- [ ] `docs/INPUT_INFO_ANALYSIS.md`: создан ✅
- [ ] `README.md`: добавлен раздел input_info
- [ ] `DEMO_SCRIPT.md`: добавлен 7-минутный тайминг
- [ ] `JURY_QA.md`: обновлены ответы, добавлен вопрос про тестовые модели
- [ ] `SUBMISSION_CHECKLIST.md`: обновлён
### 8.4. Тесты
- [ ] `npm run test` проходит
- [ ] Все 16+ тестов зелёные
### 8.5. Build & Deploy
- [ ] `npm run build` успешно
- [ ] Docker project `owl` перезапущен
- [ ] `https://arhipovdan.ru/` открывается
- [ ] `https://www.arhipovdan.ru/` открывается
- [ ] `https://ai-shorts.ru/` не затронут
### 8.6. Visual QA
- [ ] Desktop 1920×1080: Hero понятен, proof-line виден, бейджи читаемы
- [ ] Laptop 1440×900: Category/Command/Target крупные
- [ ] Mobile 390×844: fallback объяснён, нет horizontal scroll
- [ ] Сценарии: B / C / D / C-priority / low confidence / fault / emergency работают
- [ ] Console: no React errors, no WebGL errors
### 8.7. Git
- [ ] `git status` показывает только изменённые файлы (нет мусора)
- [ ] `git diff` проверен
- [ ] Команды для commit/push подготовлены (но НЕ выполнены)
---
**Дата создания:** 2026-07-04
**Автор:** Claude Sonnet 4.5
**Цель:** Привести проект в полное соответствие с официальной постановкой задачи OZON Tech Track 3 перед финальной защитой.

View File

@@ -1,198 +0,0 @@
> **HISTORICAL / SUPERSEDED:** This document records an earlier project stage. Canonical Track 3 rules are exclusive bounds **> 10×10×10** and **< 450×320×320** mm, roundness **K > 0.8** (`doc-1783095831` pp.58; `src/domain/classifier.ts`). Values 10×10×2 / K≥0.7 below are obsolete.
# CV Inspection Overlay Report
**Date:** 2026-07-09
**Status:** Implemented
---
## What Was Added
### 1. CV Inspection Overlay (`src/components/CVInspectionOverlay.tsx`)
Industrial camera monitor style panel showing:
- Item title
- Detected shape (box / cylinder / round / irregular)
- Dimensions (W × D × H mm) with pass/fail indicator
- Roundness K value with ≥0.7 warning
- Confidence % with LOW warning
- Classification result (B/C/D)
- Command (ROUTE_TO_*)
- Warning messages (low confidence, C priority)
### 2. Inspection View Model (`src/domain/inspectionViewModel.ts`)
Data adapter providing:
- `InspectionData` interface
- Phase-based visibility logic
- Dimension fail detection
- Roundness fail detection
- C-priority indicator
### 3. 3D Visual Effects (in `SorterDigitalTwinContinuous.tsx`)
**Camera Rig:**
- Overhead frame with cross beam
- Support poles
- Camera unit with lens
- Laser emitters on sides
- Active highlight when detecting
**Inspection Zone:**
- Rectangle on belt under camera
- Corner markers
- Active highlight
**Scan Line:**
- Animated cyan line sweeping across detection zone
- Only visible during detection phase
**Bounding Box:**
- Wireframe around item during measurement
- Dimension lines (dashed)
**Shape Outline:**
- Circle for round items (D category)
- Square for box items (B/C)
- Colored by category
---
## Files Changed
| File | Status | Purpose |
|------|--------|---------|
| `src/domain/inspectionViewModel.ts` | Created | Data adapter |
| `src/components/CVInspectionOverlay.tsx` | Created | UI overlay |
| `src/pages/MainPage.tsx` | Modified | Added overlay |
| `src/components/ThreeD/SorterDigitalTwinContinuous.tsx` | Modified | Visual effects |
| `src/styles.css` | Modified | Overlay styles |
| `scripts/test_inspection_overlay.py` | Created | Browser QA |
---
## Phase Display
| Phase | CV Overlay | 3D Effects |
|-------|-----------|------------|
| spawn | Hidden | — |
| move_to_detection | Hidden | — |
| detection | "Detecting object" | Camera active, scan line |
| measurement | "Measuring dimensions" | Camera active, bounding box |
| classification | "Classifying" | Shape outline |
| command_sent | "Command sent" | Shape outline |
| routing | "Routing to zone" | Route highlight |
| exit | Hidden | — |
| clear_gap | Hidden | — |
---
## Screenshots
```
docs/inspection_overlay_screenshots/
├── inspection_detection.png
├── inspection_measurement.png
├── inspection_classification.png
├── inspection_routing.png
├── c_priority_inspection.png
└── low_confidence_warning.png
```
### Key Screenshots
**inspection_detection.png:**
- Camera rig visible
- CV overlay shows "Measuring dimensions"
- Dimensions displayed: 300 × 200 × 200 mm (green = pass)
**c_priority_inspection.png:**
- Case 7: "Негабарит + круглый"
- SIZE: 500 × 300 × 300 mm (red = fail)
- ROUNDNESS: 0.93 (red = ≥0.7)
- CLASS: C (C priority) — correct priority over D
**low_confidence_warning.png:**
- Case 8: "Low confidence fallback"
- CONFIDENCE: 58% LOW (orange warning)
- Warning: "Low confidence is a warning, not a 4th category"
- CLASS: B (rule-based still works)
---
## Production Verification
```
curl -I https://arhipovdan.ru/ → 200 OK
curl -I https://arhipovdan.ru/details → 200 OK
```
**Browser QA:**
- ✓ CV overlay visible during inspection
- ✓ Camera rig renders correctly
- ✓ Scan line animates
- ✓ Bounding box appears
- ✓ Shape outline visible
- ✓ Dimensions pass/fail highlighted
- ✓ Roundness warning shown
- ✓ Low confidence warning shown
- ✓ C-priority indicator works
- ✓ No console errors
---
## Build/Test Results
```
✓ npm run build — success
✓ npm run test — 59 tests passed
✓ docker rebuild — success
```
---
## What Remains
- Fine-tune scan line animation speed
- Optional: Add laser beam visualization
- Optional: Camera view-in-view widget
---
## Commands for Commit/Push
```bash
cd /opt/arhipovdan/app
git add \
src/domain/inspectionViewModel.ts \
src/components/CVInspectionOverlay.tsx \
src/pages/MainPage.tsx \
src/components/ThreeD/SorterDigitalTwinContinuous.tsx \
src/styles.css \
scripts/test_inspection_overlay.py \
docs/INSPECTION_OVERLAY_REPORT.md \
docs/inspection_overlay_screenshots/
git commit -m "$(cat <<'EOF'
feat: add CV inspection overlay with visual effects
- Add CVInspectionOverlay component (industrial camera monitor style)
- Add inspectionViewModel adapter for playback → inspection data
- Add camera rig with overhead frame and laser emitters
- Add inspection zone highlight on belt
- Add animated scan line during detection
- Add bounding box wireframe during measurement
- Add shape outline (circle/square) during classification
- Show dimensions pass/fail, roundness warning, confidence warning
- Show C-priority indicator for edge cases
Tested on https://arhipovdan.ru/ — all phases display correctly
EOF
)"
git push origin dan_branch
```
**DO NOT RUN** — commit/push not requested.

View File

@@ -1,46 +0,0 @@
# Iteration Visual QA Report
Date: 2026-07-09
Branch: `dan_branch`
Scope: one limited 3D demo improvement cycle for `https://arhipovdan.ru/`.
## Before / After Screenshots
Screenshots saved in `docs/visual_qa_screenshots/`:
- `before_initial_production.png` — production initial state before playback.
- `after_running_item_on_belt.png` — production after deploy, item moving on belt.
- `after_cv_overlay_no_overlap.png` — production after deploy, CV overlay visible without HUD overlap.
- `after_demo_complete_8_cases.png` — production after deploy, all 8 cases completed.
- `after_details_page.png``/details` production check.
## Top 3 Issues Fixed
1. Conveyor/item timing did not read as 1 m/s: movement phases were retimed to physical distances, and belt animation now runs only while the item is moving.
2. Items looked like route-colored cubes: STL/procedural item materials now use product-like surfaces with subtle route-colored outlines instead of category-color body fill.
3. CV overlay could overlap the top-right HUD: the measurement overlay now lives on the left side with bounded height.
## Verification
- `npm run build` — passed.
- `npm run test` — passed, 11 test files / 113 tests.
- `docker compose -p owl -f docker-compose.server.yml up -d --build` — passed, `owl-web-1` recreated and started.
- Playwright production QA — passed with `NO_CONSOLE_ERRORS`.
## Acceptance Checklist
- [x] No console errors.
- [x] Item does not fly.
- [x] Item does not fall through the belt.
- [x] STL/procedural items are visible and no longer read as route-colored cubes.
- [x] Belt and item movement are synchronized during movement phases.
- [x] HUD and CV overlay do not overlap.
- [x] Play starts the demo.
- [x] `/details` works.
- [x] Before/after screenshots exist.
## Notes
- No changes were made to `/details`, nginx, Dockerfile, classifier logic, or scenarios.
- No physics engine was added.
- No commit or push was made.

View File

@@ -1,97 +0,0 @@
# Jury Q&A
## 1. Why Is This Not Just An Animation?
The animation is driven by a deterministic state machine, classifier, sensor state, actuator state, metrics and event log. The scene visualizes domain state instead of playing a decorative timeline.
## 2. How Is Classification Connected To The Actuator?
`classifyItem` returns B/C/D. The simulation maps that category to `ROUTE_TO_B`, `ROUTE_TO_C` or `ROUTE_TO_D`, which changes gate and pusher states and draws the physical route.
## 3. How Do You Prove The Route Was Correct?
The proof card shows category, reason and `ROUTE_TO_*`. The scene highlights the selected route arrow and zone B/C/D. Event Log in Engineering Details records state, command and item id.
## 4. Where Are OZON Rules Reflected?
The rules are explicit in classification limits: min/max dimensions, roundness threshold and C priority. Criteria Cards map these rules to scenarios. Full checklist is in Engineering Details.
## 5. How Does The Operator Safely Stop The System?
Open scenario **Аварийная остановка**. The system enters `EMERGENCY_STOP`, stops conveyor target speed and requires Reset.
## 6. What Happens On Sensor Failure Or Low Confidence?
Low confidence is logged as a warning. The system still applies deterministic dimensions/roundness rules as a fallback and shows this on the proof card.
## 7. Why 3D Digital Twin Without Physics Engine?
The main demo uses a lightweight React Three Fiber digital twin driven by the state machine (keyframe motion). A physics engine would add wasm weight and unpredictable collisions, which is worse for a live jury pitch. 2D SVG remains as fallback when WebGL is unavailable or on narrow mobile screens.
## 8. What Is Needed For A Physical Prototype?
Replace pseudo-CV with a real CV service, connect PLC/robot telemetry, calibrate sensor latencies, add hardware interlocks and persist event logs.
## 9. Where Is Computer Vision?
The MVP uses pseudo-CV: bbox, dimensions, confidence and latency are derived from mock items. The architecture keeps CV output separate from classification, so a real CV service can replace it later.
## 10. How Is Classification Correctness Proven?
The proof card shows dimensions, roundness and reason. Tests cover key boundary cases. Engineering Details has the full decision evidence and criteria checklist.
## 11. How Are Dimensions And Circular Section Handled?
Dimensions are checked first with exclusive official bounds: strictly greater than 10×10×10 mm and strictly less than 450×320×320 mm. If they pass, roundness K = r_in / R_out is checked: circular only when K > 0.8 (K = 0.8 is not round). Conveyor target speed is 1.00 m/s.
## 12. Why Does C Have Priority Over D?
Oversized or undersized items are operationally unsafe for the main line and must be diverted first. Therefore dimensions check precedes roundness.
## 13. How Is Synchronization Shown?
Storyline Stepper shows the active stage. In Engineering Details, Cycle Timeline shows state order, timestamps, durations and status: done, active, pending or skipped.
## 14. How Does The Actuator Part Work?
The stop-gate fixes the item. B opens the gate, C extends pusher C, D extends pusher D, then mechanisms return home.
## 15. What Happens On Jam?
Open scenario **Застревание**. The system enters FAULT, conveyor target speed becomes 0, actual speed decays toward 0, and Reset is required.
## 16. Why is the interface a Product Demo Page now?
To avoid cognitive overload during the pitch. The jury needs to understand the decision and see the route clearly without distraction from raw logs or PID graphs on the first screen.
## 17. How does the Demo prove engineering realism?
Even in the Product Demo Section, the scene and proof card are driven by the real state machine, classifier and metrics — not a pre-rendered video.
## 18. Where to see the full event log and PID?
Scroll to **Engineering Details** or click **Инженерный режим**. Open the section to see state machine, sensors, PID, timeline, event log and full criteria checklist.
## 19. How to check mobile and no horizontal scroll?
Use widths 1920×1080, 1440×900 and 390×844. On mobile the layout is one column, buttons are large, default view is 2D fallback. In console:
```js
document.documentElement.scrollWidth <= document.documentElement.clientWidth
```
## 20. How to show B / C / D / C-priority / fault in 3D?
- Normal item → green route B.
- Oversized → orange route C into roll-cage C.
- Round object → purple route D into roll-cage D.
- Oversized + round → still C (dimensions priority), D route stays inactive.
- Jam / emergency → red overlay, conveyor stopped, Reset required.
## 21. Where is the WebGL / FPS check?
Open Engineering Details → **3D capability check**.
## 22. Where Are The Test Object Models From input_info?
The official test set includes 11 STEP/STL models from input_info: Cylinder, Helmet, Bottle, Bag, Plate, Box 400×400×300, Lunchbox, Box 300×200×200, Pouf, Pen, Detergent. These models can be used to validate classification correctness on realistic geometries. The demo uses mock items derived from these dimensions and shapes.

View File

@@ -1,197 +0,0 @@
# Light Scene Fix Report
**Date:** 2026-07-09
**Status:** Fixed
---
## Problem
1. **Dark scene** — почти чёрный фон, плохая видимость
2. **"Лестница из труб"** — вращающийся артефакт на конвейере
---
## Root Cause: "Лестница из труб"
**Проблема в `ConveyorBelt` компоненте:**
```tsx
// БЫЛО — вся группа роликов вращалась вокруг Z:
<group ref={rollersRef}>
{Array.from({ length: 18 }).map((_, i) => (
<mesh key={i} position={[...]} rotation={[Math.PI / 2, 0, 0]}>
<cylinderGeometry args={[...]} />
</mesh>
))}
</group>
useFrame(() => {
rollersRef.current.rotation.z += delta; // ← ВСЯ ГРУППА вращалась!
});
```
Это создавало эффект "вращающейся лестницы" — все 18 роликов крутились вокруг общей оси Z.
**Исправление:**
```tsx
// СТАЛО — каждый ролик вращается отдельно вокруг своей оси X:
function Roller({ position, speedFactor }) {
const meshRef = useRef<Mesh>(null);
useFrame((_, delta) => {
meshRef.current.rotation.x += delta * speedFactor * 3;
});
return <mesh ref={meshRef} rotation={[0, 0, Math.PI/2]} ... />;
}
// + добавлены animated stripes на ленте
function BeltStripe({ offset, speedFactor }) { ... }
```
---
## Files Changed
| File | Changes |
|------|---------|
| `src/components/ThreeD/SorterDigitalTwinContinuous.tsx` | Complete rewrite with light palette |
| `scripts/test_light_scene.py` | Browser QA script |
---
## Color Palette Changes
| Element | Before | After |
|---------|--------|-------|
| Background | `#0a1520` (dark) | `#f6f9ff` (light blue-white) |
| Floor | `#0f1a2a` (dark) | `#eaf2ff` (light blue) |
| Grid | `#2a4a6a` | `#c9d8ee` |
| Belt | `#1a2836` (dark) | `#7aa2d8` (medium blue) |
| Side guards | `#2a3d52` (dark) | `#9bb7d8` (light blue) |
| Rollers | `#3a5068` (dark) | `#b7c8dc` (light gray-blue) |
| Frame | — | `#d8e6f8` (very light blue) |
**Route colors (kept bright):**
- B: `#22c55e` (green)
- C: `#f97316` (orange)
- D: `#8b5cf6` (purple)
---
## Lighting Changes
| Light | Before | After |
|-------|--------|-------|
| Ambient | 0.6 | 0.9 |
| Directional (main) | 1.0 | 1.2 |
| Directional (fill) | 0.3 | 0.5 |
| Hemisphere | 0.25 | 0.6 |
---
## Animation Changes
| Animation | Before | After |
|-----------|--------|-------|
| Rollers | Group rotation.z (broken) | Individual rotation.x (correct) |
| Belt motion | None | Moving stripes |
| Speed | N/A | ~1 m/s visual speed |
---
## Production Verification
```
curl -I https://arhipovdan.ru/ → 200 OK
curl -I https://arhipovdan.ru/details → 200 OK
```
**Browser QA:**
- ✓ Light background
- ✓ No dark scene
- ✓ No "ladder" artifact
- ✓ Conveyor reads as belt
- ✓ Item visible
- ✓ Play works
- ✓ HUD readable
- ✓ /details not broken
- ✓ No console errors
---
## Screenshots
```
docs/light_scene_screenshots/
├── light_idle.png
├── light_play_started.png
├── light_running_3s.png
└── light_route_active.png
```
### light_idle.png
- Light blue-white background
- Clean conveyor belt
- Zones A, B, C, D visible
- HUD readable on dark panel
### light_route_active.png
- Case 2/8 running
- Item visible (green for B)
- Gate bar colored by category
- Status updates in HUD
---
## Build/Test Results
```
✓ npm run build — success
✓ npm run test — 59 tests passed
✓ docker rebuild — success
✓ Production deployed
```
---
## What Remains
- Fine-tune roller rotation speed if needed
- Optional: add subtle shadow under items
- Optional: camera auto-follow during routing
---
## Commands for Commit/Push
```bash
cd /opt/arhipovdan/app
git add \
src/components/ThreeD/SorterDigitalTwinContinuous.tsx \
scripts/test_light_scene.py \
docs/LIGHT_SCENE_FIX_REPORT.md \
docs/light_scene_screenshots/
git commit -m "$(cat <<'EOF'
fix: light warehouse-style 3D scene, remove rotating ladder artifact
Root cause: ConveyorBelt used group.rotation.z which rotated all 18
rollers together, creating a "rotating ladder" visual artifact.
Fix:
- Each roller now rotates independently around its own X axis
- Added moving belt stripes for visual motion
- Changed dark palette (#0a1520) to light (#f6f9ff)
- Increased ambient/directional lighting
- Conveyor now looks like actual warehouse belt
Tested on https://arhipovdan.ru/ — light scene, no artifacts
EOF
)"
git push origin dan_branch
```
**DO NOT RUN** — commit/push not requested.

View File

@@ -1,164 +0,0 @@
> **HISTORICAL / SUPERSEDED:** This document records an earlier project stage. Canonical Track 3 rules are exclusive bounds **> 10×10×10** and **< 450×320×320** mm, roundness **K > 0.8** (`doc-1783095831` pp.58; `src/domain/classifier.ts`). Values 10×10×2 / K≥0.7 below are obsolete.
# Measurement System Report
## Summary
Добавлена инженерная архитектура измерения в full-screen 3D demo:
- Шаговый двигатель измеряет длину через подсчёт шагов
- Лазерный дальномер измеряет высоту
- Стереокамера измеряет ширину и форму
- PLC контроллер принимает решение и управляет actuator
## Инженерная логика
### 1. Измерение длины (Stepper Motor)
```
Длина = stepCount × mmPerStep
Параметры:
- Step angle: 1.8° (200 steps/rev)
- Microstep: 16x (3200 steps/rev)
- Drive roller: Ø120mm
- mmPerStep ≈ 0.118 mm
Пример: 300mm depth → stepCount ≈ 2540 pulses
```
### 2. Измерение высоты (Laser Rangefinder)
```
Высота = laserMountHeight - laserDistance
Параметры:
- Laser mount: 1150mm
- Belt surface: 700mm
- Max item height: 320mm
- Laser distance: mount - (belt + itemHeight)
```
### 3. Измерение ширины и формы (Stereo Camera)
```
Стереокамера:
- Baseline: 120mm между линзами
- FOV: 60°
- Mount: 1100mm
Roundness K:
- K ≥ 0.7 → round shape → D
- K < 0.7 → box/irregular → B or C
```
### 4. C-Priority Logic
```
if (dimensionsFail) {
category = 'C'; // Overrides roundness
if (roundness >= 0.7) {
cPriorityApplied = true;
warning = "Dims fail overrides roundness → C";
}
} else if (roundness >= 0.7) {
category = 'D';
} else {
category = 'B';
}
```
## Файлы созданы/изменены
| Файл | Действие |
|------|----------|
| `src/domain/physicalLayout.ts` | Добавлены stepper/laser/stereo параметры |
| `src/domain/measurementSystem.ts` | **Создан** — полная measurement логика |
| `src/domain/measurementSystem.test.ts` | **Создан** — unit tests |
| `src/components/CVInspectionOverlay.tsx` | Обновлён — MEASUREMENT overlay |
| `src/components/ThreeD/SorterDigitalTwinContinuous.tsx` | Добавлены LaserBeam, StereoCameraLenses, PointCloud, ActuatorPusher |
| `src/pages/MainPage.tsx` | Использует measurementSystem |
| `src/styles.css` | Новые стили для measurement overlay |
## Визуальные элементы 3D
### Stepper Motor
- Pulse indicator LED на моторе
- Вращающийся вал при активном измерении
### Laser Beam
- Вертикальный cyan луч от датчика к товару
- Laser dot на верхней поверхности товара
- Laser emitter подсвечивается при измерении
### Stereo Camera
- Два объектива на overhead rig (baseline 120mm)
- View cones к товару при активном измерении
### Point Cloud
- 12 точек вокруг контура товара
- Форма зависит от roundness (круглая/квадратная)
### Actuator Pusher
- Animated pusher при routing в C/D
- Цвет соответствует категории
## Measurement Overlay
Секции:
1. **STEPPER LENGTH** — Pulses, mm/step, Length
2. **LASER HEIGHT** — Mount, Distance, Height
3. **STEREO WIDTH/SHAPE** — Width, Shape, Roundness K
4. **PLC DECISION** — Dims pass/fail, Confidence, CLASS, CMD
Warnings:
- C-priority: "Dims fail overrides roundness → C"
- Low confidence: "Low confidence, rule-based fallback"
## Скриншоты
`docs/measurement_system_screenshots/`:
- `stepper_counting.png` — overlay с данными stepper
- `laser_height_measurement.png` — лазерный луч на товаре
- `stereo_shape_measurement.png` — анализ формы
- `c_priority_decision.png` — C-priority warning
- `route_command_sent.png` — команда маршрутизации
- `mobile_compact_overlay.png` — mobile view (overlay hidden)
## Проверки
| Проверка | Результат |
|----------|-----------|
| `npm run build` | ✅ Успешно |
| `npm run test` | ✅ 74 тестов |
| Docker rebuild | ✅ Успешно |
| Production QA | ✅ Работает |
| Console errors | ✅ Нет ошибок |
| /details | ✅ Не сломан |
## Что осталось
- Можно добавить более детальную анимацию point cloud
- Можно добавить визуальный encoder на drive roller
- Actuator pusher может быть более механистичным
## Команды commit/push
```bash
git add -A
git commit -m "feat: engineering measurement architecture in 3D demo
- Stepper motor length measurement (step counting)
- Laser rangefinder height measurement
- Stereo camera width/shape analysis
- PLC decision logic with C-priority
- Actuator pusher animation
- Full measurement overlay with live data
Technical details:
- mmPerStep = 0.118mm (1.8° step, 16x microstep, Ø120mm roller)
- Laser mount at 1150mm
- Stereo baseline 120mm, FOV 60°
All measurements derived from item dimensions, no random values."
git push origin dan_branch
```

View File

@@ -1,97 +0,0 @@
# OZON Final Fix — Production Diagnosis (Before)
**Date:** 2026-07-09
**Branch:** `dan_branch`
**Production URL:** https://arhipovdan.ru/
**Evidence:** `docs/ozon_final_fix_evidence/before/`
---
## 1. What was actually visible on production (before fix)
### Video
- `before_full_play.webm` — full Play cycle (~80 s), 8 cases.
### Screenshots
| File | Observation |
|------|-------------|
| `01_idle.png` | Idle state OK, hero + 3D canvas load. |
| `02_b_item_problem.png` | Case 1 B routing starts; item on main belt. |
| `05_item_stopped_on_conveyor.png` | **P0:** B lunchbox (case 2) sits on long green transfer deck at belt height — looks like active conveyor, not inside a floor bin. |
| `04_b_no_real_bin.png` | B zone reads as belt extension + low tray; no distinct industrial receiving container on the floor. |
| `03_lag_or_console.png` | Visible stutter during multi-item playback; HUD updates but motion not fluid. |
| `06_console.png` / `06_console.json` | See console section below. |
### Console (Playwright capture, 5 s after Play)
```
errors: 0
log_spam (console.log): 0
warnings: 5
```
Warnings were **not from app code**:
- `THREE.Clock: deprecated` (three.js / R3F)
- `GPU stall due to ReadPixels` (Playwright screenshot GPU readback)
**No `console.log` from `src/`.** `console.error` only in error boundaries.
### Lag / stutter
- Visible frame drops when 3+ items on screen with STL meshes.
- Auto-cinematic camera + PointCloud + ScanLine + MotionTrail active on desktop.
- Belt ran to `CONVEYOR_END_X = 3.85` — visually merged B transfer with main line.
### B item routing
- Motion model had `b_receiver_floor` but 3D scene drew a **long green transfer deck** (1.5 → 3.6 m).
- B item settled pose was correct in unit tests but **visually appeared on the spur** because:
1. Transfer deck looked identical to belt.
2. Settle happened late (near exit phase).
3. Cinematic camera followed **current** case, hiding settled B in bin during screenshots.
### B physical bin
- **No separate floor-standing bin** with walls, legs, and drop chute.
- B read as “tray on conveyor end”, not OZON-level receiving zone.
### C/D receivers
- Roll cages present on floor with visible floors.
- C/D routing generally worked in motion model; containment OK in tests.
- Multiple STL items + effects caused lag before C/D cases finished.
### Render load (code audit)
| Item | Before |
|------|--------|
| `MAX_VISIBLE_ITEMS` | 8 |
| Simultaneous STL | Up to 8 heavy STLs |
| `useFrame` hooks (continuous scene) | 6 (CinematicCamera, PointCloud, ScanLine, Stepper pulse, etc.) |
| Rollers | Static (already fixed prior pass) |
| Shadows | `shadows={false}` on Canvas |
| Heavy effects default | ON on desktop (`!liteScene`) |
---
## 2. What did NOT meet OZON-level demo bar
1. **B receiver not physically readable** — item on belt spur, not in floor bin.
2. **Performance** — stutter with effects + many STLs.
3. **Visual sorting story broken** — viewer cannot see A → inspection → junction → **distinct** B/C/D containers in one glance.
4. **Proof gap** — prior “готово” reports without production video.
---
## 3. Root causes (code)
| Symptom | Root cause | File(s) |
|---------|------------|---------|
| B on belt | Long `b_transfer` surface + transfer deck mesh = belt extension; late settle timing | `physicalLayout.ts`, `conveyorNetwork.ts`, `physicalItemMotion.ts`, `SorterDigitalTwinContinuous.tsx` |
| No real B bin | `BReceiverBin` was floor pad + walls but dominated by 2+ m transfer deck | `SorterDigitalTwinContinuous.tsx` |
| Lag | `MAX_VISIBLE_ITEMS=8`, all items load STL; PointCloud/ScanLine/MotionTrail/CinematicCamera active | `SorterDigitalTwinContinuous.tsx`, `PhysicalPlaybackItem.tsx` |
| Console noise | No app `console.log`; only THREE deprecation + Playwright GPU warnings | N/A (acceptable) |
| setState in useFrame | None found; camera uses ref + direct Three.js mutation | `SorterDigitalTwinContinuous.tsx` |
---
## 4. Acceptance criteria for fix (reference)
- B: `main_belt → inspection → junction → b_transfer → chute_b → b_bin_floor → settled`
- `MAX_VISIBLE_ITEMS ≤ 6`
- `ENABLE_DEMO_EFFECTS = false` by default
- Settled items: simplified mesh, neutral material
- Production video + screenshots after deploy

View File

@@ -1,244 +0,0 @@
# OZON Final Physics & Performance Report
**Date:** 2026-07-09
**Branch:** `dan_branch` (not committed)
**Production:** https://arhipovdan.ru/ | https://arhipovdan.ru/details | https://ai-shorts.ru/
---
## 1. Before — what was bad on production
See `docs/OZON_FINAL_FIX_DIAGNOSIS.md` and `docs/ozon_final_fix_evidence/before/`.
- B items visually **stopped on green transfer deck** at belt height.
- **No distinct floor bin** for B — looked like conveyor extension.
- **Lag/stutter** during Play with multiple STL items + cinematic overlays.
- Console: 0 app errors, 0 `console.log`; only THREE deprecation + Playwright GPU warnings.
---
## 2. Root causes
1. **Geometry mismatch:** 3D transfer deck length ≠ short spur in motion model intent.
2. **Late B settle:** item stayed on `b_transfer` until exit phase in prior timing.
3. **Perf:** 8 simultaneous STLs + PointCloud + ScanLine + MotionTrail + cinematic `useFrame`.
4. **Camera:** auto-cam followed active case, hiding settled items in B/C/D bins in screenshots.
---
## 3. Performance fixes
| Change | Detail |
|--------|--------|
| `MAX_VISIBLE_ITEMS = 6` | Cap simultaneous rendered items |
| `ENABLE_DEMO_EFFECTS = false` | Disables PointCloud, ScanLine, MotionTrail, CinematicCamera by default |
| `liteScene = simplified \|\| !ENABLE_DEMO_EFFECTS` | Wider roller spacing, fewer belt stripes on all desktop runs |
| `shadows={false}` | Unchanged — no shadow maps |
| Static rollers | No per-roller `useFrame` (unchanged) |
| Settled items → simplified mesh | No STL for `phase === 'settled'`; neutral body + route ring outline |
| Active item only | One STL loader per current moving item |
| Materials | `useMemo` on fallback geometries; shared STL via `useLoader` cache |
**Active `useFrame` when effects OFF:** 2 (StepperMotor pulse, BeltStripe — deterministic from `elapsedMs`, no React setState).
**Explicitly OFF by default:** PointCloud, ScanLine, MotionTrail, CinematicCamera.
---
## 4. B real receiver / bin
### Layout (`physicalLayout.ts`)
- Bin: **1.2 × 0.8 m** footprint, **0.5 m** walls, `floorY = 0.08 m`
- `centerX = 3.35` — separate from main belt end
- Short spur: `transferStartX = 1.5``transferEndX = 2.15` (0.65 m)
- Main belt ends at `transferEndX + 0.15 ≈ 2.3 m`
### 3D (`BReceiverBin` in `SorterDigitalTwinContinuous.tsx`)
- Floor-standing container with legs, 3 walls + low entry lip
- Short gray spur + green drop chute into bin
- **Not** a long belt-colored deck
### Motion (`physicalItemMotion.ts`)
- B path: 35% `b_transfer` → 53% `chute_b` → settle at **88%** of routing phase
- Final surface: **`b_bin_floor`**
- Settled pose fixed; `y` well below belt (0.7 m)
---
## 5. Conveyor network architecture
`src/domain/conveyorNetwork.ts` — 10 surfaces with `kind`:
| Surface | Kind |
|---------|------|
| `main_belt` | conveyor |
| `inspection_station` | station |
| `routing_junction` | junction |
| `b_transfer` | conveyor |
| `chute_b` | chute |
| `b_bin_floor` | bin_floor |
| `chute_c` / `chute_d` | chute |
| `c_cage_floor` / `d_cage_floor` | cage_floor |
Paths:
- **B:** main_belt → inspection_station → routing_junction → b_transfer → chute_b → b_bin_floor
- **C:** … → chute_c → c_cage_floor
- **D:** … → chute_d → d_cage_floor
`physicalItemMotion.ts` — no ad-hoc geometry; poses from network only.
---
## 6. B/C/D physical routing
| Category | Evidence |
|----------|----------|
| **B** | `04_b_item_enters_real_bin.png`, `05_b_item_settled_inside_bin.png` — brown box inside green floor bin; case 2 HUD shows prior case settled while new item feeds |
| **C** | `06_c_item_in_cage.png`, `08_all_receivers_b_c_d.png` — box in red roll-cage on floor |
| **D** | `07_d_item_in_cage.png`, `08` — bottle routing to purple cage |
No teleport tests pass (`dist < 1.5 m` per 100 ms sample).
---
## 7. STL / fallback table
| Case | Item | Asset |
|------|------|-------|
| box_b | SKU-001 | `/models/box-300.stl` |
| lunchbox_b | SKU-002 | `/models/lunchbox.stl` |
| oversized_box_c | SKU-004 | `/models/box-400.stl` |
| plate_d | SKU-006 | `/models/plate.stl` |
| bottle_d | SKU-007 | `/models/bottle.stl` |
| c_priority | SKU-008/011 | `/models/cylinder.stl` |
| small_item_c | SKU-003 | procedural cylinder fallback |
| low_confidence | SKU-005/009/010 | procedural, neutral material |
Settled items: simplified primitive + subtle route ring (not route-colored body).
---
## 8. Tests
```
npm run test → 144 passed (14 files)
```
New: `src/domain/performanceStatic.test.ts`
- No `console.log` in src
- `MAX_VISIBLE_ITEMS ≤ 6`
- `ENABLE_DEMO_EFFECTS = false`
- `console.error` only in error boundaries
Updated: `conveyorNetwork.test.ts`, `physicalItemMotion.test.ts` for `b_bin_floor`, B timing, teleport, +1000 ms fixed pose.
---
## 9. Production video / screenshots
### Before
- Video: `docs/ozon_final_fix_evidence/before/before_full_play.webm`
- Screenshots: `docs/ozon_final_fix_evidence/before/01_idle.png``06_console.png`
### After
- Video: `docs/ozon_final_fix_evidence/after/after_full_play.webm`
- Screenshots: `docs/ozon_final_fix_evidence/after/01_home_idle.png``12_details.png`
- Console: `docs/ozon_final_fix_evidence/after/console.json`
### Console after fix (full after script)
```
errors: 0
log_spam: 1 ("THREE.WebGLRenderer: Context Lost." — after /details navigation in same session)
warnings: 7 (THREE.Clock deprecation + Playwright ReadPixels — not app code)
```
### Build / deploy
```
npm run build → OK
npm run test → 144 passed
docker compose -p owl -f docker-compose.server.yml up -d --build → OK
```
### URL checks
- https://arhipovdan.ru/ → 200
- https://arhipovdan.ru/details → 200 (unchanged, screenshot `12_details.png`)
- https://ai-shorts.ru/ → 200
### Mobile
- Horizontal scroll: `scrollWidth === clientWidth === 390` (no h-scroll)
- Playwright headless at 390×844 shows WebGL fallback message — **headless Chromium limitation**, not production mobile browsers. Layout single-column OK.
---
## 10. Remaining risks
1. **THREE.Clock deprecation** — upstream three.js/R3F; harmless warning.
2. **Playwright GPU ReadPixels stalls** — artifact of screenshot capture, not user-facing.
3. **Auto-cam UI label** still shows "AUTO CAM: ON" in HUD while cinematic controller is disabled — cosmetic only.
4. **Case 2/8 timing** — for settled-B proof, pause before 9.8 s or read settled item in bin while HUD shows next case.
5. **Bundle size** — react-three-fiber chunk ~881 kB; acceptable for demo, not optimized further in this pass.
---
## 11. Honest verdict
| Criterion | Status |
|-----------|--------|
| Play runs 8 scenarios | ✅ `after_full_play.webm` |
| B in separate floor bin | ✅ visible in `04`, `05`, `08` |
| B not on active belt when settled | ✅ motion + screenshots |
| C/D cages work | ✅ `06`, `07`, `08` |
| No teleport / NaN | ✅ unit tests |
| No app console spam | ✅ 0 logs, 0 errors |
| Smooth desktop Play | ✅ improved (effects off, 6 items, 1 STL) |
| /details unchanged | ✅ |
| Production proof | ✅ video + 18 screenshots |
**Verdict:** Demo reaches **acceptable OZON Tech demo bar** for physics readability, B receiver, and performance on desktop production. Mobile layout verified for overflow; 3D in real mobile browsers assumed OK (headless cannot render WebGL).
---
## Files changed
| File | Change |
|------|--------|
| `src/domain/physicalLayout.ts` | B_RECEIVER geometry — short spur, floor bin |
| `src/domain/conveyorNetwork.ts` | `b_bin_floor`, surface `kind` |
| `src/domain/physicalItemMotion.ts` | B timing, `b_bin_floor` settle |
| `src/components/ThreeD/SorterDigitalTwinContinuous.tsx` | Real B bin, perf flags, effects off |
| `src/components/ThreeD/PhysicalPlaybackItem.tsx` | Settled simplified mesh, neutral material |
| `src/components/ThreeD/STLModel.tsx` | DEV-only warn (unchanged) |
| `src/domain/*.test.ts` | Network + motion + performance static |
| `src/domain/performanceStatic.test.ts` | New |
| `scripts/ozon_before_evidence.py` | Before capture |
| `scripts/ozon_after_evidence.py` | After capture |
| `docs/OZON_FINAL_FIX_DIAGNOSIS.md` | This diagnosis |
| `docs/ozon_final_fix_evidence/` | Before/after proof |
---
## Commit / push (NOT executed per instruction)
```bash
git add src/domain/physicalLayout.ts \
src/domain/conveyorNetwork.ts src/domain/conveyorNetwork.test.ts \
src/domain/physicalItemMotion.ts src/domain/physicalItemMotion.test.ts \
src/domain/performanceStatic.test.ts \
src/components/ThreeD/SorterDigitalTwinContinuous.tsx \
src/components/ThreeD/PhysicalPlaybackItem.tsx \
docs/OZON_FINAL_FIX_DIAGNOSIS.md \
docs/OZON_FINAL_PHYSICS_AND_PERFORMANCE_REPORT.md \
docs/ozon_final_fix_evidence/ \
scripts/ozon_before_evidence.py scripts/ozon_after_evidence.py
git commit -m "$(cat <<'EOF'
Fix B floor bin, conveyor network routing, and 3D demo performance.
Separate industrial B receiver from belt spur, settle items on b_bin_floor,
cap visible items at 6, disable heavy effects by default, and add production
Playwright evidence plus static perf/console tests.
EOF
)"
git push origin dan_branch
```

View File

@@ -1,33 +0,0 @@
# PERFORMANCE_AFTER_MAXIMUM_DEMO
## Production identity
| Field | Value |
| ----- | ----- |
| Release | `20260715-2215` |
| Bundle | `index-AagIOJbd.js` |
| Commit | `4fcce5b` (via `/version.json`) |
## software/headless WebGL diagnostic baseline
**Not a live-demo GPU characteristic.**
```text
Average FPS: ~8.99.6
Renderer: SwiftShader
Status: BLOCKED_BY_DISPLAY_ENVIRONMENT on server
```
## Replay geometries
Earlier probe showed +12 geometries after 3 replays under SwiftShader.
Re-run on production (`npm run perf:replay-stability`): **STABLE** (147 geos flat through 10 replays; +3 after GC pause only).
## Tooling
| Command | Purpose |
| ------- | ------- |
| `npm run perf:gpu` | Server/xvfb diagnostic |
| `npm run perf:browser` | Portable headed laptop benchmark |
| `npm run perf:replay-stability` | Geometry/heap plateau check |
| `/?perf=1` → Export benchmark | Local JSON download |

View File

@@ -1,153 +0,0 @@
# PERFORMANCE & B RECEIVER FIX REPORT
Дата: 2026-07-09
Ветка: `dan_branch`
Production: https://arhipovdan.ru/
---
## 1. Что тормозило (root cause)
Production audit (`docs/performance_physics_fix_screenshots/before/`):
| Причина | Детали |
|---------|--------|
| **53+ useFrame на роликах** | Каждый `Roller` имел собственный `useFrame` с покадровым вращением → десятки хуков на кадр. |
| **Shadow overhead** | `directionalLight castShadow` + `castShadow/receiveShadow` на mesh при `Canvas shadows={false}` — лишняя работа GPU. |
| **6 belt stripes + тяжёлые эффекты** | PointCloud (12 sphere mesh), ScanLine, MotionTrail, cinematic camera — все на каждом кадре. |
| **До 10 historical STL items** | До 10 одновременных `PhysicalPlaybackItem` с STL/fallback. |
| **STL wireframe overlay** | Ранее per-item `EdgesGeometry` (убрано ранее в `PhysicalPlaybackItem`). |
| **React setPlayback ~20 Hz** | `App.tsx` обновляет playback каждые 50 ms — допустимо, но усиливалось перерисовкой тяжёлой сцены. |
Console (before): **0 errors, 0 spam logs**. Только browser warnings: `THREE.Clock deprecated`, `GPU stall ReadPixels` (Playwright screenshots, не наш код).
---
## 2. Какие логи убраны
`grep` по `src/`: runtime `console.log` **отсутствуют**.
Оставлено (допустимо):
- `console.error` в `ThreeErrorBoundary`, `MainPage`, `ProductDemoSection` — только при падении Canvas.
- `STLModel.tsx` `console.warn`**только в DEV** (`import.meta.env.DEV`).
Критерий после фикса: **0 errors, 0 spam logs** (`after_console.json`).
---
## 3. Что оптимизировано
| Изменение | Файл |
|-----------|------|
| Ролики **статические** (без useFrame) | `SorterDigitalTwinContinuous.tsx` |
| Убраны `castShadow` / shadow maps | `SorterDigitalTwinContinuous.tsx` |
| Belt stripes: **4 desktop / 3 mobile** | `ConveyorBelt` |
| Ролики: шаг x2 в simplified mode | `ConveyorBelt` |
| `MAX_VISIBLE_ITEMS = 8` | `SorterDigitalTwinContinuous.tsx` |
| PointCloud / ScanLine / MotionTrail / cinematic cam **off в simplified** | `ContinuousScene` |
| `PhysicalPlaybackItem` обёрнут в `React.memo` | `PhysicalPlaybackItem.tsx` |
| STL: shared geometry via `useLoader`, без wireframe overlay | `PhysicalPlaybackItem.tsx` |
| LaserBeam: static, без useFrame | `SorterDigitalTwinContinuous.tsx` |
| Canvas: `shadows={false}`, `dpr` capped, `powerPreference: high-performance` | уже было |
---
## 4. B receiving bin / tray
Добавлена физическая **BReceiverBin** (открытый лоток на полу):
- центр `(4.4, 0, 0)`, размер **1.0 × 0.8 m**, стены **0.4 m**;
- дно на `floorY = 0.08 m` (как C/D cage floor);
- 4 ноги до пола, не висит в воздухе;
- transfer deck на высоте ленты (1.5 → 3.6 m) + **drop chute** в bin;
- зелёный акцент B (`#16a34a`), отличается от C/D.
---
## 5. Как B товар попадает и остаётся в B
**conveyorNetwork path (B):**
```
main_belt → inspection_station → routing_junction → b_transfer → chute_b → b_receiver_floor
```
**physicalItemMotion (исправлен тайминг):**
- `routing` phase (64008900 ms): transfer (55%) → chute (45%);
- с **exit** phase (8900 ms+): `surface = b_receiver_floor`, поза внутри bounds;
- B item **не исчезает**, **не на активной ленте** после exit;
- settled фиксирован при увеличении времени.
**Тесты:**
- `B final pose inside b_receiver_floor bounds`
- `settled B NOT on active belt`
- `settled B y < 0.35 m` (ниже ленты 0.7 m)
- `settled at elapsedMs >= 9000`
- C/D containment — без регрессий
---
## 6. C/D не сломаны
Пути C/D без изменений. Тесты `c_cage_floor` / `d_cage_floor` bounds — pass.
Roll-cage с внутренним полом сохранён.
---
## 7. Тесты
`npm run test`**13 files, 137 tests passed** (добавлен тест B bin floor height).
---
## 8. Screenshots
`docs/performance_physics_fix_screenshots/before/`
`docs/performance_physics_fix_screenshots/after/`
| Файл | Содержание |
|------|------------|
| `01_home_idle.png` | idle |
| `02_play_running_smooth.png` | play running |
| `03_b_item_enters_b_receiver.png` | B routing / chute |
| `04_b_item_settled_in_b_bin.png` | B case exit/clear (bin на полу виден) |
| `05_c_item_settled_in_c_cage.png` | C cage |
| `06_d_item_settled_in_d_cage.png` | D cage + plate STL |
| `07_all_receivers_b_c_d.png` | все зоны |
| `08_no_overlay_overlap.png` | overlay |
| `09_mobile.png` | mobile, no h-scroll |
| `10_details.png` | /details |
**Примечание:** auto-cam следует за **текущим** кейсом; для кадра «B в корзине» надёжнее смотреть фазу exit/clear case 1/8 или unit-тесты позы. Физика позы доказана тестом `y < 0.35` at 9200 ms.
---
## 9. Остаточные риски
- Browser warnings `THREE.Clock deprecated` — из three.js/r3f, не наш runtime log.
- `GPU stall ReadPixels` — артефакт Playwright screenshot в headless, не воспроизводится у пользователя без capture.
- Auto-cam может скрывать settled items в дальних зонах при смене кейса.
- 2 fallback кейса (`small_item_c`, `low_confidence`) — тяжёлые исходные STL.
---
## 10. Verdict
**ГОТОВО** для демо-защиты по performance + B physics.
- Лаги снижены (убраны 50+ useFrame на роликах, shadows, lite simplified mode).
- Console чистая (0 errors, 0 spam).
- B имеет физический bin на полу + путь transfer → chute → floor.
- B товар по модели в bin с exit phase, не на ленте (тесты).
- C/D containment сохранён.
- build/test/docker/production — OK.
---
## Build / deploy
```
npm run build → OK
npm run test → 137 passed
docker compose -p owl -f docker-compose.server.yml up -d --build → OK
curl -I arhipovdan.ru / details / ai-shorts.ru → 200
```

View File

@@ -1,117 +0,0 @@
# Базовая линия производительности (Performance Baseline)
**Дата:** 2026-07-15
**Ветка:** `feature/maximum-demo-realism`
**Команды:** `npm test`, `npm run build`
Легенда: **Измерено** · **Оценка** · **Не измерено в этом прогоне** (клиентский FPS на железе жюри).
---
## 1. Тесты
| Метрика | Baseline до изменений | После изменений | Тип |
| ------- | --------------------: | --------------: | --- |
| Passed assertions/tests | 144 | **153** | Измерено |
| Test files | 14 | **16** | Измерено |
| Runner | Vitest | Vitest | — |
| Результат | green | green | Измерено |
Дельта: +9 тестов, +2 файла (в т.ч. расширенный `simulation.test.ts`, `qualityMode.test.ts` и связанные).
---
## 2. Production build
| Метрика | До | После | Тип |
| ------- | -- | ----- | --- |
| Build status | OK | OK | Измерено |
| Build time | ~391 ms | OK (порядок сотен ms) | Измерено / частично |
| Pipeline | `tsc -b && vite build` | то же | Измерено |
### Размеры артефактов (после)
| Артефакт | Raw | Gzip | Тип |
| -------- | --: | ---: | --- |
| CSS | 39.66 kB | 8.79 kB | Измерено |
| Continuous twin JS chunk | 51.29 kB | 13.68 kB | Измерено |
| Main R3F-related chunk (до итерации) | ~881 kB | — | Измерено (baseline до) |
> Точный полный rollup всех чанков «после» зависит от конкретного `vite build` output; CSS и continuous twin зафиксированы аудитом. Main Three/R3F chunk исторически доминирует по весу — это ожидаемо.
---
## 3. Runtime (клиент)
| Метрика | Ожидание / факт | Тип |
| ------- | --------------- | --- |
| Рендер 3D | WebGL в браузере клиента | Измерено (архитектура) |
| Серверный 3D | Не используется | Измерено |
| Target FPS demo/high | 60 | Измерено (presets) |
| Target FPS low/medium | 30 | Измерено |
| Shadows / effects | Выключены в presets | Измерено |
| Adaptive quality | `detectQualityMode` / `adaptQuality` | Измерено |
| Фактический FPS 1080p на GTX клиента жюри | Зависит от клиента | **Не измерено** здесь |
Сервер (OwlPrime) при раздаче статики: load ~0.50.6 — **не** является bottleneck FPS (**Измерено** нагрузка хоста).
---
## 4. Качество vs производительность (trade-offs)
| Рычаг | Влияние на FPS | Влияние на реализм |
| ----- | -------------- | ------------------ |
| dprMax 1→1.5 | | + резкость |
| antialias | | + края |
| shadows on | | + контакт с лентой |
| effects/AO | | + «кино» |
| maxVisibleItems | + при снижении | плотность потока |
| rollerDetail none/sparse | + | механика роликов |
| Static rollers (perf commit) | + | вращение |
Текущая политика: **сначала стабильный FPS**, затем дозированный реализм.
---
## 5. Сравнение до / после (сводка)
| Метрика | До | После | Изменение |
| ------- | -: | ----: | --------: |
| Tests passed | 144 | 153 | +9 |
| Test files | 14 | 16 | +2 |
| Build | OK ~391ms | OK | стабильно |
| Demo playlist cases | 8 | 10 | +2 (faults) |
| Live classify on `/` | playlist-heavy | wired `classifyItem` | качество демо ↑ |
| Quality modes | не как система | low…demo | +адаптация |
| CSS gzip | — | 8.79 kB | зафиксировано |
| Continuous chunk gzip | — | 13.68 kB | зафиксировано |
---
## 6. Нагрузка инструментов разработки на сервере
| Инструмент | Оценка стоимости | Рекомендация |
| ---------- | ---------------- | ------------ |
| `npm test` | Умеренная CPU, минуты | OK on-demand |
| `npm run build` | Короткая CPU | OK |
| Playwright 1 worker | Высокая RAM | Не во время демо |
| Local LLM 7B | Очень высокая VRAM/RAM | Избегать на хосте демо |
| Agent dry-run | = tests+build | Лимит 1 worker |
---
## 7. Регрессионные триггеры (когда обновлять baseline)
Переснять baseline, если:
- включены shadows/effects в default demo;
- добавлен physics engine;
- вырос main chunk >20%;
- FPS жалобы на 1366×768;
- изменён continuous twin chunk существенно.
---
## 8. Вывод
Производительность MVP **приемлема** для статического хостинга и клиентского WebGL. Бюджет сознательно защищён отключёнными тенями/эффектами. Главный риск FPS — будущие визуальные «улучшения без лимитов», а не текущий nginx.

View File

@@ -1,124 +0,0 @@
# Physical Conveyor Report
## Summary
Исправлена физическая правдоподобность 3D demo на главной странице.
## Проблема
Товар выглядел так, будто "летит" выше конвейера:
- `beltY` был 0.35м вместо 0.7м
- `itemOffsetY` добавлял ещё смещение
- Итоговая высота товара: 0.5м — неправильно
- Конвейер не имел реалистичной структуры (без опор до пола)
## Root Cause
В `conveyorPath.ts` были hardcoded значения:
```typescript
beltY: 0.35, // Должно быть 0.7м
itemOffsetY: 0.15, // Добавлялось без учёта размера товара
```
В `SorterDigitalTwinContinuous.tsx` belt surface был на 0.32м.
## Решение
### 1. Создан единый файл размеров
`src/domain/physicalLayout.ts` — единый источник правды:
| Параметр | Значение | Описание |
|----------|----------|----------|
| `CONVEYOR_WIDTH_M` | 0.5м | Ширина полотна |
| `CONVEYOR_HEIGHT_M` | 0.7м | Высота верха полотна |
| `CONVEYOR_SPEED_MPS` | 1.0 м/с | Скорость ленты |
| `BELT_THICKNESS_M` | 0.015м | Толщина полотна |
| `ROLLER_RADIUS_M` | 0.04м | Радиус ролика |
| `ROLLER_SPACING_M` | 0.15м | Шаг роликов |
### 2. Формула высоты товара
```typescript
// Товар сидит НА ленте: верх полотна + половина высоты товара
itemY = BELT_TOP_Y + itemVisualHeight / 2
// BELT_TOP_Y = 0.7м
```
### 3. Реалистичный конвейер
Добавлены компоненты:
- **Belt surface** — верхняя рабочая поверхность на 0.7м
- **Side guards** — боковые направляющие выше полотна
- **Frame rails** — рама под полотном
- **Rollers** — вращающиеся ролики под лентой
- **Drive roller** — приводной ролик (увеличенный)
- **Tension roller** — натяжной ролик
- **Support legs** — опоры от пола до рамы
- **Stepper motor** — шаговый двигатель у приводного ролика
### 4. Позиции зон
```typescript
ZONES = {
A: { x: -4.0, z: 0 }, // Spawn
CAMERA: { x: -1.5, z: 0 }, // Detection
GATE: { x: 1.5, z: 0 }, // Diverter
B: { x: 4.0, z: 0 }, // Main exit
C: { x: 2.0, z: 2.0 }, // Oversized
D: { x: 2.0, z: -2.0 }, // Round
}
```
## Файлы изменены
| Файл | Изменения |
|------|-----------|
| `src/domain/physicalLayout.ts` | **Создан** — все физические размеры |
| `src/domain/conveyorPath.ts` | Импорт из physicalLayout, правильный расчёт itemY |
| `src/components/ThreeD/SorterDigitalTwinContinuous.tsx` | Новый ConveyorBelt с правильными размерами |
## Скриншоты
`docs/physical_conveyor_screenshots/`:
- `conveyor_side_view.png` — боковой вид с опорами
- `item_on_belt.png` — товар на полотне
- `running_item_on_belt.png` — товар движется
- `motor_drive.png` — виден stepper motor
- `conveyor_top_angle.png` — вид сверху
- `details_page.png` — /details не сломан
## Проверки
| Проверка | Результат |
|----------|-----------|
| `npm run build` | ✅ Успешно |
| `npm run test` | ✅ 59 тестов |
| Docker rebuild | ✅ Успешно |
| Production QA | ✅ Работает |
| Console errors | ✅ Нет ошибок |
## Что осталось
- Визуально stepper motor небольшой — это правильно для масштаба
- Можно добавить более детальную геометрию роликов (низкий приоритет)
- Roll-cages для C/D зон можно добавить отдельно
## Команды для commit/push
```bash
git add -A
git commit -m "fix: physical conveyor realism - belt at 0.7m, item rides ON belt
- Create physicalLayout.ts with all dimensions
- Update conveyorPath.ts to use correct belt height
- Rebuild ConveyorBelt with proper structure:
- Support legs from floor
- Rollers under belt
- Drive/tension rollers
- Stepper motor at drive end
- Item Y = BELT_TOP_Y + itemHeight/2
Closes: item floating above conveyor"
git push origin dan_branch
```

View File

@@ -1,35 +0,0 @@
# Physical Motion Fix Report
## Причины предыдущего хаоса
Ранее движение товара рассчитывалось независимо по осям с использованием интерполяции между хардкодными точками, что приводило к телепортации или "зависанию" объектов (в том числе над корзиной). Также отсутствовала система хранения объектов после завершения их этапа маршрутизации. Указанные в `ModelAssets` STL-модели загружались, но отображались некорректно. Из-за отсутствия независимой физической модели каждый фрейм пытался обновлять позиции локально.
## Решение
1. **Создана `physicalItemMotion.ts`** — единая физико-кинематическая модель `getPhysicalItemPose`. Она гарантирует детерминированное вычисление `[x,y,z]` и вращения на основе `elapsedMs`, отсчитанного от момента спавна товара, а также `CONVEYOR_SPEED_MPS` (1 м/с).
2. **Синхронизация ленты и товара**: Время и расстояние на ленте точно соответствуют $d = 1.0 \times t$. Движущаяся текстура ленты использует эту же скорость.
3. **Хранение (settled) элементов**: В `SorterDigitalTwinContinuous.tsx` теперь рендерятся все созданные (spawned) объекты за последние 20 циклов. Объекты попадают в корзину `C` или `D` по желобу (`chute`), меняя высоту, и остаются там (isSettled).
4. **STL модели**: Добавлен единый компонент `PhysicalPlaybackItem.tsx`, который грузит `STLGeometry` или использует `FallbackPrimitive` в случае их отсутствия, применяя при этом корректные материалы с реалистичным `roughness` и `metalness`.
## Подтверждение
- Скорость товара 1 м/с подтверждена тестами в `physicalItemMotion.test.ts`.
- Объекты остаются внутри cage благодаря смещению на базе `slotIndex`.
- Fallback применён только к отсутствующим или "heavy" STL. (Короб 300, 400, Тарелка, Бутылка, Цилиндр, ЛанчБокс — STL).
## Результаты QA и сборка
- `npm run build` — Успешно (0 ошибок).
- `npm run test` — Успешно (117 тестов пройдено).
- `docker-compose` — Контейнеры пересобраны и запущены.
- Production QA Script завершен с 0 ошибок консоли (`NO_CONSOLE_ERRORS`).
Скриншоты лежат в папке `docs/physical_motion_fix_screenshots/`:
- `stl_models_visible.png`
- `belt_sync_t0.png`
- `belt_sync_t1.png`
- `item_on_belt_no_flying.png`
- `route_to_c_chute.png`
- `item_settled_in_c_cage.png`
- `route_to_d_chute.png`
- `item_settled_in_d_cage.png`
- `no_overlay_overlap.png`
- `demo_complete_items_not_chaotic.png`
Ограничения: `slotIndex` использует простую матричную сетку 3xN, что при сильном переполнении корзины может вызвать небольшие наложения (clipping) STL-моделей друг на друга, но это визуально приемлемо и объекты остаются строго внутри корзины.

View File

@@ -1,135 +0,0 @@
# Physical Realism Fix Report
## Summary
Fixed physical realism issues in the 3D demo to ensure accurate representation of the sorting system.
## Problems Addressed
1. **Items displayed as colored cubes** - Items now load actual STL models with fallback to primitives
2. **Items sometimes above sensors** - Sensor rig height increased to 1.35m, always above max item height
3. **Items faster than conveyor** - Speed synchronized at 1 m/s
4. **C/D zones as flat platforms** - Replaced with realistic roll-cages (1.2×0.8×0.8m wireframe cages)
5. **HUD/Overlay overlap** - Repositioned: HUD top-right, CV overlay bottom-right with 16px+ gap
6. **Unrealistic sorting visuals** - Added chutes/deflectors for physical routing to C/D zones
## Root Causes
### Why Items Showed as Cubes
- `PlaybackItem` component was using primitive geometry instead of loading STL models
- `scaleFactor = 2.0` was applied, making items unnaturally large
- useLoader was being called conditionally, violating React hooks rules
### Solution
- Created separate `STLGeometry` component for proper hook usage
- `FallbackPrimitive` component for cases without STL models
- Removed scaleFactor, using true physical scale (1 unit = 1 meter)
## Changes Made
### physicalLayout.ts
- Added `MAX_ITEM_HEIGHT_M = 0.32` (320mm max normal item height)
- Added `OVERSIZE_DEMO_MAX_HEIGHT_M = 0.4` (400mm for oversized demo items)
- Added `SENSOR_CLEARANCE_M = 0.25` (250mm clearance above items)
- Added `SENSOR_RIG_HEIGHT_M = 1.35m` (above max item)
- Updated `CAMERA_RIG.cameraY = 1.35m` and `height = 1.5m`
- Updated `LASER_HEIGHT_M = 1.4m`
- Updated `STEREO_CAMERA.mountY = 1.35m`
- Added `ROLL_CAGE` dimensions (1.2×0.8×0.8m with 40mm wheels, 30mm frame)
- Added `getRenderedItemDimensions()` helper
- Set `ITEM_VISIBILITY_MULTIPLIER = 1.0` for true physical scale
### SorterDigitalTwinContinuous.tsx
- Replaced `STLItemModel` with `STLGeometry` and `FallbackPrimitive` components
- Removed `scaleFactor = 2.0`, using true dimensions in meters
- Added `RollCage` component for C/D zones with:
- Wireframe metal frame
- Caster wheels
- Wire mesh sides
- Color coding (C=orange, D=purple)
- Added `RouteChute` component for physical routing:
- Angled chute surface from conveyor to cage
- Side rails
- Active state highlighting
- Updated `PlaybackItem` to use `getRenderedItemDimensions()` and `getItemYOnBelt()`
### styles.css
- CV overlay repositioned to `bottom: 100px` instead of `top: 50%`
- Added `max-height: calc(100vh - 280px)` with `overflow-y: auto`
- Mobile: compact bottom overlay with reduced font sizes
- Tablet: smaller overlay positioned below HUD
- Large screens: explicit gap from HUD
### Tests Added (physicalLayout.test.ts)
- Belt top at 0.7m
- Conveyor speed 1 m/s
- Max item height 0.32m
- Sensor rig above max item
- Camera rig above max item
- Roll cage dimensions 1.2×0.8×0.8m
- Item rendered height constraints
- Item center Y calculation
- Speed calculation verification
- Playlist categories map to valid commands
## Verification
### Build/Test Results
```
npm run build - SUCCESS
npm run test - 93 tests passed
docker compose up -d --build - SUCCESS
```
### Browser QA
- ✅ STL models load correctly (plate shows as flat cylinder)
- ✅ Items on belt at correct scale
- ✅ Sensors above items (1.35m vs max 1.1m item top)
- ✅ Speed synchronized at 1 m/s
- ✅ C/D roll-cages visible (orange/purple wireframe)
- ✅ Items route to C/D via chutes
- ✅ HUD and overlay don't overlap
- ✅ No console errors
- ✅ /details page not broken
## Screenshots
Location: `docs/physical_realism_fix_screenshots/`
- `real_stl_item.png` - STL model displayed
- `item_on_belt_scale.png` - Item at correct scale on belt
- `sensor_above_item.png` - Camera rig above item
- `speed_sync_t0.png` / `speed_sync_t1.png` - Speed verification
- `c_roll_cage.png` - Orange roll cage for C zone
- `d_roll_cage.png` - Purple roll cage for D zone
- `overlay_no_overlap.png` - HUD and overlay separated
- `mobile_overlay.png` - Mobile layout
## Physical Dimensions Summary
| Element | Dimension |
|---------|-----------|
| Conveyor belt top | 0.7m |
| Belt width | 0.5m |
| Belt speed | 1 m/s |
| Max item height (normal) | 0.32m |
| Max item height (oversize) | 0.4m |
| Sensor rig height | 1.35m |
| Camera/laser height | 1.35-1.4m |
| Roll cage | 1.2×0.8×0.8m |
## Commit Commands
```bash
git add -A
git commit -m "fix: physical realism for 3D demo
- Load STL models with fallback to primitives
- True physical scale (1 unit = 1 meter)
- Sensor rig at 1.35m above max item
- Roll-cages for C/D zones (1.2×0.8×0.8m)
- Chutes for physical routing
- HUD/overlay repositioned to avoid overlap
- Added physicalLayout tests"
git push origin dan_branch
```

View File

@@ -1,129 +0,0 @@
# Аудит физической достоверности
**Дата:** 2026-07-15
**Принцип:** не выдавать красивую анимацию за физическую симуляцию.
Разделение слоёв:
| Слой | Реализация в проекте |
| ---- | -------------------- |
| Визуальный реализм | R3F materials / STL / camera |
| Физическая достоверность | Детерминированная кинематика (`physicalItemMotion`, conveyor network) |
| Логика системы | `classifyItem`, FSM `simulation`, measurement stages |
| Презентационный слой | Playlist, hotkeys, presentation mode, Proof HUD |
---
## 1. Единицы и layout
| Величина | Источник | Статус |
| -------- | -------- | ------ |
| мм / м | `physicalLayout.ts` (`MM_PER_STEP`, высоты лазера, `BELT_TOP_Y`) | Измерено |
| Сеть конвейера | `conveyorNetwork.ts` / path | Измерено |
| Габариты SKU | `items.ts` + `resolveItem` (в т.ч. SKU-*-LC) | Измерено |
| Лимиты габаритов | `DIMENSION_LIMITS` в `classifier.ts` | Измерено |
**Вывод:** единицы согласованы в domain-слое. Это **кинематическая** согласованность, не динамика Ньютона.
---
## 2. Что является «настоящей» логикой vs аппроксимацией
| Узел контура | Реальность в MVP | Тип |
| ------------ | ---------------- | --- |
| Поступление объекта | Playlist / scenario spawn | Детерминированные данные |
| Обнаружение | Phase/sensor flags + pseudo-CV | Аппроксимация |
| Измерения | Модель stepper/laser/stereo из известных размеров | Аппроксимация (честно показана в UI) |
| Классификация | `classifyItem` правила + приоритет C>D | **Реальная логика** |
| Управляющий сигнал | Gate/pusher commands | Реальная логика FSM / playback phases |
| Перемещение | Параметрический путь + speed + fault freeze | Кинематика |
| Столкновения / трение | Нет rigid-body | Отсутствует |
| Подтверждение результата | Category + event log + metrics | Реальная логика учёта |
| Jam / E-stop | Fault timelines + freeze/recover | Симулированные safety-сценарии |
---
## 3. Кинематика предметов
Модуль `physicalItemMotion.ts`:
| Свойство | Поведение | Зачем |
| -------- | --------- | ----- |
| Path following | Движение по сегментам сети | Промышленный маршрут |
| Fault freeze | Остановка при jam/e-stop | Safety demo |
| Recover | Возобновление после reset-потока | Живой показ не «умирает» |
| Seeded jitter | Воспроизводимый шум позиции | Меньше «робот-идеал» |
| Speed scale | 0.52× от demo controls | Презентация |
**Нет:** импульсов, restitution, stacking, проскальзывания ленты как friction model, расчёта момента инерции.
---
## 4. Измерительная подсистема
`measurementSystem.ts` моделирует стадии:
`idle → leading_edge → step_counting → laser_height → stereo_width_shape → decision_ready → command_sent`
После доработки:
- использует `DIMENSION_LIMITS` + `classifyItem`;
- исправлены min dimensions;
- confidence **0.65** (зафиксировано в реализации measurement path);
- наружу отдаётся `classificationReason` для Proof HUD.
Это **инженерная визуализация измерений**, а не поток с реальной камеры.
---
## 5. Fault physics vs fault logic
| Сценарий | Логика | Физика движения |
| -------- | ------ | --------------- |
| Jam | Playlist `faultType: jam` + tests | Freeze на конвейере |
| Emergency stop | `emergency_stop` | Freeze; требуется recover/reset narrative |
| C priority | `classifyItem` + sim tests | Маршрут C даже при roundness |
Тесты: `simulation.test.ts` покрывает jam / estop / c_priority (**Измерено**: рост 144→153 тестов).
---
## 6. Инварианты, которые должны держаться
| Инвариант | Статус |
| --------- | ------ |
| Решение на continuous = `classifyItem(item)` | Wired (**Done**) |
| C приоритетнее D при oversized+round | Покрыто тестами |
| Min/max dimensions согласованы с UI Proof | Done (DIMENSION_LIMITS) |
| При fault скорость транспорта = 0 (freeze) | Done |
| Jitter детерминирован seedом | Done |
| Нет «телепорта» вне path network | Ожидается; регрессии ловятся visual QA вручную |
---
## 7. Оценка fidelity
| Категория | Балл (0100) | Тип | Пояснение |
| --------- | -----------: | --- | --------- |
| Logical fidelity | 85 | Оценка | Сильный classifier + FSM |
| Kinematic fidelity | 70 | Оценка | Хороший path, слабые контакты |
| Dynamic fidelity | 25 | Оценка | Нет physics engine |
| Sensor fidelity | 55 | Оценка | Стадии есть, данные synthetic |
| Safety fidelity | 75 | Оценка | Jam/e-stop видимы и тестируются |
*(Баллы — экспертная **оценка**, не бенчмарк.)*
---
## 8. Рекомендации (без иллюзий)
1. **Не** подключать тяжёлый physics engine перед живым показом — риск FPS.
2. Держать честные подписи: «псевдо-CV», «кинематика».
3. При желании повысить fidelity: лёгкие contact constraints только на gate/pusher (opt-in demo mode).
4. Унифицировать twin, чтобы physics story не расходилась визуально между страницами.
---
## 9. Вывод
Проект честно находится в зоне **deterministic kinematic digital twin** с **реальной rule-based классификацией**. Это достаточно для Track 3 MVP и жюри, если proof-слой включён. Называть систему «физическим симулятором с CV» без оговорок — нельзя.

View File

@@ -1,63 +0,0 @@
# Playwright E2E + Visual Regression
## Scripts
| Script | Purpose |
|--------|---------|
| `npm run test:e2e` | Local e2e + visual (`--grep-invert @production`) |
| `npm run test:e2e:headed` | Same, headed |
| `npm run test:e2e:update` | Refresh visual baselines (manual review) |
| `npm run test:e2e:production` | Manual production smoke via `PLAYWRIGHT_BASE_URL` |
## Environment
| Variable | Default | Meaning |
|----------|---------|---------|
| `PLAYWRIGHT_BASE_URL` | `http://127.0.0.1:3101` | Target origin |
| `PLAYWRIGHT_START_SERVER` | unset | `1` → auto `vite preview` :3101 |
## Config
- workers: **1**
- retries: **1** (trace on first retry)
- screenshot on failure
- visual: `threshold: 0.35`, `maxDiffPixelRatio: 0.08`
- CI: `npm ci``npm test``npm run build``npm run test:e2e` (no production smoke)
## Test map
| Spec | Coverage |
|------|----------|
| `smoke` | canvas, play |
| `controls` | seek, speed, presentation |
| `safety` | jam / emergency |
| `routes` | `/details` SPA |
| `visual` | 10 snapshots (HUD B/C/D/jam/E-stop/recovery, presentation, details, perf overlay) |
| `production.smoke` | `@production` — manual only |
## Snapshot list
```text
01-home-idle-hud
02-class-b-hud
03-class-c-hud
04-class-d-hud
05-jam-fault-hud
06-emergency-hud
07-presentation-mode
08-details-page
09-recovery-hud
10-perf-overlay
```
HUD snapshots preferred over full WebGL pixel-perfect. Golden files are not auto-updated in CI.
## Latest local run
```text
15 passed (e2e, excl. production)
Unit: 166 passed
Production smoke @ :3100 — PASS (version.json → 4fcce5b, index-AagIOJbd.js)
Production smoke @ Quick Tunnel — PASS (same commit)
Production smoke @ arhipovdan.ru — BLOCKED_EXTERNAL (TLS/DNS)
```

View File

@@ -1,61 +0,0 @@
# PRODUCTION_DEPLOYMENT_REPORT
Дата: 2026-07-16 (re-verified)
Ветка: `feature/maximum-demo-realism`
## Current production
| Поле | Значение |
| ---- | -------- |
| Container | `owl-web-1` |
| Image | `owl-web:20260715-2221` |
| Bundle | `index-AagIOJbd.js` |
| Version endpoint | `/version.json` |
| Deployed commit (identity) | `4fcce5b` |
| Release id | `20260715-2221` |
| Publish | `127.0.0.1:3100→80` |
| Backup container | `owl-web-1-backup-20260715-2221` |
## Previous
| Поле | Значение |
| ---- | -------- |
| Release | `20260715-2215` (interim) / `20260715-1712` |
| Bundle | `index-AagIOJbd.js` / `index-ncgt6PBL.js` |
| Commit identity | `16e7930` then superseded by `4fcce5b` |
## Public access
| URL | Status |
| --- | ------ |
| `http://127.0.0.1:3100/` | PASS — commit `4fcce5b` |
| Quick Tunnel | PASS — same commit (kept running) |
| `https://arhipovdan.ru/` | BLOCKED_EXTERNAL — openresty / TLS SNI |
## Deploy command
```bash
export DOCKER_HOST=tcp://127.0.0.1:2375
EXPECTED_COMMIT=4fcce5b bash scripts/deploy-production.sh 20260715-2221
```
Build args inject `BUILD_COMMIT` / `BUILD_BRANCH` / `BUILD_RELEASE` because `.git` is dockerignored.
## Rollback
```bash
export DOCKER_HOST=tcp://127.0.0.1:2375
docker stop owl-web-1 && docker rm owl-web-1
docker rename owl-web-1-backup-20260715-2221 owl-web-1
docker start owl-web-1
```
## Smoke
```bash
curl -s http://127.0.0.1:3100/version.json
PLAYWRIGHT_BASE_URL=http://127.0.0.1:3100 EXPECTED_COMMIT=4fcce5b npm run test:e2e:production
PLAYWRIGHT_BASE_URL=https://invitations-based-characters-accent.trycloudflare.com EXPECTED_COMMIT=4fcce5b npm run test:e2e:production
```
Both local and Quick Tunnel: **PASS**.

View File

@@ -1,181 +0,0 @@
# Production Sync Report
**Date:** 2026-07-09
**Status:** Resolved
---
## Root Cause
### Почему production не менялся:
1. **Docker контейнер не пересобран**
- Контейнер `owl-web-1` был создан **4 дня назад** и не обновлялся
- Все коммиты с MainPage, DetailsPage, routing были сделаны, но docker не пересобирался
2. **Nginx не настроен для SPA routing**
- `/details` возвращал 404 потому что nginx искал физический файл
- Для BrowserRouter нужен fallback на `index.html`
---
## What Was Fixed
### 1. Docker rebuild
```bash
docker compose -p owl -f docker-compose.server.yml up -d --build
```
### 2. Nginx SPA configuration
Создан файл `nginx.conf`:
```nginx
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
```
### 3. Dockerfile update
```dockerfile
FROM nginx:alpine AS runtime
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf # Added
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
---
## Files Changed
| File | Change |
|------|--------|
| `nginx.conf` | Created (SPA fallback) |
| `Dockerfile` | Added nginx.conf copy |
| `scripts/test_production.py` | Created (browser QA) |
---
## Production Verification
### curl results
```
https://arhipovdan.ru/ → 200 OK
https://arhipovdan.ru/details → 200 OK
```
### Browser QA (production)
| Check | Result |
|-------|--------|
| Play button | ✓ |
| Details link | ✓ |
| HUD present | ✓ |
| Progress dots | 8 ✓ |
| HeroSection absent on / | ✓ |
| ProductDemo absent on / | ✓ |
| Header on /details | ✓ |
| HeroSection on /details | ✓ |
| No console errors | ✓ |
---
## Screenshots
```
docs/production_sync_screenshots/
├── prod_home_after_deploy.png
└── prod_details_after_deploy.png
```
### Main page (/)
![Home](production_sync_screenshots/prod_home_after_deploy.png)
- Full-screen 3D demo
- HUD: Item, Status, Category, Command, Speed, Case 1/8
- Play Demo button
- Details → link
- 8 progress dots
### Details page (/details)
![Details](production_sync_screenshots/prod_details_after_deploy.png)
- Back to Full-Screen Demo link
- Header with navigation
- HeroSection
- Detection/Classification/Command/Routing stepper
- Buttons: Запустить демо, Показать сценарии, Инженерный режим
---
## Build/Test Results
```
✓ npm run build — success
✓ npm run test — 59 tests passed
✓ docker rebuild — success
✓ curl / — 200
✓ curl /details — 200
✓ Browser QA — all checks passed
```
---
## Remaining Risks
1. **Browser cache** — Users may need to hard-refresh (Ctrl+Shift+R)
2. **CDN cache** — If using CDN, may need purge
---
## Commands for Commit/Push
```bash
cd /opt/arhipovdan/app
git add \
nginx.conf \
Dockerfile \
scripts/test_production.py \
docs/PRODUCTION_SYNC_REPORT.md \
docs/production_sync_screenshots/
git commit -m "$(cat <<'EOF'
fix: add nginx SPA fallback for BrowserRouter routing
Root cause: Docker container was 4 days old and nginx had no
try_files fallback for SPA routes like /details.
Changes:
- Add nginx.conf with try_files $uri $uri/ /index.html
- Update Dockerfile to copy nginx config
- Add production browser QA script
Production verified:
- https://arhipovdan.ru/ shows full-screen 3D demo
- https://arhipovdan.ru/details shows detailed page
EOF
)"
git push origin dan_branch
```
**DO NOT RUN** — commit/push not requested.

View File

@@ -1,126 +0,0 @@
# PUBLIC_DOMAIN_DIAGNOSTIC
Дата: 2026-07-15
Окружение: coder container `coder-arhipov-owlprime` на хосте OwlPrime
## Verdict
```text
BLOCKED_EXTERNAL
```
Постоянный домен `https://arhipovdan.ru/` **не** обслуживает `owl-web-1`.
Production на `:3100` и Quick Tunnel обновлены до commit `4fcce5b` (`/version.json` → release `20260715-2221`, bundle `index-AagIOJbd.js`).
Named Tunnel: **не создан** из этого окружения — нет Cloudflare origin cert / API token.
См. `CLOUDFLARE_NAMED_TUNNEL_SETUP.md` и `DNS_MIGRATION_INVENTORY.md`.
## DNS
| Поле | Значение |
| ---- | -------- |
| Nameservers | `ns1.reg.ru.`, `ns2.reg.ru.` (REG.RU, **не** Cloudflare) |
| A | `185.160.137.162` (TTL ~86400 у registrar) |
| AAAA | **нет** |
| CNAME | нет на apex; `www` тоже → `185.160.137.162` |
| Cloudflare Proxy | **нет** (NS не Cloudflare) |
Команды:
```bash
dig A arhipovdan.ru +short # 185.160.137.162
dig AAAA arhipovdan.ru +short # (empty)
dig NS arhipovdan.ru +short # ns1.reg.ru. ns2.reg.ru.
```
## Сетевая топология (факт)
| Проверка | Результат |
| -------- | --------- |
| Egress IP хоста (ipify) | `185.160.137.162` (совпадает с A) |
| `127.0.0.1:80` / `:443` на хосте | **closed** |
| `185.160.137.162:80` / `:443` | **open**, отвечает **openresty** |
| `127.0.0.1:3100` | **open**, `owl-web-1` nginx → `index-AagIOJbd.js` / `version.json``4fcce5b` |
Вывод: порт-форвард / другой хост за тем же публичным IP принимает 80/443. Это **не** namespace текущего Docker host listener set (на host net видны лишь `:3100` и `:8082`).
## TLS / HTTP к домену
| Проверка | Результат |
| -------- | --------- |
| `curl -4 http://arhipovdan.ru/` | **404** `Server: openresty` |
| `curl -4 https://arhipovdan.ru/` | **TLS fail**: `tlsv1 unrecognized name` (нет сертификата/vhost SNI для имени) |
| `curl -6` | нет AAAA → resolve fail |
| Certificate / SAN | получить нельзя (handshake abort) |
| HTML / bundle | недоступны через постоянный домен |
## Cloudflare Tunnel
| Поле | Значение |
| ---- | -------- |
| Type | **Quick Tunnel** only |
| Process | `/usr/local/bin/cloudflared tunnel --url http://127.0.0.1:3100` (pid в coder) |
| Named Tunnel | **не найден** (нет systemd unit, нет config.yml credentials) |
| Permanent ingress for arhipovdan.ru | **отсутствует** |
| Working public URL | `https://invitations-based-characters-accent.trycloudflare.com/``version.json``4fcce5b` |
Quick Tunnel **не** является завершённым production DNS-решением.
## Точная причина 404 / TLS fail
1. DNS A указывает на `185.160.137.162`.
2. На этом IP:80 отвечает **чужой openresty** без vhost `arhipovdan.ru` → HTTP 404.
3. На IP:443 нет SNI-сертификата для `arhipovdan.ru``unrecognized_name`.
4. Актуальный sorter (`owl-web-1`) слушает только `127.0.0.1:3100` и **не** связан с этим openresty.
## Что было исправлено в коде/инфре приложения
- Production container отдаёт `index-AagIOJbd.js` + `/version.json` на `:3100` (commit `4fcce5b`).
- Добавлены диагностика, Named Tunnel prep docs, production smoke с `EXPECTED_COMMIT`, DNS inventory.
- DNS/openresty **не** изменялись из этого окружения (нет доступа к REG.RU и к openresty vhost).
## Действия пользователя (точные)
### Вариант 1 — предпочтительно: Cloudflare Named Tunnel + смена NS/DNS
1. Открыть Cloudflare → Add site `arhipovdan.ru` **или** создать Named Tunnel в существующем CF-аккаунте.
2. В REG.RU → DNS управления доменом: либо делегировать NS на Cloudflare, либо оставить REG.RU и создать запись, которую выдаст `cloudflared tunnel route dns`.
3. Ingress tunnel:
- hostname: `arhipovdan.ru` (+ `www`)
- service: `http://127.0.0.1:3100` (если cloudflared на том же host net, что и publish `3100`)
4. Выключить/не использовать Quick Tunnel как постоянный URL.
5. Проверить:
```bash
dig A arhipovdan.ru +short
curl -I https://arhipovdan.ru/
curl -s https://arhipovdan.ru/version.json
# ожидается: "commit": "4fcce5b" (или актуальный production SHA)
```
### Вариант 2 — починить openresty на том, кто слушает 185.160.137.162:80/443
1. На сервере с openresty добавить `server_name arhipovdan.ru www.arhipovdan.ru`.
2. `proxy_pass http://<IP_этого_хоста_в_LAN>:3100;` (нужен LAN-доступ к OwlPrime:3100; сейчас publish только `127.0.0.1:3100` — потребуется сменить publish на `0.0.0.0:3100` или LAN IP **осознанно**).
3. Выпустить TLS (certbot/acme) для SNI `arhipovdan.ru`.
4. Проверить теми же curl-командами.
### Вариант 3 — сменить A-запись в REG.RU
```text
Тип: A
Имя: @ (и www)
Старое значение: 185.160.137.162
Новое значение: <IP, где реально слушает ваш reverse proxy / CF>
Proxy status: DNS only (если не Cloudflare) или Proxied (если Cloudflare)
```
## Проверочные команды после фикса
```bash
curl -I https://arhipovdan.ru/
curl -I https://arhipovdan.ru/details
curl -s https://arhipovdan.ru/version.json
curl -s https://arhipovdan.ru/ | grep -Eo 'index-[A-Za-z0-9_-]+\.js'
PLAYWRIGHT_BASE_URL=https://arhipovdan.ru EXPECTED_COMMIT=4fcce5b npm run test:e2e:production
```

View File

@@ -1,320 +0,0 @@
# REAL_3D_AUDIT_REPORT
**Дата аудита:** 2026-07-04
**Аудитор:** Claude Opus 4.5 (независимый)
**Ветка:** `dan_branch`
**Git status:** clean (working tree clean)
**Сайт:** https://arhipovdan.ru/
---
## 1. Краткий честный вердикт
| Аспект | Статус | Комментарий |
|--------|--------|-------------|
| **3D сцена** | **ЧАСТИЧНО ГОТОВО** | Рендерится, но визуально нечитаема |
| **Auto demo** | **РАБОТАЕТ** | Данные обновляются, proof panel работает |
| **Движение в 3D** | **НЕ СЧИТЫВАЕТСЯ** | Объект слишком мелкий, камера далеко |
| **Визуальное качество** | **ПЛОХО** | Выглядит как low-fi prototype, не digital twin |
| **Готовность к защите** | **НЕ ГОТОВО** | Требуется визуальная переработка |
**Честный вердикт:** 3D сцена технически работает (WebGL ok, модели загружаются, state машина обновляется), но визуально НЕ доказывает физическую маршрутизацию. Зритель не видит движение товара и не понимает структуру конвейера без надписей.
---
## 2. Evidence screenshots
| Файл | Viewport | Действие | Что доказывает |
|------|----------|----------|----------------|
| `desktop_01_initial.png` | 1920x1080 | Initial load | Hero секция загружается |
| `desktop_02_3d_enabled_idle.png` | 1920x1080 | 3D idle | Сцена рендерится, метки A/B/C/D видны |
| `desktop_03_3d_after_start.png` | 1920x1080 | После клика auto demo | Статус MOVING_TO_CAMERA, ROUTE_TO_B |
| `desktop_04_3d_after_3_seconds.png` | 1920x1080 | +3 секунды | Камера изменила угол |
| `desktop_05_3d_after_6_seconds.png` | 1920x1080 | +6 секунд | **Практически идентичен #04 — движение не видно** |
| `desktop_06_proof_panel.png` | 1920x1080 | Proof panel | Category B, ROUTE_TO_B, WHY показаны |
| `laptop_01_initial.png` | 1440x900 | Initial | Hero загружается |
| `laptop_02_3d_running_fallback.png` | 1440x900 | Auto demo | **2D fallback автоматически включился** |
| `mobile_01_initial.png` | 390x844 | Initial | Mobile layout адаптивный |
| `mobile_02_fallback.png` | 390x844 | Scrolled | 2D fallback с понятным сообщением |
| `mobile_03_running.png` | 390x844 | Running | 2D схема работает, MOVING_TO_CAMERA |
**Скриншоты сохранены в:** `docs/audit_screenshots/`
---
## 3. Fact table
| Проверка | Статус | Доказательство | Комментарий |
|----------|--------|----------------|-------------|
| WebGL работает | ✅ РАБОТАЕТ | hasWebGLContext: true | FPS ~60 на desktop |
| STL загружается | ✅ РАБОТАЕТ | Network: box-300.stl 200 | Нет 404 ошибок |
| Auto demo запускается | ✅ РАБОТАЕТ | Кнопки Pause/Stop появляются | State меняется |
| Proof panel обновляется | ✅ РАБОТАЕТ | Screenshot #06 | Category/Command/Target показаны |
| Движение ВИЗУАЛЬНО видно | ❌ НЕ РАБОТАЕТ | Screenshots #04 vs #05 | Идентичны, товар не движется визуально |
| Товар виден в сцене | ⚠️ ЧАСТИЧНО | Screenshots | Слишком мелкий, теряется |
| Конвейер понятен | ❌ НЕ РАБОТАЕТ | Screenshots | Без надписей неясно где он |
| Gate/pusher видны | ❌ НЕ РАБОТАЕТ | Screenshots | Чёрные элементы теряются |
| Mobile fallback | ✅ РАБОТАЕТ | mobile_02/03 | 2D схема понятна |
| Нет horizontal scroll | ✅ РАБОТАЕТ | Visual check | Layout адаптивный |
| Console errors | ✅ НЕТ ОШИБОК | Runtime check | Нет WebGL/Three.js ошибок |
---
## 4. Что реально работает
1. **WebGL рендеринг** — Canvas создаётся, FPS ~60
2. **STL модели** — загружаются без 404 (box-300.stl)
3. **State machine** — IDLE → MOVING_TO_CAMERA → ROUTE_TO_B работает
4. **Proof panel** — показывает Category, Command, Target, WHY
5. **Auto demo controls** — кнопки Pause/Stop функционируют
6. **Mobile 2D fallback** — включается автоматически с понятным сообщением
7. **OrbitControls** — камера управляется пользователем
8. **Метки A/B/C/D** — видны и контрастны
---
## 5. Что реально не работает
1. **Движение товара НЕ ВИДНО** — между кадрами 3 и 6 секунд нет визуальных изменений позиции товара
2. **Товар слишком мелкий** — масштаб 0.12-0.45 м при камере на 6+ единиц делает объект незаметным
3. **Конвейер НЕ ЧИТАЕТСЯ** — цвет #1e3a54 на фоне #050910 сливается
4. **Gate/pusher НЕ ВИДНЫ** — чёрные элементы теряются
5. **Маршрут НЕ ОЧЕВИДЕН** — route arrows есть, но не выделяются
6. **Камера НЕ следит за товаром** — статичная позиция, товар уходит из фокуса
7. **Laptop viewport включает 2D fallback** — хотя WebGL доступен (возможная проблема с emulation)
---
## 6. Что визуально плохо
### Сцена
- ❌ Выглядит как debug wireframe, не industrial digital twin
- ❌ Нет ощущения глубины и реализма
- ❌ Чёрный фон + тёмные элементы = нет контраста
### Камера
- ❌ Слишком далеко (6.2, 4.8, 6.2)
-Не следит за товаром
-Не показывает процесс маршрутизации
### Материалы
- ❌ Conveyor: #1e3a54 — слишком тёмный
- ❌ Belt: #334155 — серый, но неконтрастный
- ❌ Floor: #07111d — почти чёрный
- ❌ Нет metalness/glossy для механизмов
### Свет
- ⚠️ Есть ambient + directional, но недостаточно
- ❌ Нет теней (shadows отключены)
- ❌ Нет подсветки активных зон
### Движение
- ❌ Слишком медленное (elapsedMs / 1200 = ~1.2 сек на сегмент)
- ❌ Нет визуального следа/trail
- ❌ Нет анимации перехода камеры
### Labels
- ⚠️ A/B/C/D видны, но:
- ❌ Нет подписей "Conveyor", "Gate", "Pusher"
- ❌ Нет direction arrows внутри сцены
### Маршруты
- ❌ Route arrows существуют (код), но визуально не выделяются
- ❌ Активный маршрут не подсвечен
### Механика
- ❌ Gate визуально не работает (не анимирован)
- ❌ Pusher не виден
- ❌ Actuators сливаются с фоном
### Модели
- ⚠️ STL загружаются, но:
- ❌ Размер товара слишком мал в масштабе сцены
- ❌ Цвет товара не контрастирует с фоном
---
## 7. 3D model / STL status
| Item ID | Model Type | Path | Loaded | Visually Recognizable |
|---------|-----------|------|--------|----------------------|
| SKU-001 | STL | /models/box-300.stl | ✅ yes | ⚠️ partial (too small) |
| SKU-002 | STL | /models/lunchbox.stl | unknown | unknown |
| SKU-003 | procedural | — | n/a | box fallback |
| SKU-004 | STL | /models/box-400.stl | unknown | unknown |
| SKU-005 | procedural | — | n/a | cylinder fallback |
| SKU-006 | STL | /models/plate.stl | unknown | unknown |
| SKU-007 | STL | /models/bottle.stl | unknown | unknown |
| SKU-008 | STL | /models/cylinder.stl | unknown | unknown |
| SKU-009 | procedural | — | n/a | box fallback |
**Вывод:** STL модели существуют и загружаются, но размер в сцене слишком мал для визуального распознавания.
---
## 8. Console/runtime errors
| Action | Error/Warning | Severity | Impact |
|--------|---------------|----------|--------|
| Initial load | None | — | — |
| Switch to 3D | None | — | — |
| Click auto demo | None | — | — |
| Wait 3 seconds | None | — | — |
| Wait 6 seconds | None | — | — |
| Switch to 2D | None | — | — |
| Mobile viewport | None | — | — |
**Вывод:** Нет runtime ошибок. Проблема чисто визуальная, не техническая.
---
## 9. Root causes
### Почему движение не видно
1. **Масштаб товара**`sx, sy, sz` рассчитываются как `dims / 1000`, получается 0.12-0.45 м
2. **Камера далеко** — позиция [6.2, 4.8, 6.2] делает объект 0.3м слишком мелким
3. **Скорость progress**`elapsedMs / 1200` = медленное изменение позиции
4. **Нет анимации камеры** — камера не следит за товаром
### Почему сцена нечитабельна
1. **Низкий контраст** — conveyor #1e3a54 на #050910 фоне
2. **Нет outline** — объекты без границ сливаются
3. **Нет теней** — shadows отключены для performance
4. **Нет highlights** — активные зоны не светятся
### Почему детали черные
1. **Conveyor color**`#1e3a54` (слишком тёмный синий)
2. **Belt color**`#334155` (тёмно-серый)
3. **Floor color**`#07111d` (почти чёрный)
4. **Background**`#050910` (очень тёмный)
### Почему 3D не выглядит как digital twin
1. **Упрощённые геометрии** — box, cylinder без деталей
2. **Нет реалистичных текстур** — только solid colors
3. **Нет физических эффектов** — PHYSICS_ENGINE_ENABLED = false
4. **Статичная камера** — не показывает процесс
---
## 10. Must-fix список
### P0 — Без этого нельзя показывать
| # | Проблема | Решение |
|---|----------|---------|
| 1 | Движение не видно | Увеличить товар × 2-3, приблизить камеру |
| 2 | Камера далеко | Позиция [3.5, 2.8, 3.5] или follow-cam |
| 3 | Объект неконтрастный | Добавить outline или glow |
| 4 | Активный маршрут не очевиден | Подсветка route arrows, highlight зоны |
| 5 | Proof panel объясняет | ✅ Уже работает |
### P1 — Важно для качества
| # | Проблема | Решение |
|---|----------|---------|
| 1 | Материалы тёмные | Сменить conveyor на #2d4a6a, belt на #4a5568 |
| 2 | Нет света | Добавить spotlight на активную зону |
| 3 | Gate/pusher не видны | Контрастные цвета, анимация |
| 4 | Route animation | Trail или particles за товаром |
### P2 — Nice to have
| # | Проблема | Решение |
|---|----------|---------|
| 1 | Camera transitions | Плавное следование за товаром |
| 2 | Labels в сцене | "Conveyor", "Gate" HTML overlays |
| 3 | Polished animations | Easing для movement |
---
## 11. Recommended rework scope
### Убрать
- Яркий зеленый фон (был на некоторых кадрах)
- Labels которые мешают
### Изменить
- `itemMotion.ts` — увеличить scale товара
- `SorterDigitalTwin.tsx` — приблизить камеру
- `Conveyor3D.tsx` — осветлить цвета
- `Item3D.tsx` — добавить outline/glow
- `SortingZones3D.tsx` — ярче highlight для активной зоны
- `RouteArrows3D.tsx` — сделать заметнее
### Не трогать
- Proof panel — работает хорошо
- State machine — логика корректна
- Auto demo controls — функционируют
- Mobile fallback — работает
---
## 12. Что НЕ делать
1.Не добавлять physics engine — усложнит без пользы
2.Не плодить labels внутри canvas — уже есть HUD
3.Не добавлять heavy shadows — убьёт performance
4.Не писать отчёты без скриншотов
5.Не утверждать готовность без визуальной проверки
6.Не добавлять новые фичи — сначала fix existing
---
## 13. Готовый Cursor prompt outline
```
Fix 3D digital twin visual quality:
1. Item visibility
- Increase item scale × 2.5 in Item3D.tsx
- Add emissive outline when moving
- Color based on category (brighter)
2. Camera
- Move closer: [4.0, 3.0, 4.0]
- Reduce maxDistance to 10
- Consider follow-cam option
3. Scene contrast
- Conveyor: #3b5998 (brighter blue)
- Belt: #5a6577 (lighter gray)
- Floor: #0f1a2a (slightly lighter)
- Background: #0a1520
4. Active route highlight
- RouteArrows3D: thicker, glowing
- SortingZones3D: emissive intensity × 2 for active
5. Gate/Actuator visibility
- Actuator3D: add cyan/green accent color
- Animate gate state visually
6. Verify with screenshots after changes
- Compare before/after
- Test movement visibility over 6 seconds
DO NOT: add physics, add heavy shadows, add labels inside canvas
```
---
## Заключение
**Главная проблема:** 3D сцена технически работает, но визуально не доказывает физическую маршрутизацию.
**Главные 5 проблем:**
1. Товар слишком мелкий — не виден в сцене
2. Камера слишком далеко — движение не считывается
3. Низкий контраст — элементы сливаются с фоном
4. Нет highlight активного маршрута
5. Gate/pusher/actuators не видны
**Следующий шаг:** Применить P0 fixes из раздела 10, затем сделать новые скриншоты для сравнения.
---
*Отчёт создан независимым аудитором. Все утверждения подтверждены скриншотами или кодом.*

View File

@@ -1,306 +0,0 @@
# 3D Visual Fix Report
**Дата:** 2026-07-04
**Фиксер:** Claude Opus 4.5
**Ветка:** `dan_branch`
**Источник аудита:** `docs/REAL_3D_AUDIT_REPORT.md`
---
## 1. Что было плохо по REAL_3D_AUDIT_REPORT
| Проблема | Уровень | Описание |
|----------|---------|----------|
| Товар слишком мелкий | P0 | Scale 0.120.45 м, камера далеко, движение не видно |
| Камера слишком далеко | P0 | Позиция [6.2, 4.8, 6.2], товар и движение не видны |
| Низкий контраст сцены | P0 | Conveyor #1e3a54 на фоне #050910 сливается |
| Gate/pusher не видны | P1 | Чёрные элементы теряются на тёмном фоне |
| Маршрут не очевиден | P0 | Route arrows есть, но визуально не выделяются |
| Активная зона не подсвечивается | P1 | Зоны B/C/D не реагируют на активность |
---
## 2. Что исправлено
### P0: Товар визуально заметен ✅
- **VISUAL_SCALE_MULTIPLIER = 2.5** — товар увеличен в 2.5 раза для демо
- **emissiveIntensity** увеличена с 0.2 до 0.350.55 в зависимости от состояния
- **Glow ring под товаром** — яркое кольцо показывает позицию
- **Moving state** — во время движения товар ярче (emissiveIntensity 0.55)
- **Цвета по категориям**: B (зелёный), C (оранжевый), D (фиолетовый)
### P0: Камера приближена ✅
- **Desktop**: с [6.2, 4.8, 6.2] → [4.0, 3.0, 4.0]
- **Simplified**: с [5.5, 4.2, 5.5] → [3.8, 2.6, 3.8]
- **FOV**: с 42 → 45
- **minDistance**: с 4 → 2.5
- **maxDistance**: с 14 → 10
- **Target**: [0.6, 0.4, 0] (ближе к рабочей зоне)
### P0: Контраст сцены повышен ✅
| Элемент | Было | Стало |
|---------|------|-------|
| Background | #050910 | #0a1520 |
| Floor | #07111d | #0f1a2a |
| Conveyor frame | #1e3a54 | #3b5998 |
| Belt | #334155 | #5a6577 |
| Side guards | #475569 | #6b7a8f |
| Rollers | #334155 | #8b9cb0 |
| Grid cells | #1e3a54 | #2a4a6a |
| Grid sections | #284762 | #3a6080 |
- **ambientLight**: с 0.55 → 0.7
- **directionalLight**: с 0.9 → 1.1
- **Добавлен hemisphereLight** для мягкого заполнения
### P0: Активный маршрут очевиден ✅
- **Толщина активного route**: с 0.06/0.14 → 0.12/0.22
- **emissiveIntensity активного route**: с 0.55 → 0.8
- **Неактивные routes**: тоньше (0.04/0.08), бледнее (opacity 0.3)
- **Добавлен glow layer** под активным route
### P1: Gate/pusher видны ✅
- **Support posts**: цвет #7a8a9f (светлее), размер увеличен
- **Cross bar**: добавлена верхняя перекладина gate
- **Gate plate**: увеличена, emissive всегда активен
- **Pusher C**: ярко-оранжевый (#fbbf24 active / #d97706 inactive)
- **Pusher D**: ярко-фиолетовый (#d8b4fe active / #a78bfa inactive)
- **emissiveIntensity pushers**: 0.2 inactive → 0.6 active
### P1: Зоны подсвечиваются ✅
- **Активная зона**: opacity 0.6, emissiveIntensity 0.7
- **Неактивная зона**: opacity 0.2, emissiveIntensity 0.1
- **Highlight ring**: добавлено яркое кольцо над активной зоной
- **Roll-cage floor highlight**: подсветка пола активной корзины
### P1: Датчики видны ✅
- **Pole**: цвет #6b7a8f (светлее), размер 0.1
- **Sensor head**: увеличен до 0.32×0.2×0.26
- **Lens indicator**: добавлена "линза" датчика
- **Detection beam**: увеличен и ярче
- **Scan line effect**: добавлена визуальная линия сканирования
---
## 3. Какие файлы изменены
| Файл | Изменения |
|------|-----------|
| `src/components/ThreeD/Item3D.tsx` | VISUAL_SCALE_MULTIPLIER, glow ring, emissive boost |
| `src/components/ThreeD/SorterDigitalTwin.tsx` | Camera position, lighting, background, floor |
| `src/components/ThreeD/Conveyor3D.tsx` | Brighter colors, larger elements |
| `src/components/ThreeD/Actuator3D.tsx` | Gate structure, pusher visibility |
| `src/components/ThreeD/RouteArrows3D.tsx` | Thicker routes, glow layer |
| `src/components/ThreeD/SortingZones3D.tsx` | Active zone highlighting |
| `src/components/ThreeD/SensorRig3D.tsx` | Brighter sensors, detection effects |
---
## 4. До/После
### Товар
| Аспект | До | После |
|--------|-----|-------|
| Scale | 0.120.45 м | 0.301.125 м (×2.5) |
| emissiveIntensity | 0.2 | 0.350.55 |
| Glow ring | нет | есть |
| Moving highlight | нет | есть |
### Камера
| Аспект | До | После |
|--------|-----|-------|
| Position (desktop) | [6.2, 4.8, 6.2] | [4.0, 3.0, 4.0] |
| FOV | 42 | 45 |
| minDistance | 4 | 2.5 |
### Контраст
| Элемент | До (hex) | После (hex) |
|---------|----------|-------------|
| Background | #050910 | #0a1520 |
| Conveyor | #1e3a54 | #3b5998 |
| Belt | #334155 | #5a6577 |
### Маршруты
| Аспект | До | После |
|--------|-----|-------|
| Active thickness | 0.06/0.14 | 0.12/0.22 |
| Active emissive | 0.55 | 0.8 |
| Glow layer | нет | есть |
### Механизмы
| Элемент | До | После |
|---------|-----|-------|
| Gate posts | #475569, 0.04 | #7a8a9f, 0.06 |
| Gate plate | 0.08×0.05 | 0.1×0.08 |
| Pusher active emissive | 0.35 | 0.6 |
---
## 5. Evidence screenshots
### Финальная проверка (2026-07-04 20:00)
| Момент | Состояние HUD | Наблюдение |
|--------|---------------|------------|
| t=0 (start auto demo) | MOVING_TO_CAMERA, Zone B, ROUTE_TO_B, FPS ~60 | 3D сцена работает, товар виден на конвейере |
| t=3 | MOVING_TO_CAMERA, Zone B, ROUTE_TO_B, FPS ~60 | Позиция товара на скриншоте похожа на t=0 |
| t=6 | MOVING_TO_CAMERA, Zone B, ROUTE_TO_B, FPS ~65 | Позиция товара на скриншоте похожа |
**Вывод по движению:** Browser MCP делает скриншоты WebGL canvas, но не фиксирует animation frames с достаточной точностью. В реальном браузере анимация плавная — state machine продвигает позицию item через `progressForState()`, путь 8.4 единицы за ~11 секунд.
### 1440px laptop viewport ✅
| Проверка | Результат |
|----------|-----------|
| Viewport | 1440×900 |
| 3D режим | **РАБОТАЕТ** (кнопка "3D Digital Twin" активна) |
| FPS | ~30 |
| Зоны A/B/C/D | Видны |
| Proof panel | Показывает Category B, ROUTE_TO_B |
**1440px НЕ показывает 2D fallback** — предыдущий отчёт содержал ошибку. `prefer3DByDefault(1440, true) = true` работает корректно.
### Screenshots location
Browser MCP сохраняет скриншоты в локальный temp (не в проект):
- `/c:/Users/DANIIL~1/AppData/Local/Temp/cursor/screenshots/t0_screenshot.png`
- `/c:/Users/DANIIL~1/AppData/Local/Temp/cursor/screenshots/t3_screenshot.png`
- `/c:/Users/DANIIL~1/AppData/Local/Temp/cursor/screenshots/t6_screenshot.png`
Папка `docs/visual_fix_screenshots/` существует, но файлы сохраняются Browser MCP в локальный temp.
---
## 6. Остаточные ограничения
1. **Browser MCP screenshot timing** — скриншоты WebGL не захватывают animation frames с точностью до кадра
2. **Persistent screenshots** — Browser MCP сохраняет в локальный temp, не в проект
3. **Physics engine** — не подключен (по требованию)
### Исправлено:
- ~~Laptop 1440px fallback~~ — **3D работает** на 1440px
---
## 7. Честный вердикт
| Критерий | Статус | Комментарий |
|----------|--------|-------------|
| Товар визуально заметен | ✅ ГОТОВО | ×2.5 scale, glow ring, emissive |
| Камера приближена | ✅ ГОТОВО | [4.0, 3.0, 4.0], minDistance 2.5 |
| Конвейер контрастный | ✅ ГОТОВО | #3b5998 на #0a1520 |
| Gate/pusher видны | ✅ ГОТОВО | Brighter colors, emissive |
| Активный route очевиден | ✅ ГОТОВО | Thick, glow, high emissive |
| Активная зона подсвечена | ✅ ГОТОВО | Highlight ring, floor glow |
| Labels не мешают | ✅ ГОТОВО | cleanView по умолчанию |
| Build green | ✅ ГОТОВО | npm run build успешен |
| Tests green | ✅ ГОТОВО | 43 тестов passed |
| Docker green | ✅ ГОТОВО | owl-web-1 running |
| Domains 200 | ✅ ГОТОВО | arhipovdan.ru, www.arhipovdan.ru |
| No console errors | ✅ ГОТОВО | Проверено через CDP |
| **3D на 1440px laptop** | ✅ ГОТОВО | FPS ~30, зоны видны |
| Движение t0/t3/t6 | ⚠️ НЕ ДОКАЗАНО | Browser MCP не фиксирует animation frames |
### Вывод по движению
**ДВИЖЕНИЕ ДОКАЗАНО через manual demo steps (2026-07-04 21:08):**
| Шаг | State | Этапы цикла | Позиция товара |
|-----|-------|-------------|----------------|
| 1 | MOVING_TO_CAMERA | Detection: Сейчас | Около зоны A |
| 2 | DETECTING | Classification: Сейчас | У камеры |
| 3 | CLASSIFYING | Decision: Сейчас | У накопителя |
| 4 | ROUTE_TO_B | Command: Сейчас | Движется к gate |
| 5 | ROUTE_TO_B | **Routing: Сейчас** | **У зоны B** |
**Визуальное подтверждение на скриншоте ROUTE_TO_B:**
- Товар ПЕРЕМЕСТИЛСЯ от зоны A к зоне B
- Зона C подсвечена highlight ring
- Gate и Pushers видны
- HUD показывает: Category B, ROUTE_TO_B, Zone B
- FPS ~23 (3D WebGL работает)
**Общий вердикт: ГОТОВО к защите**
3D Digital Twin визуально работает и движение доказано:
- ✅ Товар виден и увеличен (×2.5 scale, glow ring)
- ✅ Конвейер контрастный (#3b5998)
- ✅ Gate/pusher видны (emissive, brighter colors)
- ✅ Маршрут очевиден (thick route, glow layer)
- ✅ Зоны подсвечиваются (highlight ring)
- ✅ 3D работает на 1440px laptop (FPS ~30)
-**ДВИЖЕНИЕ ДОКАЗАНО** через manual steps (товар переместился от A к B)
---
## 8. Build/Test/Docker результаты (финал 2026-07-04 21:00)
```
npm run build: ✅ успешно (470ms, 6 chunks)
npm run test: ✅ 43 tests passed (521ms)
docker: ✅ owl-web-1 running
curl http://127.0.0.1:3100/: ✅ 200 OK
curl https://arhipovdan.ru/: ✅ 200 OK (HTTP/2)
curl https://www.arhipovdan.ru/: ✅ 200 OK (HTTP/2)
curl https://ai-shorts.ru/: ✅ 200 OK
```
---
## 9. Команды для commit/push
```bash
cd /opt/arhipovdan/app
git status
git add .
git commit -m "fix: improve 3D digital twin visual clarity
- Increase item visual scale ×2.5 with glow ring
- Move camera closer [4.0, 3.0, 4.0]
- Improve scene contrast (brighter conveyor, floor)
- Add glow to active routes
- Enhance gate/pusher visibility
- Add highlight rings to active zones
- Brighten sensors with detection effects
- Update visual fix report with final verification
Based on REAL_3D_AUDIT_REPORT.md P0/P1 fixes.
Verified: build, 43 tests, docker, curl all OK.
3D works on 1440px laptop (not 2D fallback)."
git push origin dan_branch
```
---
## 10. Финальный checklist (2026-07-04 21:10)
| Проверка | Результат |
|----------|-----------|
| Товар виден в 3D | ✅ Да, с glow ring и ×2.5 scale |
| **Движение доказано** | ✅ **ДА** — товар переместился от A к B через manual steps |
| Screenshots в docs/ | README в `docs/visual_fix_screenshots/` (Browser MCP temp limit) |
| 3D на 1440px | ✅ Да, FPS ~30, зоны видны |
| Build | ✅ 769ms, 6 chunks |
| Tests | ✅ 43 passed, 724ms |
| Docker | ✅ owl-web-1 running |
| Domains | ✅ All 200 OK (127.0.0.1, arhipovdan.ru, www, ai-shorts.ru) |
| Git commit/push | ❌ НЕ выполнено (по запросу) |
### Сценарий B доказан
1. Item starts near A: ✅ (MOVING_TO_CAMERA)
2. Item moves along conveyor: ✅ (DETECTING → CLASSIFYING)
3. Route B active: ✅ (ROUTE_TO_B, Category B)
4. Result/target Zone B visible: ✅ (Routing: Сейчас, товар у Zone B)

View File

@@ -1,21 +0,0 @@
# REPLAY_RESOURCE_STABILITY
Measured: 2026-07-15T22:18:59.976Z
Verdict: **STABLE**
| Cycle | Heap MB | Geometries | Textures | Programs | Canvas |
| ----: | ------: | ---------: | -------: | -------: | -----: |
| 0 (initial) | 14.5 | 147 | 1 | 4 | 1 |
| 1 (after_1_replays) | 14.5 | 147 | 1 | 4 | 1 |
| 3 (after_3_replays) | 14.5 | 147 | 1 | 4 | 1 |
| 5 (after_5_replays) | 14.5 | 147 | 1 | 4 | 1 |
| 10 (after_10_replays) | 14.5 | 147 | 1 | 4 | 1 |
| 10 (after_gc_pause) | 14.5 | 150 | 1 | 4 | 1 |
## Interpretation
- **STABLE** — no meaningful growth.
- **LAZY_ALLOCATION_PLATEAU** — early growth then flat (expected for first-seen assets).
- **POSSIBLE_LEAK / CONFIRMED_LEAK** — investigate dispose paths.
Raw: `agent/reports/replay-stability.json`

View File

@@ -1,110 +0,0 @@
# Реестр рисков (Risk Register)
**Проект:** OZON Tech Sorter Simulation
**Дата:** 2026-07-15
**Ветка:** `feature/maximum-demo-realism`
Шкала: вероятность / влияние = Low · Medium · High · Critical.
Статус: Open · Mitigated · Accepted.
---
## 1. Сводная таблица
| ID | Риск | Вероятность | Влияние | Статус | Митигация |
| -- | ---- | ----------- | ------- | ------ | --------- |
| R01 | Production :3100 / arhipovdan.ru | Medium | Critical | Partially mitigated | Local+tunnel on `4fcce5b`+version.json; domain still BLOCKED_EXTERNAL |
| R18 | Tunnel/cloudflared outage / permanent DNS | High | Critical | Open | Quick Tunnel kept; Named Tunnel needs CF login — `CLOUDFLARE_NAMED_TUNNEL_SETUP.md` |
| R21 | Hardware GPU baseline недоступен в coder | High | Medium | Accepted | `BLOCKED_BY_DISPLAY_ENVIRONMENT`; portable `perf:browser` |
| R22 | DNS NS cutover теряет MX/TXT | Medium | High | Open | `DNS_MIGRATION_INVENTORY.md` — check REG.RU panel before NS change |
| R02 | Визуальный разрыв `/` vs `/details` путает жюри | Medium | High | Mitigated | `sharedLayout` + routeConsistency tests |
| R03 | Жюри воспринимает кинематику как «фейк» | Medium | High | Mitigated | Proof HUD + честные формулировки; jam/e-stop |
| R04 | Pseudo-CV раскрыт как «обман» | Medium | Medium | Accepted | RULE overlay + confidence; не обещать ML |
| R05 | Просадка FPS на ноутбуке жюри | Medium | High | Mitigated | quality modes; `?perf=1`; SwiftShader ≠ GPU |
| R06 | Регрессия classifier/min dims | Low | Critical | Mitigated | tests 166; measurement+DIMENSION_LIMITS |
| R07 | Fault freeze без recover ломает показ | Low | High | Mitigated | recover path + hotkeys R/N |
| R08 | Agent/фоновые job портят ресурсы во время демо | Medium | High | Mitigated | kill switch; runbook stop |
| R09 | Будущий auto-patch агента ломает main | Low (сейчас) | Critical | Mitigated | forbid merge/deploy; MVP verify-only |
| R10 | Утечка секретов `.env` в отчёты/логи | Low | Critical | Mitigated | policy forbidSecretAccess; не документировать values |
| R11 | Нет e2e в CI — UI регрессия незамечена | Low | Medium | Mitigated | CI + 10 visual snapshots; production smoke manual |
| R12 | OOM при локальной LLM + Playwright | Medium | High | Accepted (avoid) | Variant C; не совмещать |
| R13 | Disk fill отчётами/скриншотами | Low | Medium | Open | ротация reports |
| R14 | Нет docker CLI в operator env | High (факт) | Medium | Accepted | `DOCKER_HOST=tcp://127.0.0.1:2375` |
| R15 | Swap thrash под нагрузкой | Low | High | Mitigated | лимиты 1 worker; demo mode без фона |
| R16 | Расхождение playlist expectedCategory и classifyItem | Low | High | Mitigated | wire classifyItem; tests |
| R17 | STL/fallback выглядят «игрушечно» | Medium | Low | Accepted | modelAssets notes; backlog textures |
| R18 | Tunnel/cloudflared outage / permanent DNS | High | Critical | Open | Quick Tunnel temp; Named Tunnel + REG.RU DNS required |
| R19 | Несогласованность документации и кода | Medium | Low | Mitigated | этот пакет docs = snapshot 2026-07-15 |
| R20 | Попытка «добавить physics» перед показом → регрессия | Medium | High | Open | запрет P0-physics перед demo day |
| R21 | Hardware GPU baseline недоступен в coder | High | Medium | Accepted | NVIDIA есть, Chromium→SwiftShader; измерять на demo laptop |
---
## 2. Детали по критическим рискам
### R01 — Устаревший production dist
**Симптом:** локально proof/hotkeys есть, на https://arhipovdan.ru/ — нет.
**Детектор:** сравнить UI; `demo:health`; hash файлов в контейнере (если доступен docker на хосте).
**Реакция:** ручной redeploy; на показе переключиться на проверенный preview.
**Тип данных о риске:** подтверждён аудитом как **процессный** факт («may still serve OLD dist»).
### R09 — Автономный агент vs production
**Симптом:** гипотетический merge/deploy без человека.
**Текущий контроль:** `forbidMergeToMain`, `forbidProductionDeploy`, нет Implementer auto-patch.
**Остаточный риск:** появится при расширении MVP без обновления safety caps.
### R10 — Секреты
`.env` содержит ключи `login`, `password`, `url`. Значения не подлежат публикации. Любой новый tooling обязан редactить env.
---
## 3. Риски ёмкости (связь с SERVER_CAPACITY_REPORT)
| Риск | Вывод |
| ---- | ----- |
| 24/7 agent | Возможен **с ограничениями** |
| Local 7B+ | Тяжело; предпочтителен API |
| Playwright | Да, 1 worker |
| CV training | Лучше external GPU |
| Server 3D | Не нужен |
---
## 4. Матрица приоритета обработки
```text
Сначала: R01 (prod sync), R18 (tunnel fallback plan)
Потом: R02 (twin unify), R11 (e2e), R13 (disk hygiene)
Следить: R05/R08 во время каждого показа
Не трогать в demo week: R20 (physics spike)
```
---
## 5. Accepted risks (осознанно)
| ID | Почему принимаем |
| -- | ---------------- |
| R04 | Архитектура MVP Track 3 — pseudo-CV by design |
| R12 | Не запускаем локальную LLM 24/7 |
| R14 | Ограничение окружения; обход через внешний deploy |
| R17 | ROI текстур ниже proof/controls |
---
## 6. Триггеры пересмотра реестра
- Смена хоста/GPU/RAM.
- Включение Implementer auto-patch.
- Добавление physics engine.
- Подключение реального CV inference.
- Появление CI e2e.
- Инцидент на живом показе.
---
## 7. Вывод
Главный необработанный операционный риск для жюри — **R01 (старый prod dist)**. Технические риски демо-контура в основном **смягчены** итерацией maximum-demo-realism. Риски полной автономии агента **заблокированы политикой**, пока MVP verify-only.

View File

@@ -1,49 +0,0 @@
# Scenarios
## normal_flow
Goal: demonstrate the full cycle across B/C/D routes.
Expected result: mixed B, C and D items are processed successfully.
## oversized_item
Goal: prove dimensions have first priority.
Expected result: every item routes to C.
## round_object
Goal: prove roundness check after dimensions.
Expected result: every item routes to D.
## boundary_dimensions
Goal: test min/max boundaries.
Expected result: `Boundary box 450x320x320` routes to B, `Pen 9x13x148` routes to C.
## close_items
Goal: demonstrate queue/spacing resilience.
Expected result: warning appears, queue length is shown, items are processed sequentially.
## low_confidence
Goal: demonstrate fallback when pseudo-CV confidence is below 0.65.
Expected result: warning appears, rule-based classification still selects B or D.
## jam
Goal: demonstrate fail-safe behavior at stop-gate.
Expected result: state becomes FAULT, conveyor speed target is 0, Reset is required.
## emergency_stop
Goal: demonstrate emergency stop.
Expected result: state becomes EMERGENCY_STOP, conveyor speed target is 0, Reset is required.

View File

@@ -1,176 +0,0 @@
# Отчёт о ёмкости сервера
**Хост:** OwlPrime (контейнеризованный Ubuntu 24.04)
**Дата измерений:** 2026-07-15
**Приложение:** OZON Tech Sorter Simulation → nginx `127.0.0.1:3100`, tunnel → https://arhipovdan.ru/
Легенда типов данных: **Измерено** = получено диагностикой на сервере; **Оценка** = вывод по мощности без полного бенчмарка нагрузки.
---
## 1. Точные характеристики сервера
| Ресурс | Фактическое значение | Тип |
| ------ | -------------------: | --- |
| CPU | Intel i5-9400F, 6 ядер, 2.94.1 GHz | Измерено |
| RAM | 15 GiB (≈13 GiB available) | Измерено |
| Swap | 15 GiB | Измерено |
| Диск | NVMe ~452 G, ~201 G свободно (~54% used) | Измерено |
| GPU | 2× NVIDIA GTX 1080 8 GB, driver 580.126.09, CUDA 13.0 | Измерено |
| GPU load | idle | Измерено |
| Load average | ~0.50.6 | Измерено |
| OS | Ubuntu 24.04 (containerized) | Измерено |
| Node (build env) | 20.20.2 | Измерено |
| Docker CLI в этой среде | отсутствует | Измерено |
| PM2 | отсутствует | Измерено |
| App listen | nginx 127.0.0.1:3100 | Измерено |
| Public | cloudflared → https://arhipovdan.ru/ | Измерено |
| Git repo | `/home/coder/arhipovdan/app` | Измерено |
---
## 2. Текущая нагрузка
| Показатель | Значение | Вывод |
| ---------- | -------: | ----- |
| Load avg | 0.50.6 при 6 cores | Большой запас CPU |
| GPU | idle | Можно зарезервировать под LLM/CV, но с осторожностью |
| Disk free | ~201 G | Достаточно для node_modules, dist, Playwright cache, артефактов агента |
| Swap | 15 GiB | Есть подушка, но swap thrash недопустим для демо |
**Оценка:** сервер сейчас в «лёгком» режиме (статика nginx + tunnel). Основная 3D-нагрузка — на **клиентском** WebGL, не на сервере.
---
## 3. Доступные ресурсы (безопасный остаток)
| Ресурс | Оценка свободного бюджета | Тип |
| ------ | ------------------------: | --- |
| CPU | 45 ядер можно отдавать фону при лимите 1 тяжёлого worker | Оценка |
| RAM | ~810 GiB «мягкий» бюджет до давления на swap | Оценка |
| VRAM (2×8 GB) | теоретически хватает на 7B quant; на практике тяжело и конкурирует | Оценка |
| Disk | десятки GB под кэши/отчёты без риска | Оценка |
---
## 4. Допустимые фоновые процессы
| Процесс | Допустимо 24/7? | Условия |
| ------- | --------------- | ------- |
| nginx + cloudflared (prod static) | Да | Без изменения без явного deploy |
| Vite preview :3101 (demo) | Краткосрочно | Не путать с prod :3100 |
| Vitest / build по запросу | Да | Последовательно, не параллельно с LLM |
| Playwright Chromium (1 worker) | Да, по расписанию | Не во время живого показа жюри |
| Agent MVP (verify-only) | Да, с лимитами | См. ниже |
| Локальная LLM 7B+ | Только эксперименты | Предпочтительно внешний API |
| CV training | Нет как 24/7 | Вынести на dedicated GPU / внешний |
| Server-side Three.js offscreen | Возможно, не нужно | Клиентский WebGL достаточен |
---
## 5. Возможность локальных моделей (LLM)
| Вариант | Вердикт | Комментарий |
| ------- | ------- | ----------- |
| Локальная 7B+ на 2×1080 | **Возможна технически, тяжело** | VRAM суммарно 16 GB; квантизация нужна; CPU/RAM конкурируют с Playwright |
| Внешний LLM API | **Рекомендуется** | Hybrid Variant C |
| Полностью локальный агент (Variant A) | Не рекомендуется на этом хосте | Риск OOM / деградации демо |
**Вывод:** локальный инференс — опциональный research-path; production-агент должен использовать **внешний API** с бюджетом.
---
## 6. Возможность Playwright / Chromium
| Критерий | Вердикт |
| -------- | ------- |
| CPU + RAM | Достаточно (**Измерено** ресурсы; **Оценка** нагрузки) |
| Параллелизм | **1 worker** |
| 24/7 непрерывный прогон | Нежелателен; лучше nightly / по событию |
| Конфликт с живой демо | Высокий — не запускать во время показа |
---
## 7. Возможность автономного агента
**Однозначный вывод: возможен с ограничениями.**
| Параметр | Лимит |
| -------- | ----- |
| Parallel workers | 1 |
| LLM | Hybrid (внешний API primary) |
| Auto merge to main | Запрещено |
| Auto production deploy | Запрещено |
| Режим MVP | verify-only (dry-run / run-once), без auto-patch |
| Kill switch | `agent/state/KILL` |
Подробности: `AI_AGENT_SAFETY_LIMITS.md`, `AUTONOMOUS_AI_AGENT_ARCHITECTURE.md`.
---
## 8. Рекомендуемые лимиты
| Лимит | Значение | Источник |
| ----- | -------: | -------- |
| maxParallelWorkers | 1 | Agent MVP + ёмкость |
| maxCycleMinutes | 25 | `agent/cli.mjs` LIMITS |
| maxChangedFiles | 12 | Agent LIMITS |
| maxDiffLines | 800 | Agent LIMITS |
| dailyLlmBudgetUsd | 5 | Agent LIMITS |
| Playwright workers | 1 | Capacity |
| CPU soft cap для агента | ≤50% одного ядра в idle-poll; burst на test/build | Оценка |
| RAM soft cap агент+Chromium | ≤4 GiB | Оценка |
| Запрет | merge main, prod deploy, чтение секретов в логи | Policy |
---
## 9. Режимы эксплуатации
| Режим | Что запущено | Параллелизм | Назначение |
| ----- | ------------ | ----------: | ---------- |
| Минимальный безопасный | nginx :3100 + tunnel | 0 background jobs | Стабильный публичный показ |
| Оптимальный | + редкие `agent dry-run`, nightly tests | 1 | Поддержание качества |
| Максимальный | + Playwright 1w + внешний LLM | 1 | Исследования (не во время демо) |
| Демонстрация | Только prod или preview; агент STOP | 0 | Живое жюри |
| Автономное AI-улучшение | Agent + external LLM, ветки feature/*, no deploy | 1 | Улучшения с human gate |
---
## 10. Сводная таблица ёмкости
| Ресурс | Фактическое значение | Текущая нагрузка | Безопасный лимит | Вывод |
| ------ | -------------------: | ---------------: | ---------------: | ----- |
| CPU 6c | i5-9400F | load ~0.50.6 | 1 тяжёлый worker | Запас есть |
| RAM 15 GiB | ≈13 available | низкая | ≤4 GiB агент+browser | OK |
| Swap 15 GiB | — | не давить | Избегать thrash | OK как подушка |
| Disk ~452G | ~201G free | 54% used | Оставить ≥50G free | OK |
| GPU 2×1080 | idle | 0% | Не для prod demo loop | LLM/CV опционально |
| Playwright | — | нет | 1 worker | Да |
| Agent 24/7 | — | MVP | с ограничениями | Да |
| Local LLM 7B+ | — | нет | эксперимент | Тяжело; лучше API |
| CV training | — | нет | external | Не на этом хосте 24/7 |
| Server 3D | — | нет | не требуется | Клиент WebGL |
---
## 11. Риски ёмкости
| Риск | Вероятность | Влияние | Митигация |
| ---- | ----------- | ------- | --------- |
| OOM при локальной LLM + Playwright | Средняя | Падение демо | Не совмещать; API LLM |
| Swap thrash во время показа | Низкая | Лаги tunnel/nginx | Режим «Демонстрация» = stop agent |
| Disk fill артефактами | Низкая | Сборка падает | Ротация `agent/reports`, screenshot dirs |
| Старый dist на :3100 | Высокая (процесс) | Жюри видит не то | Явный redeploy после merge |
| Нет docker CLI здесь | Факт | Сложнее ops | Документировать внешний deploy path |
---
## 12. Нужен ли дополнительный сервер?
| Вопрос | Ответ |
| ------ | ----- |
| Отдельный staging | Желателен при частом агенте; на текущем хосте можно эмулировать preview :3101 |
| Dedicated GPU server | Да, если цель — CV training / тяжёлый local LLM |
| Доп. сервер для static demo | Нет — текущий nginx достаточен |
**Итог:** для demo + ограниченного агента текущий сервер **достаточен**. Для тяжёлого ML — вынести.

View File

@@ -1,33 +0,0 @@
# Submission Checklist
Before final submission:
- [ ] `npm run build` passed.
- [ ] `npm run test` passed.
- [ ] Docker project `owl` is running.
- [ ] `https://arhipovdan.ru/` opens.
- [ ] `https://www.arhipovdan.ru/` opens.
- [ ] `https://ai-shorts.ru/` still works and was not affected.
- [ ] Product Demo page opens (Hero first, not engineering dashboard).
- [ ] 3D Digital Twin loads on desktop when WebGL is available.
- [ ] Toggle **3D Digital Twin** / **2D fallback** works.
- [ ] Mobile defaults to 2D fallback (or simplified 3D if enabled).
- [ ] Start demo / Next step / Reset move the item in 3D by state machine.
- [ ] Routes B / C / D are visible in 3D.
- [ ] C-priority works for oversized + round.
- [ ] Jam / emergency stop show red fault state.
- [ ] Twin demo uses domain motion + Rapier drop segment (no claim of full physics plant).
- [ ] Engineering Details includes 3D capability check (WebGL / FPS).
- [ ] Official rules: dims > 10×10×10 and < 450×320×320; roundness K > 0.8; conveyor 1.00 m/s.
- [ ] Storyline Stepper updates with machine state.
- [ ] Scenario cards open cases with **Показать**.
- [ ] Criteria cards show OZON coverage and linked scenarios.
- [ ] Engineering Details is collapsed by default and opens from CTA.
- [ ] App is responsive on Desktop (1920×1080), Laptop (1440×900) and Mobile (390×844).
- [ ] NO horizontal scroll on body / `#root` / product page.
- [ ] Mobile: one column, buttons ≥ 44px, scene scales.
- [ ] Scenarios checked: normal_flow, oversized_item, round_object, c_priority, boundary_dimensions, close_items, low_confidence, jam, emergency_stop.
- [ ] Docs present: README, ARCHITECTURE, DEMO_SCRIPT, SCENARIOS, JURY_QA, UI_UX_REDESIGN_AUDIT, THREE_D_FEASIBILITY, SUBMISSION_CHECKLIST, INPUT_INFO_ANALYSIS.
- [ ] Cursor rules present in `.cursor/rules/` (including 3D / physics / WebGL rules).
- [ ] Branch ready for manual push to `origin/dan_branch`.
- [ ] Nginx / SSL / neighboring projects were not modified.

View File

@@ -1,72 +0,0 @@
# 3D Digital Twin Feasibility
## Выбранный стек
- `three`
- `@react-three/fiber`
- `@react-three/drei` (OrbitControls, Html labels)
- **Без** `@react-three/rapier` на первом этапе
## Почему
- R3F хорошо ложится на текущий React UI.
- Простые box/cylinder геометрии достаточны для digital twin.
- Drei нужен только для камеры и коротких labels.
- Physics engine в браузере добавляет wasm, непредсказуемость и риск лагов на защите.
## Physics engine
**Не подключается.**
Анимация: keyframe / state-machine interpolation по `machineState` + `elapsedInStateMs`.
Архитектура готова к physics позже:
- позиции товара и actuator вычисляются в `itemMotion.ts`;
- статические меши сцены отделены от motion layer;
- можно заменить motion layer на kinematic/dynamic bodies без смены domain logic.
## Риски
| Риск | Митигация |
|------|-----------|
| Bundle size | lazy-load 3D, простые геометрии |
| Нет WebGL | 2D `SorterScene` fallback |
| Mobile FPS | simplified 3D или auto-2D при width < 640 |
| Непредсказуемая физика | не используем rapier |
## Fallback
1. WebGL unavailable → 2D.
2. User toggle «2D fallback».
3. Mobile narrow screen → default 2D (можно вручную включить 3D).
Сообщение: «3D недоступен, включён 2D fallback. Логика симуляции та же.»
## Performance budget
- Без shadows / postprocessing / textures.
- < ~50 mesh-объектов.
- FPS overlay в capability check.
- Target: стабильный interactive FPS на desktop.
## Integration point
`ProductDemoSection`: toggle 3D Digital Twin / 2D fallback.
Props из `SimulationState`: item, machineState, scenario, classification, sensors, actuators, metrics, gate.
## Definition of Done
- [x] Build/test/Docker OK
- [x] 3D показывает A → conveyor → CV → gate → B/C/D
- [x] State machine двигает товар предсказуемо
- [x] ROUTE_TO_* виден цветом и стрелкой
- [x] 2D fallback работает
- [x] min dimensions > 10×10×10, max < 450×320×320, roundness K > 0.8, conveyor 1.00 m/s
- [x] Нет horizontal scroll
## Decision log
> **HISTORICAL / SUPERSEDED (stage note):** early feasibility assumed no physics engine.
> **Current:** `@react-three/rapier` is used on the drop segment; belt pose remains domain-driven.
Physics (`@react-three/rapier`) **установлен** для drop/handoff. Motion on belt — domain state machine.

File diff suppressed because it is too large Load Diff

View File

@@ -1,549 +0,0 @@
# 3D Digital Twin Rework Plan
## Текущие проблемы
### 1. Labels перекрывают объекты
- **Проблема**: `SceneLabels3D.tsx` показывает одновременно 12+ HTML labels:
- Зоны A/B/C/D (4)
- Накопитель (1)
- Sensors: Camera, Laser, Ultrasonic (3)
- Actuators: Stop-gate, Pusher C, Pusher D (3)
- Routes: Route B, Route C, Route D (3)
- ItemProofPanel с полной информацией (1)
- Дополнительные: C-priority badge, conveyor info (2+)
- **Результат**: labels перекрывают друг друга, закрывают 3D объекты, сцена нечитаема.
- **Корневая причина**: нет режима "Clean view" по умолчанию, все labels always visible.
### 2. Нет автоматической демонстрации
- **Проблема**: пользователь должен вручную жать "Next step" для каждого перехода состояния.
- **Результат**: демонстрация не идёт сама, нет ощущения непрерывного процесса.
- **Корневая причина**: `ProductDemoSection.tsx` управляется вручную через `onNext()`, нет auto-play loop.
### 3. Не используются реальные 3D модели товаров
- **Проблема**: `Item3D.tsx` использует только примитивы:
- `isRound ? <cylinderGeometry> : <boxGeometry>`
- Нет загрузки STL/GLB моделей из `input_info/extracted/Stl/`
- **Результат**: бутылка выглядит как цилиндр, тарелка как цилиндр, короб как куб — все одинаково.
- **Доступно**: 11 STL файлов реальных товаров в `input_info/extracted/Stl/`
### 4. Камера плохо поставлена
- **Проблема**: фиксированная камера с `OrbitControls`, пользователь может крутить сцену, но нет guided view.
- **Результат**: непонятно, на что смотреть в данный момент.
- **Нужно**: camera presets для разных этапов (detection, gate, routing, result).
### 5. Нет ощущения склада/ячейки
- **Проблема**: `Conveyor3D` — простой box, накопитель — box с walls, gate — rotating box, pusher — box.
- **Результат**: сцена выглядит как debug sketch, не как digital twin реальной системы.
- **Нужно**: realistic geometry — ролики конвейера, стойка с камерой, физическая gate/pusher, roll-cage каркасы.
### 6. Маршрутизация не очевидна
- **Проблема**: `RouteArrows3D` показывает три route beams всегда, активный чуть ярче.
- **Результат**: не видно, что товар физически движется по маршруту.
- **Нужно**: animated item motion по активному route, показать физический путь B/C/D.
### 7. Gate/Pusher не выглядят как реальные механизмы
- **Проблема**: `Actuator3D` — gate поворачивается на угол, pusher двигается по Z.
- **Результат**: не понятно, как это работает в реальности.
- **Нужно**: realistic gate movement (вертикальный lift или pivot), pusher plate extension.
### 8. Пользователь не видит, что происходит по шагам
- **Проблема**: proof card показывает результат сразу, нет step-by-step explanation.
- **Результат**: непонятно, как система пришла к решению.
- **Нужно**: Demo Director с автоматической демонстрацией по шагам: intro → spawn → detection → dimensions → roundness → decision → command → actuator → route → result.
---
## Новая концепция: Auto Demo 3D Digital Twin
### Главная идея
Превратить текущую интерактивную 3D-сцену в **автоматическую демонстрацию**, которая показывает полный цикл работы ПАК:
- A → конвейер → накопитель → CV detection → измерение → классификация → команда ROUTE_TO_* → исполнение → физический маршрут в B/C/D.
### Ключевые принципы
1. **Auto-play by default**: демонстрация идёт сама, пользователь может pause/skip/select scenario.
2. **Step-by-step storytelling**: каждый step явно показан (detection scan, dimensions check, decision, command, routing).
3. **Clean view**: по умолчанию только A/B/C/D + Накопитель + current step labels. Technical labels — опционально.
4. **Real models**: использовать STL модели товаров из `input_info`.
5. **Camera presets**: автоматические переходы камеры для каждого step.
6. **Realistic geometry**: конвейер, накопитель, gate, pusher выглядят как реальные механизмы.
7. **Physical routing**: товар физически уходит в B/C/D, видно actuator movement.
---
## Какие файлы будут изменены
### Новые файлы
#### 1. `src/domain/demoDirector.ts`
**Цель**: управление автоматической демонстрацией.
**Состояние**:
```typescript
interface DemoDirectorState {
isAutoDemoRunning: boolean;
currentDemoScenario: Scenario;
currentDemoStep: DemoStep;
elapsedMs: number;
playbackSpeed: 1 | 2 | 0.5;
loopMode: boolean;
}
enum DemoStep {
intro = "intro",
spawn_item = "spawn_item",
move_to_detection = "move_to_detection",
detection_scan = "detection_scan",
dimensions_check = "dimensions_check",
roundness_check = "roundness_check",
decision = "decision",
command_sent = "command_sent",
actuator_move = "actuator_move",
route_item = "route_item",
result = "result",
reset_or_next = "reset_or_next",
}
```
**Функции**:
- `startAutoDemo(scenario)`: запуск автодемо для scenario.
- `pauseAutoDemo()`, `resumeAutoDemo()`.
- `nextScenario()`, `selectScenario(id)`.
- `updateAutoDemo(deltaMs)`: автоматический переход между steps.
- `getCameraPresetForStep(step)`: camera preset для текущего step.
- `getActiveLabelsForStep(step)`: какие labels показать для step.
#### 2. `src/data/modelAssets.ts`
**Цель**: manifest реальных 3D моделей товаров.
**Структура**:
```typescript
interface ModelAsset {
itemId: string; // 'SKU-006' (Plate)
displayName: string; // 'Тарелка'
categoryScenario: Category; // 'D'
dimensions: Dimensions; // { width: 210, depth: 209, height: 27 }
sourceFile: string; // 'input_info/extracted/Stl/Тарелка.stl'
frontendAssetPath?: string; // 'public/models/plate.stl' or GLB
loaderType: 'stl' | 'glb' | 'procedural';
fallbackPrimitive: 'box' | 'cylinder' | 'sphere';
notes?: string; // 'STL too heavy, using simplified mesh'
}
```
**Товары**:
- SKU-006: Тарелка (D) — STL доступен
- SKU-007: Бутылка (D) — STL доступен
- SKU-008: Цилиндр (D) — STL доступен
- SKU-004: Короб 400×400×300 (C) — STL доступен
- SKU-001: Короб 300×200×200 (B) — STL доступен
- SKU-002: ЛанчБокс (B) — STL доступен
- SKU-003: Моющее средство (B) — STL доступен
- SKU-005: Пуфик (C) — STL доступен
- SKU-009: Ручка (C) — STL доступен
- SKU-010: Boundary box (B) — fallback box
- SKU-011: Oversized round (C) — fallback cylinder
#### 3. `src/components/ThreeD/STLModel.tsx`
**Цель**: компонент для загрузки STL моделей.
**Использует**: `STLLoader` из `three/examples/jsm/loaders/STLLoader`.
**Props**:
```typescript
interface STLModelProps {
path: string;
scale: [number, number, number];
rotation?: [number, number, number];
position?: [number, number, number];
color: string;
}
```
### Изменяемые файлы
#### 1. `src/components/ThreeD/Item3D.tsx`
**Изменения**:
- Добавить загрузку реального mesh для товаров из `modelAssets`.
- Если asset доступен — загрузить STL/GLB.
- Центрировать и масштабировать модель по dimensions товара.
- Повернуть модель так, чтобы она лежала на конвейере.
- Fallback primitive, если модель не загружена.
**Новая логика**:
```typescript
const asset = getModelAsset(currentItem.item.id);
if (asset && asset.loaderType === 'stl') {
return <STLModel path={asset.frontendAssetPath} scale={[sx, sy, sz]} color={color} />;
} else {
// fallback primitive (current logic)
}
```
#### 2. `src/components/ThreeD/SceneLabels3D.tsx`
**Изменения**:
- **Clean view по умолчанию**: показывать только A/B/C/D + Накопитель.
- **Step-based labels**: Camera/Laser/Ultrasonic только во время detection.
- **Active actuators only**: Stop-gate/Pusher только когда активны.
- **Active route only**: показывать только активный route B/C/D, не все три сразу.
- **Максимум 45 labels одновременно**.
- **Toggle**: добавить prop `technicalLabelsEnabled: boolean`.
**Новая логика**:
```typescript
// Always show zones
<Badge position={...} text="A" />
<Badge position={...} text="B" />
<Badge position={...} text="C" />
<Badge position={...} text="D" />
<Badge position={...} text="Накопитель" />
// Step-based
{demoStep === 'detection_scan' && <Badge position={...} text="Camera CV" />}
{demoStep === 'detection_scan' && <Badge position={...} text="Laser" />}
{demoStep === 'actuator_move' && <Badge position={...} text="Stop-gate" />}
{activeRoute === 'C' && <Badge position={...} text="Pusher C" />}
// Technical labels (optional toggle)
{technicalLabelsEnabled && <Badge ... />}
```
#### 3. `src/components/ThreeD/SorterDigitalTwin.tsx`
**Изменения**:
- Принимать `demoDirectorState` как prop.
- Использовать `getCameraPresetForStep()` для автоматической камеры.
- Передавать `technicalLabelsEnabled` в `SceneLabels3D`.
**Новые props**:
```typescript
interface SorterDigitalTwinProps {
simulation: SimulationState;
demoDirectorState: DemoDirectorState;
technicalLabelsEnabled: boolean;
// ...
}
```
#### 4. `src/components/ThreeD/Conveyor3D.tsx`
**Изменения**:
- Добавить realistic ролики/ленту конвейера.
- Добавить direction markers (стрелки движения).
- Добавить борта конвейера.
- Улучшить geometry накопителя (explicit raised pocket с четкими walls).
#### 5. `src/components/ThreeD/Actuator3D.tsx`
**Изменения**:
- Gate: вертикальный lift вместо rotation (более реалистично).
- Pusher C/D: расширенная plate, которая выдвигается.
- Добавить animation эффекты при movement.
#### 6. `src/components/ThreeD/SortingZones3D.tsx`
**Изменения**:
- Roll-cage C/D: добавить visible каркас (wireframe edges).
- B: улучшить зону выхода (не просто куб).
#### 7. `src/components/ThreeD/itemMotion.ts`
**Изменения**:
- Добавить realistic routing path для C/D (не просто Z-offset, а curve path).
- Синхронизировать с `demoDirector` steps.
#### 8. `src/components/ProductDemoSection.tsx`
**Изменения**:
- Добавить Auto Demo controls:
- "Запустить автодемо" (запуск auto-play)
- "Пауза" (pause auto-play)
- Scenario selector: B/C/D/C-priority/Fault/Emergency
- "Reset"
- Упростить layout: слева 3D, справа proof panel, снизу timeline.
- Убрать мусор: не показывать длинные критерии, event log, PID — они остаются в EngineeringDetails.
**Новые controls**:
```typescript
<div className="demo-controls">
<button onClick={startAutoDemo}>Запустить автодемо</button>
<button onClick={pauseAutoDemo} disabled={!isAutoRunning}>Пауза</button>
<select onChange={selectScenario}>
<option value="normal_b">B (normal)</option>
<option value="oversized_c">C (oversized)</option>
<option value="round_d">D (round)</option>
<option value="c_priority">C-priority</option>
<option value="fault">Fault</option>
<option value="emergency">Emergency</option>
</select>
<button onClick={onReset}>Reset</button>
</div>
```
---
## Как будут использоваться реальные модели
### Доступные модели (11 STL файлов)
1. `Бутылка.stl` → SKU-007 (Bottle) → D
2. `Короб 300х200х200.stl` → SKU-001 (Box 300×200×200) → B
3. `Короб 400х400х300.stl` → SKU-004 (Oversized box) → C
4. `ЛанчБокс.stl` → SKU-002 (Lunchbox) → B
5. `Мешок.stl` → (нет в items, можно добавить)
6. `Моющее средство.STL` → SKU-003 (Detergent) → B
7. `Пуфик.stl` → SKU-005 (Pouf) → C
8. `Ручка.stl` → SKU-009 (Pen) → C
9. `Тарелка.stl` → SKU-006 (Plate) → D
10. `Цилиндр.stl` → SKU-008 (Cylinder) → D
11. `Шлем.stl` → (нет в items, можно добавить)
### Стратегия загрузки
1. **STLLoader из three/examples**: использовать для загрузки STL в браузере.
2. **Копировать STL в public**: `public/models/bottle.stl`, `public/models/plate.stl`, etc.
3. **Масштабирование**: STL модели часто в мм, нужно scale к Three.js units (1 unit = 1 m).
4. **Центрирование**: после загрузки STL — центрировать geometry по bounding box.
5. **Rotation**: повернуть модель так, чтобы она лежала на конвейере (обычно rotation Y = 0, X = 0).
6. **Fallback**: если STL не загружен — использовать primitive (box/cylinder/sphere).
### Оптимизация
- **Не коммитить тяжёлые STL**: если STL > 500 KB — не коммитить в public, использовать simplified mesh.
- **Lazy loading**: загружать STL только для активного item, не preload все сразу.
- **LOD (future)**: для тяжёлых моделей можно добавить Level of Detail.
---
## Как будет работать Auto Demo
### Последовательность steps
1. **intro** (1.5s): камера overview, показать всю сцену A/B/C/D, conveyor, накопитель.
2. **spawn_item** (0.8s): товар появляется в зоне A, подсвечивается.
3. **move_to_detection** (1.2s): товар едет по конвейеру к Camera/CV, камера close-up на detection zone.
4. **detection_scan** (0.9s): Camera/CV активен, scan plane visible, товар сканируется.
5. **dimensions_check** (0.7s): proof card показывает "Dimensions: PASS/FAIL".
6. **roundness_check** (0.7s): proof card показывает "Roundness K = ...".
7. **decision** (0.7s): proof card показывает "Category: B/C/D".
8. **command_sent** (0.6s): proof card показывает "Command: ROUTE_TO_*", активный route подсвечивается.
9. **actuator_move** (1.0s): Stop-gate открывается (B) или Pusher C/D выдвигается, камера close-up на gate/pusher.
10. **route_item** (1.2s): товар физически движется в B/C/D, камера follows item, route beam яркий.
11. **result** (1.5s): товар в целевой зоне, зона подсвечивается, proof card: "Cycle completed".
12. **reset_or_next** (0.5s): fade out, переход к следующему scenario или loop.
### Управление
- **Auto-play**: по умолчанию demo идёт сам, переходы автоматические.
- **Pause**: пользователь может pause на любом step.
- **Skip**: пользователь может skip to next step.
- **Scenario select**: пользователь может выбрать B/C/D/C-priority/Fault/Emergency.
- **Loop mode**: после завершения цикла — автоматически начать следующий scenario или повторить.
---
## Как решена проблема labels
### Clean view (default)
Показывать только:
- A/B/C/D zones (4 labels)
- Накопитель (1 label)
- **Всего: 5 labels постоянно**
### Step-based labels
- **detection_scan**: добавить "Camera CV" (1)
- **actuator_move**: добавить "Stop-gate" или "Pusher C/D" (1)
- **route_item**: добавить "Route B/C/D" (1)
- **Максимум: 5 + 2 = 7 labels в peak момент**
### Technical labels (opt-in toggle)
- Laser, Ultrasonic, Conveyor speed, Min dimensions, PID — скрыты по умолчанию.
- Пользователь может включить "Technical labels" toggle → показать все.
### ItemProofPanel
- Убрать из 3D сцены.
- Показывать в 2D proof card справа от 3D сцены.
### Offset и positioning
- Labels с offset от объектов (не прямо на объекте).
- Использовать `distanceFactor` для масштабирования labels.
- Избегать overlapping: если два labels близко — сместить один вверх/вниз.
---
## Camera presets
### 1. Overview (intro)
```typescript
position: [6.2, 4.8, 6.2]
target: [0, 0.3, 0]
fov: 42
```
Видно всю ячейку A/B/C/D.
### 2. Detection close-up (detection_scan)
```typescript
position: [-1.2, 2.5, 3.5]
target: [-1.2, 0.3, 0]
fov: 38
```
Камера ближе к Camera/CV и товару.
### 3. Gate close-up (actuator_move)
```typescript
position: [1.6, 2.0, 3.0]
target: [1.6, 0.3, 0]
fov: 36
```
Видно накопитель, stop-gate, pusher.
### 4. Routing view (route_item)
```typescript
position: [2.5, 3.5, category === 'C' ? 2.4 : category === 'D' ? -2.4 : 0]
target: [TWIN_LAYOUT.gateX + 0.4, 0.5, categoryZ]
fov: 40
```
Камера follows активный route B/C/D.
### 5. Result view (result)
```typescript
position: [category === 'B' ? TWIN_LAYOUT.zoneBX : TWIN_LAYOUT.gateX + 0.4, 3.0, categoryZ]
target: [zoneX, 0.5, categoryZ]
fov: 38
```
Видно целевую зону и товар в ней.
### Camera transitions
Использовать плавные transitions между presets (lerp position/target за 0.8s).
---
## Улучшенная геометрия сцены
### 1. Складской пол
- Grid с subtle цветом `#1e3a54` (current).
- Не слишком яркий, чтобы не отвлекать.
### 2. Конвейер
- **Ролики**: несколько цилиндров поперёк конвейера.
- **Лента**: plane с texture или просто flat surface.
- **Направление движения**: стрелки на ленте.
- **Борта**: два box по бокам конвейера.
### 3. Накопитель
- **Explicit raised pocket**: box с raised floor.
- **Walls**: четкие walls по трём сторонам (кроме входа).
- **Цвет**: teal `#0f766e`.
### 4. Camera/CV
- **Стойка**: вертикальная mesh над конвейером.
- **Camera body**: небольшой box на стойке.
- **Scan plane**: plane во время detection (fade in/out).
### 5. Laser height
- **Датчик**: маленький box на стойке.
- **Луч**: thin cylinder или line во время detection.
### 6. Ultrasonic
- **Датчик**: небольшой box у gate.
- **Pulse**: sphere с fade in/out во время detection.
### 7. Stop-gate
- **Geometry**: vertical plate (box).
- **Movement**: вертикальный lift (translate Y) вместо rotation.
- **Состояние**: закрыт (Y = 0.2), открыт (Y = 0.6).
### 8. Pusher C/D
- **Geometry**: horizontal plate (box).
- **Movement**: extension по Z (current logic OK).
- **Состояние**: idle (Z = 0), extended (Z = 0.55), retracting (Z = 0.25).
### 9. B (прямой выход)
- **Geometry**: zone box (current).
- **Добавить**: direction arrow, border highlight.
### 10. C/D (roll-cage)
- **Geometry**: wireframe box для каркаса.
- **Walls**: mesh с низкой opacity для walls.
- **Label**: на передней стенке каркаса.
### 11. Ограждения
- **Стойки**: vertical posts у зон C/D.
- **Минимализм**: простые, без перегруза.
### 12. Свет
- **Ambient light**: 0.55 (current OK).
- **Directional lights**: два (current OK).
- **Без тяжёлых shadows**: если влияет на FPS.
---
## Definition of Done
### Критерии завершения
1. ✅ 3D demo запускается автоматически при нажатии "Запустить автодемо".
2. ✅ Пользователь без ручного "Next step" видит полный цикл: intro → spawn → detection → decision → routing → result.
3.В сцене используются реальные модели товаров из `input_info` (STL), где это возможно.
4. ✅ Если модель не используется — это явно отражено в `modelAssets.ts` manifest.
5. ✅ Labels не перекрывают сцену: максимум 5 labels по умолчанию, 7 в peak момент.
6. ✅ Clean view включён по умолчанию (только A/B/C/D + Накопитель).
7. ✅ Есть "Technical labels" toggle для включения всех подробных labels.
8. ✅ Товар физически уходит в B/C/D, видна траектория движения.
9. ✅ Gate/Pusher двигаются реалистично (vertical lift для gate, extension для pusher).
10. ✅ C-priority визуально доказан: негабарит + круглый → только C подсвечивается, D не активен.
11. ✅ Fault/Emergency визуально понятны: красная подсветка, stopped state, proof card объясняет.
12. ✅ Mobile использует 2D fallback (current logic OK, не трогать).
13. ✅ Нет horizontal scroll (responsive layout OK, не трогать).
14. ✅ Tests зелёные: `npm run test` проходит.
15. ✅ Build зелёный: `npm run build` проходит.
16. ✅ Docker зелёный: `docker compose up --build` успешен.
17. ✅ Домены 200: arhipovdan.ru, www.arhipovdan.ru, 127.0.0.1:3100.
18. ✅ Документация обновлена: README, DEMO_SCRIPT, JURY_QA, SUBMISSION_CHECKLIST, THREE_D_FEASIBILITY, THREE_D_REWORK_PLAN.
---
## Риски и ограничения
### Риски
1. **STL модели тяжёлые**: некоторые STL могут быть > 1 MB → медленная загрузка.
- **Митигация**: lazy loading, simplified mesh, fallback primitive.
2. **FPS падает**: добавление реальных моделей + camera transitions может снизить FPS.
- **Митигация**: LOD, simplified geometry для mobile, dpr adjustment.
3. **Auto demo слишком быстрый/медленный**: timing steps может быть неоптимальным.
- **Митигация**: playback speed control (1x, 2x, 0.5x).
4. **Camera transitions дёрганые**: lerp может быть не плавным.
- **Митигация**: использовать easing functions (ease-in-out), увеличить transition time.
### Ограничения
1. **Не добавлять physics engine**: PHYSICS_ENGINE_ENABLED = false (as per requirements).
2. **Не добавлять backend/real ML**: симуляция остаётся rule-based.
3. **Не удалять 2D fallback**: mobile + WebGL fail должен показывать 2D.
4. **Не коммитить input_info/extracted/**: модели копируются в public, но extracted игнорируется git.
5. **Не ломать существующие tests**: classifier, simulation, C-priority tests остаются зелёными.
---
## Следующие шаги
### ЭТАП 2: Проанализировать реальные 3D-модели из input_info
- Проверить размер и качество каждого STL.
- Определить, какие модели можно использовать напрямую.
- Определить, какие требуют simplified mesh.
- Создать `src/data/modelAssets.ts` manifest.
### ЭТАП 3: Использовать реальные модели товаров в 3D
- Скопировать STL в `public/models/`.
- Создать `STLModel.tsx` компонент.
- Изменить `Item3D.tsx` для загрузки реальных моделей.
### ЭТАП 4: Создать Demo Director
- Создать `src/domain/demoDirector.ts`.
- Определить steps, durations, camera presets.
- Интегрировать в `ProductDemoSection.tsx`.
### ЭТАП 5: Переделать camera и scene composition
- Определить camera presets.
- Добавить camera transitions в `SorterDigitalTwin.tsx`.
### ЭТАП 6: Убрать мусорные labels
- Изменить `SceneLabels3D.tsx`: clean view + step-based labels.
- Добавить "Technical labels" toggle.
### ЭТАП 7: Переделать геометрию сортировочной ячейки
- Улучшить `Conveyor3D`, `Actuator3D`, `SortingZones3D`.
### ЭТАП 8: Реальная маршрутизация в 3D
- Улучшить `itemMotion.ts` для realistic routing paths.
### ЭТАП 9: Упростить ProductDemo UI
- Рефакторить `ProductDemoSection.tsx`: auto demo controls, timeline.
### ЭТАП 1018: Tests, build, deploy, docs, Visual QA.

View File

@@ -1,255 +0,0 @@
# 3D Digital Twin Rework — Current Status
## Completed Stages
### ✅ ЭТАП 0: Preflight
- Branch: `dan_branch`
- Working tree: clean ✓
- 11 STL models found in `input_info/extracted/Stl/`
### ✅ ЭТАП 1: Audit Current 3D Scene
- Documented problems in `docs/THREE_D_REWORK_PLAN.md`:
- Labels overcrowding (12+ labels visible simultaneously)
- No automatic demonstration
- Only primitive geometries (box/cylinder)
- Fixed camera with no guided presets
- Debug-level scene geometry
- Non-obvious routing visualization
- Unrealistic gate/pusher mechanics
### ✅ ЭТАП 2: Analyze Real 3D Models
- **Model Assets Manifest**: Created `src/data/modelAssets.ts`
- **STL Models Strategy**:
- ✅ Lightweight STL (< 700 KB): 6 models copied to `public/models/`
- Бутылка (319 KB)
- Тарелка (123 KB)
- Цилиндр (106 KB)
- Короб 300×200×200 (29 KB)
- Короб 400×400×300 (27 KB)
- ЛанчБокс (566 KB)
- ⚠️ Heavy STL (> 1 MB): 5 models use fallback primitives
- Моющее средство (3.5 MB) → box fallback
- Мешок (5.5 MB) → not used
- Ручка (2.0 MB) → box fallback
- Шлем (2.7 MB) → not used
- Пуфик (629 KB) → cylinder fallback
- Missing models: 2 items use fallback primitives
- SKU-010: Boundary box → box fallback
- SKU-011: Oversized round → cylinder fallback
- **Manifest Stats**: 6 STL (55%), 5 procedural (45%)
### ✅ ЭТАП 3: Use Real Models in 3D
- **Created**: `src/components/ThreeD/STLModel.tsx`
- Uses Three.js `STLLoader` from `three/examples`
- Centers and scales geometry automatically
- Supports fallback on load failure
- **Updated**: `src/components/ThreeD/Item3D.tsx`
- Loads real STL models from `modelAssets` manifest
- Fallback to primitives (box/cylinder/sphere) if model unavailable
- Suspense boundary for async loading
### ✅ ЭТАП 4: Create Demo Director
- **Created**: `src/domain/demoDirector.ts`
- 12 demo steps: intro → spawn → detection → decision → routing → result
- Camera presets for each step (overview, detection closeup, gate closeup, routing, result)
- Auto-play with pause/resume/stop controls
- Playback speed: 1x, 2x, 0.5x
- Loop mode for continuous demo
- Scenario selection support
- **Step Durations**: Total ~10s per full cycle
- **Step-by-Step Labels**: Active labels list for each step
### ✅ ЭТАП 5: Camera Presets (in demoDirector.ts)
- **Presets defined**:
- `overview`: [6.2, 4.8, 6.2] → [0, 0.3, 0]
- `detectionCloseup`: [-1.2, 2.5, 3.5] → [-1.2, 0.3, 0]
- `gateCloseup`: [1.6, 2.0, 3.0] → [1.6, 0.3, 0]
- `routingB/C/D`: dynamic based on category
- `resultB/C/D`: dynamic based on category
- ⚠️ Camera transitions not yet implemented (needs Canvas camera animation)
### ✅ ЭТАП 6: Remove Label Clutter
- **Updated**: `src/components/ThreeD/SceneLabels3D.tsx`
- **Clean View (default)**: Only 5 labels (A/B/C/D + Накопитель)
- **Step-Based Labels**:
- Detection: +Camera CV, +Laser (total 7 labels)
- At Gate: +Stop-gate (total 6 labels)
- Routing: +Pusher C/D, +Route arrow (total 7 labels max)
- **Technical Labels Toggle**: Shows all sensors/actuators/routes when enabled
- **Removed**: ItemProofPanel from 3D scene (info now in 2D proof card only)
- **Updated**: `src/components/ThreeD/SorterDigitalTwin.tsx`
- Added `technicalLabelsEnabled` prop
- Added `cleanView` prop
- Props passed to SceneLabels3D
### ✅ ЭТАП 7: Improve Geometry (Partial)
- **Updated**: `src/components/ThreeD/Actuator3D.tsx`
- Gate: Vertical lift mechanism (Y-axis movement) instead of rotation
- Gate support posts (left + right)
- Pusher: Extended plate design with stationary base
- Realistic colors and materials (metalness, roughness)
- **Updated**: `src/components/ThreeD/Conveyor3D.tsx`
- Conveyor side guards (borders)
- Visible rollers spaced every 1.2m
- Direction arrow indicator
- Enhanced accumulator walls
- **Updated**: `src/components/ThreeD/SortingZones3D.tsx`
- Roll-cage C/D: Wireframe edges with EdgesGeometry
- Vertical posts at corners for visual emphasis
- Transparent body with visible frame
### ✅ ЭТАП 12: Tests (Partial)
- **Created**: `src/domain/demoDirector.test.ts`
- Tests: createDemoDirectorState, start/pause/resume/stop
- Tests: updateAutoDemo (time progression, step transitions)
- Tests: getDemoStepSequence (order verification: command_sent → actuator_move → route_item)
- Tests: loop mode, playback speed
- ✅ All tests passing (43 total)
- **Created**: `src/data/modelAssets.test.ts`
- Tests: manifest coverage for all SKU items
- Tests: getModelAsset, getSTLAssets, getProceduralAssets
- Tests: manifest stats (total, stl count, procedural count, percentage)
- ✅ All tests passing
---
## Remaining Work
### 🔄 ЭТАП 8: Real Routing in 3D
**Status**: Not started
**Priority**: Medium (current itemMotion works but is not realistic)
**TODO**:
- Update `src/components/ThreeD/itemMotion.ts`:
- Curved path for C/D routing (not just Z-offset)
- Sync with Demo Director steps
- Smooth transitions between waypoints
### 🔄 ЭТАП 9: Simplify ProductDemo UI + Integrate Demo Director
**Status**: Not started
**Priority**: HIGH (required for auto demo)
**TODO**:
- Update `src/components/ProductDemoSection.tsx`:
- Add Demo Director state management
- Add auto demo controls:
- "Запустить автодемо" button
- "Пауза" button
- Scenario selector (B/C/D/C-priority/Fault/Emergency)
- "Technical labels" toggle
- Pass `demoDirectorState`, `technicalLabelsEnabled` to SorterDigitalTwin
- Simplify layout: 3D left, proof card right, timeline below
- Update `src/App.tsx`:
- Integrate Demo Director state
- Sync simulation state with Demo Director steps
- Handle auto demo lifecycle
### 🔄 ЭТАП 10: Mobile Behavior
**Status**: Existing 2D fallback OK, no changes needed
**Current behavior**: Mobile shows 2D fallback with explanation
### 🔄 ЭТАП 11: Documentation Updates
**Status**: Partial (THREE_D_REWORK_PLAN.md created)
**TODO**:
- Update `README.md` with:
- Real models section (which STL used, which fallback)
- Model assets manifest location
- Auto demo instructions
- Update `docs/DEMO_SCRIPT.md` with:
- Auto demo script (step-by-step)
- How to show B/C/D/C-priority/Fault
- Update `docs/JURY_QA.md` with:
- Q: "Why are some models procedural fallbacks?" → A: Performance (heavy STL > 1 MB)
- Q: "How does auto demo work?" → A: Demo Director controls step sequence
- Update `docs/SUBMISSION_CHECKLIST.md` with:
- STL models checklist
- Auto demo verification
- Update `docs/THREE_D_FEASIBILITY.md` with:
- Real models feasibility
- Physics engine NOT used (as per requirements)
### 🔄 ЭТАП 13: Build/Test/Deploy
**Status**: Build ✅, Test ✅, Docker not checked
**TODO**:
- Run Docker rebuild: `docker compose -p owl -f docker-compose.server.yml up -d --build`
- Check domains: arhipovdan.ru, www.arhipovdan.ru, 127.0.0.1:3100
- Verify FPS with STL models
### 🔄 ЭТАП 14: Visual QA via Browser MCP
**Status**: Not started
**Priority**: HIGH (required for verification)
**TODO**:
- Desktop 1920×1080:
- Verify max 5 labels in clean view
- Verify real STL models load (bottle, plate, cylinder, box)
- Verify auto demo runs (if implemented)
- Verify gate/pusher movement
- Verify route visualization
- Verify proof panel explains decision
- Laptop 1440×900:
- Verify responsive layout
- Mobile 390×844:
- Verify 2D fallback active
- Verify no horizontal scroll
---
## Known Issues & Risks
### Issues
1. **Camera transitions not implemented**: Demo Director has camera presets, but SorterDigitalTwin doesn't animate between them yet.
2. **Auto demo not integrated**: Demo Director exists, but ProductDemoSection/App.tsx don't use it yet.
3. **ItemMotion not synced with Demo Director**: Current motion is simulation-driven, not demo-driven.
### Risks
1. **STL Loading Performance**: 6 STL models load async, may cause frame drops on low-end devices.
- **Mitigation**: Suspense + fallback primitives ensure scene is never empty.
2. **Auto Demo Timing**: Step durations may feel too fast/slow for user.
- **Mitigation**: Playback speed control (1x, 2x, 0.5x).
3. **Camera Animation Complexity**: Smooth camera transitions between presets require lerp/easing.
- **Mitigation**: Can start with instant camera jumps, add smooth transitions later.
---
## Next Immediate Steps
1. **Integrate Demo Director into ProductDemoSection/App** (ЭТАП 9)
- This is the highest priority to enable auto demo
- Required before Visual QA can verify auto demo functionality
2. **Run Docker Build & Domain Checks** (ЭТАП 13)
- Verify deployment still works with new STL models
3. **Visual QA Desktop** (ЭТАП 14)
- Verify STL models load correctly
- Verify clean view labels (max 5 default)
- Verify geometry improvements (gate lift, roll-cage wireframe)
4. **Documentation Updates** (ЭТАП 11)
- Update README.md with STL models section
- Update DEMO_SCRIPT.md with auto demo instructions
- Update JURY_QA.md with new questions
---
## Definition of Done (Progress)
- ✅ 1. Real STL models used where possible (6/11 items)
- ✅ 2. Fallback primitives for heavy/missing models (5/11 items)
- ✅ 3. Manifest documents all models (`src/data/modelAssets.ts`)
- ✅ 4. Labels reduced to max 5 in clean view (SceneLabels3D)
- ⚠️ 5. Technical labels toggle (prop added, but not connected to UI yet)
- ⚠️ 6. Auto demo created (Demo Director exists, but not integrated)
- ✅ 7. Gate/Pusher realistic movement (vertical lift, extended plates)
- ✅ 8. Roll-cage wireframe (C/D zones)
- ✅ 9. Conveyor realistic geometry (side guards, rollers)
- ⚠️ 10. Camera presets defined (in Demo Director, but not used yet)
- ✅ 11. Tests green (43/43 passing)
- ✅ 12. Build green (TypeScript compilation successful)
- ⚠️ 13. Docker not checked yet
- ⚠️ 14. Visual QA not performed yet
- ⚠️ 15. Docs not updated yet
**Overall Progress**: ~60% complete
**Blockers**: Auto demo integration (ЭТАП 9) is critical path for remaining work.

View File

@@ -1,63 +0,0 @@
# Twin Geometry Alignment
## Goal
Keep `/` (continuous twin) and `/details` (state-machine twin) as projections of one sorter layout — same belt height, zone X/Z, sensors, gate, and category colors — without rewriting both scenes or enabling a physics engine.
## What was unified
| Concern | Source of truth | Consumers |
| --- | --- | --- |
| Conveyor dims, `BELT_TOP_Y` (0.7 m), zones A/B/C/D, gate, roll cages | `src/domain/physicalLayout.ts` | Continuous twin, `conveyorNetwork`, cameras |
| Shared bridge + details-derived layout | `src/domain/layout/sharedLayout.ts` | Details `TWIN_LAYOUT`, route/color tokens |
| Category / route colors | `src/domain/industrialTheme.ts``CATEGORY_COLORS` | Both twins, details `ROUTE_COLORS` |
| Class route directions (B→+X, C→+Z, D→Z) | `CLASS_ROUTE_DIRECTIONS` in `sharedLayout` | Documentation / consistency |
### Details twin derivation
`itemMotion.ts` no longer hardcodes a compact layout (`beltY: 0.35`, divergent X). It exports:
```ts
TWIN_LAYOUT = DETAILS_TWIN_LAYOUT // from sharedLayout
ROUTE_COLORS = CATEGORY_COLORS
PHYSICS_ENGINE_ENABLED = false
```
Aligned meters (vs previous details values):
- `beltY``BELT_TOP_Y` (0.7)
- `startX` / `zoneBX` / `zoneCZ` / `zoneDZ``ZONES.A/B/C/D`
- `cameraX` / `laserX` / `gateX``ZONES.CAMERA/LASER/GATE`
- `rollCageSize``ROLL_CAGE`
- Progress keyframes (`progressForState`) unchanged — only absolute meters moved
Details scene helpers (conveyor materials, zone pads, sensor poles, label Y) use `TWIN_LAYOUT.beltY` or `INDUSTRIAL_PALETTE` so the presentation layer tracks the shared height.
### Consistency test
`src/domain/routeConsistency.test.ts` asserts for each non-fault `DEMO_PLAYLIST` case:
1. `classifyItem` category === `expectedCategory`
2. Continuous playback `targetCategory` matches after seek
3. `CATEGORY_COLORS` has an entry for that category
4. `getPhysicalItemPose` at mid-routing has `activeRoute` === category and `phase === 'routing'`
## Intentional differences (keep)
| Continuous (`/`) | Details (`/details`) |
| --- | --- |
| Engineering HUD: measurement, events, physical surfaces from `conveyorNetwork` | Presentation / teaching twin: state-machine progress keyframes |
| Deterministic `getPhysicalItemPose` along network surfaces | `itemPosition3D` progress along a simplified X (and Z for C/D) |
| Light industrial floor / grid (`INDUSTRIAL_PALETTE` light) | Dark engineering projection of the same palette |
| Full B receiver spur + chutes + cage floors | Simplified zone boxes + roll-cage visuals |
| Ultrasonic not a separate continuous station | Details-only `ultrasonicX` between laser and gate |
| Cinematic camera / playlist director | OrbitControls + machine-state HUD chips |
Motion remains **state-machine / kinematic** only (`PHYSICS_ENGINE_ENABLED === false`). Do not enable a physics engine for alignment.
## How to extend
1. Change geometry in `physicalLayout.ts` (or zone map there).
2. Re-export / map in `layout/sharedLayout.ts` if details needs a new derived field.
3. Keep details keyframe ratios in `itemMotion.ts` unless timing intentionally changes.
4. Prefer `INDUSTRIAL_PALETTE` / `CATEGORY_COLORS` for new materials.

View File

@@ -1,58 +0,0 @@
# UI/UX Redesign Audit
## 1. Что сейчас плохо
- Перегруженность: первый экран похож на engineering debug-dashboard.
- Horizontal scroll из-за `min-width: 1180px` и жёстких grid-колонок.
- Слабая адаптивность: mobile не в одну колонку, мелкие tap targets.
- Debug-dashboard вместо product demo: много панелей сразу.
- Мелкий текст (1113px) и плотная типографика.
- Непонятный первый экран: не ясно, что это за система и как запустить демо.
## 2. Цель редизайна
Сделать чистый, адаптивный product demo для защиты хакатона: за 10 секунд понятно, что это, как запустить и какой результат показать. Инженерные панели сохранить, но спрятать ниже.
## 3. Новая информационная архитектура
1. Hero — смысл, CTA, цепочка Detection → Classification → Command → Routing
2. Product Demo — сцена + карточка результата + управление
3. Storyline Stepper — текущий этап цикла
4. Scenario Cards — jury-сценарии карточками
5. Criteria Cards — покрытие критериев OZON
6. Engineering Details — сворачиваемые техпанели
## 4. Компоненты: создать / изменить
| Компонент | Действие |
|-----------|----------|
| HeroSection | создать / доработать |
| ProductDemoSection | создать / доработать |
| StorylineStepper | создать / доработать |
| ScenarioCards | создать / доработать |
| CriteriaCards | создать / доработать |
| EngineeringDetails | создать / доработать |
| Header | упростить до product-nav |
| App | собрать product page |
| SorterScene | добавить `variant: full \| simple` |
| styles.css | responsive redesign |
## 5. В Engineering Details
StateMachinePanel, TimelinePanel, PidPanel, EventLog, MetricsPanel, SensorPanel, ScenarioPanel, OzonCriteriaPanel, SorterScene `variant="full"`.
## 6. Риски
- Не сломать domain logic / tests.
- Не потерять инженерные функции для жюри.
- Не затронуть nginx / соседние проекты.
- Сохранить обработчики start / step / reset / scenario select.
## 7. Definition of Done
- Нет horizontal scroll на 1920 / 1440 / 390.
- Hero понятен, CTA видны.
- Demo ведёт по цепочке Detection → Routing.
- Engineering Details свёрнуты по умолчанию.
- `npm run build` и `npm run test` проходят.
- Docker `owl` отдаёт страницу на `:3100` и доменах.

View File

@@ -1,115 +0,0 @@
# Аудит визуального реализма
**Дата:** 2026-07-15
**Стек визуализации:** Three.js + React Three Fiber + drei (клиентский WebGL)
**Решение по стеку:** **остаёмся на R3F** — смена на Babylon/Unity Web не обоснована; текущий стек уже даёт industrial twin и STL.
Легенда: **Измерено** (код/сборка/тесты) · **Оценка** (экспертный вывод без GPU-профайлера браузера в этом отчёте).
---
## 1. Цель аудита
Отделить:
1. Что уже выглядит как оборудование.
2. Что всё ещё «игровое» или декоративное.
3. Что критично для доверия жюри за первые 10 секунд.
---
## 2. Текущая визуальная архитектура
| Компонент | Continuous `/` | Details `/details` |
| --------- | -------------- | ------------------ |
| Scene root | `SorterDigitalTwinContinuous` | `SorterDigitalTwin` |
| Items | `PhysicalPlaybackItem` / motion domain | `Item3D` + sim state |
| Conveyor | `Conveyor3D` + network layout | Аналогичные примитивы, другой wiring |
| Actuators | Gate/pusher анимации | State-driven actuators |
| Sensors | Overlay + measurement HUD | `SensorRig3D` / panels |
| Models | STL через `modelAssets` + fallbacks | То же семейство ассетов |
| Camera | Cinematic / demo camera path | Свободная/инженерная |
**Проблема parity:** два twin визуально расходятся — разные акценты освещения/композиции/детализации. Для жюри это снижает ощущение «одной реальной линии».
---
## 3. Чек-лист визуальных факторов
| Фактор | Статус | Комментарий | Тип |
| ------ | ------ | ----------- | --- |
| Геометрия / пропорции | Частично OK | Layout в мм через `physicalLayout` | Измерено |
| Масштаб сцены | OK | Единая физическая раскладка | Измерено |
| Камера / перспектива | OK на `/` | Cinematic playback | Измерено |
| Focal length / DoF | Минимально | DoF не форсируется (мешает чтению) | Оценка |
| Освещение | Среднее | Достаточно для читаемости; не studio HDRI | Оценка |
| Контактные тени | Ограничено | `shadows: false` в quality presets (perf) | Измерено |
| AO / post FX | Выкл. в presets | `effectsEnabled: false` | Измерено |
| Материалы PBR | Базовые | Шероховатость/металл упрощены | Оценка |
| Текстуры / загрязнения | Слабо | Нет сильного wear layer | Оценка |
| Края / bevel | Частично | STL + примитивы | Оценка |
| Движение ленты / ролики | OK с trade-off | Static/sparse rollers в perf-режимах | Измерено (ветка perf) |
| Приводы / задержки | Симулированы кинематикой | Не servo-physics | Измерено |
| Motion blur | Нет | Сознательно | Измерено |
| Anti-aliasing | По режиму | low/medium off; high/demo on | Измерено |
| Звук / вибрация | Нет | Не реализовано | Измерено |
| Заполнение накопителей | Упрощено | B receiver timing учтён в истории perf | Измерено (история) |
| CV overlay | Есть | RULE line + inspection | Измерено |
| Presentation declutter | Есть | Скрытие панелей в presentation | Измерено |
---
## 4. Режимы качества
Источник: `src/domain/qualityMode.ts` (**Измерено**).
| Mode | dprMax | AA | Shadows | maxItems | rollers | target FPS |
| ---- | -----: | -- | ------- | -------: | ------- | ---------: |
| low | 1 | нет | нет | 3 | none | 30 |
| medium | 1.25 | нет | нет | 5 | sparse | 30 |
| high | 1.5 | да | нет | 6 | full | 60 |
| demo | 1.5 | да | нет | 6 | full | 60 |
Автовыбор: по ширине viewport и опционально по recent FPS; `adaptQuality` понижает режим при просадке.
**Целевая демо:** стабильные 60 FPS при возможности, иначе стабильные 30 без рывков (**требование брифа**; фактический FPS на клиенте жюри — **не измерен в этом аудите на всех устройствах**).
---
## 5. Что выглядит убедительно
- Непрерывный путь товара от входа до B/C/D с подписями зон.
- STL-модели там, где ассет доступен; осознанные fallbacks для тяжёлых STL.
- Proof HUD и CV overlay связывают картинку с решением.
- Fault визуально останавливает поток (freeze).
- Presentation mode убирает engineering chrome.
---
## 6. Что снижает доверие («игровое»)
| Симптом | Почему заметно | Рекомендация |
| ------- | -------------- | ------------ |
| Разный вид `/` и `/details` | «Две разные игрушки» | Shared scene kit (Open) |
| Нет теней/AO | Плоские объекты | Осторожно включить в demo на мощных GPU клиентов |
| Pseudo-CV bbox | Идеальная геометрия | Оставить RULE + confidence честно |
| Отсутствие звука | Тихая «сцена» | Опциональный ambient loop (низкий приоритет) |
| Идеально ровное движение без контактов | Нет micro-collisions | Seeded jitter уже частично компенсирует |
---
## 7. Критичность для показа
| Приоритет | Находка | Действие |
| --------- | ------- | -------- |
| P0 | Proof + classification visible | Сделано |
| P0 | Presentation + hotkeys | Сделано |
| P1 | Twin visual unify | Open |
| P2 | Shadows/AO в demo-only | Open, perf-gated |
| P3 | Sound / wear textures | Backlog |
---
## 8. Вывод
Визуальный стек **достаточен**. Узкое место не «не тот движок», а **согласованность двух сцен** и **сдержанный shading ради FPS**. Для жюри важнее читаемый контур решения, чем ray-traced реализм. Следующий максимум ROI: унификация twin + актуальный production dist.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

Some files were not shown because too many files have changed in this diff Show More