diff --git a/docs/tasks/Index.md b/docs/tasks/Index.md index d96a49e7..e4654a8a 100644 --- a/docs/tasks/Index.md +++ b/docs/tasks/Index.md @@ -63,4 +63,5 @@ | TASK-0021 | ToDo | High | architecture | unassigned | 2d | docs/tasks/items/TASK-0021.md | Привести проект в порядок: разнести код по asmdef, навести структуру Editor/Runtime и добавить базовые автотесты. | | TASK-0022 | ToDo | Highest | worldgen | unassigned | 1d | docs/tasks/items/TASK-0022.md | Интегрировать спавн врагов в VoxelWorldGenerator: спавнить по загрузке чанка и учитывать kill-state. | | TASK-0023 | InProgress | Highest | ai | abysscion | 2d | `docs/tasks/items/TASK-0023.md` | Реализовать runtime NavMesh bake для voxel-чанка и интегрировать обновление навигации при загрузке/изменении чанков. | -| TASK-0024 | ToDo | Highest | art | unassigned | 2d | docs/tasks/items/TASK-0024.md | Заменить Minecraft-placeholder арт на легальные ассеты для продакшена и зафиксировать источник/лицензии. | \ No newline at end of file +| TASK-0024 | ToDo | Highest | art | unassigned | 2d | docs/tasks/items/TASK-0024.md | Заменить Minecraft-placeholder арт на легальные ассеты для продакшена и зафиксировать источник/лицензии. | +| TASK-0025 | ToDo | Highest | gameplay-core | unassigned | 3d | docs/tasks/items/TASK-0025.md | Перевести player movement на host-authoritative NavMesh pipeline с server-side path planning и shared debug path preview для всех клиентов. | diff --git a/docs/tasks/items/TASK-0025.md b/docs/tasks/items/TASK-0025.md new file mode 100644 index 00000000..4e7d9326 --- /dev/null +++ b/docs/tasks/items/TASK-0025.md @@ -0,0 +1,500 @@ +--- +id: TASK-0025 +title: Host-authoritative player navigation с shared debug path preview +summary: Перевести player movement на host-authoritative NavMesh pipeline с server-side path planning, authoritative path following и общим debug path preview для всех клиентов. +priority: Highest +area: gameplay-core +owner: unassigned +created: 2026-04-08 +updated: 2026-04-08 +execution_time: 3d +depends_on: + - TASK-0002 + - TASK-0023 +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/Scripts/Players/PlayerMoving.cs + - Assets/Features/VoxelWorld/Prefabs/TestPlayer.prefab + - Assets/Features/VoxelWorld/Scenes/VoxelWorldTestScene.unity + - Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.cs + - Assets/Features/VoxelWorld/Contracts/NavMeshWorldContracts.cs + - Assets/Features/VoxelWorldNavMesh/Runtime/VoxelWorldNavMeshService.cs +--- + +# TASK-0025 - Host-authoritative player navigation с shared debug path preview + +## Status + +Статус задачи ведется в `docs/tasks/Index.md` и является каноническим там. + +## Why + +Если игрок тоже должен двигаться по NavMesh, текущий local/client-authoritative movement pipeline становится архитектурно слабым для multiplayer: + +- клиент не должен быть источником канонического movement outcome; +- локальный `NavMeshAgent` не должен быть authoritative mover для player actor; +- path planning и path following должны принадлежать хосту; +- shared debug path preview для движущихся игроков должен отображать именно authoritative path, который принят хостом, а не локальную клиентскую догадку. + +Без этого возрастает риск: + +- desync между client local movement и host state; +- race-condition между player spawn и nav coverage readiness; +- неотлаживаемых расхождений path preview между peers; +- ошибок вроде `Failed to create agent because it is not close enough to the NavMesh`, если movement pipeline завязан на lifecycle локального `NavMeshAgent`. + +## Expected Outcome + +- Игрок отправляет только команду перемещения, а не итог движения. +- Хост валидирует destination на своем NavMesh, строит authoritative path и двигает actor канонически. +- Клиенты получают authoritative movement state и сглаживают presentation. +- Для каждого движущегося игрока существует authoritative debug path preview, который видят все клиенты. +- Player spawn и first move command не зависят от hidden scene hacks и не требуют client-authoritative `NavMeshAgent`. + +## Current Context + +В проекте уже зафиксированы и частично реализованы базовые решения по миру и runtime NavMesh: + +- `TASK-0023` ввел runtime NavMesh как sidecar-модуль поверх voxel world. +- `VoxelWorldGenerator` уже публикует nav source snapshots и world interest. +- `VoxelWorldNavMeshService` строит NavMesh локально на каждом peer по region-based схеме. + +При этом current player flow пока не соответствует целевой модели: + +- `Assets/Scripts/Players/PlayerMoving.cs` ориентирован на локальное movement execution; +- `Assets/Features/VoxelWorld/Prefabs/TestPlayer.prefab` все еще держит player movement в client-oriented конфигурации; +- spawn/nav readiness для player-on-navmesh еще не оформлены как отдельный контракт; +- shared debug path preview для player movement отсутствует. + +## Source Of Truth + +- `docs/architecture/mvp-world-authority-navmesh.md` +- `docs/plans/TASK-0023-runtime-navmesh-implementation-plan.md` +- фактический player/network/world flow в текущем коде проекта + +## Read First + +- `Assets/Scripts/Players/PlayerMoving.cs` +- `Assets/Scripts/Players/CameraFollow.cs` +- `Assets/Features/VoxelWorld/Prefabs/TestPlayer.prefab` +- `Assets/Features/VoxelWorld/Scenes/VoxelWorldTestScene.unity` +- `Assets/Features/VoxelWorld/Runtime/VoxelWorldGenerator.cs` +- `Assets/Features/VoxelWorld/Contracts/NavMeshWorldContracts.cs` +- `Assets/Features/VoxelWorldNavMesh/Runtime/VoxelWorldNavMeshService.cs` +- `Assets/Scripts/VoxelWorld/VoxelWorldNavMeshLifetimeScope.cs` + +## Fixed Decisions + +### 1. Player movement outcome is host-authoritative + +Клиент отправляет только move intent. Канонические: + +- target acceptance/rejection; +- path corners; +- progress по path; +- итоговая позиция; +- movement completion/cancel. + +Все это вычисляется и хранится на хосте. + +### 2. Player uses NavMesh for movement, but client NavMesh is not authoritative + +Клиент может использовать локальный NavMesh только для optional preview/query UX, но не как source of truth для gameplay movement state. + +### 3. Do not use client-local `NavMeshAgent` as canonical player mover + +Нельзя строить player movement correctness на `NavMeshAgent`, который локально двигает owner-клиента. + +Предпочтительный путь: + +- host-side `NavMesh.SamplePosition`; +- host-side `NavMesh.CalculatePath`; +- host-side explicit path follower; +- movement execution через контролируемый mover, предпочтительно `CharacterController.Move` или эквивалентный deterministic-ish explicit mover. + +### 4. Client does not send path corners or final movement outcome + +Клиенту нельзя отправлять: + +- path corners; +- velocity как authoritative instruction; +- final position; +- movement completion. + +Клиент отправляет только request: + +- sequence id; +- requested destination; +- при необходимости легкий UX/debug metadata, не влияющий на authority. + +### 5. Shared debug path preview must be authoritative-path-based + +Обязательный debug preview, который видят все клиенты, должен строиться из authoritative path, рассчитанного хостом. + +Допустим временный local provisional preview у инициирующего клиента, но он: + +- не считается каноническим; +- должен визуально отличаться; +- должен исчезать или заменяться после host accept/reject. + +### 6. Spawn/nav readiness must be explicit + +Нельзя строить pipeline по модели: + +- player actor spawned; +- NavMesh может быть еще не готов; +- movement runtime надеется, что agent потом сам корректно «встанет» на NavMesh. + +Нужен явный readiness contract или equivalent bootstrap policy для spawn regions / first move command. + +## Scope In + +- host-authoritative player movement по NavMesh; +- client command pipeline для выбора destination; +- host-side destination validation; +- host-side path planning; +- host-side path following; +- replication authoritative movement state на клиентов; +- shared debug path preview для каждого движущегося игрока на всех клиентах; +- optional local provisional preview для owner-клиента; +- nav-aware spawn/bootstrap и первый move command; +- интеграция через contracts + DI + MessagePipe, совместимая с `TASK-0023`. + +## Scope Out + +- NPC navigation system; +- crowd simulation; +- сложная prediction/reconciliation система для player nav movement; +- production UI polish path markers; +- ownership migration; +- репликация NavMesh data blob; +- multi-agent support beyond current single-agent MVP; +- сохранение movement path через reconnect/persistence beyond current runtime session. + +## Required Architecture + +### Movement flow + +#### Client + +1. Игрок выбирает destination. +2. Input layer определяет world point. +3. Optional: строит provisional local preview path для UX owner-клиента. +4. Отправляет host command с sequence id и requested destination. + +#### Host + +1. Проверяет ownership и допустимость команды. +2. Проверяет movement lock/state. +3. Проверяет nav readiness для области. +4. Делает `NavMesh.SamplePosition`. +5. Делает `NavMesh.CalculatePath`. +6. Если путь валиден, обновляет canonical movement state. +7. Публикует/реплицирует accepted authoritative path и path preview. +8. Если путь невалиден, отправляет reject reason. + +#### Host tick + +1. Берет текущий active path. +2. Вычисляет движение к текущему corner. +3. Двигает player actor через explicit mover. +4. Продвигает current corner index. +5. По завершении очищает active path preview и переводит actor в `Idle`. + +#### Clients + +1. Получают authoritative movement state. +2. Сглаживают presentation. +3. Отображают authoritative debug path preview для всех moving players. + +### Assembly boundaries + +Рекомендуется отдельный feature-модуль: + +- `Assets/Features/PlayerNavigation/Contracts/PlayerNavigation.Contracts.asmdef` +- `Assets/Features/PlayerNavigation/Runtime/PlayerNavigation.Runtime.asmdef` + +Нельзя вшивать player navigation hardwired внутрь `VoxelWorldGenerator` или `VoxelWorldNavMeshService`. + +### DI and integration model + +Использовать: + +- contracts; +- DI через `VContainer`; +- typed `MessagePipe` publishers/subscribers; +- reader interfaces для текущего snapshot state. + +Не использовать `GlobalMessagePipe`. + +### Message vs reader split + +Через MessagePipe: + +- lifecycle movement events; +- accept/reject events; +- preview invalidation/update events. + +Через reader contracts: + +- текущее состояние movement state; +- текущее состояние authoritative path preview; +- nav readiness / spawn readiness snapshot, если требуется queryable access. + +## Required New Contracts + +### Reader interfaces + +Нужны как минимум: + +- `IPlayerMovementStateReader` +- `IPlayerPathPreviewReader` +- `ISpawnNavReadinessReader` или эквивалентный узкий readiness contract + +Минимальная ответственность: + +- `IPlayerMovementStateReader` умеет вернуть current movement snapshot игрока; +- `IPlayerPathPreviewReader` умеет вернуть active authoritative preview для игрока или списка игроков; +- `ISpawnNavReadinessReader` умеет ответить, готова ли nav coverage для spawn/first-move области. + +### DTO / snapshots + +Ожидаются как минимум: + +- `PlayerMovementStateSnapshot` +- `PlayerPathPreviewSnapshot` +- `PlayerMoveRequest` +- `PlayerMoveCommandResult` + +Минимальный состав `PlayerMovementStateSnapshot`: + +- player network id; +- status; +- command sequence; +- current position; +- target position; +- move speed; +- current corner index; +- authoritative path corners; +- updated timestamp/network tick. + +Минимальный состав `PlayerPathPreviewSnapshot`: + +- player network id; +- command sequence; +- corners; +- `IsAuthoritative`; +- `IsActive`. + +### Enums + +Нужны как минимум: + +- `PlayerMovementStatus` +- `PlayerMoveRejectReason` + +Примерные состояния: + +- `Idle` +- `AwaitingPath` +- `Moving` +- `Blocked` +- `Completed` +- `Cancelled` +- `Rejected` + +Примерные reject reasons: + +- `NoNavCoverage` +- `DestinationNotOnNavMesh` +- `PathInvalid` +- `PathPartial` +- `MovementLocked` +- `NotOwner` + +## Required Messages + +Нужны typed MessagePipe messages для: + +- `PlayerMoveRequestedMessage` +- `PlayerMoveAcceptedMessage` +- `PlayerMoveRejectedMessage` +- `PlayerMoveStateChangedMessage` +- `PlayerPathPreviewChangedMessage` +- `PlayerMovementStoppedMessage` + +Если для spawn/bootstrap это необходимо, допустим дополнительный readiness message, но только как supplement к reader contract, а не как единственный источник истины. + +## Required Runtime Responsibilities + +### 1. Client input sender + +Должен: + +- собирать local click-to-move input; +- вычислять world destination; +- отправлять network command хосту; +- optional: запускать provisional local preview. + +Не должен: + +- канонически двигать actor; +- принимать final authoritative решения по path validity. + +### 2. Host command validator / planner + +Должен: + +- валидировать ownership; +- валидировать destination; +- делать `NavMesh.SamplePosition`; +- делать `NavMesh.CalculatePath`; +- обновлять canonical movement state; +- публиковать accepted/rejected results. + +### 3. Host path follower + +Должен: + +- исполнять movement по accepted path; +- отслеживать current corner index; +- завершать, отменять или репланить путь; +- синхронизировать authoritative transform/state. + +### 4. Shared preview state + renderer + +Должны: + +- хранить authoritative debug path data; +- раздавать snapshot presentation-слою; +- визуализировать active path preview для всех observed players. + +Shared preview не должен строиться заново локально на каждом клиенте по его client NavMesh. Источник preview для всех клиентов должен быть authoritative path state от хоста. + +## FishNet Requirements + +### Client -> Host + +Использовать `ServerRpc` для move command: + +- `RequestMoveTo(uint sequence, Vector3 requestedWorldPoint)` + +### Host -> Clients + +Допустимы: + +- authoritative replicated movement state; +- отдельные `TargetRpc` / `ObserversRpc` для accept/reject; +- отдельная lightweight replication для shared path preview. + +Клиент не должен иметь возможности двигать чужого player actor. + +## Required Changes In Existing Code + +### `Assets/Scripts/Players/PlayerMoving.cs` + +Нужно перестроить из local movement executor в input sender / thin player navigation entrypoint. + +### `Assets/Features/VoxelWorld/Prefabs/TestPlayer.prefab` + +Нужно пересмотреть: + +- текущий movement pipeline; +- конфигурацию `NetworkTransform`; +- player navigation components; +- visual debug preview attachment points. + +Client-authoritative movement не должен оставаться каноническим режимом для nav-based player movement. + +### `Assets/Features/VoxelWorld/Scenes/VoxelWorldTestScene.unity` + +Нужно проверить: + +- spawn bootstrap; +- nav warmup around spawn points; +- наличие всего необходимого для тестирования shared path preview. + +### Nav readiness bootstrap + +Нужно добавить явную стратегию, чтобы player spawn / first command не зависели от случайного отсутствия NavMesh в стартовой области. + +## Debug Path Preview Requirements + +Это обязательная часть задачи. + +### Functional requirement + +Каждый движущийся игрок должен иметь debug path preview, который видят все клиенты. + +Примеры: + +- игрок A движется -> path видят A, B, C; +- игрок B движется -> path видят A, B, C. + +### Canonical rule + +Shared preview должен отображать именно authoritative path, рассчитанный хостом. + +### Optional owner-local preview + +Допустим provisional local preview до host ответа, но он должен: + +- визуально отличаться; +- не считаться каноническим; +- исчезать или заменяться после accept/reject. + +### Minimal rendering expectation + +Минимально допустимо: + +- `LineRenderer` или эквивалентный lightweight renderer; +- обновление при accept/replan/stop/complete; +- очистка при completion/cancel/reject. + +## Acceptance Criteria + +- Клиент может отправить move request по клику в мир. +- Хост валидирует destination на своем NavMesh. +- Хост строит authoritative path. +- Игрок движется по host-side path, а не по client-authoritative local mover. +- Недопустимые destination/path корректно reject'ятся с reason. +- Для каждого moving player существует authoritative path preview. +- Этот preview виден на всех клиентах. +- Preview корректно обновляется при новой команде, replan, stop, cancel и completion. +- Если реализован provisional local preview, он визуально отделен от authoritative shared preview. +- Player spawn и first move command не зависят от hidden `NavMeshAgent` attach hacks. +- Pipeline не опирается на `Camera.main` как канонический источник authority/interest. + +## Verification + +- ручной тест: single host, single client, move command accepted/rejected; +- ручной тест: host + 2 clients, оба клиента видят preview друг друга; +- ручной тест: invalid destination вне walkable area, host reject без runtime errors; +- ручной тест: новая команда во время движения корректно заменяет active path; +- ручной тест: late join видит текущее движение и active preview moving players; +- ручной тест: первый move command после старта сцены не приводит к runtime ошибкам из-за отсутствия nav coverage; +- ручной тест: completion/cancel очищает preview на всех клиентах. + +## Risks / Open Questions + +- Если оставить рядом client-authoritative movement и host-side nav movement, возникнет двойная симуляция и несогласованное состояние. +- Если shared preview строить по локальному client NavMesh, разные peers могут видеть разный path debug. +- Если spawn/nav readiness не будет оформлен явно, lifecycle race останется даже при правильном movement flow. +- Возможно потребуется отдельный узкий contract для nav coverage readiness, которого пока нет в `TASK-0023` runtime-поверхности. + +## Human Decisions Needed + +- none currently + +## Decision Log + +- `2026-04-08` - задача создана после фиксации runtime NavMesh sidecar и обсуждения правильной host-authoritative модели player movement по NavMesh. + +## Handoff Notes + +Реализация задачи должна опираться на уже принятые решения по world authority и runtime NavMesh. Если в ходе реализации возникнет соблазн повесить `NavMeshAgent` на player prefab как client-local authoritative mover, это нужно считать архитектурно неправильным shortcut'ом и не делать. + +Shared debug path preview обязателен и должен отображать authoritative path для всех клиентов, а не purely local preview инициатора.