16 KiB
TASK-0023 Runtime NavMesh Implementation Plan
Goal
Реализовать runtime NavMesh для procedural voxel world как подключаемый sidecar-модуль в отдельной assembly, без camera-driven assumptions, с совместимостью с будущей peer-host multiplayer моделью и уже внедренными VContainer + MessagePipe.
Inputs And Assumptions
- текущая test scene:
Assets/Features/VoxelWorld/Scenes/VoxelWorldTestScene.unity - основной runtime генерации мира:
Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.cs - в проекте уже есть
ApplicationLifetimeScopeсMessagePiperegistration - первая итерация scheduler может начинать приоритизацию от одного player actor
- внешний interest-контракт сразу остается actor-level interest set:
players + active NPC - один тип агента
- динамические изменения мира пока не реализуются, но контракты под них должны быть предусмотрены
- WebGL-host остается целевой платформой, поэтому базовый pipeline не зависит от потоков
Chosen Technical Direction
1. NavMesh не внедряется в VoxelWorldGenerator как internal partial-логика
Решение:
VoxelWorldGeneratorостается producer world state и chunk geometry- runtime NavMesh живет в отдельном модуле и подписывается на world contracts
Почему:
- это соответствует целевой модели feature-модулей как подключаемых подсистем
- модуль можно будет реально отключить
- world feature не будет знать детали nav scheduling и
NavMeshDatalifecycle
2. Модуль использует MessagePipe для событий, но не опирается только на сообщения
Решение:
MessagePipeиспользуется для lifecycle/invalidation notifications- reader-интерфейсы используются для чтения текущего состояния world geometry и interest points
Почему:
- message-only подход ломается на late subscription и startup ordering
- NavMesh service должен уметь стартовать позже publisher-а и восстановить актуальное состояние
3. Runtime pipeline строится через NavMeshBuilder.UpdateNavMeshDataAsync
Почему:
- это дает контроль над
NavMeshData,Bounds, build sources и budget - лучше подходит для region-based rebuild под WebGL-host, чем sample-подход с одним sliding volume
4. NavMesh строится по nav regions, а не per-chunk и не full-volume вокруг target
Выбор:
- отдельный
NavMeshDataна nav region - стартовый размер region:
2x2чанка, configurable
Почему:
- per-chunk ведет к слишком большому числу мелких build operations
- один большой moving volume слишком дорог и плохо контролируется по бюджету
- region-based rebuild дает лучший компромисс между стоимостью и связностью
5. Целью считается эквивалентная walkable topology, а не bit-identical NavMesh artifact
Почему:
- runtime NavMesh остается derived cache, а не canonical network state
- gameplay correctness не должен опираться на клиентский NavMesh как на источник authority
- это не создает ложного требования к protocol-grade детерминизму там, где он не нужен для MVP
6. Источники build sources публикуются world feature как узкие source descriptors
Выбор:
- world feature отдает snapshot nav sources чанка через стабильный reader contract
- текущая реализация
VoxelWorldможет внутри строить эти sources изGroundColliderиMountainCollider, но эта деталь не протекает в API sidecar-модуля
Почему:
- не нужен scene-wide scanning
- не требуется отдельная nav-only геометрия на первом этапе
- sidecar-модуль не цементируется на конкретных
Collider-компонентах и scene hierarchy мира
Target Module Boundaries
Assembly Layout
Assets/Features/VoxelWorld/Contracts/VoxelWorld.Contracts.asmdefAssets/Features/VoxelWorld/Runtime/VoxelWorld.Runtime.asmdefAssets/Features/VoxelWorldNavMesh/Runtime/VoxelWorld.NavMesh.Runtime.asmdef
Почему так:
Contractsфиксируют стабильную внешнюю границуRuntimeреализует мир и публикует contractsVoxelWorld.NavMesh.Runtimeостается optional consumer-модулем
Contracts To Add
Reader Interfaces
IChunkNavSourceReaderIWorldInterestReader
Минимальная ответственность:
IChunkNavSourceReaderумеет вернуть текущие nav build sources чанка и список уже загруженных чанковIWorldInterestReaderумеет вернуть текущий actor-level interest set
DTO / Contracts
ChunkNavSourceSnapshotChunkNavBuildSourceDescriptorWorldInterestPoint
Состав DTO:
-
ChunkNavSourceSnapshot -
Vector2Int Coord -
int Version -
ChunkNavBuildSourceDescriptor[] Sources -
ChunkNavBuildSourceDescriptor -
NavMeshBuildSourceShape Shape -
Matrix4x4 Transform -
Vector3 Sizeдля box-source -
Mesh Meshдля mesh-source -
int Area -
WorldInterestPoint -
Vector3 Position -
float Priority -
WorldInterestKind Kind
Примечание:
- DTO должен содержать только то, что реально нужно для NavMesh source collection и build prioritization
- private nested types
VoxelWorldGeneratorне должны утекать наружу Transform,MeshColliderиBoxColliderне должны становиться каноническим внешним состоянием NavMesh integration, если достаточно source descriptors
Message Types
ChunkNavGeometryReadyMessageChunkNavGeometryRemovedMessageWorldInterestChangedMessage- позже:
ChunkWalkabilityChangedMessageили аналогичный delta-invalidating message
Правило:
- сообщения несут ключ и минимальные данные invalidation
- тяжелое актуальное состояние читается через reader interfaces
Required Changes In VoxelWorldGenerator
1. Перестать быть единственной точкой nav logic
VoxelWorldGenerator должен только:
- генерировать и стримить чанки
- создавать/apply collider mesh
- публиковать world contracts
Он не должен:
- владеть dirty nav region queue
- владеть
NavMeshData - напрямую запускать
NavMeshBuilder
2. Реализовать reader interfaces
IChunkNavSourceReaderIWorldInterestReader
3. Публиковать сообщения после world lifecycle changes
Нужно публиковать:
ChunkNavGeometryReadyMessageпосле фактического применения collider meshChunkNavGeometryRemovedMessageперед уничтожением чанкаWorldInterestChangedMessageпри изменении actor-level interest set
4. Убрать каноническую зависимость от Camera.main
Для первой итерации допускается scene wiring через explicit target reference, но не через runtime fallback на Camera.main как на архитектурную норму.
NavMesh Module Structure
Рекомендуемые файлы:
Assets/Features/VoxelWorldNavMesh/Runtime/VoxelWorldNavMeshService.csAssets/Features/VoxelWorldNavMesh/Runtime/VoxelWorldNavMeshTypes.csAssets/Features/VoxelWorldNavMesh/Runtime/VoxelWorldNavMeshConfig.csAssets/Features/VoxelWorldNavMesh/Runtime/VoxelWorldNavMeshModule.cs
Дополнительно, если нужен bridge для scene binding на переходном этапе:
Assets/Features/VoxelWorldNavMesh/Runtime/VoxelWorldNavMeshEntry.cs
DI Composition
Registration Model
VoxelWorld регистрирует:
VoxelWorldGeneratorкак реализацию reader interfaces- publishers соответствующих world messages
VoxelWorldNavMesh регистрирует:
VoxelWorldNavMeshService- его subscribers
- config instance
Important Rule
- feature-код не должен использовать
GlobalMessagePipeкак основную integration point IPublisher<T>иISubscriber<T>должны приходить через DI
Почему:
- это сохраняет тестируемость
- не создает скрытых runtime-зависимостей
- лучше соответствует модульному подключению через
LifetimeScope
NavMesh Service Responsibilities
- подписаться на world lifecycle messages
- на старте получить snapshot уже загруженных чанков через
IChunkNavSourceReader - построить initial set dirty regions
- поддерживать
NavMeshDataпо регионам - собирать
NavMeshBuildSourceиз source descriptors, а не через прямой доступ к world colliders - запускать throttled
UpdateNavMeshDataAsync - переоценивать build priority относительно current interest set
- удалять region data, когда она выходит из активного диапазона и становится пустой
Region Runtime Data
Dictionary<Vector2Int, NavRegionRuntime> navRegionsQueue<Vector2Int> dirtyNavRegionsHashSet<Vector2Int> queuedNavRegions
NavRegionRuntime должен хранить:
NavMeshData NavMeshDataNavMeshDataInstance InstanceAsyncOperation ActiveBuildint Versionbool BuildRequestedWhileRunningBounds BuildBounds
New Config Settings
Добавить в NavMesh module config:
int navRegionSizeInChunks = 2int maxConcurrentNavMeshBuilds = 1int maxNavMeshBuildsPerFrame = 1float navBoundsHorizontalPaddingfloat navBoundsVerticalPaddingint navWarmupRadiusInRegions
Примечание:
- для первой итерации
maxConcurrentNavMeshBuildsдолжен оставаться1
Build Flow
Initial Sync
- Сервис стартует.
- Через
IChunkNavSourceReaderполучает список уже загруженных чанков. - Помечает соответствующие nav regions dirty.
- Через
IWorldInterestReaderполучает текущий interest set. - Запускает scheduler.
Incremental Update
- Приходит
ChunkNavGeometryReadyMessage. - Сервис читает актуальные sources через
IChunkNavSourceReader. - Помечает nav region dirty.
- Если chunk расположен на границе region, дополнительно маркирует соседний region.
Removal
- Приходит
ChunkNavGeometryRemovedMessage. - Сервис помечает соответствующий region dirty.
- Если region опустел и вышел из активной зоны, удаляет
NavMeshDataInstance.
Interest Update
- Приходит
WorldInterestChangedMessage. - Сервис обновляет current interest set.
- Scheduler пересчитывает порядок rebuild и warmup regions.
Source Collection Rules
Для каждого затронутого region:
- собрать build sources только из чанков региона и соседнего margin
- использовать только известные source snapshots из reader interface
- не сканировать произвольные объекты сцены
Для каждого chunk snapshot:
- добавить sources из
ChunkNavBuildSourceDescriptor - если текущий
VoxelWorldстроит эти descriptors изGroundColliderиMountainCollider, это остается его внутренней деталью и не становится contract-level зависимостью NavMesh-модуля
Performance Rules
- не делать full-scene bake
- не пересобирать NavMesh синхронно через
BuildNavMesh()на gameplay path - не строить систему в расчете на обязательный background threading
- держать максимум один активный region build
- rebuild запускать только после фактического применения collider mesh
- события из
MessagePipeне должны тащить heavy geometry payload, только invalidation keys - scheduler должен быть bounded и deterministic по budget
Verification Plan
Functional
- Запустить
VoxelWorldTestScene. - Убедиться, что NavMesh module можно отключить и world generation продолжает работать.
- Подключить NavMesh module и проверить появление walkable NavMesh на уже загруженных чанках.
- Проверить, что agent из AI Navigation samples строит путь по поверхности.
- Проверить unload чанков: старый NavMesh не должен оставлять висячие walkable islands.
Integration
- Проверить старт сервиса после world generator: missed events не должны ломать initial sync.
- Проверить, что модуль работает только через DI-injected
MessagePipeи reader interfaces. - Проверить, что отключение регистрации
VoxelWorldNavMeshне ломает world feature. - Проверить, что внешний interest contract допускает один или несколько interest points, даже если первая scene wiring пока подает только одного player actor.
Performance
- Быстро перемещать actor target по миру.
- Снять показатели:
- active nav regions
- queued dirty regions
- builds started
- stale rebuilds dropped
- worst-frame rebuild spikes
Explicit Non-Goals For This Iteration
NavMeshObstaclecarving- multi-agent bake
- networked NavMesh replication
- ownership migration для чанков или NPC
- финальная multiplayer interest model вокруг всех actors
- прямые зависимости NavMesh feature от
VoxelWorldGeneratorinternals
Execution Order
- Добавить contracts assembly для world-to-navmesh integration.
- Добавить message types и reader interfaces.
- Адаптировать
VoxelWorldGeneratorпод публикацию world contracts и messages. - Создать отдельную assembly и runtime module
VoxelWorld.NavMesh.Runtime. - Реализовать
VoxelWorldNavMeshServiceс initial sync через readers и incremental updates черезMessagePipe. - Реализовать region scheduler и
NavMeshBuilder.UpdateNavMeshDataAsync. - Подключить модуль через DI registration.
- Провести ручную проверку и зафиксировать фактические perf observations.