feat(M01): add explicit streaming focus

Work-Package: M01-RND-STREAMING-FOCUS-001

Agent: sindo-main-codex
This commit is contained in:
2026-07-13 15:41:16 +04:00
parent fbef131bcb
commit 7815385b3b
12 changed files with 253 additions and 61 deletions
+43 -14
View File
@@ -5,9 +5,9 @@
| Field | Value |
|---|---|
| Status | Partial |
| Target/work package | M00 active; декомпозиция M01M03 |
| Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; декомпозиция M02M03 |
| Owners | Renderer workstream / milestone integrator |
| Last verified | M00 visual-diff worktree, 2026-07-11 |
| Last verified | Worktree `work/sindo-main-codex/m01-streaming-focus`, 2026-07-13 |
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
## Purpose
@@ -26,7 +26,11 @@
```mermaid
flowchart LR
Focus[Camera/player/editor focus] --> Loader[StreamingWorldLoader]
Player[Player or spectator Node3D] --> Adapter[Explicit source adapter]
Editor[Editor viewport adapter] --> Focus[StreamingFocus]
Capture[Capture tool] --> Adapter
Adapter --> Focus
Focus --> Loader[StreamingWorldLoader]
Assets[Extracted WDT/ADT/M2/WMO/BLP] --> Native[Native loaders]
Cache[Baked terrain/M2/WMO caches] --> Loader
Native --> Parsed[Parsed tile/model data]
@@ -60,7 +64,11 @@ Forbidden dependencies:
|---|---|---|---|---|
| `map_name` | Exported property | WDT/map directory name | Set before world load | Missing WDT produces diagnostic/empty world |
| `extracted_dir` | Exported property | Root of legally extracted client data | Session lifetime | Missing files reported per loader path |
| `camera_path` | Exported property | Current streaming focus compatibility path | Main thread, scene lifetime | Missing camera prevents normal focus update |
| `StreamingFocus` | Immutable value | Typed Godot-basis position without Node/Camera dependency | Any thread; caller-owned reference | Null position is a caller contract violation |
| `streaming_focus_source_path` | Exported adapter property | Samples any explicit `Node3D` source into `StreamingFocus` | Main thread, scene lifetime | Missing/wrong node warns once and retains prior focus |
| `set_streaming_focus` | Public method | Accepts focus from app/editor/tool composition | Main thread/session | Null focus is ignored by refresh until replaced |
| `refresh_streaming_focus` | Public method | Samples configured source and applies refresh thresholds | Main thread/session | Returns `false` when no valid focus exists |
| `camera_path` | Exported property | Camera used only by optional automatic overview positioning | Main thread, scene lifetime | Missing camera skips automatic positioning |
| `quality_preset` | Exported property | Applies renderer budgets/radii | Before/at runtime depending property | Invalid combination should be diagnosed |
| `runtime_stats_enabled` | Exported property | Enables periodic performance snapshot | Runtime mutable | Logging overhead only |
| `hitch_profiler_enabled` | Exported property | Enables named hitch sections | Runtime mutable | Logging overhead only |
@@ -71,7 +79,9 @@ Forbidden dependencies:
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Map/focus/configuration | Scene/app/editor | `StreamingWorldLoader` | Caller config, copied/read | Main thread/session |
| Input | `StreamingFocus` | Player/spectator/editor/capture adapter | `StreamingWorldLoader` | Immutable caller value | Main thread/session |
| Input | Focus-source `NodePath` | Runtime/test scene composition | Loader source adapter | Scene-owned node, sampled position | Main thread/scene lifetime |
| Input | Map/configuration | Scene/app/editor | `StreamingWorldLoader` | Caller config, copied/read | Main thread/session |
| Input | WDT/ADT/M2/WMO/BLP bytes | Extracted asset repository | Native loaders/builders | Loader result owns parsed data | Worker or controlled load |
| Input | Baked resources | Cache build tools | Streamer/builders | Shared immutable resource/cache entry | Session/cache lifetime |
| Output | Desired tile/detail operations | Streaming planner inside loader | Finalize queues | Loader-owned | Cross-frame |
@@ -90,7 +100,12 @@ Side effects:
```mermaid
flowchart TD
F[Streaming focus] --> T[Compute desired ADT tiles]
P[Player/spectator Node3D] --> A[Explicit scene adapter]
C[Capture camera] --> A
E[Editor viewport] --> EA[Editor adapter]
A --> F[StreamingFocus]
EA --> F
F --> T[Compute desired ADT tiles]
WDT[WDT tile catalog] --> T
T --> Q[Tile load queue]
Q --> P[Worker parse/cache load]
@@ -113,13 +128,14 @@ flowchart TD
```mermaid
stateDiagram-v2
[*] --> Initializing
Initializing --> Ready: WDT/catalog/config ready
Initializing --> WaitingForFocus: WDT/catalog/config ready
Initializing --> Degraded: missing loaders/data/cache
Ready --> Streaming: focus update
WaitingForFocus --> Streaming: valid StreamingFocus
Streaming --> Streaming: load/finalize/evict ticks
Streaming --> WaitingForFocus: world reset without replacement focus
Streaming --> SwitchingMap: map/focus reset
SwitchingMap --> Initializing
Ready --> ShuttingDown
WaitingForFocus --> ShuttingDown
Degraded --> ShuttingDown
Streaming --> ShuttingDown
ShuttingDown --> [*]
@@ -129,12 +145,14 @@ stateDiagram-v2
```mermaid
sequenceDiagram
participant Focus as Camera/Player/Editor
participant Source as Player/Editor/Capture source
participant Focus as StreamingFocus adapter
participant Stream as StreamingWorldLoader
participant Worker as Worker task
participant Budget as Main-thread budget
participant Render as SceneTree/RenderingServer
Focus->>Stream: focus position changed
Source->>Focus: sample explicit Godot position
Focus->>Stream: set/refresh typed focus
Stream->>Stream: desired tiles and priorities
Stream->>Worker: parse/load tile and detail data
Worker-->>Stream: immutable result
@@ -150,6 +168,8 @@ sequenceDiagram
## Ownership, threading and resources
- `StreamingWorldLoader` владеет tile states, queues, task registries, active world instances и render caches session scope.
- Focus producers own the immutable `StreamingFocus` reference; the loader retains
the latest reference and converts it to `Vector3` only inside the render boundary.
- Worker tasks не должны менять SceneTree и shared Resource concurrently.
- Parsed/grouped results передаются обратно через guarded result queues.
- Mesh/material/node/RID finalization выполняется main thread и ограничивается exported budgets.
@@ -163,6 +183,8 @@ sequenceDiagram
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Missing WDT/ADT | File/catalog check | Tile/map unavailable | Loader warning/error | Fix extraction/profile, reload |
| Missing focus source | Explicit NodePath resolution | Retain current streamed content and prior valid focus | One warning until source recovers | Fix composition path or call `set_streaming_focus` |
| No focus supplied | Refresh contract | No target recomputation; queues continue draining | `refresh_streaming_focus()` returns `false` | Supply player/editor/capture focus |
| Cache version mismatch | Required format constant | Reject stale cache/fallback | Version diagnostic | Rebuild matching cache |
| Worker failure | Task result/empty data | Skip/degrade affected asset | Queue/task warning | Retry/rebuild source |
| Main-thread hitch | Named section timing | Frame spike, work remains queued | `HITCH` log | Lower budget/fix finalize path |
@@ -184,6 +206,9 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
## Persistence, cache and migration
- `StreamingFocus` is runtime-only and is not serialized. Scene migration adds
`streaming_focus_source_path`; existing `camera_path` remains valid only for
optional automatic overview positioning.
- Renderer reads caches under `data/cache`; caches are not committed.
- Format changes require `FORMAT_VERSION`/required-version bump and rebuild instructions.
- Atomic cache publication and unified manifest are target-state work from FND/M03.
@@ -198,7 +223,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
## Verification
- Unit/contract tests: material mapping, unique-ID dedupe, placement probes, baseline manifest, five-point coordinate calibration, synthetic perceptual checkpoint diff, camera-pose grid plan.
- Unit/contract tests: streaming-focus contract/wiring, material mapping, unique-ID dedupe, placement probes, baseline manifest, five-point coordinate calibration, synthetic perceptual checkpoint diff, camera-pose grid plan.
- Integration/E2E: Eastern Kingdoms/Kalimdor streaming scenes and seven cold/warm checkpoints.
- Fidelity evidence: семь локальных build 12340 reference JPG покрывают terrain/ADT/M2/WMO/liquid/sky и реальный `GryphonRoost01`; automated cold/warm metrics имеют `compared=14`, `missing=0`. Sky использует приблизительно paired camera, а animated-M2 остаётся asset-paired/placement-unpaired из-за synthetic probe.
- Performance budgets: M00 report records cold/warm p95 and max hitch; no final acceptance threshold yet.
@@ -207,7 +232,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
## Extension points
- Target `WorldRenderFacade` for runtime/editor/capture callers.
- Target `StreamingFocus` independent of Camera3D.
- Extend `StreamingFocus` with measured direction/velocity needs when the pure planner is extracted.
- Extracted planner, budget scheduler, terrain, M2, WMO, liquid, sky and character services.
- Separate Blizzlike/Enhanced material and quality profiles.
@@ -221,12 +246,14 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| Liquids | Partial | MH2O/MLIQ paths | LiquidType/depth/shore fidelity |
| Sky/light | Partial | DBC controller/logs | Zone transition paired validation |
| Character presentation | Prototype | Character creator/player experiment | Full customization/equipment states |
| Camera-independent streaming focus | Implemented | Typed contract, two player-focused runtime scenes and three explicitly camera-focused diagnostic tools | Multi-focus/direction/velocity belong to planner extraction |
| Stable renderer facade | Planned | M03 | Current caller uses monolithic Node API |
## Known gaps and risks
- Monolithic streamer mixes planning, jobs, caches and presentation.
- Direct camera path remains until M01/M03.
- `camera_path` remains only for optional automatic overview positioning; it no
longer drives runtime streaming or discovers the active viewport camera.
- Original-client paired fidelity evidence incomplete.
- Первый paired run выявил coordinate/placement mismatch: несколько совпадающих server-derived camera positions оказываются под terrain или внутри WMO/rocks OpenWC.
- Terrain-height probe исключил under-terrain состояние для всех пяти точек; waterfall exact-XZ miss классифицирован как TriangleMesh seam/edge и подтверждён nearby sample в 2 units.
@@ -247,6 +274,8 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| Path | Responsibility |
|---|---|
| `src/scenes/streaming/streaming_world_loader.gd` | Current runtime orchestration, queues, caches and attachment |
| `src/domain/streaming/streaming_focus.gd` | Immutable camera-independent streaming position contract |
| `src/tools/verify_streaming_focus.gd` | Headless contract, dependency and runtime/tool wiring regression |
| `addons/mpq_extractor/loaders/adt_builder.gd` | Terrain/material/liquid construction |
| `addons/mpq_extractor/loaders/m2_builder.gd` | Static M2 mesh/material construction |
| `addons/mpq_extractor/loaders/m2_native_animated_builder.gd` | Native animated M2 experiment |