chore: consolidate documentation and clean repository
Co-authored-by: Cursor <cursoragent@cursor.com>
@@ -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 не развален.
|
||||
@@ -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 можно моделировать логикой, а не реальными столкновениями.
|
||||
@@ -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 всего проекта.
|
||||
@@ -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: 900–1199px
|
||||
- tablet: 640–899px
|
||||
- mobile: < 640px
|
||||
|
||||
Требования:
|
||||
- body/html/#root не должны создавать horizontal scroll.
|
||||
- Проверять overflow-x.
|
||||
- Все секции используют width: min(100%, ...), max-width, padding через clamp().
|
||||
- На mobile:
|
||||
- одна колонка;
|
||||
- крупные кнопки;
|
||||
- короткие тексты;
|
||||
- инженерные детали свернуты;
|
||||
- сцена масштабируется;
|
||||
- сценарии идут карточками.
|
||||
- На desktop:
|
||||
- Hero + demo в понятных секциях;
|
||||
- 2 колонки только там, где хватает ширины;
|
||||
- без внутреннего хаотичного скролла.
|
||||
@@ -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-эффекты без инженерного смысла.
|
||||
@@ -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%.
|
||||
@@ -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
@@ -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__
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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 / ←** — предыдущий
|
||||
- **1–0** — прыжок к кейсу
|
||||
- **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 стабилен (~30–60)
|
||||
3. Console без ошибок
|
||||
4. Кейсы 9–10: 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`, стр. 5–8):**
|
||||
- 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`.
|
||||
|
||||
1146
agent/cli.mjs
@@ -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"
|
||||
}
|
||||
}
|
||||
5
arduino_code/Test/.gitignore
vendored
@@ -1,5 +0,0 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
10
arduino_code/Test/.vscode/extensions.json
vendored
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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}` (0–7).
|
||||
|
||||
| Топик | Тип данных | Описание | Пример 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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
1
backend_control/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/testing
|
||||
@@ -1,61 +0,0 @@
|
||||
# Интерфейс управления
|
||||
|
||||

|
||||
|
||||
## Область Управления
|
||||
|
||||
`Целевой 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)`
|
||||
|
||||

|
||||
|
||||
## Область Управление Серво
|
||||
|
||||
Тут можно задать угол сервопривода и отключить сервопривод
|
||||
|
||||
## Облать Статус и телеметрия
|
||||
|
||||
Вывод информации о сервоприводе включен или выключен. Так же выведен текущий угол.
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
python -m venv testing
|
||||
|
||||
source testing/bin/activate
|
||||
pip install -r requirements.txt
|
||||
@@ -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()
|
||||
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 51 KiB |
@@ -1,2 +0,0 @@
|
||||
paho-mqtt
|
||||
customtkinter
|
||||
@@ -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.5–8; `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 часов**
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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 — единственный устойчивый путь.
|
||||
@@ -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.5–8; `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. Это профессиональная демонстрация продукта.
|
||||
@@ -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)
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.5–8; `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.
|
||||
@@ -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.5–2×, 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)
|
||||
|
||||
Демонстрация должна за 5–10 секунд отвечать зрителю:
|
||||
|
||||
1. Что это за система (промышленная сортировка OZON Tech).
|
||||
2. Откуда берётся решение (измерения → правила → B/C/D).
|
||||
3. Что делают механизмы (gate/pusher/маршрут).
|
||||
4. Что происходит при аварии (jam / e-stop + recover).
|
||||
5. Что это не «мультфильм»: proof HUD, журнал, метрики.
|
||||
|
||||
Целевые свойства:
|
||||
|
||||
- визуально реалистичный industrial twin;
|
||||
- физически правдоподобное (хотя бы кинематически согласованное) движение;
|
||||
- стабильные 30–60 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 (E1–E7 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. Остальное — по необходимости.
|
||||
@@ -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. За 30–60 минут до показа
|
||||
|
||||
### 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. Сценарий показа (рекомендуемый тайминг ~6–8 мин)
|
||||
|
||||
| Мин | Что делать | Что говорит оператор |
|
||||
| --: | ---------- | -------------------- |
|
||||
| 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 1–0 для прыжка к кейсу |
|
||||
|
||||
---
|
||||
|
||||
## 4. Hotkeys оператора
|
||||
|
||||
| Клавиша | Действие |
|
||||
| ------- | -------- |
|
||||
| Space | Пауза / продолжить |
|
||||
| N | Следующий кейс |
|
||||
| B | Предыдущий |
|
||||
| R | Reset |
|
||||
| P / F | Presentation / fullscreen |
|
||||
| E | Журнал событий |
|
||||
| 1–0 | Быстрый переход по кейсам |
|
||||
|
||||
Скорость: **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; не коммитить секреты.
|
||||
@@ -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:00–0:20** Что это за система? ПАК предварительной сортировки, полный цикл A → CV → B/C/D.
|
||||
- **0:20–0:45** Полный контур: Detection → Classification → Command → Actuator → Route.
|
||||
- **0:45–1:30** Категория B: габариты проходят, нет круга, зелёный маршрут в основной сортировщик.
|
||||
- **1:30–2:15** Категория C: негабаритный товар, оранжевый маршрут в roll-cage C.
|
||||
- **2:15–3:00** Категория D: габариты проходят, но круг в сечении (K > 0.8), фиолетовый маршрут в roll-cage D.
|
||||
- **3:00–3:45** C-priority: негабаритный + круглый → всё равно C (приоритет габаритов).
|
||||
- **3:45–4:30** Low confidence и fault: rule-based fallback, FAULT состояние, EMERGENCY_STOP.
|
||||
- **4:30–5:30** Engineering Details: timeline, PID, sensors, event log, 3D capability check.
|
||||
- **5:30–6:30** Критерии OZON и метрики: cycle time, устойчивость, воспроизводимость.
|
||||
- **6:30–7: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.
|
||||
@@ -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
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
**Можно смело переходить к записи видео защиты.** Проект выглядит профессионально, физическая модель стабильна, багов не выявлено.
|
||||
@@ -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 без ошибок.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.5–2×, hotkeys (Space/N/B/R/P/E/F/1–0), 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`.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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`
|
||||
@@ -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.5–2×, hotkeys Space/N/B/R/P/E/F/1–0, 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) |
|
||||
| 1–0 | 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; это задокументировано честно.
|
||||
@@ -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.5–8; `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:00–0:20 что это
|
||||
- 0:20–0:45 полный контур
|
||||
- 0:45–1:30 B
|
||||
- 1:30–2:15 C
|
||||
- 2:15–3:00 D
|
||||
- 3:00–3:45 C-priority
|
||||
- 3:45–4:30 low confidence/fault
|
||||
- 4:30–5:30 Engineering Details
|
||||
- 5:30–6:30 критерии и метрики
|
||||
- 6:30–7: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:00–0:20** Что это за система? ПАК предварительной сортировки, полный цикл A → CV → B/C/D.
|
||||
- **0:20–0:45** Полный контур: Detection → Classification → Command → Actuator → Route.
|
||||
- **0:45–1:30** Категория B: габариты проходят, нет круга, зелёный маршрут в основной сортировщик.
|
||||
- **1:30–2:15** Категория C: негабаритный товар, оранжевый маршрут в roll-cage C.
|
||||
- **2:15–3:00** Категория D: габариты проходят, но круг в сечении (K ≥ 0.7), фиолетовый маршрут в roll-cage D.
|
||||
- **3:00–3:45** C-priority: негабаритный + круглый → всё равно C (приоритет габаритов).
|
||||
- **3:45–4:30** Low confidence и fault: rule-based fallback, FAULT состояние, EMERGENCY_STOP.
|
||||
- **4:30–5:30** Engineering Details: timeline, PID, sensors, event log, 3D capability check.
|
||||
- **5:30–6:30** Критерии OZON и метрики: cycle time, устойчивость, воспроизводимость.
|
||||
- **6:30–7: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 перед финальной защитой.
|
||||
@@ -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.5–8; `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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.5–8; `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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.9–9.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 |
|
||||
@@ -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 (6400–8900 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
|
||||
```
|
||||
@@ -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.5–0.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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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-моделей друг на друга, но это визуально приемлемо и объекты остаются строго внутри корзины.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.5–2× от 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
|
||||
|
||||
| Категория | Балл (0–100) | Тип | Пояснение |
|
||||
| --------- | -----------: | --- | --------- |
|
||||
| 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» без оговорок — нельзя.
|
||||
@@ -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)
|
||||
```
|
||||
@@ -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**.
|
||||
@@ -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 (/)
|
||||
|
||||

|
||||
|
||||
- Full-screen 3D demo
|
||||
- HUD: Item, Status, Category, Command, Speed, Case 1/8
|
||||
- Play Demo button
|
||||
- Details → link
|
||||
- 8 progress dots
|
||||
|
||||
### Details page (/details)
|
||||
|
||||

|
||||
|
||||
- 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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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, затем сделать новые скриншоты для сравнения.
|
||||
|
||||
---
|
||||
|
||||
*Отчёт создан независимым аудитором. Все утверждения подтверждены скриншотами или кодом.*
|
||||
@@ -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.12–0.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.35–0.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.12–0.45 м | 0.30–1.125 м (×2.5) |
|
||||
| emissiveIntensity | 0.2 | 0.35–0.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)
|
||||
@@ -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`
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.9–4.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.5–0.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.5–0.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 | 4–5 ядер можно отдавать фону при лимите 1 тяжёлого worker | Оценка |
|
||||
| RAM | ~8–10 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.5–0.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 — вынести.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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, не все три сразу.
|
||||
- **Максимум 4–5 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.
|
||||
|
||||
### ЭТАП 10–18: Tests, build, deploy, docs, Visual QA.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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: много панелей сразу.
|
||||
- Мелкий текст (11–13px) и плотная типографика.
|
||||
- Непонятный первый экран: не ясно, что это за система и как запустить демо.
|
||||
|
||||
## 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` и доменах.
|
||||
@@ -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.
|
||||
|
Before Width: | Height: | Size: 221 KiB |
|
Before Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 269 KiB |
|
Before Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 205 KiB |