refine runtime navmesh contracts and plan

This commit is contained in:
Alexander Borisov
2026-04-08 02:39:17 +03:00
parent 6227542d2d
commit 4e1cf273fa
3 changed files with 59 additions and 31 deletions
@@ -100,6 +100,7 @@
Последствия: Последствия:
- каноничность gameplay не должна зависеть от клиентского NavMesh - каноничность gameplay не должна зависеть от клиентского NavMesh
- client NavMesh используется для локальных потребностей, но authoritative decisions по NPC остаются у хоста - client NavMesh используется для локальных потребностей, но authoritative decisions по NPC остаются у хоста
- при одинаковом world state peers должны приходить к функционально эквивалентной walkable topology, но NavMesh не считается protocol-grade bit-identical артефактом, от которого зависит correctness multiplayer state
### 5. Будущие изменения проходимости мира передаются как authoritative world deltas ### 5. Будущие изменения проходимости мира передаются как authoritative world deltas
@@ -140,11 +141,12 @@
- rebuild должен быть incremental, throttled и bounded - rebuild должен быть incremental, throttled и bounded
- полносценовый bake вокруг камеры не подходит как каноническая модель - полносценовый bake вокруг камеры не подходит как каноническая модель
### 7. Первая итерация NavMesh покрывает область вокруг player actor, но долгосрочный контракт расширяется до players + active NPC ### 7. Первая итерация NavMesh может приоритизировать одного player actor, но внешний interest-контракт сразу задается как actor set
Решение: Решение:
- для первой проверки гипотезы build priority привязывается к player actor - для первой проверки гипотезы scheduler может стартовать от одного player actor
- целевой контракт для multiplayer host: nav coverage должна учитывать игроков и активных NPC - внешний reader/read-model контракт не должен жестко фиксировать single-point модель
- целевой контракт для multiplayer host: nav coverage должна учитывать игроков и активных NPC как actor-level interest set
Почему выбрано: Почему выбрано:
- это минимальный объем для MVP-проверки без ранней переплаты за сложную interest model - это минимальный объем для MVP-проверки без ранней переплаты за сложную interest model
@@ -158,6 +160,7 @@
Последствия: Последствия:
- в коде нельзя оставлять `Camera.main` как канонический источник world/nav interest - в коде нельзя оставлять `Camera.main` как канонический источник world/nav interest
- target должен представлять actor-level interest, а не presentation-level camera - target должен представлять actor-level interest, а не presentation-level camera
- reader-контракт для интереса должен уметь вернуть один или несколько actor-level interest points; даже если первая scene wiring временно дает только один player actor, это не должно цементироваться во внешний API
### 8. Для MVP поддерживается один тип NavMesh agent ### 8. Для MVP поддерживается один тип NavMesh agent
@@ -219,9 +222,10 @@
- `VoxelWorldGenerator` пока содержит private nested types и внутренние детали, которые нельзя делать частью внешнего API - `VoxelWorldGenerator` пока содержит private nested types и внутренние детали, которые нельзя делать частью внешнего API
Последствия: Последствия:
- нужны contracts для `IChunkNavGeometryReader` и `IWorldInterestReader` - нужны query contracts для чтения актуальных nav build sources чанков и actor-level interest set, например `IChunkNavSourceReader` и `IWorldInterestReader`
- нужны message types для `ChunkNavGeometryReady`, `ChunkNavGeometryRemoved` и `WorldInterestChanged` - нужны message types для `ChunkNavGeometryReady`, `ChunkNavGeometryRemoved` и `WorldInterestChanged`
- `GlobalMessagePipe` не считается канонической точкой интеграции для feature-кода - `GlobalMessagePipe` не считается канонической точкой интеграции для feature-кода
- world-to-navmesh contracts не должны делать `Transform`, `MeshCollider` и `BoxCollider` каноническим внешним состоянием там, где достаточно узких source descriptors для build/invalidation
## Long-Term Risks ## Long-Term Risks
@@ -9,8 +9,8 @@
- текущая test scene: `Assets/Features/VoxelWorld/Scenes/VoxelWorldTestScene.unity` - текущая test scene: `Assets/Features/VoxelWorld/Scenes/VoxelWorldTestScene.unity`
- основной runtime генерации мира: `Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.cs` - основной runtime генерации мира: `Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.cs`
- в проекте уже есть `ApplicationLifetimeScope` с `MessagePipe` registration - в проекте уже есть `ApplicationLifetimeScope` с `MessagePipe` registration
- первая итерация NavMesh coverage строится вокруг player actor - первая итерация scheduler может начинать приоритизацию от одного player actor
- долгосрочный контракт остается `players + active NPC` - внешний interest-контракт сразу остается actor-level interest set: `players + active NPC`
- один тип агента - один тип агента
- динамические изменения мира пока не реализуются, но контракты под них должны быть предусмотрены - динамические изменения мира пока не реализуются, но контракты под них должны быть предусмотрены
- WebGL-host остается целевой платформой, поэтому базовый pipeline не зависит от потоков - WebGL-host остается целевой платформой, поэтому базовый pipeline не зависит от потоков
@@ -55,16 +55,23 @@
- один большой moving volume слишком дорог и плохо контролируется по бюджету - один большой moving volume слишком дорог и плохо контролируется по бюджету
- region-based rebuild дает лучший компромисс между стоимостью и связностью - region-based rebuild дает лучший компромисс между стоимостью и связностью
### 5. Источники build sources берутся из chunk colliders, публикуемых world feature ### 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
Выбор: Выбор:
- `GroundCollider` дает box source - world feature отдает snapshot nav sources чанка через стабильный reader contract
- `MountainCollider.sharedMesh` дает mesh source - текущая реализация `VoxelWorld` может внутри строить эти sources из `GroundCollider` и `MountainCollider`, но эта деталь не протекает в API sidecar-модуля
Почему: Почему:
- не нужен scene-wide scanning - не нужен scene-wide scanning
- не требуется отдельная nav-only геометрия на первом этапе - не требуется отдельная nav-only геометрия на первом этапе
- это наиболее близкое к gameplay представление walkable/non-walkable world geometry - sidecar-модуль не цементируется на конкретных `Collider`-компонентах и scene hierarchy мира
## Target Module Boundaries ## Target Module Boundaries
@@ -83,27 +90,41 @@
### Reader Interfaces ### Reader Interfaces
- `IChunkNavGeometryReader` - `IChunkNavSourceReader`
- `IWorldInterestReader` - `IWorldInterestReader`
Минимальная ответственность: Минимальная ответственность:
- `IChunkNavGeometryReader` умеет вернуть текущую nav-геометрию чанка и список уже загруженных чанков - `IChunkNavSourceReader` умеет вернуть текущие nav build sources чанка и список уже загруженных чанков
- `IWorldInterestReader` умеет вернуть текущую primary interest point - `IWorldInterestReader` умеет вернуть текущий actor-level interest set
### DTO / Contracts ### DTO / Contracts
- `ChunkNavGeometry` - `ChunkNavSourceSnapshot`
- `ChunkNavBuildSourceDescriptor`
- `WorldInterestPoint`
Состав DTO: Состав DTO:
- `ChunkNavSourceSnapshot`
- `Vector2Int Coord` - `Vector2Int Coord`
- `Transform Root`
- `BoxCollider GroundCollider`
- `MeshCollider MountainCollider`
- `int Version` - `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 - DTO должен содержать только то, что реально нужно для NavMesh source collection и build prioritization
- private nested types `VoxelWorldGenerator` не должны утекать наружу - private nested types `VoxelWorldGenerator` не должны утекать наружу
- `Transform`, `MeshCollider` и `BoxCollider` не должны становиться каноническим внешним состоянием NavMesh integration, если достаточно source descriptors
### Message Types ### Message Types
@@ -132,7 +153,7 @@
### 2. Реализовать reader interfaces ### 2. Реализовать reader interfaces
- `IChunkNavGeometryReader` - `IChunkNavSourceReader`
- `IWorldInterestReader` - `IWorldInterestReader`
### 3. Публиковать сообщения после world lifecycle changes ### 3. Публиковать сообщения после world lifecycle changes
@@ -140,7 +161,7 @@
Нужно публиковать: Нужно публиковать:
- `ChunkNavGeometryReadyMessage` после фактического применения collider mesh - `ChunkNavGeometryReadyMessage` после фактического применения collider mesh
- `ChunkNavGeometryRemovedMessage` перед уничтожением чанка - `ChunkNavGeometryRemovedMessage` перед уничтожением чанка
- `WorldInterestChangedMessage` при смене actor-level interest point - `WorldInterestChangedMessage` при изменении actor-level interest set
### 4. Убрать каноническую зависимость от `Camera.main` ### 4. Убрать каноническую зависимость от `Camera.main`
@@ -183,12 +204,12 @@
## NavMesh Service Responsibilities ## NavMesh Service Responsibilities
- подписаться на world lifecycle messages - подписаться на world lifecycle messages
- на старте получить snapshot уже загруженных чанков через `IChunkNavGeometryReader` - на старте получить snapshot уже загруженных чанков через `IChunkNavSourceReader`
- построить initial set dirty regions - построить initial set dirty regions
- поддерживать `NavMeshData` по регионам - поддерживать `NavMeshData` по регионам
- собирать `NavMeshBuildSource` из chunk colliders - собирать `NavMeshBuildSource` из source descriptors, а не через прямой доступ к world colliders
- запускать throttled `UpdateNavMeshDataAsync` - запускать throttled `UpdateNavMeshDataAsync`
- переоценивать build priority относительно current interest point - переоценивать build priority относительно current interest set
- удалять region data, когда она выходит из активного диапазона и становится пустой - удалять region data, когда она выходит из активного диапазона и становится пустой
## Region Runtime Data ## Region Runtime Data
@@ -223,15 +244,15 @@
### Initial Sync ### Initial Sync
1. Сервис стартует. 1. Сервис стартует.
2. Через `IChunkNavGeometryReader` получает список уже загруженных чанков. 2. Через `IChunkNavSourceReader` получает список уже загруженных чанков.
3. Помечает соответствующие nav regions dirty. 3. Помечает соответствующие nav regions dirty.
4. Через `IWorldInterestReader` получает текущую точку интереса. 4. Через `IWorldInterestReader` получает текущий interest set.
5. Запускает scheduler. 5. Запускает scheduler.
### Incremental Update ### Incremental Update
1. Приходит `ChunkNavGeometryReadyMessage`. 1. Приходит `ChunkNavGeometryReadyMessage`.
2. Сервис читает актуальную geometry через `IChunkNavGeometryReader`. 2. Сервис читает актуальные sources через `IChunkNavSourceReader`.
3. Помечает nav region dirty. 3. Помечает nav region dirty.
4. Если chunk расположен на границе region, дополнительно маркирует соседний region. 4. Если chunk расположен на границе region, дополнительно маркирует соседний region.
@@ -244,19 +265,19 @@
### Interest Update ### Interest Update
1. Приходит `WorldInterestChangedMessage`. 1. Приходит `WorldInterestChangedMessage`.
2. Сервис обновляет current interest point. 2. Сервис обновляет current interest set.
3. Scheduler пересчитывает порядок rebuild и warmup regions. 3. Scheduler пересчитывает порядок rebuild и warmup regions.
## Source Collection Rules ## Source Collection Rules
Для каждого затронутого region: Для каждого затронутого region:
- собрать build sources только из чанков региона и соседнего margin - собрать build sources только из чанков региона и соседнего margin
- использовать только известную geometry из reader interface - использовать только известные source snapshots из reader interface
- не сканировать произвольные объекты сцены - не сканировать произвольные объекты сцены
Для каждого chunk geometry: Для каждого chunk snapshot:
- добавить `NavMeshBuildSourceShape.Box` из `GroundCollider` - добавить sources из `ChunkNavBuildSourceDescriptor`
- добавить `NavMeshBuildSourceShape.Mesh` из `MountainCollider.sharedMesh`, если mesh не пустой - если текущий `VoxelWorld` строит эти descriptors из `GroundCollider` и `MountainCollider`, это остается его внутренней деталью и не становится contract-level зависимостью NavMesh-модуля
## Performance Rules ## Performance Rules
@@ -283,6 +304,7 @@
1. Проверить старт сервиса после world generator: missed events не должны ломать initial sync. 1. Проверить старт сервиса после world generator: missed events не должны ломать initial sync.
2. Проверить, что модуль работает только через DI-injected `MessagePipe` и reader interfaces. 2. Проверить, что модуль работает только через DI-injected `MessagePipe` и reader interfaces.
3. Проверить, что отключение регистрации `VoxelWorldNavMesh` не ломает world feature. 3. Проверить, что отключение регистрации `VoxelWorldNavMesh` не ломает world feature.
4. Проверить, что внешний interest contract допускает один или несколько interest points, даже если первая scene wiring пока подает только одного player actor.
### Performance ### Performance
+2
View File
@@ -96,4 +96,6 @@ AI врагов (`TASK-0012`) опирается на NavMesh. Воксельн
## Handoff Notes ## Handoff Notes
Реализация задачи должна идти с учетом принятых решений и уже проведенного ресерча в `docs/architecture/mvp-world-authority-navmesh.md`, `docs/plans/TASK-0023-runtime-navmesh-implementation-plan.md` и текущего runtime-контекста `Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.cs`. Если формулировки task-card расходятся с каноническими решениями и зафиксированным ресерчем, приоритет у этих файлов.
Если в проекте нет пакета NavMeshComponents, возможно придется добавить его или реализовать минимальный runtime builder. Если в проекте нет пакета NavMeshComponents, возможно придется добавить его или реализовать минимальный runtime builder.