Files
TheDeclineOfWarriors/docs/plans/TASK-0023-runtime-navmesh-implementation-plan.md
T
Alexander Borisov 6227542d2d document modular navmesh and agent prompts
Update the runtime NavMesh architecture to a DI and MessagePipe sidecar model, and add reusable agent prompt templates that capture the project's current multiplayer, WebGL, and modularity constraints.
2026-04-08 02:19:03 +03:00

316 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
- первая итерация NavMesh coverage строится вокруг player actor
- долгосрочный контракт остается `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. Источники build sources берутся из chunk colliders, публикуемых world feature
Выбор:
- `GroundCollider` дает box source
- `MountainCollider.sharedMesh` дает mesh source
Почему:
- не нужен scene-wide scanning
- не требуется отдельная nav-only геометрия на первом этапе
- это наиболее близкое к gameplay представление walkable/non-walkable world geometry
## 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
- `IChunkNavGeometryReader`
- `IWorldInterestReader`
Минимальная ответственность:
- `IChunkNavGeometryReader` умеет вернуть текущую nav-геометрию чанка и список уже загруженных чанков
- `IWorldInterestReader` умеет вернуть текущую primary interest point
### DTO / Contracts
- `ChunkNavGeometry`
Состав DTO:
- `Vector2Int Coord`
- `Transform Root`
- `BoxCollider GroundCollider`
- `MeshCollider MountainCollider`
- `int Version`
Примечание:
- DTO должен содержать только то, что реально нужно для NavMesh source collection
- private nested types `VoxelWorldGenerator` не должны утекать наружу
### 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
- `IChunkNavGeometryReader`
- `IWorldInterestReader`
### 3. Публиковать сообщения после world lifecycle changes
Нужно публиковать:
- `ChunkNavGeometryReadyMessage` после фактического применения collider mesh
- `ChunkNavGeometryRemovedMessage` перед уничтожением чанка
- `WorldInterestChangedMessage` при смене actor-level interest point
### 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<T>` и `ISubscriber<T>` должны приходить через DI
Почему:
- это сохраняет тестируемость
- не создает скрытых runtime-зависимостей
- лучше соответствует модульному подключению через `LifetimeScope`
## NavMesh Service Responsibilities
- подписаться на world lifecycle messages
- на старте получить snapshot уже загруженных чанков через `IChunkNavGeometryReader`
- построить initial set dirty regions
- поддерживать `NavMeshData` по регионам
- собирать `NavMeshBuildSource` из chunk colliders
- запускать throttled `UpdateNavMeshDataAsync`
- переоценивать build priority относительно current interest point
- удалять region data, когда она выходит из активного диапазона и становится пустой
## Region Runtime Data
- `Dictionary<Vector2Int, NavRegionRuntime> navRegions`
- `Queue<Vector2Int> dirtyNavRegions`
- `HashSet<Vector2Int> 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. Через `IChunkNavGeometryReader` получает список уже загруженных чанков.
3. Помечает соответствующие nav regions dirty.
4. Через `IWorldInterestReader` получает текущую точку интереса.
5. Запускает scheduler.
### Incremental Update
1. Приходит `ChunkNavGeometryReadyMessage`.
2. Сервис читает актуальную geometry через `IChunkNavGeometryReader`.
3. Помечает nav region dirty.
4. Если chunk расположен на границе region, дополнительно маркирует соседний region.
### Removal
1. Приходит `ChunkNavGeometryRemovedMessage`.
2. Сервис помечает соответствующий region dirty.
3. Если region опустел и вышел из активной зоны, удаляет `NavMeshDataInstance`.
### Interest Update
1. Приходит `WorldInterestChangedMessage`.
2. Сервис обновляет current interest point.
3. Scheduler пересчитывает порядок rebuild и warmup regions.
## Source Collection Rules
Для каждого затронутого region:
- собрать build sources только из чанков региона и соседнего margin
- использовать только известную geometry из reader interface
- не сканировать произвольные объекты сцены
Для каждого chunk geometry:
- добавить `NavMeshBuildSourceShape.Box` из `GroundCollider`
- добавить `NavMeshBuildSourceShape.Mesh` из `MountainCollider.sharedMesh`, если mesh не пустой
## 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.
### 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.