# 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` с `MessagePipe` registration - первая итерация 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 и `NavMeshData` lifecycle ### 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.asmdef` - `Assets/Features/VoxelWorld/Runtime/VoxelWorld.Runtime.asmdef` - `Assets/Features/VoxelWorldNavMesh/Runtime/VoxelWorld.NavMesh.Runtime.asmdef` Почему так: - `Contracts` фиксируют стабильную внешнюю границу - `Runtime` реализует мир и публикует contracts - `VoxelWorld.NavMesh.Runtime` остается optional consumer-модулем ## Contracts To Add ### Reader Interfaces - `IChunkNavSourceReader` - `IWorldInterestReader` Минимальная ответственность: - `IChunkNavSourceReader` умеет вернуть текущие nav build sources чанка и список уже загруженных чанков - `IWorldInterestReader` умеет вернуть текущий actor-level interest set ### DTO / Contracts - `ChunkNavSourceSnapshot` - `ChunkNavBuildSourceDescriptor` - `WorldInterestPoint` Состав 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 - `ChunkNavGeometryReadyMessage` - `ChunkNavGeometryRemovedMessage` - `WorldInterestChangedMessage` - позже: `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 - `IChunkNavSourceReader` - `IWorldInterestReader` ### 3. Публиковать сообщения после world lifecycle changes Нужно публиковать: - `ChunkNavGeometryReadyMessage` после фактического применения collider mesh - `ChunkNavGeometryRemovedMessage` перед уничтожением чанка - `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.cs` - `Assets/Features/VoxelWorldNavMesh/Runtime/VoxelWorldNavMeshTypes.cs` - `Assets/Features/VoxelWorldNavMesh/Runtime/VoxelWorldNavMeshConfig.cs` - `Assets/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` и `ISubscriber` должны приходить через 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 navRegions` - `Queue dirtyNavRegions` - `HashSet queuedNavRegions` `NavRegionRuntime` должен хранить: - `NavMeshData NavMeshData` - `NavMeshDataInstance Instance` - `AsyncOperation ActiveBuild` - `int Version` - `bool BuildRequestedWhileRunning` - `Bounds BuildBounds` ## New Config Settings Добавить в NavMesh module config: - `int navRegionSizeInChunks = 2` - `int maxConcurrentNavMeshBuilds = 1` - `int maxNavMeshBuildsPerFrame = 1` - `float navBoundsHorizontalPadding` - `float navBoundsVerticalPadding` - `int navWarmupRadiusInRegions` Примечание: - для первой итерации `maxConcurrentNavMeshBuilds` должен оставаться `1` ## Build Flow ### Initial Sync 1. Сервис стартует. 2. Через `IChunkNavSourceReader` получает список уже загруженных чанков. 3. Помечает соответствующие nav regions dirty. 4. Через `IWorldInterestReader` получает текущий interest set. 5. Запускает scheduler. ### Incremental Update 1. Приходит `ChunkNavGeometryReadyMessage`. 2. Сервис читает актуальные sources через `IChunkNavSourceReader`. 3. Помечает nav region dirty. 4. Если chunk расположен на границе region, дополнительно маркирует соседний region. ### Removal 1. Приходит `ChunkNavGeometryRemovedMessage`. 2. Сервис помечает соответствующий region dirty. 3. Если region опустел и вышел из активной зоны, удаляет `NavMeshDataInstance`. ### Interest Update 1. Приходит `WorldInterestChangedMessage`. 2. Сервис обновляет current interest set. 3. 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 1. Запустить `VoxelWorldTestScene`. 2. Убедиться, что NavMesh module можно отключить и world generation продолжает работать. 3. Подключить NavMesh module и проверить появление walkable NavMesh на уже загруженных чанках. 4. Проверить, что agent из AI Navigation samples строит путь по поверхности. 5. Проверить unload чанков: старый NavMesh не должен оставлять висячие walkable islands. ### Integration 1. Проверить старт сервиса после world generator: missed events не должны ломать initial sync. 2. Проверить, что модуль работает только через DI-injected `MessagePipe` и reader interfaces. 3. Проверить, что отключение регистрации `VoxelWorldNavMesh` не ломает world feature. 4. Проверить, что внешний interest contract допускает один или несколько interest points, даже если первая scene wiring пока подает только одного player actor. ### Performance 1. Быстро перемещать actor target по миру. 2. Снять показатели: - active nav regions - queued dirty regions - builds started - stale rebuilds dropped - worst-frame rebuild spikes ## Explicit Non-Goals For This Iteration - `NavMeshObstacle` carving - multi-agent bake - networked NavMesh replication - ownership migration для чанков или NPC - финальная multiplayer interest model вокруг всех actors - прямые зависимости NavMesh feature от `VoxelWorldGenerator` internals ## Execution Order 1. Добавить contracts assembly для world-to-navmesh integration. 2. Добавить message types и reader interfaces. 3. Адаптировать `VoxelWorldGenerator` под публикацию world contracts и messages. 4. Создать отдельную assembly и runtime module `VoxelWorld.NavMesh.Runtime`. 5. Реализовать `VoxelWorldNavMeshService` с initial sync через readers и incremental updates через `MessagePipe`. 6. Реализовать region scheduler и `NavMeshBuilder.UpdateNavMeshDataAsync`. 7. Подключить модуль через DI registration. 8. Провести ручную проверку и зафиксировать фактические perf observations.