501 lines
20 KiB
Markdown
501 lines
20 KiB
Markdown
---
|
||
id: TASK-0027
|
||
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-0027 - 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 инициатора.
|