Files
TheDeclineOfWarriors/docs/plans/TASK-0023-runtime-navmesh-implementation-plan.md
T
2026-04-08 02:39:17 +03:00

16 KiB
Raw Blame History

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<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> 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. Через 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.