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.
12 KiB
12 KiB
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 = 16generationRadius = 3maxMountainHeight = 6renderRegionSizeInChunks = 4maxAsyncChunkJobs = 2maxChunkBuildsPerFrame = 1maxChunkMeshBuildsPerFrame = 1maxColliderAppliesPerFrame = 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 sourceMountainCollider.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.csAssets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.NavMesh.Types.cs
Причина:
- nav lifecycle напрямую зависит от chunk lifecycle, которым уже владеет
VoxelWorldGenerator - это минимальное изменение без раннего DI/refactor
New Runtime Data
NavRegionRuntimeNavMeshData NavMeshDataNavMeshDataInstance InstanceAsyncOperation ActiveBuildint Versionbool IsDirtybool BuildRequestedWhileRunningBounds BuildBoundsList<NavMeshBuildSource>reusable sources buffer
Queue<Vector2Int> dirtyNavRegionsHashSet<Vector2Int> queuedNavRegionsDictionary<Vector2Int, NavRegionRuntime> navRegions
New Config Settings
Добавить в VoxelWorldConfig отдельную секцию NavMesh:
int navRegionSizeInChunks = 2int maxNavMeshBuildsPerFrame = 1int maxConcurrentNavMeshBuilds = 1float navBoundsVerticalPaddingfloat navBoundsHorizontalPaddingint navWarmupRadiusInRegions
Примечание:
maxConcurrentNavMeshBuildsдля первой итерации должен остаться1- horizontal padding нужен для корректной стыковки границ region data
Integration With World Lifecycle
Chunk load / update flow
GenerateChunkDataзавершает данные чанка.RenderChunkсобирает render snapshot и collider mesh.- После фактического применения collider mesh chunk помечает свой nav region dirty.
- Если чанк лежит у границы nav region, дополнительно dirty-mark соседний region, который делит с ним границу.
- Scheduler позже запускает rebuild региона по budget.
Chunk unload flow
- Перед
runtime.Dispose()определить nav region чанка. - Пометить соответствующий region dirty.
- Если region стал пустым и вышел из active nav range, удалить его
NavMeshDataInstance.
Interest target flow
- Убрать каноническую зависимость от
Camera.mainкак источника стриминга/nav interest. - Ввести actor-level target semantics.
- Для сохранения сцены использовать rename с
FormerlySerializedAs, если будет меняться имя поля. - Для первой итерации target задается явно со сцены или от будущего player actor bootstrap.
Region Build Flow
- Определить
regionCoordпо координате чанка. - Вычислить
Boundsрегиона с padding по XZ и по высоте. - Собрать build sources только из чанков, попадающих в region и в соседний margin вокруг него.
- Для каждого активного чанка добавить:
NavMeshBuildSourceShape.BoxизGroundColliderNavMeshBuildSourceShape.MeshизMountainCollider.sharedMesh, если mesh не пустой
- Запустить
NavMeshBuilder.UpdateNavMeshDataAsyncдля region-localNavMeshData. - При завершении проверить актуальность версии и либо оставить 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
- Запустить
VoxelWorldTestScene. - Использовать debug
NavMeshAgentиз AI Navigation samples. - Проверить, что агент строит путь по поверхности уже загруженных чанков.
- Быстро перемещать actor target по миру и отслеживать отсутствие заметных фризов.
- Проверить 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
- Добавить nav settings в
VoxelWorldConfigи resolved settings. - Добавить runtime структуры nav regions и dirty scheduler в
VoxelWorldGenerator. - Привязать dirty-marking к chunk collider apply и unload.
- Реализовать source collection из chunk colliders.
- Реализовать region-local
NavMeshDatalifecycle и async rebuild. - Убрать camera-driven fallback из world/nav interest path.
- Добавить debug visualization и ручную проверку через sample agent.
- Задокументировать фактические perf observations после первой проверки гипотезы.