# 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` reusable sources buffer - `Queue dirtyNavRegions` - `HashSet queuedNavRegions` - `Dictionary 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 после первой проверки гипотезы.