diff --git a/docs/architecture/mvp-world-authority-navmesh.md b/docs/architecture/mvp-world-authority-navmesh.md new file mode 100644 index 00000000..9f7633c4 --- /dev/null +++ b/docs/architecture/mvp-world-authority-navmesh.md @@ -0,0 +1,219 @@ +# MVP World, Authority And Runtime NavMesh + +## Status + +Этот документ считается каноническим для решений по детерминированному миру, authority model и runtime NavMesh, пока его явно не заменят более новым архитектурным решением. + +## Purpose + +Зафиксировать долгосрочные решения для MVP, чтобы downstream-задачи по FishNet, worldgen, AI и persistence не уехали в разные стороны. + +## Scope + +- deterministic voxel world generation +- authority model для session gameplay +- runtime NavMesh в procedural world +- риски WebGL-host режима + +## Fixed Decisions + +### 1. Базовый мир генерируется детерминированно и локально на каждом peer + +Решение: +- базовая геометрия мира не стримится от хоста по сети +- каждый peer генерирует чанк локально из одинакового `seed`, одинакового `VoxelWorldConfig` и одинаковой версии world rules + +Почему выбрано: +- для WebGL и peer-host модели это минимизирует сетевой трафик +- убирает постоянную сетевую репликацию геометрии чанков +- снимает с хоста роль единственной точки генерации базового мира +- хорошо сочетается с уже существующим `VoxelWorldGenerator`, который строит чанк из deterministic inputs + +Почему не выбран host-generated world streaming: +- хост получал бы лишнюю CPU-нагрузку на генерацию и лишнюю сетевую нагрузку на раздачу чанков +- late join и догрузка дальних областей становились бы тяжелее по сети +- это хуже укладывается в бюджет WebGL-host + +Последствия: +- `seed`, world config и их версия становятся частью session handshake +- любое расхождение по config/version между peers недопустимо и должно считаться protocol drift + +### 2. Host остается authoritative для NPC, AI и другого gameplay state + +Решение: +- NPC симулируются на хосте +- pathfinding NPC, агро, боевые решения и каноническое положение NPC принадлежат хосту +- клиенты получают состояние NPC по сети и могут делать только визуальное сглаживание + +Почему выбрано: +- NPC влияют на бой, урон, столкновения и progression, значит их нельзя отдавать в authority случайному клиенту +- это радикально снижает риск читов и эксплуатационных багов +- упрощает late join, reconnect и дебаг сетевой симуляции + +Почему не выбран client-owned NPC: +- ownership у первого встретившего игрока нестабилен при совместной игре +- миграция owner во время боя ломает воспроизводимость path state, aggro state и hit timing +- возрастает риск desync и эксплойтов +- резко усложняется отладка и сопровождение + +Последствия: +- `client-authority` допустим только для ввода игрока и только при отдельной валидации на сервере +- для NPC authority migration в MVP не используется + +### 3. У чанков нет owner и нет chunk ownership migration + +Решение: +- чанк не закрепляется за конкретным игроком как за владельцем +- базовый чанк является общей детерминированной сущностью мира, а не network-owned объектом + +Почему выбрано: +- при deterministic world generation ownership чанка не дает полезного выигрыша +- chunk ownership добавляет coordination cost, миграцию ответственности и новые классы сетевых гонок без пользы для MVP +- это плохо совместимо с late join и с будущими world deltas + +Почему не выбран owner-per-chunk: +- первый увидевший чанк игрок не является надежным authority source +- потребуется сложная логика передачи владения при сближении игроков и при disconnect +- любые расхождения по владельцу чанка приводят к hidden state drift + +Последствия: +- изменения чанка в будущем пойдут не через owner migration, а через authoritative world deltas от хоста + +### 4. Runtime NavMesh строится локально на каждом peer по фактической локальной геометрии чанка + +Решение: +- NavMesh не реплицируется по сети как data blob +- каждый peer строит NavMesh у себя локально из актуальной локальной геометрии мира +- NavMesh всегда считается производным кэшем от world state, а не каноническим состоянием сессии + +Почему выбрано: +- NavMesh data тяжелая и плохо подходит для сетевой репликации в peer-host модели +- при deterministic base world и одинаковых world deltas peers могут независимо прийти к одинаковой walkable topology +- это сохраняет сеть для gameplay state, а не для производных навигационных артефактов + +Почему не выбран network-streamed NavMesh: +- лишний трафик и высокая сложность синхронизации +- плохая масштабируемость для догрузки чанков и late join +- NavMesh все равно пришлось бы пересобирать при локальных изменениях геометрии + +Последствия: +- каноничность gameplay не должна зависеть от клиентского NavMesh +- client NavMesh используется для локальных потребностей, но authoritative decisions по NPC остаются у хоста + +### 5. Будущие изменения проходимости мира передаются как authoritative world deltas + +Решение: +- базовый мир идет из deterministic generation +- любые будущие баррикады, спеллы, разрушаемость, carve и другие изменения мира передаются как authoritative deltas от хоста +- после применения delta каждый peer локально перестраивает затронутые nav regions + +Почему выбрано: +- это отделяет immutable base generation от mutable session state +- обеспечивает late join: новому игроку можно отдать base seed/config и журнал world deltas +- не требует вводить ownership migration для чанков + +Почему не выбран fully local mutable world: +- local-first изменения мира не могут быть каноническими в кооперативной сетевой игре +- конфликтуют с античитом, late join и persistence + +Последствия: +- NavMesh pipeline обязан уметь маркировать локальные nav regions как dirty после world delta + +### 6. NavMesh pipeline должен работать в single-thread budget; многопоточность в WebGL считается только опциональным ускорением + +Решение: +- архитектура runtime NavMesh не должна зависеть от наличия потоков +- базовый режим должен укладываться в бюджет кадра на одном потоке +- если deployment позже подтвердит поддержку `SharedArrayBuffer` и `COOP/COEP`, можно добавить threaded optimization, но не делать ее обязательной + +Почему выбрано: +- WebGL-host остается одной из целевых платформ +- WebGL multithreading требует специальных заголовков и эксплуатационной дисциплины на стороне хостинга +- завязка critical gameplay pipeline на эту инфраструктуру слишком рискованна для MVP + +Почему не выбран threaded-only pipeline: +- он может работать в editor/desktop и развалиться в реальном WebGL deployment +- создаст ложное ощущение приемлемого бюджета, которого не будет на production-hosting + +Последствия: +- rebuild должен быть incremental, throttled и bounded +- полносценовый bake вокруг камеры не подходит как каноническая модель + +### 7. Первая итерация NavMesh покрывает область вокруг player actor, но долгосрочный контракт расширяется до players + active NPC + +Решение: +- для первой проверки гипотезы build priority привязывается к player actor +- целевой контракт для multiplayer host: nav coverage должна учитывать игроков и активных NPC + +Почему выбрано: +- это минимальный объем для MVP-проверки без ранней переплаты за сложную interest model +- при этом заранее фиксируется, что player-only coverage не является конечной архитектурой + +Почему не выбран camera-driven center: +- камера не является каноническим gameplay actor +- в multiplayer и especially on host камера может не совпадать с зоной активной симуляции +- привязка к `Camera.main` ломает переносимость решения из test scene в сетевую сессию + +Последствия: +- в коде нельзя оставлять `Camera.main` как канонический источник world/nav interest +- target должен представлять actor-level interest, а не presentation-level camera + +### 8. Для MVP поддерживается один тип NavMesh agent + +Решение: +- сейчас поддерживается только один `agentTypeID` + +Почему выбрано: +- проект на стадии hypothesis/MVP +- это уменьшает стоимость runtime bake и настройки AI Navigation +- не раздувает матрицу тестирования до появления реальной необходимости + +Почему не выбран multi-agent bake сразу: +- рост CPU и memory costs +- усложнение отладки при почти нулевой текущей пользе + +Последствия: +- при появлении разных классов существ нужно отдельно пересмотреть agent taxonomy + +### 9. В этой фазе решение остается scene-local и не привязывается к VContainer или Addressables + +Решение: +- runtime NavMesh реализуется как часть текущего scene-local world runtime +- VContainer и Addressables в этой задаче не вводятся + +Почему выбрано: +- в проекте пока нет production-ready composition root поверх gameplay world +- принудительное добавление DI boundary сейчас даст больше шума, чем пользы +- Addressables не подключены и не требуются для гипотезы NavMesh generation + +Почему не выбран ранний DI/bootstrap refactor: +- это отвлекает от основной гипотезы по производительности и корректности NavMesh +- возникает преждевременная архитектурная сложность при еще нестабильных правилах мира + +Последствия: +- код должен оставаться достаточно изолированным, чтобы позже его можно было вынести в runtime service + +## Long-Term Risks + +### Critical + +- WebGL-host может не выдержать одновременно world streaming, runtime NavMesh rebuild и server-authoritative NPC AI. +- Любой drift по `seed`, `VoxelWorldConfig` или world rules между peers приведет к расхождению геометрии и локального NavMesh. + +### High + +- Цель в `50` активных NPC может упереться не в один subsystem, а в суммарный CPU budget хоста. +- Будущие изменения геометрии потребуют точной invalidation strategy по nav regions; без нее rebuild cost быстро выйдет из-под контроля. +- Если client movement в будущем начнет опираться на локальный NavMesh как на authority source, появятся расхождения с host simulation. + +### Medium + +- Late join требует не только `seed/config`, но и корректного воспроизведения authoritative world deltas. +- Если region size выбрать слишком крупным, rebuild будет дорогим; если слишком мелким, возрастет число build operations и seam-risk на границах. + +## Downstream Implications + +- `TASK-0001`: этот документ закрывает часть канонических MVP-решений по world/authority/navmesh. +- `TASK-0002`: session handshake должен включать world seed, config/version и protocol compatibility checks. +- `TASK-0012`: enemy AI проектируется только как host-authoritative. +- `TASK-0023`: runtime NavMesh обязан быть local-build, throttled и без camera-driven assumptions. diff --git a/docs/plans/TASK-0023-runtime-navmesh-implementation-plan.md b/docs/plans/TASK-0023-runtime-navmesh-implementation-plan.md new file mode 100644 index 00000000..cef4ed44 --- /dev/null +++ b/docs/plans/TASK-0023-runtime-navmesh-implementation-plan.md @@ -0,0 +1,229 @@ +# TASK-0023 Runtime NavMesh Implementation Plan + +## Goal + +Реализовать runtime NavMesh для procedural voxel world без фризов и без camera-driven assumptions, с архитектурой, совместимой с будущей peer-host multiplayer моделью. + +## Inputs And Assumptions + +- текущая test scene: `Assets/Features/VoxelWorld/Scenes/VoxelWorldTestScene.unity` +- основной runtime: `Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.cs` +- текущий world config asset: + - `chunkSize = 16` + - `generationRadius = 3` + - `maxMountainHeight = 6` + - `renderRegionSizeInChunks = 4` + - `maxAsyncChunkJobs = 2` + - `maxChunkBuildsPerFrame = 1` + - `maxChunkMeshBuildsPerFrame = 1` + - `maxColliderAppliesPerFrame = 1` +- первая итерация учитывает область вокруг player actor +- долгосрочный контракт остается `players + active NPC` +- один тип агента +- динамические изменения мира пока не реализуются, но точки расширения под них должны быть предусмотрены + +## Chosen Technical Direction + +### 1. Не использовать `NavMeshSurfaceVolumeUpdater` как основу решения + +Причина: +- пример из samples двигает один build volume за tracked agent и не подходит как production-модель для chunk streaming +- он делает слишком coarse-grained rebuild и оставляет мало контроля над budget, dirty queue и multi-region state + +### 2. Использовать ручной runtime pipeline через `NavMeshBuilder.UpdateNavMeshDataAsync` + +Причина: +- дает прямой контроль над build sources, bounds, lifecycle `NavMeshData` и количеством одновременных rebuild +- позволяет отказаться от scene-wide source collection и собирать только известные chunk sources +- лучше подходит для throttling под WebGL-host + +### 3. Строить NavMesh не per-chunk, а по небольшим nav regions + +Выбор: +- отдельный `NavMeshData` на nav region +- стартовый размер region рекомендуется сделать `2x2` чанка, configurable отдельно от render regions + +Почему выбран region-based подход: +- per-chunk rebuild создает слишком много мелких операций и лишние seam-риски на границах +- один большой sliding volume вокруг interest target слишком дорог для WebGL-host +- небольшой region дает контролируемый компромисс между стоимостью rebuild и связностью навигации + +### 4. Источник build sources брать из runtime collider-геометрии чанков + +Выбор: +- `GroundCollider` каждого чанка дает box source +- `MountainCollider.sharedMesh` дает mesh source + +Почему так: +- не нужно сканировать всю сцену +- не нужно строить отдельную nav-only геометрию на первом этапе +- collider topology уже является ближайшим к gameplay физическим представлением поверхности + +### 5. Rebuild делать через dirty queue и budgeted scheduler + +Выбор: +- region помечается dirty при `ApplyColliderMesh` и при unload чанка +- scheduler сортирует dirty regions по расстоянию до interest actor +- одновременно идет максимум один nav rebuild +- если region снова стал dirty во время build, версия region увеличивается и после завершения запускается новый rebuild только для актуальной версии + +Почему так: +- это bounded и предсказуемо для WebGL-host +- исключает лавинообразные rebuild при быстром перемещении игрока + +## Proposed Runtime Structure + +### File Placement + +- расширить `VoxelWorldGenerator` новыми partial-файлами, а не вводить отдельный service layer на этой стадии +- рекомендуемые файлы: + - `Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.NavMesh.cs` + - `Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.NavMesh.Types.cs` + +Причина: +- nav lifecycle напрямую зависит от chunk lifecycle, которым уже владеет `VoxelWorldGenerator` +- это минимальное изменение без раннего DI/refactor + +### New Runtime Data + +- `NavRegionRuntime` + - `NavMeshData NavMeshData` + - `NavMeshDataInstance Instance` + - `AsyncOperation ActiveBuild` + - `int Version` + - `bool IsDirty` + - `bool BuildRequestedWhileRunning` + - `Bounds BuildBounds` + - `List` reusable sources buffer +- `Queue dirtyNavRegions` +- `HashSet queuedNavRegions` +- `Dictionary navRegions` + +### New Config Settings + +Добавить в `VoxelWorldConfig` отдельную секцию `NavMesh`: +- `int navRegionSizeInChunks = 2` +- `int maxNavMeshBuildsPerFrame = 1` +- `int maxConcurrentNavMeshBuilds = 1` +- `float navBoundsVerticalPadding` +- `float navBoundsHorizontalPadding` +- `int navWarmupRadiusInRegions` + +Примечание: +- `maxConcurrentNavMeshBuilds` для первой итерации должен остаться `1` +- horizontal padding нужен для корректной стыковки границ region data + +## Integration With World Lifecycle + +### Chunk load / update flow + +1. `GenerateChunkData` завершает данные чанка. +2. `RenderChunk` собирает render snapshot и collider mesh. +3. После фактического применения collider mesh chunk помечает свой nav region dirty. +4. Если чанк лежит у границы nav region, дополнительно dirty-mark соседний region, который делит с ним границу. +5. Scheduler позже запускает rebuild региона по budget. + +### Chunk unload flow + +1. Перед `runtime.Dispose()` определить nav region чанка. +2. Пометить соответствующий region dirty. +3. Если region стал пустым и вышел из active nav range, удалить его `NavMeshDataInstance`. + +### Interest target flow + +1. Убрать каноническую зависимость от `Camera.main` как источника стриминга/nav interest. +2. Ввести actor-level target semantics. +3. Для сохранения сцены использовать rename с `FormerlySerializedAs`, если будет меняться имя поля. +4. Для первой итерации target задается явно со сцены или от будущего player actor bootstrap. + +## Region Build Flow + +1. Определить `regionCoord` по координате чанка. +2. Вычислить `Bounds` региона с padding по XZ и по высоте. +3. Собрать build sources только из чанков, попадающих в region и в соседний margin вокруг него. +4. Для каждого активного чанка добавить: + - `NavMeshBuildSourceShape.Box` из `GroundCollider` + - `NavMeshBuildSourceShape.Mesh` из `MountainCollider.sharedMesh`, если mesh не пустой +5. Запустить `NavMeshBuilder.UpdateNavMeshDataAsync` для region-local `NavMeshData`. +6. При завершении проверить актуальность версии и либо оставить data, либо сразу перезапустить rebuild актуальной версии. + +## Region Granularity And Boundary Rules + +### Start choice + +- `navRegionSizeInChunks = 2` + +Почему не `1`: +- слишком много мелких `NavMeshData` +- больше seam pressure на стыках +- выше scheduler overhead + +Почему не `4`: +- rebuild слишком дорогой для частого runtime update на WebGL-host +- это уже заметный кусок от всего active world при `generationRadius = 3` + +### Boundary handling + +- build bounds должны быть больше чистого region rectangle +- source collection должна захватывать соседние чанки на один region-margin +- region dirty-mark должен учитывать chunk changes на границах + +Причина: +- без overlap на границах легко получить cracks и непредсказуемую связность между соседними `NavMeshData` + +## Multiplayer And Authority Contract For This Task + +- базовый voxel world генерируется локально у каждого peer из одинакового deterministic input +- NavMesh строится локально у каждого peer и не реплицируется по сети +- authoritative gameplay использует host-side NPC simulation +- текущая итерация NavMesh coverage вокруг player actor считается временным MVP simplification +- при переходе к реальной multiplayer-сцене host должен строить priority coverage вокруг `players + active NPC` +- будущие world changes должны приходить как authoritative deltas и маркировать nav regions dirty локально на каждом peer + +## Performance Rules + +- не делать full-scene bake +- не пересобирать NavMesh синхронно через `BuildNavMesh()` на gameplay path +- не сканировать произвольные scene objects через generic collection APIs, если можно собрать sources из известных chunk runtimes +- держать максимум один активный build +- переиспользовать buffers, где это возможно +- rebuild запускать только после фактического применения collider mesh +- unload и load чанков должны только маркировать region dirty, а не запускать немедленный build вне scheduler + +## Verification Plan + +### Manual verification + +1. Запустить `VoxelWorldTestScene`. +2. Использовать debug `NavMeshAgent` из AI Navigation samples. +3. Проверить, что агент строит путь по поверхности уже загруженных чанков. +4. Быстро перемещать actor target по миру и отслеживать отсутствие заметных фризов. +5. Проверить unload чанков: после ухода области старый NavMesh не должен оставлять висячие walkable islands в уже удаленных регионах. + +### Debug instrumentation + +- gizmos для region bounds и состояния region build +- лог счетчиков: + - active nav regions + - dirty nav regions + - builds started/completed/cancelled as stale + +## Explicit Non-Goals For This Iteration + +- NavMeshObstacle carving +- multi-agent bake +- DI integration через VContainer +- Addressables integration +- ownership migration для чанков или NPC +- финальная multiplayer interest model вокруг всех actors + +## Execution Order + +1. Добавить nav settings в `VoxelWorldConfig` и resolved settings. +2. Добавить runtime структуры nav regions и dirty scheduler в `VoxelWorldGenerator`. +3. Привязать dirty-marking к chunk collider apply и unload. +4. Реализовать source collection из chunk colliders. +5. Реализовать region-local `NavMeshData` lifecycle и async rebuild. +6. Убрать camera-driven fallback из world/nav interest path. +7. Добавить debug visualization и ручную проверку через sample agent. +8. Задокументировать фактические perf observations после первой проверки гипотезы. diff --git a/docs/tasks/items/TASK-0023.md b/docs/tasks/items/TASK-0023.md index a0bc132c..52c0b3ef 100644 --- a/docs/tasks/items/TASK-0023.md +++ b/docs/tasks/items/TASK-0023.md @@ -6,12 +6,14 @@ priority: Highest area: ai owner: abysscion created: 2026-03-31 -updated: 2026-04-07 +updated: 2026-04-08 execution_time: 2d depends_on: - TASK-0003 canonical_docs: - docs/tasks/Index.md + - docs/architecture/mvp-world-authority-navmesh.md + - docs/plans/TASK-0023-runtime-navmesh-implementation-plan.md related_files: - Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.cs --- @@ -94,4 +96,4 @@ AI врагов (`TASK-0012`) опирается на NavMesh. Воксельн ## Handoff Notes -Если в проекте нет пакета NavMeshComponents, возможно придется добавить его или реализовать минимальный runtime builder. \ No newline at end of file +Если в проекте нет пакета NavMeshComponents, возможно придется добавить его или реализовать минимальный runtime builder.