36c67558dd
Fix the authority and world-state assumptions before implementation so runtime NavMesh work can stay consistent with the project's future DI and peer-host multiplayer model.
230 lines
12 KiB
Markdown
230 lines
12 KiB
Markdown
# TASK-0023 Runtime NavMesh Implementation Plan
|
||
|
||
## Goal
|
||
|
||
Реализовать runtime NavMesh для procedural voxel world без фризов и без camera-driven assumptions, с архитектурой, совместимой с будущей peer-host multiplayer моделью.
|
||
|
||
## Inputs And Assumptions
|
||
|
||
- текущая test scene: `Assets/Features/VoxelWorld/Scenes/VoxelWorldTestScene.unity`
|
||
- основной runtime: `Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.cs`
|
||
- текущий world config asset:
|
||
- `chunkSize = 16`
|
||
- `generationRadius = 3`
|
||
- `maxMountainHeight = 6`
|
||
- `renderRegionSizeInChunks = 4`
|
||
- `maxAsyncChunkJobs = 2`
|
||
- `maxChunkBuildsPerFrame = 1`
|
||
- `maxChunkMeshBuildsPerFrame = 1`
|
||
- `maxColliderAppliesPerFrame = 1`
|
||
- первая итерация учитывает область вокруг player actor
|
||
- долгосрочный контракт остается `players + active NPC`
|
||
- один тип агента
|
||
- динамические изменения мира пока не реализуются, но точки расширения под них должны быть предусмотрены
|
||
|
||
## Chosen Technical Direction
|
||
|
||
### 1. Не использовать `NavMeshSurfaceVolumeUpdater` как основу решения
|
||
|
||
Причина:
|
||
- пример из samples двигает один build volume за tracked agent и не подходит как production-модель для chunk streaming
|
||
- он делает слишком coarse-grained rebuild и оставляет мало контроля над budget, dirty queue и multi-region state
|
||
|
||
### 2. Использовать ручной runtime pipeline через `NavMeshBuilder.UpdateNavMeshDataAsync`
|
||
|
||
Причина:
|
||
- дает прямой контроль над build sources, bounds, lifecycle `NavMeshData` и количеством одновременных rebuild
|
||
- позволяет отказаться от scene-wide source collection и собирать только известные chunk sources
|
||
- лучше подходит для throttling под WebGL-host
|
||
|
||
### 3. Строить NavMesh не per-chunk, а по небольшим nav regions
|
||
|
||
Выбор:
|
||
- отдельный `NavMeshData` на nav region
|
||
- стартовый размер region рекомендуется сделать `2x2` чанка, configurable отдельно от render regions
|
||
|
||
Почему выбран region-based подход:
|
||
- per-chunk rebuild создает слишком много мелких операций и лишние seam-риски на границах
|
||
- один большой sliding volume вокруг interest target слишком дорог для WebGL-host
|
||
- небольшой region дает контролируемый компромисс между стоимостью rebuild и связностью навигации
|
||
|
||
### 4. Источник build sources брать из runtime collider-геометрии чанков
|
||
|
||
Выбор:
|
||
- `GroundCollider` каждого чанка дает box source
|
||
- `MountainCollider.sharedMesh` дает mesh source
|
||
|
||
Почему так:
|
||
- не нужно сканировать всю сцену
|
||
- не нужно строить отдельную nav-only геометрию на первом этапе
|
||
- collider topology уже является ближайшим к gameplay физическим представлением поверхности
|
||
|
||
### 5. Rebuild делать через dirty queue и budgeted scheduler
|
||
|
||
Выбор:
|
||
- region помечается dirty при `ApplyColliderMesh` и при unload чанка
|
||
- scheduler сортирует dirty regions по расстоянию до interest actor
|
||
- одновременно идет максимум один nav rebuild
|
||
- если region снова стал dirty во время build, версия region увеличивается и после завершения запускается новый rebuild только для актуальной версии
|
||
|
||
Почему так:
|
||
- это bounded и предсказуемо для WebGL-host
|
||
- исключает лавинообразные rebuild при быстром перемещении игрока
|
||
|
||
## Proposed Runtime Structure
|
||
|
||
### File Placement
|
||
|
||
- расширить `VoxelWorldGenerator` новыми partial-файлами, а не вводить отдельный service layer на этой стадии
|
||
- рекомендуемые файлы:
|
||
- `Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.NavMesh.cs`
|
||
- `Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.NavMesh.Types.cs`
|
||
|
||
Причина:
|
||
- nav lifecycle напрямую зависит от chunk lifecycle, которым уже владеет `VoxelWorldGenerator`
|
||
- это минимальное изменение без раннего DI/refactor
|
||
|
||
### New Runtime Data
|
||
|
||
- `NavRegionRuntime`
|
||
- `NavMeshData NavMeshData`
|
||
- `NavMeshDataInstance Instance`
|
||
- `AsyncOperation ActiveBuild`
|
||
- `int Version`
|
||
- `bool IsDirty`
|
||
- `bool BuildRequestedWhileRunning`
|
||
- `Bounds BuildBounds`
|
||
- `List<NavMeshBuildSource>` reusable sources buffer
|
||
- `Queue<Vector2Int> dirtyNavRegions`
|
||
- `HashSet<Vector2Int> queuedNavRegions`
|
||
- `Dictionary<Vector2Int, NavRegionRuntime> navRegions`
|
||
|
||
### New Config Settings
|
||
|
||
Добавить в `VoxelWorldConfig` отдельную секцию `NavMesh`:
|
||
- `int navRegionSizeInChunks = 2`
|
||
- `int maxNavMeshBuildsPerFrame = 1`
|
||
- `int maxConcurrentNavMeshBuilds = 1`
|
||
- `float navBoundsVerticalPadding`
|
||
- `float navBoundsHorizontalPadding`
|
||
- `int navWarmupRadiusInRegions`
|
||
|
||
Примечание:
|
||
- `maxConcurrentNavMeshBuilds` для первой итерации должен остаться `1`
|
||
- horizontal padding нужен для корректной стыковки границ region data
|
||
|
||
## Integration With World Lifecycle
|
||
|
||
### Chunk load / update flow
|
||
|
||
1. `GenerateChunkData` завершает данные чанка.
|
||
2. `RenderChunk` собирает render snapshot и collider mesh.
|
||
3. После фактического применения collider mesh chunk помечает свой nav region dirty.
|
||
4. Если чанк лежит у границы nav region, дополнительно dirty-mark соседний region, который делит с ним границу.
|
||
5. Scheduler позже запускает rebuild региона по budget.
|
||
|
||
### Chunk unload flow
|
||
|
||
1. Перед `runtime.Dispose()` определить nav region чанка.
|
||
2. Пометить соответствующий region dirty.
|
||
3. Если region стал пустым и вышел из active nav range, удалить его `NavMeshDataInstance`.
|
||
|
||
### Interest target flow
|
||
|
||
1. Убрать каноническую зависимость от `Camera.main` как источника стриминга/nav interest.
|
||
2. Ввести actor-level target semantics.
|
||
3. Для сохранения сцены использовать rename с `FormerlySerializedAs`, если будет меняться имя поля.
|
||
4. Для первой итерации target задается явно со сцены или от будущего player actor bootstrap.
|
||
|
||
## Region Build Flow
|
||
|
||
1. Определить `regionCoord` по координате чанка.
|
||
2. Вычислить `Bounds` региона с padding по XZ и по высоте.
|
||
3. Собрать build sources только из чанков, попадающих в region и в соседний margin вокруг него.
|
||
4. Для каждого активного чанка добавить:
|
||
- `NavMeshBuildSourceShape.Box` из `GroundCollider`
|
||
- `NavMeshBuildSourceShape.Mesh` из `MountainCollider.sharedMesh`, если mesh не пустой
|
||
5. Запустить `NavMeshBuilder.UpdateNavMeshDataAsync` для region-local `NavMeshData`.
|
||
6. При завершении проверить актуальность версии и либо оставить data, либо сразу перезапустить rebuild актуальной версии.
|
||
|
||
## Region Granularity And Boundary Rules
|
||
|
||
### Start choice
|
||
|
||
- `navRegionSizeInChunks = 2`
|
||
|
||
Почему не `1`:
|
||
- слишком много мелких `NavMeshData`
|
||
- больше seam pressure на стыках
|
||
- выше scheduler overhead
|
||
|
||
Почему не `4`:
|
||
- rebuild слишком дорогой для частого runtime update на WebGL-host
|
||
- это уже заметный кусок от всего active world при `generationRadius = 3`
|
||
|
||
### Boundary handling
|
||
|
||
- build bounds должны быть больше чистого region rectangle
|
||
- source collection должна захватывать соседние чанки на один region-margin
|
||
- region dirty-mark должен учитывать chunk changes на границах
|
||
|
||
Причина:
|
||
- без overlap на границах легко получить cracks и непредсказуемую связность между соседними `NavMeshData`
|
||
|
||
## Multiplayer And Authority Contract For This Task
|
||
|
||
- базовый voxel world генерируется локально у каждого peer из одинакового deterministic input
|
||
- NavMesh строится локально у каждого peer и не реплицируется по сети
|
||
- authoritative gameplay использует host-side NPC simulation
|
||
- текущая итерация NavMesh coverage вокруг player actor считается временным MVP simplification
|
||
- при переходе к реальной multiplayer-сцене host должен строить priority coverage вокруг `players + active NPC`
|
||
- будущие world changes должны приходить как authoritative deltas и маркировать nav regions dirty локально на каждом peer
|
||
|
||
## Performance Rules
|
||
|
||
- не делать full-scene bake
|
||
- не пересобирать NavMesh синхронно через `BuildNavMesh()` на gameplay path
|
||
- не сканировать произвольные scene objects через generic collection APIs, если можно собрать sources из известных chunk runtimes
|
||
- держать максимум один активный build
|
||
- переиспользовать buffers, где это возможно
|
||
- rebuild запускать только после фактического применения collider mesh
|
||
- unload и load чанков должны только маркировать region dirty, а не запускать немедленный build вне scheduler
|
||
|
||
## Verification Plan
|
||
|
||
### Manual verification
|
||
|
||
1. Запустить `VoxelWorldTestScene`.
|
||
2. Использовать debug `NavMeshAgent` из AI Navigation samples.
|
||
3. Проверить, что агент строит путь по поверхности уже загруженных чанков.
|
||
4. Быстро перемещать actor target по миру и отслеживать отсутствие заметных фризов.
|
||
5. Проверить unload чанков: после ухода области старый NavMesh не должен оставлять висячие walkable islands в уже удаленных регионах.
|
||
|
||
### Debug instrumentation
|
||
|
||
- gizmos для region bounds и состояния region build
|
||
- лог счетчиков:
|
||
- active nav regions
|
||
- dirty nav regions
|
||
- builds started/completed/cancelled as stale
|
||
|
||
## Explicit Non-Goals For This Iteration
|
||
|
||
- NavMeshObstacle carving
|
||
- multi-agent bake
|
||
- DI integration через VContainer
|
||
- Addressables integration
|
||
- ownership migration для чанков или NPC
|
||
- финальная multiplayer interest model вокруг всех actors
|
||
|
||
## Execution Order
|
||
|
||
1. Добавить nav settings в `VoxelWorldConfig` и resolved settings.
|
||
2. Добавить runtime структуры nav regions и dirty scheduler в `VoxelWorldGenerator`.
|
||
3. Привязать dirty-marking к chunk collider apply и unload.
|
||
4. Реализовать source collection из chunk colliders.
|
||
5. Реализовать region-local `NavMeshData` lifecycle и async rebuild.
|
||
6. Убрать camera-driven fallback из world/nav interest path.
|
||
7. Добавить debug visualization и ручную проверку через sample agent.
|
||
8. Задокументировать фактические perf observations после первой проверки гипотезы.
|