6227542d2d
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.
252 lines
18 KiB
Markdown
252 lines
18 KiB
Markdown
# MVP World, Authority And Runtime NavMesh
|
||
|
||
## Status
|
||
|
||
Этот документ считается каноническим для решений по детерминированному миру, authority model, модульным границам и runtime NavMesh, пока его явно не заменят более новым архитектурным решением.
|
||
|
||
## Purpose
|
||
|
||
Зафиксировать долгосрочные решения для MVP, чтобы downstream-задачи по FishNet, worldgen, DI, AI и persistence не уехали в разные стороны.
|
||
|
||
## Scope
|
||
|
||
- deterministic voxel world generation
|
||
- authority model для session gameplay
|
||
- модульные границы feature-подсистем
|
||
- 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. Runtime NavMesh реализуется как sidecar-модуль, а не как hardwired часть world generator
|
||
|
||
Решение:
|
||
- `VoxelWorld` остается владельцем world state и chunk lifecycle
|
||
- NavMesh реализуется отдельным подключаемым модулем в собственной assembly
|
||
- модуль подключается через DI и может быть отключен без переписывания world feature
|
||
|
||
Почему выбрано:
|
||
- это соответствует целевой модели feature-подсистем как подключаемых модулей
|
||
- позволяет держать `VoxelWorld` core меньше и стабильнее
|
||
- упрощает отключение NavMesh в сценах или режимах, где он не нужен
|
||
|
||
Почему не выбран partial-вариант внутри `VoxelWorldGenerator`:
|
||
- он быстрее в реализации, но цементирует NavMesh внутрь world feature
|
||
- делает отключение модуля искусственным
|
||
- увеличивает связанность и мешает дальнейшему DI-разделению
|
||
|
||
Последствия:
|
||
- world feature обязан публиковать стабильные контракты для sidecar-потребителей
|
||
- NavMesh-модуль не должен зависеть от private nested runtime types `VoxelWorldGenerator`
|
||
|
||
### 10. Для модульной интеграции используется комбинация MessagePipe и reader-интерфейсов
|
||
|
||
Решение:
|
||
- `MessagePipe` используется для событий world lifecycle и invalidation
|
||
- отдельные reader-интерфейсы используются для получения актуального snapshot state
|
||
- NavMesh service получает `IPublisher<T>` и `ISubscriber<T>` через DI, а не через global lookup
|
||
|
||
Почему выбрано:
|
||
- сообщения хорошо решают слабую связность и optional-subscription
|
||
- одних сообщений недостаточно, потому что модуль может стартовать позже и пропустить часть lifecycle events
|
||
- reader-интерфейсы позволяют восстановить текущее состояние без зависимости от конкретной реализации мира
|
||
|
||
Почему не выбран message-only подход:
|
||
- missed events ломают начальную инициализацию и late subscription
|
||
- пришлось бы тащить тяжелые mutable runtime objects прямо в сообщения
|
||
- модуль становился бы хрупким при reorder startup sequence
|
||
|
||
Почему не выбран direct-reference подход:
|
||
- прямые ссылки на `VoxelWorldGenerator` убивают модульность
|
||
- `VoxelWorldGenerator` пока содержит private nested types и внутренние детали, которые нельзя делать частью внешнего API
|
||
|
||
Последствия:
|
||
- нужны contracts для `IChunkNavGeometryReader` и `IWorldInterestReader`
|
||
- нужны message types для `ChunkNavGeometryReady`, `ChunkNavGeometryRemoved` и `WorldInterestChanged`
|
||
- `GlobalMessagePipe` не считается канонической точкой интеграции для feature-кода
|
||
|
||
## 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.
|
||
- Если contracts world feature окажутся слишком узкими или, наоборот, будут протекать внутренними типами генератора, sidecar-модуль быстро потеряет изоляцию.
|
||
|
||
### Medium
|
||
|
||
- Late join требует не только `seed/config`, но и корректного воспроизведения authoritative world deltas.
|
||
- Если region size выбрать слишком крупным, rebuild будет дорогим; если слишком мелким, возрастет число build operations и seam-risk на границах.
|
||
- Неаккуратное использование `GlobalMessagePipe` вместо DI-инъекции создаст скрытую runtime-зависимость и усложнит тестирование.
|
||
|
||
## Downstream Implications
|
||
|
||
- `TASK-0001`: этот документ закрывает часть канонических MVP-решений по world/authority/navmesh/module boundaries.
|
||
- `TASK-0002`: session handshake должен включать world seed, config/version и protocol compatibility checks.
|
||
- `TASK-0012`: enemy AI проектируется только как host-authoritative.
|
||
- `TASK-0023`: runtime NavMesh обязан быть local-build, throttled, sidecar-модулем и не должен иметь camera-driven assumptions.
|