Files
ozone-tech_owl_prime/docs/FINAL_PHYSICS_FIX_REPORT.md

181 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 без ошибок.